目录
前言
因本人备赛蓝桥杯嵌入式省赛,故编写此学习笔记进行学习上的记录。
上文我们实现了IIC程序设计,本文我们进行ADC程序设计。
1. ADC原理
STM32G431内部集成2个有最高12位ADC(ADC1、ADC2),它们是逐次逼近型模数转换器。
1.1 主要特性
可配置的转换精度:6位,8位,10位,12位
转换电压范围: 0 ~ VREF+(一般接到3.3V电源,不能超过STM32芯片电源电压)
19个转换通道:16个外部通道(IO引脚) + 3个内部通道(温度传感器、内部电压参考、电池供电监测)
采样时间可配置
扫描方向可配置
多种转换模式:单次,连续
数据存放对齐方式可配置:左对齐,右对齐(ADC的结果存储在一个左对齐或右对齐的 16 位数据寄存器中)
启动转换方式可配置:软件触发,硬件触发
可设置上下门限的模拟看门狗
DMA功能
在转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断
1.2 模拟输出电路图
嵌入式实训平台CT117E-M4的模拟输出如下
其中PB15引脚连接R37电阻,PB12引脚连接R38电阻。
2. 使用CubeMax进行源工程的配置
2.1 引脚配置
将PB12配置为ADC1_IN11
将PB15配置为ADC2_IN15
2.2 配置AD1
在Pinout&Configuration页面中的【Analog】目录中AD1的Mode选择IN11 Single。
在【Config】中配置【ADC_Settings】的【Clock Prescaler】选择【Asynchronous clock mode divided by 2】。
在【Config】中配置【ADC_Regular_Conversion Mode】的【Rank】选择【Sampling Time】为【640.5 Cycles】。
2.3 配置AD2
在Pinout&Configuration页面中的【Analog】目录中AD2的Mode选择IN15 Single。
在【Config】中配置【ADC_Settings】的【Clock Prescaler】选择【Asynchronous clock mode divided by 2】。
在【Config】中配置【ADC_Regular_Conversion Mode】的【Rank】选择【Sampling Time】为【640.5 Cycles】。
2.4 配置时钟
在【Clock Configuration】 中将ADC12的时钟源改为PLLP
配置完成后就生成代码
3. 代码编程
3.1 预备工作
接下来我们在Test_Project工程里的Src文件夹创建BSP\ADC\bsp_adc.c,同理,在Inc文件夹创建BSP\ADC\bsp_adc.h。这就是我们后面要编写的中间层代码文件。
打开Test_Project工程,进行文件Group的添加
在bsp_adc.c中添加依赖头文件
#include "ADC/bsp_adc.h"
在Group中添加HAL库的相关文件,添加stm32g4xx_hal_adc.c和stm32g4xx_hal_adc_ex.c。
在stm32g4xx_hal_conf.h中去掉#define HAL_ADC_MODULE_ENABLED 的注释
#define HAL_ADC_MODULE_ENABLED
3.2 bsp_adc.h文件编写
拷贝Source工程生成的adc.h代码,并定义获取ADC的函数
#include "main.h"
extern ADC_HandleTypeDef hadc1;
extern ADC_HandleTypeDef hadc2;
void ADC1_Init(void);
void ADC2_Init(void);
uint16_t getADC1(void);
uint16_t getADC2(void);
3.3 bsp_adc.c文件编写
拷贝Source工程生成的adc.c代码。
#include "ADC/bsp_adc.h"
ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;
void ADC1_Init(void)
{
ADC_MultiModeTypeDef multimode = {0};
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure the ADC multi-mode
*/
multimode.Mode = ADC_MODE_INDEPENDENT;
if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_11;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void ADC2_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc2.Instance = ADC2;
hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
hadc2.Init.Resolution = ADC_RESOLUTION_12B;
hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc2.Init.GainCompensation = 0;
hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc2.Init.LowPowerAutoWait = DISABLE;
hadc2.Init.ContinuousConvMode = DISABLE;
hadc2.Init.NbrOfConversion = 1;
hadc2.Init.DiscontinuousConvMode = DISABLE;
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc2.Init.DMAContinuousRequests = DISABLE;
hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc2.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc2) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_15;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static uint32_t HAL_RCC_ADC12_CLK_ENABLED=0;
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
HAL_RCC_ADC12_CLK_ENABLED++;
if(HAL_RCC_ADC12_CLK_ENABLED==1){
__HAL_RCC_ADC12_CLK_ENABLE();
}
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
else if(adcHandle->Instance==ADC2)
{
HAL_RCC_ADC12_CLK_ENABLED++;
if(HAL_RCC_ADC12_CLK_ENABLED==1){
__HAL_RCC_ADC12_CLK_ENABLE();
}
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
HAL_RCC_ADC12_CLK_ENABLED--;
if(HAL_RCC_ADC12_CLK_ENABLED==0){
__HAL_RCC_ADC12_CLK_DISABLE();
}
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_12);
}
else if(adcHandle->Instance==ADC2)
{
HAL_RCC_ADC12_CLK_ENABLED--;
if(HAL_RCC_ADC12_CLK_ENABLED==0){
__HAL_RCC_ADC12_CLK_DISABLE();
}
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_15);
}
}
编写获取ADC1和ADC2的代码,首先进行ADC采集的开始,然后调用HAL库的获取并返回。
uint16_t getADC1(void)
{
uint16_t adc = 0;
HAL_ADC_Start(&hadc1);
adc = HAL_ADC_GetValue(&hadc1);
return adc;
}
uint16_t getADC2(void)
{
uint16_t adc = 0;
HAL_ADC_Start(&hadc2);
adc = HAL_ADC_GetValue(&hadc2);
return adc;
}
3.4 main.c编写
3.4.1 时钟函数配置
拷贝Source工程生成的时钟配置代码
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
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 = RCC_PLLM_DIV3;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses 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_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_ADC12;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_PLL;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
3.4.2 头文件引用
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "LCD\bsp_lcd.h"
#include "ADC\bsp_adc.h"
3.4.3 变量声明
//变量声明
__IO uint32_t uwTick_Lcd_Set_Point;//LCD减速
//*LCD显示专用变量
unsigned char Lcd_Disp_String[22];
3.4.4 子函数声明
//***子函数声明区
void SystemClock_Config(void);
void Lcd_Proc(void);
3.4.5 函数定义
void Lcd_Proc(void)
{
if((uwTick - uwTick_Lcd_Set_Point)<200)
return;
uwTick_Lcd_Set_Point = uwTick;
sprintf((char*)Lcd_Disp_String,"R37:%4.2fV",(getADC2()*3.3)/4096);
LCD_DisplayStringLine(Line3,Lcd_Disp_String);
sprintf((char*)Lcd_Disp_String,"R38:%4.2fV",(getADC1()*3.3)/4096);
LCD_DisplayStringLine(Line4,Lcd_Disp_String);
}
3.4.6 main函数编写
int main(void)
{
HAL_Init();
SystemClock_Config();
LCD_Init();
LCD_Clear(White);
LCD_SetBackColor(White);
LCD_SetTextColor(Blue);
ADC1_Init();
ADC2_Init();
while (1)
{
Lcd_Proc();
}
}
4. 测试
将代码进行编译并下载到开发板上。效果如下图所示。
至此,本节就大功告成了!