STM32 CubeMX學習:7. ADC模數轉化
系列文章目錄
- 前期的準備
- 點亮 LED
- 閃爍 LED
- 定時器閃爍LED
- PWM控制LED亮度
- 常見的PWM裝置
- 按鍵的外部中斷
- ADC模數轉換
- 序列槽收發
- 序列槽列印遙控器資料
- 未完待續…
文章目錄
- STM32 CubeMX學習:7. ADC模數轉化
- 0 前言
- 1. 基礎知識學習
-
- 1.1 ADC原理介紹
- 1.2 STM32F4x ADC介紹
- 2. 程式學習
-
- 2.1 ADC在CubeMX的配置
- 2.2 内部VREFINT電壓的使用
- 2.3 相關函數介紹
- 2.4 程式執行流程
- 3. 進階學習
- 總結
0 前言
我們已經知道,在單片機中傳輸的信号均為數字信号,通過離散的高低電平表示數字邏輯的1和0,但是在現實的實體世界中,隻存在模拟信号,即連續變化的信号,将這些連續變化的信号——比如熱,光,聲音,速度通過各種傳感器轉化成連續的電信号,再通過ADC功能将連續的模拟信号轉化成離散的數字信号給單片機進行處理。
1. 基礎知識學習
1.1 ADC原理介紹
一般ADC的工作流程為采樣,比較,轉換。
- 采樣:是指對某一時刻的模拟電壓進行采集,
- 比較:是指将采樣的電壓在比較電路中進行比較,
-
轉換:是指将比較電路中結果轉換成數字量。
在我們這裡,使用的STM32f4采用12位逐次逼近型ADC(SAR-ADC)。現在,以下圖為例,介紹3位ADC的比較過程:
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 不同的位數分别賦予1/2,1/4,1/8的權值,模拟信号的采樣值為Vin,
1)與1/2Vref進行比較,Vin大于1/2Vref,則将第一位标記為1,
2)與3/4Vref進行比較,Vin小于3/4Vref,則将第二位标記為0,
3)與5/8Vref進行比較,Vin小于5/8Vref,則将第三位标記為0。
圖中的Vin通過這個三位的ADC後輸出的結果為100。轉換的結果為1/2Vref,通過這樣逐次比較過程,将采樣取得的模拟電壓和内部參考電壓Vref的權重值進行比較,不同的位數賦予不同的權值。
但是,如果我們輸入為一個未知的電壓,完整的比較流程圖如下:
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 STM32支援最高12位ADC,一般ADC的位數越多則轉換精度越高,但與此同時轉換的速度也會變慢。
此外,STM32内部有一個校準電壓VREFINT,電壓為1.2V,當供電電壓不為3.3V,可以使用内部的vrefint通道采集1.2V電壓作為Vref,以提高精度。
1.2 STM32F4x ADC介紹
- STM32F4x ADC特點
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 - STM32F40x系列ADC外部通道和引腳對應關系
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結
2. 程式學習
2.1 ADC在CubeMX的配置
這次我們使用引腳PF10作為電源ADC引腳,使用ADC3的通道8,STM32内部的1.2V校準電壓Vrefint在ADC1中。是以我們需要按如下方式在CubeMX裡進行配置。
- 開啟ADC1和ADC3分别用于内部1.2V的Vrefint通道讀取和電池電壓ADC3的通道8讀取。
- 在cubeMX中開啟ADC1,在設定中将Vrefint Channel勾選,用于讀取内部參考電壓。ADC在cubeMX中的設定如圖采樣頻率設定為PCLK2/4,采樣位數為12位,資料設定為右對齊,其餘均保持預設。其中Vrefine在stm32内部完成,沒有對應引腳。 最終ADC配置如圖所示。
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 《補充》:但是,這裡還是把其中參數的具體含義給大家科普一下吧STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結
名稱 | 功能 |
---|---|
Clock Prescaler | 設定采樣時鐘頻率 |
Resolution | 設定采樣精度 |
Data Alignment | 設定資料對齊方式 |
Scan Conversion Mode | 掃描轉換模式開啟/關閉 |
Continuous Conversion Mode | 連續轉換模式開啟/關閉 |
Discontinuous Conversion Mode | 非連續轉換模式開啟/關閉 |
DMA Continuous Requests | DMA連續啟動開啟/關閉 |
End of Conversion Selection | 每個通道轉換結束後發送EOC标志/所有通道轉換結束後發送EOC标志 |
- 在CubeMX中開啟ADC3,并打開其IN8用于電池電壓的讀取,其設定和ADC1一緻。可以看到引腳圖像中ADC3對應的PF10變綠。
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 - 這裡我們需要把PC0,PC1,PC2引腳設定為輸入模式,且這三個引腳拉為高電平,如圖所示
STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結 - 點選GENERATE CODE生成代碼
2.2 内部VREFINT電壓的使用
VREFINT 即 ADC的内部參照電壓1.2V。通過将采樣内部參照電壓1.2V的 ADC 值和 Vref 的權重值進行比較,進而得到 ADC 的輸出值。一般來說STM32的 ADC 采用 Vcc 作為 Vref ,但為了防止 Vcc 存在波動較大導緻Vref不穩定,進而導緻采樣值的比較結果不準确,STM32可以通過内部已有的參照電壓VREFINT來進行校準,接着以VREFINT為參照來比較ADC的采樣值,進而獲得比較高的精度,VREFINT的電壓為1.2V。
通過一個函數對1.2V的電壓進行多次采樣,并計算其平均值,接着将其與ADC采出的資料值做對比,得到機關數字電壓對應的模拟電壓值voltage_vrefint_proportion,其計算公式如下,設采樣得到的數字值為average_adc:
a v e r a g e _ a d c = t o t a l _ d a c 200 average\_adc=\frac{total\_dac}{200} average_adc=200total_dac
v o l t a g e _ v r e f i n t _ p r o p o r t i o n = 1.2 v a v e r a g e _ a d c = 200 ∗ 1.2 t o t a l _ a d c voltage\_vrefint\_proportion=\frac{1.2v}{average\_adc}=\frac{200 ∗ 1.2}{total\_adc} voltage_vrefint_proportion=average_adc1.2v=total_adc200∗1.2
其代碼如下所示
void init_vrefint_reciprocal(void)
{
uint8_t i = 0;
uint32_t total_adc = 0;
for(i = 0; i < 200; i++)
{
total_adc += adcx_get_chx_value(&hadc1, ADC_CHANNEL_VREFINT);
}
voltage_vrefint_proportion = 200 * 1.2f / total_adc;
}
2.3 相關函數介紹
Hal庫提供了以下很多有關ADC的函數
- ADC通道設定函數
函數作用 | 設定ADC通道的各個屬性值,包括轉換通道,序列排序,采樣時間等 |
---|---|
傳回值 | HAL_StatusTypeDef,HAL庫定義的幾種狀态,如果成功使ADC開始工作,則傳回HAL_OK |
參數1 | TIM_HandleTypeDef * hadc即ADC的句柄指針,如果是adc1就輸入&hadc1,adc2就輸入&adc2 |
參數2 | ADC_ChannelConfTypeDef* sConfig即指向ADC設定的結構體指針。我們先對sConfig結構體進行指派,然後再将其指針作為參數輸入函數 |
- ADC開啟采樣函數
函數作用 | 開啟ADC的采樣 |
---|---|
傳回值 | HAL_StatusTypeDef,HAL庫定義的幾種狀态,如果成功使ADC開始工作,則傳回HAL_OK |
參數 | TIM_HandleTypeDef * hadc即ADC的句柄指針,如果是adc1就輸入&hadc1,adc2就輸入&adc2 |
- 等待ADC轉換結束函數
函數作用 | 等待ADC轉換結束 |
---|---|
傳回值 | HAL_StatusTypeDef,HAL庫定義的幾種狀态,如果成功使ADC開始工作,則傳回HAL_OK |
參數1 | TIM_HandleTypeDef * hadc即ADC的句柄指針,如果是adc1就輸入&hadc1,adc2就輸入&adc2 |
參數2 | HAL_StatusTypeDef,HAL庫定義的幾種狀态,如果成功使ADC開始工作,則傳回HAL_OK |
- 擷取ADC函數
函數作用 | 擷取ADC值 |
---|---|
傳回值 | HAL_StatusTypeDef,HAL庫定義的幾種狀态,如果成功使ADC開始工作,則傳回HAL_OK |
參數 | TIM_HandleTypeDef * hadc即ADC的句柄指針,如果是adc1就輸入&hadc1,adc2就輸入&adc2 |
2.4 程式執行流程
本程式中,首先對内部參考電壓電壓VREFINT進行adc采樣将其作為校準值,在init_vrefint_reciprocal中,對VREFINT電壓進行200次的采樣,接着求其均值後使用VREFINT的電壓值1.2V去除以該ADC采樣得到的均值,算出voltage_vrefint_proportion,後續ADC中采樣到的電壓值與voltage_vrefint_proportion相乘就可以計算出以内部參考電壓做過校準的ADC值了。
void init_vrefint_reciprocal(void)
{
uint8_t i = 0; uint32_t total_adc = 0;
for(i = 0; i < 200; i++)
{
total_adc += adcx_get_chx_value(&hadc1, ADC_CHANNEL_VREFINT);
}
voltage_vrefint_proportion = 200 * 1.2f / total_adc;
}
接着通過ADC對經過了分壓電路的電池電壓值進行采樣,将該采樣結果與voltage_vrefint_proportion相乘,就得到了取值範圍在0-3.3V之間的ADC采樣值,由于這個采樣值是分壓後的結果,需要反向計算出電壓的值。分壓的電阻值為200KΩ和22KΩ,由于 (22K Ω + 200K Ω) / 22K Ω = 10.09,乘以這個值之後就可以得到電池的電壓值。
(注意,這裡我們是根據一個具體的分壓電路給出的計算公式,當我們實際使用時,要根據自己所使用的具體電路修改對應的計算方法,)
fp32 get_battery_voltage(void)
{
fp32 voltage;
uint16_t adcx = 0;
adcx = adcx_get_chx_value(&hadc3, ADC_CHANNEL_8);
//(22K Ω + 200K Ω) / 22K Ω = 10.090909090909090909090909090909
voltage = (fp32)adcx * voltage_vrefint_proportion * 10.090909090909090909090909090909f;
return voltage;
}
其實,我們還可以通過ADC獲得闆載的溫度傳感器的溫度值,同樣是先經過ADC值進行采樣,采樣結束後,将ADC采樣結果adc帶入公式temperate = (adc - 0.76f) * 400.0f + 25.0f,進而計算出溫度值。
fp32 get_temprate(void)
{
uint16_t adcx = 0;
fp32 temperate;
adcx = adcx_get_chx_value(&hadc1, ADC_CHANNEL_TEMPSENSOR);
temperate = (fp32)adcx * voltage_vrefint_proportion;
temperate = (temperate - 0.76f) * 400.0f + 25.0f;
return temperate;
}
在我們完成代碼時,還需要像以前一樣自己建立一個Boards目錄,在裡面建立一個bsp_adc.c檔案
#include "bsp_adc.h"
#include "main.h"
extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc3;
volatile fp32 voltage_vrefint_proportion = 8.0586080586080586080586080586081e-4f;
static uint16_t adcx_get_chx_value(ADC_HandleTypeDef *ADCx, uint32_t ch)
{
static ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ch;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;//ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(ADCx, &sConfig) != HAL_OK)
{
Error_Handler();
}
HAL_ADC_Start(ADCx);
HAL_ADC_PollForConversion(ADCx, 10);
return (uint16_t)HAL_ADC_GetValue(ADCx);
}
void init_vrefint_reciprocal(void)
{
uint8_t i = 0;
uint32_t total_adc = 0;
for(i = 0; i < 200; i++)
{
total_adc += adcx_get_chx_value(&hadc1, ADC_CHANNEL_VREFINT);
}
voltage_vrefint_proportion = 200 * 1.2f / total_adc;
}
fp32 get_temprate(void)
{
uint16_t adcx = 0;
fp32 temperate;
adcx = adcx_get_chx_value(&hadc1, ADC_CHANNEL_TEMPSENSOR);
temperate = (fp32)adcx * voltage_vrefint_proportion;
temperate = (temperate - 0.76f) * 400.0f + 25.0f;
return temperate;
}
fp32 get_battery_voltage(void)
{
fp32 voltage;
uint16_t adcx = 0;
adcx = adcx_get_chx_value(&hadc3, ADC_CHANNEL_8);
//(22K Ω + 200K Ω) / 22K Ω = 10.090909090909090909090909090909
voltage = (fp32)adcx * voltage_vrefint_proportion * 10.090909090909090909090909090909f;
return voltage;
}
uint8_t get_hardware_version(void)
{
uint8_t hardware_version;
hardware_version = HAL_GPIO_ReadPin(HW0_GPIO_Port, HW0_Pin)
| (HAL_GPIO_ReadPin(HW1_GPIO_Port, HW1_Pin)<<1)
| (HAL_GPIO_ReadPin(HW2_GPIO_Port, HW2_Pin)<<2);
return hardware_version;
}
接着建立一個對應的bsp_adc.h檔案放置于對應目錄之下
#ifndef BSP_ADC_H
#define BSP_ADC_H
#include "#ifndef BSP_ADC_H
#define BSP_ADC_H
#include "struct_typedef.h"
extern void init_vrefint_reciprocal(void);
extern fp32 get_temprate(void);
extern fp32 get_battery_voltage(void);
extern uint8_t get_hardware_version(void);
#endif
"
extern void init_vrefint_reciprocal(void);
extern fp32 get_temprate(void);
extern fp32 get_battery_voltage(void);
extern uint8_t get_hardware_version(void);
#endif
在這個檔案中,我們還用到了一個定義結構體的頭檔案:struct_typedef.h
#ifndef STRUCT_TYPEDEF_H
#define STRUCT_TYPEDEF_H
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed long long int64_t;
/* exact-width unsigned integer types */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef unsigned char bool_t;
typedef float fp32;
typedef double fp64;
#endif
最終的main.c檔案如下所示
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_adc.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
fp32 voltage;
fp32 temperature;
uint8_t handware_version;
/* USER CODE END 0 */
/**
* @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_GPIO_Init();
MX_ADC1_Init();
MX_ADC3_Init();
/* USER CODE BEGIN 2 */
//use vrefint voltage to calibrate
//使用基準電壓來校準
init_vrefint_reciprocal();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//get battery voltage
//擷取電源電壓
voltage = get_battery_voltage();
//get chip temperate
//擷取片内溫度
temperature = get_temprate();
//get handware version
//擷取硬體版本
handware_version = get_hardware_version();
HAL_Delay(100);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 6;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
3. 進階學習
逐次型ADC是通過一位位進行比較得出轉換值,其原理圖如下所示。
如圖所示,我們發現整個電路由比較器、D/A轉換器、緩沖寄存器和若幹控制邏輯電路構成。作用如下所示:
- 比較器:用于輸入電壓值D/A轉換器輸出電壓進行比較,當輸入電壓大于D/A轉換器電壓時,輸出為1,反之輸出為0;
- D/A轉換器:ADC的逆向過程,将緩沖寄存器的記錄數字量轉換成模拟量。
- 緩沖寄存器:記錄目前轉換的數字量。
整個過程如下:
- 将緩沖寄存器清零;
- 将逐次逼近寄存器 最高位 置1;
- 把數字值送入D/A轉換器,經D/A轉換後的模拟量送入比較器,稱為 Vo;
- Vo與比較器的待轉換的模拟量Vi 比較,若Vo<Vi,該位被保留,否則被清0。
- 再置 寄存器 次高位為1,将寄存器中新的數字量送D/A轉換器,
- 輸出的 Vo再與Vi比較,若Vo<Vi,該位被保留,否則被清0。
-
循環此過程,直到寄存器最低位,得到數字量的輸出。
在這個過程中,D/A轉換器使用的電壓為STM32的電源電壓Vref+和Vref-,如圖所示:
STM32的标準電壓為 3.3V。例如第一次轉換時,輸出為 1/2Vre電壓,即1.65V。但由于外部供電電壓不一定為 3.3V。故而為了在這種情況提高ADC精度,STM32内部有VERFINT1.2V穩定電壓,可以使用ADC采樣該電壓來提高ADC的精度。STM32 CubeMX學習:7. ADC模數轉化STM32 CubeMX學習:7. ADC模數轉化0 前言1. 基礎知識學習2. 程式學習3. 進階學習總結
代碼我已經放到了我的GitHub倉庫,如有需要可以下載下傳使用:
CubeMX學習
總結
這次的部落格,我們重點學習了ADC的相關知識,ADC是模拟量變成數字量的過程,單片機擷取電壓,電流傳感器等模拟量的方法。通過ADC功能,我們能夠擷取各種傳感器的模拟值,并将其轉化為單片機可以處理的數字量,也可以擷取電池的電壓值,并将其通過電量形式顯示出來。