一惦蚊、ADC簡介
ADC(Analog-to-Digital Converter),即模擬-數(shù)字轉(zhuǎn)換器,可以將連續(xù)變化的模擬信號轉(zhuǎn)換為離散的數(shù)字信號逃贝,進(jìn)而使用數(shù)字電路進(jìn)行處理,稱之為數(shù)字信號處理迫摔。
STM32f103 系列有 3 個 ADC沐扳,精度為 12 位,每個 ADC 最多有 16 個外部通道句占。其中 ADC1 和 ADC2 都有 16 個外部通道沪摄,ADC3 根據(jù) CPU 引腳的不同通道數(shù)也不同,一般都有 8 個外部通道纱烘。各通道的A/D轉(zhuǎn)換可以單次杨拐、連續(xù)、掃描或間斷模式執(zhí)行凹炸。ADC的結(jié)果可以左對齊或右對齊方式存儲在16位數(shù)據(jù)寄存器中戏阅。模擬看門狗特性允許應(yīng)用程序檢測輸入電壓是否超出用戶定義的高/低閥值。ADC 的輸入時鐘不得超過14MHz啤它,它是由PCLK2經(jīng)分頻產(chǎn)生奕筐。
二、ADC通道選擇
STM32 的 ADC 多達(dá) 18 個通道变骡,其中外部的 16 個通道就是框圖中的 ADCx_IN0离赫、ADCx_IN1...ADCx_IN5。這 16 個通道對應(yīng)著不同的 IO 口塌碌,具體是哪一個 IO 口可以從手冊查詢到渊胸。其中 ADC1/2/3 還有內(nèi)部通道:ADC1 的通道 16 連接到了芯片內(nèi)部的溫度傳感器,Vrefint 連接到了通道 17台妆。ADC2 的模擬通道 16 和 17 連接到了內(nèi)部的 VSS翎猛。ADC3 的模擬通道 9胖翰、14、15切厘、16 和 17 連接到了內(nèi)部的 VSS萨咳。
三、引腳確定
開發(fā)板板載一個貼片滑動變阻器疫稿,引腳為 PC1培他,對應(yīng) ADC1 的通道 11
四、新建工程
1. 打開 STM32CubeMX 軟件遗座,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設(shè)置舀凛,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration,配置系統(tǒng)時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后途蒋,輸入回車猛遍,軟件會自動修改所有配置
4. 配置調(diào)試模式
非常重要的一步,否則會造成第一次燒錄程序后續(xù)無法識別調(diào)試器
SYS 設(shè)置碎绎,選擇 Debug 為 Serial Wire
五螃壤、ADC1
5.1 參數(shù)配置
在 Analog
中選擇 ADC1
設(shè)置,并選擇 IN11
通道11
或者在右邊圖找到
PC1
引腳筋帖,選擇 ADC1_IN11
具體配置參數(shù)如下奸晴。
-
ADCs_Common_Settings:
-
Mode:
Independent mod
獨立 ADC 模式,當(dāng)使用一個 ADC 時是獨立模式日麸,使用兩個 ADC 時是雙模式寄啼,在雙模式下還有很多細(xì)分模式可選,具體配置 ADC_CR1:DUALMOD 位代箭。
-
Mode:
-
ADC_Settings:
-
Data Alignment:
Right alignment
轉(zhuǎn)換結(jié)果數(shù)據(jù)右對齊墩划,一般我們選擇右對齊模式。
Left alignment
轉(zhuǎn)換結(jié)果數(shù)據(jù)左對齊嗡综。 -
Scan Conversion Mode:
Disabled
禁止掃描模式乙帮。如果是單通道 AD 轉(zhuǎn)換使用 DISABLE。
Enabled
開啟掃描模式极景。如果是多通道 AD 轉(zhuǎn)換使用 ENABLE察净。 -
Continuous Conversion Mode:
Disabled
單次轉(zhuǎn)換。轉(zhuǎn)換一次后停止需要手動控制才重新啟動轉(zhuǎn)換盼樟。
Enabled
自動連續(xù)轉(zhuǎn)換氢卡。 -
DiscontinuousConvMode:
Disabled
禁止間斷模式。這個在需要考慮功耗問題的產(chǎn)品中很有必要晨缴,也就是在某個事件觸發(fā)下译秦,開啟轉(zhuǎn)換。
Enabled
開啟間斷模式。
-
Data Alignment:
-
ADC_Regular_ConversionMode:
Enable Regular Conversions
是否使能規(guī)則轉(zhuǎn)換筑悴。
Number Of Conversion
ADC轉(zhuǎn)換通道數(shù)目们拙,有幾個寫幾個就行。
External Trigger Conversion Source
外部觸發(fā)選擇阁吝。這個有多個選擇睛竣,一般采用軟件觸發(fā)方式。 -
Rank:
Channel
ADC轉(zhuǎn)換通道
Sampling Time
采樣周期選擇求摇,采樣周期越短,ADC 轉(zhuǎn)換數(shù)據(jù)輸出周期就越短但數(shù)據(jù)精度也越低殊者,采樣周期越長与境,ADC 轉(zhuǎn)換數(shù)據(jù)輸出周期就越長同時數(shù)據(jù)精度越高。 -
ADC_Injected_ConversionMode:
Enable Injected Conversions
是否使能注入轉(zhuǎn)換猖吴。注入通道只有在規(guī)則通道存在時才會出現(xiàn)摔刁。 -
WatchDog:
Enable Analog WatchDog Mode
是否使能模擬看門狗中斷。當(dāng)被 ADC 轉(zhuǎn)換的模擬電壓低于低閾值或者高于高閾值時海蔽,就會產(chǎn)生中斷共屈。
5.2 配置NVIC
使能 ADC 中斷
5.3 ADC時鐘配置
ADC 的轉(zhuǎn)換時間跟 ADC 的輸入時鐘和采樣時間有關(guān)。
公式為:Tconv = 采樣時間 + 12.5 個周期党窜。當(dāng) ADCLK = 14MHZ (最高)拗引,采樣時間設(shè)置為 1.5 周期(最快),那么總的轉(zhuǎn)換時間(最短)Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us幌衣。
一般我們設(shè)置 PCLK2=72M矾削,經(jīng)過 ADC 預(yù)分頻器能分頻到最大的時鐘只能是 12M
,采樣周期設(shè)置為 1.5 個周期豁护,算出最短的轉(zhuǎn)換時間為 1.17us哼凯,這個才是最常用的。
5.4 生成代碼
輸入項目名和項目路徑
選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5
每個外設(shè)生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應(yīng)的外設(shè)文件楚里。 如 GPIO 初始化代碼生成在 gpio.c 中断部。
點擊 GENERATE CODE 生成代碼
六、獨立模式單通道采集中斷方式
單通道采集適用 AD 轉(zhuǎn)換完成中斷班缎,在中斷服務(wù)函數(shù)中讀取數(shù)據(jù)蝴光,不使用 DMA 傳輸,在多通道采集時才使用 DMA 傳輸吝梅。
6.1 修改中斷回調(diào)函數(shù)
打開 stm32f1xx_it.c
中斷服務(wù)函數(shù)文件虱疏,找到 ADC1 中斷的服務(wù)函數(shù) ADC1_2_IRQHandler()
中斷服務(wù)函數(shù)里面就調(diào)用了 ADC 中斷處理函數(shù) HAL_ADC_IRQHandler()
打開 stm32f1xx_hal_adc.c
文件,找到 ADC 中斷處理函數(shù)原型 HAL_ADC_IRQHandler()
苏携,其主要作用就是判斷是哪個 ADC 產(chǎn)生中斷做瞪,清除中斷標(biāo)識位,然后調(diào)用中斷回調(diào)函數(shù) 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
*/
這個函數(shù)不應(yīng)該被改變装蓬,如果需要使用回調(diào)函數(shù)著拭,請重新在用戶文件中實現(xiàn)該函數(shù)。
HAL_ADC_ConvCpltCallback()
按照官方提示我們應(yīng)該再次定義該函數(shù)牍帚,__weak
是一個弱化標(biāo)識儡遮,帶有這個的函數(shù)就是一個弱化函數(shù),就是你可以在其他地方寫一個名稱和參數(shù)都一模一樣的函數(shù)暗赶,編譯器就會忽略這一個函數(shù)鄙币,而去執(zhí)行你寫的那個函數(shù);而 UNUSED(hadc)
蹂随,這就是一個防報錯的定義十嘿,當(dāng)傳進(jìn)來的ADC號沒有做任何處理的時候,編譯器也不會報出警告岳锁。其實我們在開發(fā)的時候已經(jīng)不需要去理會中斷服務(wù)函數(shù)了绩衷,只需要找到這個中斷回調(diào)函數(shù)并將其重寫即可而這個回調(diào)函數(shù)還有一點非常便利的地方這里沒有體現(xiàn)出來,就是當(dāng)同時有多個中斷使能的時候激率,STM32CubeMX會自動地將幾個中斷的服務(wù)函數(shù)規(guī)整到一起并調(diào)用一個回調(diào)函數(shù)咳燕,也就是無論幾個中斷,我們只需要重寫一個回調(diào)函并判斷傳進(jìn)來的定時器號即可乒躺。
接下來我們就在 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 */
在中斷回調(diào)函數(shù)中進(jìn)行讀取數(shù)據(jù)招盲,將數(shù)據(jù)存放在變量 ADC_ConvertedValue
中。
6.2 添加全局變量
在 main.c 定義相關(guān)變量聪蘸。
// ADC轉(zhuǎn)換值
__IO uint32_t ADC_ConvertedValue;
// 用于保存轉(zhuǎn)換計算后的電壓值
float ADC_Vol;
6.3 添加ADC中斷啟動函數(shù)
在 main.c 中宪肖,while 循環(huán)前,ADC初始化后健爬,添加ADC中斷開啟函數(shù)控乾,這樣在第一次接收到數(shù)據(jù)的時候才會觸發(fā)中斷。
/**
* @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校準(zhǔn)
HAL_ADC_Start_IT(&hadc1); //開啟ADC中斷轉(zhuǎn)換
/* 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 添加電壓值轉(zhuǎn)換
模擬電壓經(jīng)過 ADC 轉(zhuǎn)換后娜遵,是一個 12 位的數(shù)字值蜕衡,如果通過串口以 16 進(jìn)制打印出來的話,可讀性比較差设拟,那么有時候我們就需要把數(shù)字電壓轉(zhuǎn)換成模擬電壓慨仿,也可以跟實際的模擬電壓(用萬用表測)對比,看看轉(zhuǎn)換是否準(zhǔn)確纳胧。
我們一般在設(shè)計原理圖的時候會把 ADC 的輸入電壓范圍設(shè)定在:0~3.3v
镰吆,因為 ADC 是 12 位的,那么 12 位滿量程對應(yīng)的就是 3.3V跑慕,12 位滿量程對應(yīng)的數(shù)字值是:2^12万皿。數(shù)值 0 對應(yīng)的就是 0V摧找。如果轉(zhuǎn)換后的數(shù)值為 X ,X 對應(yīng)的模擬電壓為 Y牢硅,那么會有這么一個等式成立: 2^12 / 3.3 = X / Y蹬耘,=> Y = (3.3 * X ) / 2^12。
串口打印功能查看 STM32CubeMX學(xué)習(xí)筆記(6)——USART串口使用
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
ADC_Vol =(float) ADC_ConvertedValue/4096*3.3; // 讀取轉(zhuǎn)換的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庫與標(biāo)準(zhǔn)庫代碼比較
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 標(biāo)準(zhǔn)庫的代碼:
__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 ;
// 連續(xù)轉(zhuǎn)換模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部觸發(fā)轉(zhuǎn)換位岔,軟件開啟即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 轉(zhuǎn)換結(jié)果右對齊
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 轉(zhuǎn)換通道1個
ADC_InitStructure.ADC_NbrOfChannel = 1;
// 初始化ADC
ADC_Init(ADCx, &ADC_InitStructure);
// 配置ADC時鐘為PCLK2的8分頻如筛,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置 ADC 通道轉(zhuǎn)換順序和采樣時間
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1,
ADC_SampleTime_55Cycles5);
// ADC 轉(zhuǎn)換結(jié)束產(chǎn)生中斷,在中斷服務(wù)程序中讀取轉(zhuǎn)換值
ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
// 開啟ADC 抒抬,并開始轉(zhuǎn)換
ADC_Cmd(ADCx, ENABLE);
// 初始化ADC 校準(zhǔn)寄存器
ADC_ResetCalibration(ADCx);
// 等待校準(zhǔn)寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx));
// ADC開始校準(zhǔn)
ADC_StartCalibration(ADCx);
// 等待校準(zhǔn)完成
while(ADC_GetCalibrationStatus(ADCx));
// 由于沒有采用外部觸發(fā)妙黍,所以使用軟件觸發(fā)ADC轉(zhuǎn)換
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
static void ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 優(yōu)先級分組
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 配置中斷優(yōu)先級
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的轉(zhuǎn)換值
ADC_ConvertedValue = ADC_GetConversionValue(ADCx);
}
ADC_ClearITPendingBit(ADCx,ADC_IT_EOC);
}
MX_ADC1_Init();
對應(yīng) ADCx_GPIO_Config();ADCx_Mode_Config();ADC_NVIC_Config();
HAL_ADC_Init(&hadc1)
對應(yīng) ADC_Init(ADCx, &ADC_InitStructure)
HAL_ADCEx_Calibration_Start(&hadc1);
對應(yīng) ADC_StartCalibration(ADCx);
HAL_ADC_Start_IT(&hadc1);
對應(yīng) ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
HAL_ADC_GetValue(hadc);
對應(yīng) ADC_GetConversionValue(ADCx);
七、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間瞧剖,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除可免。
? 由 Leung 寫于 2021 年 1 月 19 日
? 參考:STM32CubeMX系列教程7:模數(shù)轉(zhuǎn)換(ADC)
《嵌入式-STM32開發(fā)指南》第二部分 基礎(chǔ)篇 - 第8章 模擬輸入輸出-ADC(HAL庫)