[DMA 設定步驟]
要編寫 DMA 程式,可按照如下步驟進行設定:(1) 啟動 ADC1 及 GPIOA 的 RCC 時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); //啟用ADC1和GPIOA時鐘(2) 設定 GPIO Pin 並初始化 GPIOA
GPIO_InitTypeDef GPIO_InitStructure; //定義 GPIO_InitTypeDef 類型的結構體 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 ; // 啟用PA0-PA2當做類比輸入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模擬輸入引腳 GPIO_Init(GPIOA, &GPIO_InitStructure); //輸入時不用設置速率相關 GPIO 相關設定說明,請參考:STM32筆記(17):按鍵控制-使用 GPIO 讀取。
(3) 啟用 DMA1 和 ADC1 的 RCC 時鐘
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //啟用DMA時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //啟用ADC1時鐘(4) 設定 DMA 通道
DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); //恢復 DMA1頻道初始值 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 = Sample_Num*Channel_Num; //保存DMA要傳輸的資料總大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設地址固定 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //記憶體位址自增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //設定外設資料寬度為半字16位元 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //設定記憶體資料寬度為半字16位元 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止記憶體到記憶體的傳輸 DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); //啟用 DMA channel1(5) 設定 ADC1
ADC_InitTypeDef ADC_InitStructure; 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 = 3; //要轉換的通道數目1 ADC_Init(ADC1, &ADC_InitStructure);(6)設定 ADC 時鐘及通道
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 設定ADC時鐘,為PCLK2的8分頻,即9MHz /*設定 ADC1 的通道0-2為239.5個採樣週期,序列為1 */ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 ); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5 ); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5 );(7)設定 ADC 時鐘及通道
ADC_DMACmd(ADC1, ENABLE); //啟動 ADC1 DMA ADC_Cmd(ADC1, ENABLE); //啟動 ADC1(8)對 ADC 進行校準
ADC_ResetCalibration(ADC1); //復位校準暫存器 while(ADC_GetResetCalibrationStatus(ADC1)); //等待校準暫存器復位完成 ADC_StartCalibration(ADC1); //ADC校準 while(ADC_GetCalibrationStatus(ADC1)); //等待校準完成依照上述幾個步驟,就可完成定 DMA 的控制,以下就用 DMA 的三個通道,紀錄三個 GPIO Pin 電壓的量測值,每個 GPIO Pin 接一個可變電阻,來改變電壓值,分別顯示三個電壓值在 OLED 上。
[材料]
- STM32F103C8T6 主板 x 1
- OLED SSD1306 顯示器 x 1
- STLINK V2 模擬下載器 x 1
- 可變電阻 x 3
- 麵包板 x 1
- 連接線 x N 條
[接線與電路圖]
三個可變電阻左右兩側分別接 3.3v 及 GND,中間的接腳分別接在 STM32F103x 的 PA0、PA1 及 PA2,與 SSD1306 連接的方式如下:STM32F103x | SSD1306 OLED |
---|---|
3.3v | VDD |
GND | GND |
B8 | SCK/SCL |
B9 | SDA |
[程式]
主程式一開始先對 OLED 進行初始化,進入迴圈循環後就改變 CCR 值,等中斷產生,主程式 main.c 如下:#include "stm32f10x.h" #include "delay.h" #include "oled.h" #include "bmp.h" #include "adcx.h" float ADValue[Channel_Num]; int main (void) { uint16_t vtg; delay_init(); //延時函數初始化 OLED_Init(); //初始化OLED ADC_GPIO_Config(); ADC_Mode_Config(); OLED_Clear(0); //清除螢幕 OLED_ShowString(1, 2, "Voltage1:",16); OLED_ShowString(1, 4, "Voltage2:",16); OLED_ShowString(1, 6, "Voltage3:",16); while (1) { u16 x; u16 colnum; for(x=0;x<3;x++){ ADValue[x] = Read_ADC_AverageValue(x); colnum = 2+(x*2); OLED_ShowNum(72, colnum, ADValue[x] / 4096 * 3.3, 1, 16); //整數位 OLED_ShowString(82, colnum, ".",16); vtg = (uint16_t)(ADValue[x] / 4096 * 3.3 * 100) % 100; //小數點後兩位 if (vtg < 10) { OLED_ShowNum(90, colnum, vtg, 2, 16); OLED_ShowString(89, colnum,"0",16); //個位數時前面補0 } else { OLED_ShowNum(90, colnum, vtg, 2, 16); } delay_ms(300); } } }adcx.c 程式如下:
#include "stm32f10x.h" #include "delay.h" #include "adcx.h" #define ADC1_DR_Address ((u32)0x40012400+0x4c) __IO uint16_t ADC_ConvertedValue[Sample_Num][Channel_Num]; u16 AD_After_Filter[Channel_Num]; // 初始化 GPIO void ADC_GPIO_Config(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); //啟用ADC1和GPIOA時鐘 // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //啟用ADC1和GPIOA時鐘 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 ; // 啟用PA0-PA2當做類比輸入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模擬輸入引腳 GPIO_Init(GPIOA, &GPIO_InitStructure); //輸入時不用設置速率 } // 設定 ADC1 的工作模式為 MDA 模式 void ADC_Mode_Config(void) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //啟用DMA時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //啟用ADC1時鐘 /* DMA channel1 configuration */ DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); //DMA1的通道1 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 = Sample_Num*Channel_Num; //保存DMA要傳輸的資料總大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設地址固定 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //記憶體位址自增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //設定外設資料寬度為半字16位元 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //設定記憶體資料寬度為半字16位元 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止記憶體到記憶體的傳輸 DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); //啟用 DMA channel1 /* ADC1 configuration */ ADC_InitTypeDef ADC_InitStructure; 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 = 3; //要轉換的通道數目1 ADC_Init(ADC1, &ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 設定ADC時鐘,為PCLK2的8分頻,即9MHz /*配置ADC1的通道0-2為239.5個採樣週期,序列為1 */ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 ); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5 ); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5 ); ADC_DMACmd(ADC1, ENABLE); //啟動 ADC1 DMA ADC_Cmd(ADC1, ENABLE); //啟動 ADC1 ADC_ResetCalibration(ADC1); //復位校準寄存器 while(ADC_GetResetCalibrationStatus(ADC1)); //等待校準寄存器復位完成 ADC_StartCalibration(ADC1); //ADC校準 while(ADC_GetCalibrationStatus(ADC1)); //等待校準完成 //ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 由於沒有採用外部觸發,所以使用軟體觸發ADC轉換 } u16 Read_ADC_AverageValue(u16 Channel) { u8 t; u32 sum = 0; //完成一次DMA傳輸,資料大小10*5 DMA_SetCurrDataCounter(DMA1_Channel1,Sample_Num*Channel_Num); //設置DMA的傳送數量為10*5 DMA_Cmd(DMA1_Channel1,ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使用軟體轉換啟動功能 while(DMA_GetFlagStatus(DMA1_FLAG_TC1)!=SET); //等待DMA傳送完成 DMA_ClearFlag(DMA1_FLAG_TC1); //清除DMA傳送完成標誌 DMA_Cmd(DMA1_Channel1,DISABLE); ADC_SoftwareStartConvCmd(ADC1, DISABLE); for(t=0;t<Sample_Num;t++) { sum += ADC_ConvertedValue[t][Channel]; } AD_After_Filter[Channel] = sum/Sample_Num; sum = 0; return AD_After_Filter[Channel]; //delay_ms(5); }adcx.h 程式如下:
#ifndef __ADCX_H #define __ADCX_H #include "stm32f10x.h" #define Sample_Num 10 #define Channel_Num 3 extern __IO uint16_t ADC_ConvertedValue[Sample_Num][Channel_Num]; extern u16 AD_After_Filter[Channel_Num]; void ADCX_Init(void); uint16_t ADCX_GetValue(u8 ch); u16 Get_Temp(void); u16 Get_Adc_Average(u8 ch,u8 times); short Get_Temprate(void); void ADC_GPIO_Config(void); void ADC_Mode_Config(void); u16 Read_ADC_AverageValue(u16 Channel); #endif完整的程式請參考 Github:9.Using ADC DMA Read Voltage
[結果]
轉動可變電阻時,GPIO 的電壓值隨著改變,值介於 0~3.3v 之間。各位有沒有發現,我的可變電阻從左至右,分別接在 PA0, PA2 及 PA3,對應設定的通道從 0~2,可是影片中,旋轉中間接到 PA1 的可變電阻,竟然是 Voltage3 的值改變,我還沒找到在哪裡出問題,有空在回來除錯。[參考資料]
- STM32F103 Reference manual
- STM32F10xxx參考手冊
- STM32 DMA原理,步骤超细详解,一文看懂DMA
- STM32F103学习笔记(二):多通道直流电压ADC(DMA方式)
不好意思,請問可以教學使用Timer觸發雙ADC同步規則採樣(Dual regular simultaneous mode)嗎,目前遇到一個問題是ADC Sampling Time與Timer觸發採樣頻率的計算,當我想使Tconv(Sampling Time+Conversion Time)<(1/Timer採樣頻率)時,卻只能採樣第一次便停止了,反而是當Tconv>(1/Timer採樣頻率)時可以持續採樣轉換。
回覆刪除張貼留言