STM32 ADC基本介紹+DMA外設到記憶體
1.ADC介紹
STM32f103系列有3個ADC,精度為12位,每個ADC最多有16個外部通道。其中ADC1和ADC2都有16個外部通道,ADC3一般有8個外部通道,各通道的A/D轉換可以單次、連續、掃描或間斷執行,ADC轉換的結果可以左對齊或右對齊儲存在16位資料暫存器中。ADC的輸入時鐘不得超過14MHz,其時鐘頻率由PCLK2分頻產生。
ADC功能框圖:把整體框圖分成若干個部分,按照順序介紹具體的作用。
圖1
(1)第一部分限定了ADC電壓輸入範圍 。ADC 輸入範圍為: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、 VDDA 、 VSSA、這四個外部引腳決定。我們在設計原理圖的時候一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的輸入電壓範圍為: 0~3.3V。如果我們想讓輸入的電壓範圍變寬,去到可以測試負電壓或者更高的正電壓,我們可以在外部加一個電壓調理電路,把需要轉換的電壓抬升或者降壓到 0~3.3V,這樣 ADC 就可以測量了。
圖2(引用於野火資料)
(3)在第2、3部分之間有註明規則通道最多16個,注入通道最多4個。規則通道就是最常用的通道,注入通道就是一種優先順序比較高的通道,注入通道可以理解為插入,中斷型別的轉換。比如當前正在轉換一個規則通道,突然來了一個注入通道,那就要先轉換注入通道,然後在繼續轉換規則通的。
(4)第4、5部分是注入通道資料暫存器和規則轉換資料暫存器。在第3部分中。從規則通道或者注入通道採集到模擬電壓後,在第3部分中的模擬至數字轉化器中進行轉換,轉換後的數字訊號存放到對應的資料暫存器和規則轉換資料暫存器。可以看到注入通道資料暫存器有4個,正好對應前面說的注入通道最多有4個,但是規則通道只有1個。也就是說如果ADC有多個通道在同時採集模擬訊號,這些通道的最後的轉換結果都會存放在同一個規則通道資料暫存器中,這樣會發生資料覆蓋,解決辦法就是使用DMA。後面有DMA處理多個通道的程式。
關於轉換順序:知道了ADC的轉換通道後,如果ADC只使用一個通道來轉換,那就很簡單,但如果是使用多個通道進行轉換就涉及到一個先後順序了,畢竟規則轉換通道只有一個數據暫存器。多個通道的使用順序分為倆種情況:規則通道的轉換順序和注入通道的轉換順序。
規則通道轉換順序:
規則通道中的轉換順序由三個暫存器控制:SQR1、SQR2、SQR3,它們都是32位暫存器。SQR暫存器控制著轉換通道的數目和轉換順序,只要在對應的暫存器位SQx中寫入相應的通道,這個通道就是第x個轉換。具體的對應關係如下:
ADC規則序列暫存器ADC_SQR1:
在設定多個規則通道時,需要在SQR1暫存器的位23:20中寫入總共有多少個規則通道需要被轉換,然後再SQR1、SQR2、SQR3中設定每個通道的順序。
注入通道轉換順序
和規則通道轉換順序的控制一樣,注入通道的轉換也是通過注入暫存器來控制,只不過只有一個JSQR暫存器來控制,控制關係如下:
需要注意的是,只有當JL=4的時候,注入通道的轉換順序才會按照JSQ1、JSQ2、JSQ3、JSQ4的順序執行。當JL<4時,注入通道的轉換順序恰恰相反,也就是執行順序為:JSQ4、JSQ3、JSQ2、JSQ1。這個在設定的時候需要注意下。
(5)第6部分,通過地址資料匯流排從第4、5部分的資料暫存器讀取轉換後的資料。或者通過DMA請求,不經過CPU直接轉移資料。
(6)第7部分時關於中斷的。關於ADC轉換有3箇中斷:規則轉換結束中斷、注入轉換結束中斷、看門狗比較中斷,既然產生中斷了就要涉及到NVIC了。
(7)在設定好上面的部分,就可以開始轉換了,但是什麼時候開始轉換,可以手動開始轉換,也可以使用其他事件觸發開始轉換。由圖1中紅色框圖8、9、10選擇。事件觸發又分成2種,一種是觸發規則通道的轉換,另一種時觸發注入通道的轉換。
手動觸發轉換:11.12.3 ADC控制暫存器2(ADC_CR2)
其他事件觸發轉換:11.12.3 ADC控制暫存器2(ADC_CR2)
觸發規則轉換:
觸發注入轉換:
需要注意的是:還有兩個位EXTTRIG和JEXTTRIG用來作為開關,是否開啟其他事件能夠觸發ADC的轉換。
在定時器的框圖中,下圖紅色框2中,就是用來觸發ADC轉換的事件。
2.ADC結構體配置
typedef struct { uint32_t ADC_Mode; // ADC 工作模式選擇 FunctionalState ADC_ScanConvMode; // ADC 掃描(多通道)或者單次(單通道)模式選擇 FunctionalState ADC_ContinuousConvMode; // ADC 單次轉換或者連續轉換選擇 uint32_t ADC_ExternalTrigConv; // ADC 轉換觸發訊號選擇 uint32_t ADC_DataAlign; // ADC 資料暫存器對齊格式 uint8_t ADC_NbrOfChannel; // ADC 採集通道數 } ADC_InitTypeDef;
1.工作模式:Configures the ADC to operate in independent ordual mode 獨立模式(只用一個ADC)還是雙ADC模式。
2.掃描或者單次:This parameter can be set to ENABLE or DISABLE
3.單次轉換或者多次轉換:This parameter can be set to ENABLE or DISABLE
4.ADC 轉換觸發訊號選擇:可以選擇ADC_ExternalTrigConv_None代表不適用外部事件觸發,或者選擇要觸發的具體事件
5.對齊格式:左對齊或者右對齊。因為是16位暫存器,轉換結果是12位的,
6.採集通道數量:總共需要採集的通道數量(區分規則還是注入,不混合計算)
關於獨立模式、連續轉換、掃描轉換可能有些模糊,舉個例子具體說明一下區別:
用ADC1 規則通道的順序為CH0,CH1,CH2,CH3,
不啟動掃描模式
在單次轉換模式下:
啟動ADC1,則
1.開始轉換CH1(ADCx_SQR的第一通道)
轉換完成後停止,等待ADC的下一次啟動,繼續從第一步開始轉換
在連續轉換模式下:
啟動ADC1,則
1.開始轉換CH0(ADC_SQR的第一通道)
轉換完成後回到第一步。
啟動掃描模式下
在單次轉換模式下:
啟動ADC1,則
1.開始轉換CH0、
2.轉換完成後自動開始轉換CH1
3.轉換完成後自動開始轉換CH2
4.轉換完成後自動開始轉換CH3
5.轉換完成後停止,等待ADC的下一次啟動下一次ADC啟動後從第一步開始轉換
在連續轉換模式下:
啟動ADC1,則
1.開始轉換CH0
2.轉換完成後自動開始轉換CH1
3.轉換完成後自動開始轉換CH2
4.轉換完成後自動開始轉換CH3
5.轉換完成後返回第一步繼續轉換CH0
3.結合實驗理解ADC的使用方法
3.1ADC採集電壓-單規則通道-中斷
使用ADC2,CH11,IO為PC1
1 static void ADCx_GPIO_Config(void)//PC1配置 2 { 3 GPIO_InitTypeDef GPIO_InitStructure; 4 5 // 開啟 ADC IO埠時鐘 6 ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE ); 7 8 // 配置 ADC IO 引腳模式 9 // 必須為模擬輸入 10 GPIO_InitStructure.GPIO_Pin = ADC_PIN; 11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; 12 13 // 初始化 ADC IO 14 GPIO_Init(ADC_PORT, &GPIO_InitStructure); 15 } 16 17 /** 18 * @brief 配置ADC工作模式 19 * @param 無 20 * @retval 無 21 */ 22 static void ADCx_Mode_Config(void) 23 { 24 ADC_InitTypeDef ADC_InitStructure; 25 26 // 開啟ADC時鐘 27 ADC_APBxClock_FUN ( ADC_CLK, ENABLE ); 28 29 // ADC 模式配置 30 // 只使用一個ADC,屬於獨立模式 31 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 32 33 // 禁止掃描模式,多通道才要,單通道不需要 34 ADC_InitStructure.ADC_ScanConvMode = DISABLE ; 35 36 // 連續轉換模式 37 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 38 39 // 不用外部觸發轉換,軟體開啟即可 40 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; 41 42 // 轉換結果右對齊 43 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; 44 45 // 轉換通道1個 46 ADC_InitStructure.ADC_NbrOfChannel = 1; 47 48 // 初始化ADC 49 ADC_Init(ADCx, &ADC_InitStructure); 50 51 // 配置ADC時鐘為PCLK2的8分頻,即9MHz 52 RCC_ADCCLKConfig(RCC_PCLK2_Div8); 53 54 // 配置 ADC 通道轉換順序和取樣時間 55 ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, 56 ADC_SampleTime_55Cycles5); 57 58 // ADC 轉換結束產生中斷,在中斷服務程式中讀取轉換值 59 ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE); 60 61 // 開啟ADC ,並開始轉換 62 ADC_Cmd(ADCx, ENABLE); 63 64 // 初始化ADC 校準暫存器 65 ADC_ResetCalibration(ADCx); 66 // 等待校準暫存器初始化完成 67 while(ADC_GetResetCalibrationStatus(ADCx)); 68 69 // ADC開始校準 70 ADC_StartCalibration(ADCx); 71 // 等待校準完成 72 while(ADC_GetCalibrationStatus(ADCx)); 73 74 // 由於沒有采用外部觸發,所以使用軟體觸發ADC轉換 75 ADC_SoftwareStartConvCmd(ADCx, ENABLE); 76 } 77 78 static void ADC_NVIC_Config(void)//中斷配置 79 { 80 NVIC_InitTypeDef NVIC_InitStructure; 81 // 優先順序分組 82 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 83 84 // 配置中斷優先順序 85 NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ; 86 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 87 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 88 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 89 NVIC_Init(&NVIC_InitStructure); 90 }
ADC的規則轉換結束中斷函式:
1 void ADC_IRQHandler(void) //ADC中斷 2 { 3 if (ADC_GetITStatus(ADCx,ADC_IT_EOC)==SET) //判斷是否是轉換結束 4 { 5 // 讀取ADC的轉換值 6 ADC_ConvertedValue = ADC_GetConversionValue(ADCx);//通過庫函式把讀取到的值賦值給變數 ADC_ConvertedValue
7 }
8 ADC_ClearITPendingBit(ADCx,ADC_IT_EOC); }
對於只使用一個通道來說,使用中斷也可以。下圖是把3.3V接到PC1之後的結果。
3.2ADC採集電壓-單規則通道-DMA
上面使用中斷的方式,現在使用DMA方式,讀取一個通道的資料,基本配置一樣,去掉了ADC轉換結束的中斷,添加了DMA初始化結構體,因為要使用DMA傳輸,DMA也是一個外設,在使用一個外設的時候,肯定是要初始化這個外設的結構體的。這裡使用DMA和ADC的時候只要注意幾點既可以了:
1.傳輸方向:ADC的轉換結果資料暫存器->記憶體(一個變數)
2.傳輸大小:16位
3.外設地址和記憶體地址都不自增
4.DMA傳輸模式:迴圈模式DMA_Mode_Circular
5.注意區分使能DMA通道,和使能ADC的DMA請求
// 使能 DMA 通道 。讓DMA通道就緒 52 DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
82 // 使能ADC DMA 請求 。讓ADC去使用已經就緒的DMA通道。在這一步才開始進行DMA傳輸 83 ADC_DMACmd(ADCx, ENABLE);
98 // 由於沒有采用外部觸發,所以使用軟體觸發ADC轉換 。開啟ADC轉換 99 ADC_SoftwareStartConvCmd(ADCx, ENABLE);
6.這裡只有一個ADC規則通道,需要對這一個通道進行配置。下個實驗,多規則通道使用DMA,也要單獨對每個通道設定。
// 配置 ADC 通道轉換順序為1,第一個轉換,取樣時間為55.5個時鐘週期 80 ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
7.如果使用DMA的3箇中斷(傳輸一半、傳輸完成、傳輸出錯),則配置NVIC。
下面是具體程式碼:
1 static void ADCx_Mode_Config(void) 2 { 3 DMA_InitTypeDef DMA_InitStructure; 4 ADC_InitTypeDef ADC_InitStructure; 5 6 // 開啟DMA時鐘 7 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 8 // 開啟ADC時鐘 9 ADC_APBxClock_FUN ( ADC_CLK, ENABLE ); 10 11 // 復位DMA控制器 12 DMA_DeInit(ADC_DMA_CHANNEL); 13 14 // 配置 DMA 初始化結構體 15 // 外設基址為:ADC 資料暫存器地址 16 DMA_InitStructure.DMA_PeripheralBaseAddr = ( uint32_t ) ( & ( ADCx->DR ) ); 17 18 // 儲存器地址,實際上就是一個內部SRAM的變數 19 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; 20 21 // 資料來源來自外設 22 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 23 24 // 緩衝區大小為1,緩衝區的大小應該等於儲存器的大小 25 DMA_InitStructure.DMA_BufferSize = 1; 26 27 // 外設暫存器只有一個,地址不用遞增 28 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 29 30 // 儲存器地址固定 31 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; 32 33 // 外設資料大小為半字,即兩個位元組 34 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 35 36 // 儲存器資料大小也為半字,跟外設資料大小相同 37 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; 38 39 // 迴圈傳輸模式 40 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 41 42 // DMA 傳輸通道優先順序為高,當使用一個DMA通道時,優先順序設定不影響 43 DMA_InitStructure.DMA_Priority = DMA_Priority_High; 44 45 // 禁止儲存器到儲存器模式,因為是從外設到儲存器 46 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 47 48 // 初始化DMA 49 DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure); 50 51 // 使能 DMA 通道 52 DMA_Cmd(ADC_DMA_CHANNEL , ENABLE); 53 54 // ADC 模式配置 55 // 只使用一個ADC,屬於單模式 56 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 57 58 // 禁止掃描模式,多通道才要,單通道不需要 59 ADC_InitStructure.ADC_ScanConvMode = DISABLE ; 60 61 // 連續轉換模式 62 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; 63 64 // 不用外部觸發轉換,軟體開啟即可 65 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; 66 67 // 轉換結果右對齊 68 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; 69 70 // 轉換通道1個 71 ADC_InitStructure.ADC_NbrOfChannel = 1; 72 73 // 初始化ADC 74 ADC_Init(ADCx, &ADC_InitStructure); 75 76 // 配置ADC時鐘為PCLK2的8分頻,即9MHz 77 RCC_ADCCLKConfig(RCC_PCLK2_Div8); 78 79 // 配置 ADC 通道轉換順序為1,第一個轉換,取樣時間為55.5個時鐘週期 80 ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5); 81 82 // 使能ADC DMA 請求 83 ADC_DMACmd(ADCx, ENABLE); 84 85 // 開啟ADC ,並開始轉換 86 ADC_Cmd(ADCx, ENABLE); 87 88 // 初始化ADC 校準暫存器 89 ADC_ResetCalibration(ADCx); 90 // 等待校準暫存器初始化完成 91 while(ADC_GetResetCalibrationStatus(ADCx)); 92 93 // ADC開始校準 94 ADC_StartCalibration(ADCx); 95 // 等待校準完成 96 while(ADC_GetCalibrationStatus(ADCx)); 97 98 // 由於沒有采用外部觸發,所以使用軟體觸發ADC轉換 99 ADC_SoftwareStartConvCmd(ADCx, ENABLE); 100 }
把3.3V電壓接到PC1上:
3.3ADC採集電壓-多規則通道-DMA
使用ADC1的通道11-通道14,對應PC1-PC4。多通道對於單通道來說,只要注意幾點即可:
1.使用4個通道要使用4個變數接收對應通道的轉換資料。
2.使用一個數組,接收4個通道的資料。目的是為了記憶體地址自增時方便。
3.4個規則通道要進行單獨配置。
1 // 配置ADC 通道的轉換順序和取樣時間 2 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5); 3 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5); 4 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 3, ADC_SampleTime_55Cycles5); 5 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_55Cycles5); 6 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 5, ADC_SampleTime_55Cycles5); 7 ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 6, ADC_SampleTime_55Cycles5);
3.4ADC採集電壓-雙ADC採集-同步規則
上面使用的時候,都是使用單獨某一個ADC。也就是模式設定為ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;如果使用雙重ADC,則設定模式為其他選項
使用雙重ADC模式就是為了讓2個ADC同時採集一個通道的訊號,雙重 ADC 模式較獨立模式一個最大的優勢就是提高了取樣率,彌補了單個 ADC 取樣不夠快的缺點。暫時沒用到,後面用到了再來寫吧。