双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)

摘要:
实验使用如下所示的双轴按键摇杆控制器,来控制TFTLCD上显示的直线。线路连接:PC1-ADC1channel_11;PC0-ADC1channel_10;PC2-SW;实验准备:1、实验中对摇杆两个模拟段输入的检测需要使用STM32的ADC功能;2、在数据转换之后的移动数据时使用DMA,以将数据及时转移出ADC的寄存器;我们先来看看主函数,在主函数中我们定义了浮点型数组floatADC_ConvertedValueLocal[2];用于保存转换计算后的电压值,还有在adc.c文件中定义的数组ADC_ConvertedValue[2];用来装转换后的数据。

实验使用如下所示的双轴按键摇杆控制器,来控制TFTLCD上显示的直线。首先介绍一下双轴按键摇杆控制器。原理:十字摇杆为一个双向的10K电阻器,随着摇杆方向不同,抽头的阻值随着变化。本模块使用5V供电在本实验中使用3.3V,原始状态下X,Y读出电压为2.5V左右(本实验为1.65V),当随箭头方向按下,读出电压值随着增加,最大到5V(本实验最大为3.3V);箭头相反方向按下,读出电压值减少,最小为0V。即模块特设二路模拟输出和一路数字输出接口,输出值分别对应(X,Y)双轴偏移量,其类型为模拟量;按键表示用户是否在Z轴上按下,其类型为数字开关量。坐标标识符清晰简明、准确定位;用其可以轻松控制物体(如二自由度舵机云台)在二维空间运动。

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第1张

实验目的:

在屏幕的中心区域显示一条射线,射线起点为屏幕中心(120,160),射线方向与摇杆歪的方向相同,射线长度与歪的程度有关。线路连接:

PC1 -ADC1 channel_11;
PC0 - ADC1 channel_10;
PC2 - SW;

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第2张

实验准备:

1、实验中对摇杆两个模拟段输入的检测需要使用STM32 的ADC功能;

2、在数据转换之后的移动数据时使用DMA,以将数据及时转移出ADC的寄存器;

我们先来看看主函数,在主函数中我们定义了浮点型数组float ADC_ConvertedValueLocal[2];用于保存转换计算后的电压值 ,还有在adc.c文件中定义的数组ADC_ConvertedValue[2];用来装转换后的数据。

#include <stdio.h>#include "stm32f10x.h"#include "led.h"#include "delay.h"#include "key.h"#include "timer.h"#include "beep.h"#include "usart.h"#include "adc.h"#include "lcd.h"

//ADC1转换的电压值通过MDA方式传到SRAM
extern __IO uint16_t ADC_ConvertedValue[2];

//局部变量,用于保存转换计算后的电压值             
float ADC_ConvertedValueLocal[2];    

int main(void)
{
  u8 x=0;
  u8 lcd_id[12];            //存放LCD ID字符串
  SysTick_Init();//延时初始化
  USART1_Int(9600);
  LCD_Init();
  ADC1_Init();POINT_COLOR=RED;
  while(1) 
    {        
        
        if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)!=1){
            LCD_Clear( YELLOW);
            LCD_ShowString(20,100,200,100,16, "THANKS TO YOU!");    
            Delay_ms(2000);    
            LCD_Clear( WHITE);
        }
        
        for(x=0;x<2;x++){
            ADC_ConvertedValueLocal[x] =(float) ADC_ConvertedValue[x]/4096*3.3; //读取转换的AD值
            LCD_ShowxNum(0,0+x*20,ADC_ConvertedValue[x],5,16,0);
        }

        LCD_DrawLine(124, 158, 165-(50*ADC_ConvertedValueLocal[1])+42 , 50*ADC_ConvertedValueLocal[0]+78);                
Delay_ms(100);    
        LCD_Fill(34,72,210,246,LGRAY);
        
    } 
}

在主函数的while(1)循环之前,我进了三个初始化,分别是:

  USART1_Int(9600);
  LCD_Init();
  ADC1_Init();

在这里第一个就不在介绍,不了解的可以参考:http://www.ciast.net/post/2015119.html。第二个在这里也不作为重点介绍,TFTLCD的介绍可以参见http://www.ciast.net/post/20151112.html,在这里我们会使用到lcd.c中定义的一些操作液晶屏的函数,如下所示:

//清屏函数
//color:要清屏的填充色
voidLCD_Clear(u16 color);
//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)   
//color:要填充的颜色
voidLCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color);
//画线
//x1,y1:起点坐标
//x2,y2:终点坐标  
voidLCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);
//显示数字,高位为0,还是显示
//x,y:起点坐标
//num:数值(0~999999999);     
//len:长度(即要显示的位数)
//size:字体大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非叠加显示;1,叠加显示.
voidLCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode);
//显示字符串
//x,y:起点坐标
//width,height:区域大小  
//size:字体大小
//*p:字符串起始地址          
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)

这些功能将在while(1)循环中使用。我们现在着重要将的是ADC1_Init();这个初始化函数,我们找到这个函数的定义,内容如下:

/** 函数名:ADC1_Init
 * 描述  :无
 * 输入  :无
 * 输出  :无
 * 调用  :外部调用
 */
void ADC1_Init(void)
{
    ADC1_GPIO_Config();
    ADC1_Mode_Config();
}

这个函数由另外两个函数组成,分别是:

/** 函数名:ADC1_GPIO_Config
 * 描述  :使能ADC1和DMA1的时钟,初始化PC.00、PC.01和PC.02
 * 输入  : 无
 * 输出  :无
 * 调用  :内部调用
 */
static void ADC1_GPIO_Config(void);
/*函数名:ADC1_Mode_Config
 * 描述  :配置ADC1的工作模式为MDA模式
 * 输入  : 无
 * 输出  :无
 * 调用  :内部调用
 */
static void ADC1_Mode_Config(void);

首先看的是第一个函数,即引脚定义:

/** 函数名:ADC1_GPIO_Config
 * 描述  :使能ADC1和DMA1的时钟,初始化PC.01
 * 输入  : 无
 * 输出  :无
 * 调用  :内部调用
 */
static void ADC1_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /*Enable DMA clock */RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    /*Enable ADC1 and GPIOC clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 |RCC_APB2Periph_GPIOC, ENABLE);
    
    /*Configure PC.01  as analog input */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 |GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN;
    GPIO_Init(GPIOC, &GPIO_InitStructure);                //PC1 PC0,输入时不用设置速率
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);                //PC2 设置按键功能}

我将两个模拟输入设置为GPIO_Mode_AIN(模拟输入),将PC2 按键功能键设置成上拉输入(摇杆的SW引脚已经被上拉)。下面是重点:

在ADC1_GPIO_Config(void)函数中,我们主要进行的是DMA和ADC1双通道的设置,下面我们对负责传输的DMA进行设置:

DMA_InitTypeDef DMA_InitStructure;

/*DMA channel1 configuration */DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;     //ADC地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;//内存地址
    DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 2;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;    //半字
    DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;        //循环传输
    DMA_InitStructure.DMA_Priority =DMA_Priority_High;
    DMA_InitStructure.DMA_M2M =DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    /*Enable DMA channel1 */DMA_Cmd(DMA1_Channel1, ENABLE);

下面我们开始使用ADC1的通道10和通道11来转换接受到的虚拟信号:

ADC_InitTypeDef ADC_InitStructure;

/*ADC1 configuration */
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;    //独立ADC模式
    ADC_InitStructure.ADC_ScanConvMode =     ENABLE ;      //禁止扫描模式,扫描模式用于多通道采集
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;    //开启连续转换模式,即不停地进行ADC转换
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;    //不使用外部触发转换
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;     //采集数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 2;         //要转换的通道数目2
    ADC_Init(ADC1, &ADC_InitStructure);
    
    /*配置ADC时钟,为PCLK2的8分频,即9Hz*/RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
    /*配置ADC1的通道11为55.    5个采样周期,序列为1 */ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 2, ADC_SampleTime_55Cycles5 );//ADC1;ADC1通道0;第2转换;采样时间为239.5周期

    ADC_DMACmd(ADC1, ENABLE);      /*Enable ADC1 DMA */ADC_Cmd(ADC1, ENABLE);          /*Enable ADC1 */ADC_ResetCalibration(ADC1);       /*复位校准寄存器 */
    while(ADC_GetResetCalibrationStatus(ADC1));     /*等待校准寄存器复位完成 */ADC_StartCalibration(ADC1);       /*ADC校准 */
    while(ADC_GetCalibrationStatus(ADC1));      /*等待校准完成*/
    
    
    /*由于没有采用外部触发,所以使用软件触发ADC转换 */ADC_SoftwareStartConvCmd(ADC1, ENABLE);

我把整个ADC的设置分成了四个部分:

第一部分:主要是ADC的初始化参数配置函数,具体设置可以参考:http://www.ciast.net/post/20151226.html,在这里使用独立ADC模式模式,由于使用了双通道,所以ADC_ScanConvMode = ENABLE ;即开启扫描模式,且ADC_NbrOfChannel = 2;

第二部分:主要是配置ADC时钟和规则通道设置。这里由于ADC的频率不能大于12MHz,所以我们选择分频为PCLK2的8分频,即9Hz。规则通道的设置是每一个通道都要进行的,可以设置通道和扫描顺序以及扫描周期等,这也是ADC设置的重点,可以参考http://www.ciast.net/post/20151226.html

第三部分:主要是使能DMA和ADC,以及复位校准;

第四部分:使用软件触发ADC转换,转换开始

至此初始化函数介绍完毕,下面开始while(1)循环部分的介绍:

  while(1) 
    {        
        if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)!=1){
            LCD_Clear( YELLOW);
            LCD_ShowString(20,100,200,100,16, "THANKS TO YOU!");    
            Delay_ms(2000);    
            LCD_Clear( WHITE);
        }
        
        for(x=0;x<2;x++){
            ADC_ConvertedValueLocal[x] =(float) ADC_ConvertedValue[x]/4096*3.3; //读取转换的AD值
            LCD_ShowxNum(0,0+x*20,ADC_ConvertedValue[x],5,16,0);
        }

        LCD_DrawLine(124, 158, 165-(50*ADC_ConvertedValueLocal[1])+42 , 50*ADC_ConvertedValueLocal[0]+78);                
        Delay_ms(100);    
        LCD_Fill(34,72,210,246,LGRAY);
    } 

其中的重点是FOR语句:

        for(x=0;x<2;x++){
            ADC_ConvertedValueLocal[x] =(float) ADC_ConvertedValue[x]/4096*3.3; //读取转换的AD值
            LCD_ShowxNum(0,0+x*20,ADC_ConvertedValue[x],5,16,0);
        }

在这个循环语句中,我们读取两个通道中的数据。其中ADC_ConvertedValue[x]为我们在一开始就定义的用来存数处理过的数据的数组,其中X取1和2。为什么这个数组可以接受到两个通道的数据呢?其实在DMA中我们设置了数据的传输:

    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;     //ADC地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;//内存地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

其中 传输方向为从外设到内存,而内存地址,我们取得就是这个数组的首地址,这样转换过的数据自然就被传到了这个数组中。那么传输之前数据的处理又是怎么进行的呢?从下面的程序可以看到,我们在ADC设置时,使能了扫描和连续转换,且通道数目为2,那么在连续转换时,ADC就会按照下面ADC_RegularChannelConfig中设置的转换顺序进行连续的转换所有的通道(即转换完通道1后转换通道2)。每次转换完一个通道后,转换好的数据就会被DMA转走,从而回到了上面的步骤。

    ADC_InitStructure.ADC_ScanConvMode =     ENABLE ;      //禁止扫描模式,扫描模式用于多通道采集
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;    //开启连续转换模式,即不停地进行ADC转换
    ADC_InitStructure.ADC_NbrOfChannel = 2;         //要转换的通道数目2
----------------------------------------------------------------------------------------
    ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 2, ADC_SampleTime_55Cycles5 );//ADC1;ADC1通道0;第2转换;采样时间为239.5周期

下面的语句是FOR语句下面的,实现的是话射线功能,我们使用的屏是240*320的,图形具体尺寸见下图:

LCD_DrawLine(124, 158, 165-(50*ADC_ConvertedValueLocal[1])+42 , 50*ADC_ConvertedValueLocal[0]+78);                
                  
Delay_ms(100);    
LCD_Fill(34,72,210,246,LGRAY);

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第3张

中间一点是理论中心点(120,160),但是由于摇杆的电压不是太稳定,出现了电压波动,所示实际中心点是(124,159),中间的正方形是射线的边界。

还有最后的IF语句,设置的是按下是的操作,实现的是按下后在屏幕上刷黄色屏,出现一个字符串:

if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)!=1){
     LCD_Clear( YELLOW);
     LCD_ShowString(20,100,200,100,16, "THANKS TO YOU!");    
     Delay_ms(2000);    
     LCD_Clear( WHITE);
 }

现在整个实验就结束了。这个实验今天从早上7点多开始,我一直弄到下午4点半才成功,中间还百度了很多资料。

最终效果如下:

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第4张

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第5张

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第6张

双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)第7张

[完] 选自:http://www.ciast.net/post/20151227.html CIAST.NET @zgc261

免责声明:文章转载自《双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Innodb buffer pool/redo log_buffer 相关java8学习之Collectors工厂类源码分析与实战下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

linux驱动(七)gpiolib库详解

---恢复内容开始--- 1:什么是gpiolib,为什么要有gpiolib? linux中从2.6.35以后就开始有gpiolib库了,gpiolib的作用是对所有的gpio实行统一管理,因为驱动在工作的时候,会出现好几个驱动共同使用同一个gpio的情况; 这会造成混乱。所以内核提供了一些方法来管理gpio资源; 2:如何学习gpiolib 第一:gpi...

∑–△型模数转换器(ADC)简介

∑–△型模数转换器(ADC) 1.概述 近年来,随着超大规模集成电路制造水平的提高,Σ-Δ型模数转换器正以其分辨率高、线性度好、成本低等特点得到越来越广泛的应用。Σ-Δ型模数转换器方案早在20世纪60年代就已经有人提出,然而,直到不久前,在器件商品化生产方面,这种工艺还是行不通的。今天,随着1微米技术的成熟及更小的CMOS几何尺寸,Σ-Δ结构的模数转换...

STM32之ADC配置,ADC_Mode模式理解

对于STM32,在使用ADC的时候需要配置几个参数。 第一个参数是ADC_Mode,这里设置为独立模式: ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 在这个模式下,双ADC不能同步,每个ADC接口独立工作。所以如果不需要ADC同步或者只是用了一个ADC的时候,就应该设成独立模式了。 第二个参数是...

零拷贝

零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现 CPU 的零参与,彻底消除 CPU 在这方面的负载。实现零拷贝用到的最主要技术是 DMA 数据传输技术...

FPGA智能网卡综述(4): 开源DMA

https://zhuanlan.zhihu.com/p/359754525 Corundum 原版见FCCM2020 代码见:https://github.com/ucsdsysnet/corundum 这个专题主要对基于FPGA的智能网卡技术进行介绍,上篇介绍了NanoPU、PANIC、Tonic等,这次介绍“Corundum: An Open-Sou...

ESP32开发(2)esp32-cam采集图像

ESP32-CAM摄像头开发板 USB转串口下载器 杜邦连接线若干        注意:GPIO0连接GND(下拉)的作用是让ESP32-CAM进入下载启动模式,这个模式里,才能利用Arduino IDE给ESP32编程,否则IDE会报错,代码烧录完成后,我们需要断开GPIO0和GND的连接,让ESP32进入正常的内存启动模式。 配置ESP32环...