一、ADC簡介
ADC(Analog-to-Digital Converter),即模擬-數字轉換器,可以將連續變化的模擬信號轉換爲離散的數字信號,進而使用數字電路進行處理,稱之爲數字信號處理。
STM32f103 系列有 3 個 ADC,精度爲 12 位,每個 ADC 最多有 16 個外部通道。其中 ADC1 和 ADC2 都有 16 個外部通道,ADC3 根據 CPU 引腳的不同通道數也不同,一般都有 8 個外部通道。各通道的A/D轉換可以單次、連續、掃描或間斷模式執行。ADC的結果可以左對齊或右對齊方式存儲在16位數據寄存器中。模擬看門狗特性允許應用程序檢測輸入電壓是否超出用戶定義的高/低閥值。ADC 的輸入時鐘不得超過14MHz,它是由PCLK2經分頻產生。
二、ADC通道選擇
STM32 的 ADC 多達 18 個通道,其中外部的 16 個通道就是框圖中的 ADCx_IN0、ADCx_IN1…ADCx_IN5。這 16 個通道對應着不同的 IO 口,具體是哪一個 IO 口可以從手冊查詢到。其中 ADC1/2/3 還有內部通道:ADC1 的通道 16 連接到了芯片內部的溫度傳感器,Vrefint 連接到了通道 17。ADC2 的模擬通道 16 和 17 連接到了內部的 VSS。ADC3 的模擬通道 9、14、15、16 和 17 連接到了內部的 VSS。
三、引腳確定
開發闆闆載一個貼片滑動變阻器,引腳爲 PC1,對應 ADC1 的通道 11
四、新建工程
1. 打開 STM32CubeMX 軟件,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設置,選擇 HSE(外部高速時鐘) 爲 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration,配置系統時鐘 SYSCLK 爲 72MHz
修改 HCLK 的值爲 72 後,輸入回車,軟件會自動修改所有配置
4. 配置調試模式
非常重要的一步,否則會造成第一次燒錄程序後續無法識別調試器
SYS 設置,選擇 Debug 爲 Serial Wire
五、ADC1
5.1 參數配置
在 Analog
中選擇 ADC1
設置,並選擇 IN11
通道11
或者在右邊圖找到 PC1
引腳,選擇 ADC1_IN11
具體配置參數如下。
- ADCs_Common_Settings:
- Mode:
Independent mod
獨立 ADC 模式,當使用一個 ADC 時是獨立模式,使用兩個 ADC 時是雙模式,在雙模式下還有很多細分模式可選,具體配置 ADC_CR1:DUALMOD 位。
- Mode:
- ADC_Settings:
- Data Alignment:
Right alignment
轉換結果數據右對齊,一般我們選擇右對齊模式。
Left alignment
轉換結果數據左對齊。 - Scan Conversion Mode:
Disabled
禁止掃描模式。如果是單通道 AD 轉換使用 DISABLE。
Enabled
開啓掃描模式。如果是多通道 AD 轉換使用 ENABLE。 - Continuous Conversion Mode:
Disabled
單次轉換。轉換一次後停止需要手動控制才重新啓動轉換。
Enabled
自動連續轉換。 - DiscontinuousConvMode:
Disabled
禁止間斷模式。這個在需要考慮功耗問題的產品中很有必要,也就是在某個事件觸發下,開啓轉換。
Enabled
開啓間斷模式。
- Data Alignment:
- ADC_Regular_ConversionMode:
Enable Regular Conversions
是否使能規則轉換。
Number Of Conversion
ADC轉換通道數目,有幾個寫幾個就行。
External Trigger Conversion Source
外部觸發選擇。這個有多個選擇,一般採用軟件觸發方式。 - Rank:
Channel
ADC轉換通道
Sampling Time
採樣週期選擇,採樣週期越短,ADC 轉換數據輸出週期就越短但數據精度也越低,採樣週期越長,ADC 轉換數據輸出週期就越長同時數據精度越高。 - ADC_Injected_ConversionMode:
Enable Injected Conversions
是否使能注入轉換。注入通道只有在規則通道存在時纔會出現。 - WatchDog:
Enable Analog WatchDog Mode
是否使能模擬看門狗中斷。當被 ADC 轉換的模擬電壓低於低閾值或者高於高閾值時,就會產生中斷。
5.2 配置NVIC
使能 ADC 中斷
5.3 ADC時鐘配置
ADC 的轉換時間跟 ADC 的輸入時鐘和採樣時間有關。
公式爲:Tconv = 採樣時間 + 12.5 個週期。當 ADCLK = 14MHZ (最高),採樣時間設置爲 1.5 週期(最快),那麼總的轉換時間(最短)Tconv = 1.5 週期 + 12.5 週期 = 14 週期 = 1us。
一般我們設置 PCLK2=72M,經過 ADC 預分頻器能分頻到最大的時鐘只能是 12M
,採樣週期設置爲 1.5 個週期,算出最短的轉換時間爲 1.17us,這個纔是最常用的。
5.4 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發環境 MDK-ARM V5
每個外設生成獨立的 ’.c/.h’
文件
不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設文件。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
六、獨立模式單通道採集中斷方式
單通道採集適用 AD 轉換完成中斷,在中斷服務函數中讀取數據,不使用 DMA 傳輸,在多通道採集時才使用 DMA 傳輸。
6.1 修改中斷回調函數
打開 stm32f1xx_it.c
中斷服務函數文件,找到 ADC1 中斷的服務函數 ADC1_2_IRQHandler()
中斷服務函數裏面就調用了 ADC 中斷處理函數 HAL_ADC_IRQHandler()
打開 stm32f1xx_hal_adc.c
文件,找到 ADC 中斷處理函數原型 HAL_ADC_IRQHandler()
,其主要作用就是判斷是哪個 ADC 產生中斷,清除中斷標識位,然後調用中斷回調函數 HAL_ADC_ConvCpltCallback()
。
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
這個函數不應該被改變,如果需要使用回調函數,請重新在用戶文件中實現該函數。
HAL_ADC_ConvCpltCallback()
按照官方提示我們應該再次定義該函數,__weak
是一個弱化標識,帶有這個的函數就是一個弱化函數,就是你可以在其他地方寫一個名稱和參數都一模一樣的函數,編譯器就會忽略這一個函數,而去執行你寫的那個函數;而 UNUSED(hadc)
,這就是一個防報錯的定義,當傳進來的ADC號沒有做任何處理的時候,編譯器也不會報出警告。其實我們在開發的時候已經不需要去理會中斷服務函數了,只需要找到這個中斷回調函數並將其重寫即可而這個回調函數還有一點非常便利的地方這裏沒有體現出來,就是當同時有多箇中斷使能的時候,STM32CubeMX會自動地將幾個中斷的服務函數規整到一起並調用一個回調函數,也就是無論幾個中斷,我們只需要重寫一個回調函並判斷傳進來的定時器號即可。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_ADC_ConvCpltCallback()
/* USER CODE BEGIN EV */
extern __IO uint32_t ADC_ConvertedValue;
/* USER CODE END EV */
/* USER CODE BEGIN 1 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
ADC_ConvertedValue = HAL_ADC_GetValue(hadc);
}
/* USER CODE END 1 */
在中斷回調函數中進行讀取數據,將數據存放在變量 ADC_ConvertedValue
中。
6.2 添加全局變量
在 main.c 定義相關變量。
// ADC轉換值
__IO uint32_t ADC_ConvertedValue;
// 用於保存轉換計算後的電壓值
float ADC_Vol;
6.3 添加ADC中斷啓動函數
在 main.c 中,while 循環前,ADC初始化後,添加ADC中斷開啓函數,這樣在第一次接收到數據的時候纔會觸發中斷。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_USART1_UART_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1); //AD校準
HAL_ADC_Start_IT(&hadc1); //開啓ADC中斷轉換
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
6.4 添加電壓值轉換
模擬電壓經過 ADC 轉換後,是一個 12 位的數字值,如果通過串口以 16 進制打印出來的話,可讀性比較差,那麼有時候我們就需要把數字電壓轉換成模擬電壓,也可以跟實際的模擬電壓(用萬用表測)對比,看看轉換是否準確。
我們一般在設計原理圖的時候會把 ADC 的輸入電壓範圍設定在:0~3.3v
,因爲 ADC 是 12 位的,那麼 12 位滿量程對應的就是 3.3V,12 位滿量程對應的數字值是:2^12。數值 0 對應的就是 0V。如果轉換後的數值爲 X ,X 對應的模擬電壓爲 Y,那麼會有這麼一個等式成立: 2^12 / 3.3 = X / Y,=> Y = (3.3 * X ) / 2^12。
串口打印功能查看 STM32CubeMX學習筆記(6)——USART串口使用
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
ADC_Vol =(float) ADC_ConvertedValue/4096*3.3; // 讀取轉換的AD倿
printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue);
printf("The current AD value = %f V \r\n\r\n",ADC_Vol); //實際電壓倿
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
6.5 HAL庫與標準庫代碼比較
STM32CubeMX 使用 HAL 庫生成的代碼:
__IO uint32_t ADC_ConvertedValue;
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {
0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_11;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief This function handles ADC1 and ADC2 global interrupts.
*/
void ADC1_2_IRQHandler(void)
{
/* USER CODE BEGIN ADC1_2_IRQn 0 */
/* USER CODE END ADC1_2_IRQn 0 */
HAL_ADC_IRQHandler(&hadc1);
/* USER CODE BEGIN ADC1_2_IRQn 1 */
/* USER CODE END ADC1_2_IRQn 1 */
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
ADC_ConvertedValue = HAL_ADC_GetValue(hadc);
}
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_IT(&hadc1);
使用 STM32 標準庫的代碼:
__IO uint16_t ADC_ConvertedValue;
/**
* @brief ADC GPIO 初始化
* @param 無
* @retval 無
*/
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 打開 ADC IO端口時鐘
ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
// 配置 ADC IO 引腳模式
// 必須爲模擬輸入
GPIO_InitStructure.GPIO_Pin = ADC_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
// 初始化 ADC IO
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
/**
* @brief 配置ADC工作模式
* @param 無
* @retval 無
*/
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 打開ADC時鐘
ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
// ADC 模式配置
// 只使用一個ADC,屬於獨立模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 禁止掃描模式,多通道纔要,單通道不需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE ;
// 連續轉換模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部觸發轉換,軟件開啓即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 轉換結果右對齊
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 轉換通道1個
ADC_InitStructure.ADC_NbrOfChannel = 1;
// 初始化ADC
ADC_Init(ADCx, &ADC_InitStructure);
// 配置ADC時鐘爲PCLK2的8分頻,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置 ADC 通道轉換順序和採樣時間
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1,
ADC_SampleTime_55Cycles5);
// ADC 轉換結束產生中斷,在中斷服務程序中讀取轉換值
ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
// 開啓ADC ,並開始轉換
ADC_Cmd(ADCx, ENABLE);
// 初始化ADC 校準寄存器
ADC_ResetCalibration(ADCx);
// 等待校準寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx));
// ADC開始校準
ADC_StartCalibration(ADCx);
// 等待校準完成
while(ADC_GetCalibrationStatus(ADCx));
// 由於沒有采用外部觸發,所以使用軟件觸發ADC轉換
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
static void ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 優先級分組
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 配置中斷優先級
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief ADC初始化
* @param 無
* @retval 無
*/
void ADCx_Init(void)
{
ADCx_GPIO_Config();
ADCx_Mode_Config();
ADC_NVIC_Config();
}
void ADC_IRQHandler(void)
{
if (ADC_GetITStatus(ADCx,ADC_IT_EOC)==SET)
{
// 讀取ADC的轉換值
ADC_ConvertedValue = ADC_GetConversionValue(ADCx);
}
ADC_ClearITPendingBit(ADCx,ADC_IT_EOC);
}
MX_ADC1_Init();
對應 ADCx_GPIO_Config();ADCx_Mode_Config();ADC_NVIC_Config();
HAL_ADC_Init(&hadc1)
對應 ADC_Init(ADCx, &ADC_InitStructure)
HAL_ADCEx_Calibration_Start(&hadc1);
對應 ADC_StartCalibration(ADCx);
HAL_ADC_Start_IT(&hadc1);
對應 ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
HAL_ADC_GetValue(hadc);
對應 ADC_GetConversionValue(ADCx);
七、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間,否則下次使用 STM32CubeMX 重新生成代碼後,會被刪除。
• 由 Leung 寫於 2021 年 1 月 19 日
• 參考:STM32CubeMX系列教程7:模數轉換(ADC)
《嵌入式-STM32開發指南》第二部分 基礎篇 - 第8章 模擬輸入輸出-ADC(HAL庫)