實驗使用如下所示的雙軸按鍵搖桿控制器,來控制TFTLCD上顯示的直線。首先介紹一下雙軸按鍵搖桿控制器。原理:十字搖桿為一個雙向的10K電阻器,隨着搖桿方向不同,抽頭的阻值隨着變化。本模塊使用5V供電(在本實驗中使用3.3V),原始狀態下X,Y讀出電壓為2.5V左右(本實驗為1.65V),當隨箭頭方向按下,讀出電壓值隨着增加,最大到5V(本實驗最大為3.3V);箭頭相反方向按下,讀出電壓值減少,最小為0V。即模塊特設二路模擬輸出和一路數字輸出接口,輸出值分別對應(X,Y)雙軸偏移量,其類型為模擬量;按鍵表示用戶是否在Z軸上按下,其類型為數字開關量。坐標標識符清晰簡明、准確定位;用其可以輕松控制物體(如二自由度舵機雲台)在二維空間運動。

實驗目的:
在屏幕的中心區域顯示一條射線,射線起點為屏幕中心(120,160),射線方向與搖桿歪的方向相同,射線長度與歪的程度有關。線路連接:
PC1 - ADC1 channel_11; PC0 - ADC1 channel_10;
PC2 - SW;

實驗准備:
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:要清屏的填充色 void LCD_Clear(u16 color); //在指定區域內填充單個顏色 //(sx,sy),(ex,ey):填充矩形對角坐標,區域大小為:(ex-sx+1)*(ey-sy+1) //color:要填充的顏色 void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color); //畫線 //x1,y1:起點坐標 //x2,y2:終點坐標 void LCD_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,疊加顯示. void LCD_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);

中間一點是理論中心點(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點半才成功,中間還百度了很多資料。
最終效果如下:




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