更多交流欢迎关注作者抖音号:81849645041
目标
掌握ARM Cortex-M 系列芯片外设ADC注入模式的工作原理,通过配置STM32F407的ADC来分别测出规则通道和注入通道的电压值。
原理
配置ADC1通道4(PA4)工作在规则通道,配置ADC1通道5(PA5)工作在注入通道,其中通道5的注入模式选择定时器触发,每一秒触发一次,并通过串口打印出测得电压值。通道4选择连续转换并且每500ms打印一次,因此当规则通道打印两次数据的时候,注入通道打印一次数据。
准备
MDK5 开发环境。
STM32F4xx HAL库。
STM32F407 开发板。
STM32F4xx 参考手册。
STM32F407 开发板电路原理图。
串口调试助手软件。
USB转TTL模块。
步骤
- 首先创建TIM1_Config()函数,初始化触发定时器和PWM,函数中配置定时器周期为1秒,且PWM的比较值为5000。
static void TIM1_Config(void)
{
__TIM1_CLK_ENABLE(); // 使能定时器1时钟
TIM_HandleTypeDef TIM_Handle; // 定时器初始化结构体变量
TIM_OC_InitTypeDef TIM_OC_Handle; // 定时器输出初始化结构体变量
// 定时器初始化
TIM_Handle.Channel = HAL_TIM_ACTIVE_CHANNEL_4; // 通道4
TIM_Handle.Instance = TIM1; // 选择定时器1
TIM_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟1分频
TIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
TIM_Handle.Init.Period = 10000 - 1; // 自动重装载值
TIM_Handle.Init.Prescaler = 16800 - 1; // 预分频系数
HAL_TIM_PWM_Init(&TIM_Handle); // 初始化定时器
// 定时器输出PWM初始化
TIM_OC_Handle.OCMode = TIM_OCMODE_PWM1; // 模式选择PWM1
TIM_OC_Handle.OCPolarity = TIM_OCPOLARITY_LOW; // 输出比较极性为低
TIM_OC_Handle.Pulse = 5000; // 设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50%
HAL_TIM_PWM_ConfigChannel(&TIM_Handle, &TIM_OC_Handle, TIM_CHANNEL_4); // 配置PWM输出
HAL_TIM_PWM_Start(&TIM_Handle, TIM_CHANNEL_4); // 开始PWM输出
}
- 创建ADC_InjectInit()函数,分别初始化规则通道和注入通道。
第一步:初始化ADC规则通道4,为连续转换模式且使用软件触发。
第二步:初始化注入通道5,为定时器1通道4的上升沿触发。
第三步:调用TIM1_Config()函数,初始化定时器,并开启ADC通道转换。
// ADC规则/注入通道初始化
void ADC_InjectInit(void)
{
// 初始化ADC
ADC_Handler.Instance = ADC1;
ADC_Handler.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8; // 4分频,ADCCLK = PCLK2/4 =84/4=21MHZ
ADC_Handler.Init.Resolution = ADC_RESOLUTION_12B; // 12位模式
ADC_Handler.Init.DataAlign = ADC_DATAALIGN_RIGHT; //右对齐
ADC_Handler.Init.ScanConvMode = DISABLE; // 不扫描模式
ADC_Handler.Init.EOCSelection = DISABLE; // 开启EOC转换一次中断
ADC_Handler.Init.ContinuousConvMode = ENABLE; // 开启连续转换
ADC_Handler.Init.NbrOfConversion = 1; // 1个转换在规则序列
ADC_Handler.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 使用软件触发
ADC_Handler.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; // 使用软件触发
HAL_ADC_Init(&ADC_Handler); // 初始化
ADC_ChannelConfTypeDef ADC_ChanneConf; // 规则通道初始化结构体
ADC_InjectionConfTypeDef sConfigInjected; // 注入通道初始化结构体
// 规则通道初始化
ADC_ChanneConf.Channel = ADC_CHANNEL_4; // 通道4
ADC_ChanneConf.Rank = 1; // 第一个采样
ADC_ChanneConf.SamplingTime = ADC_SAMPLETIME_480CYCLES; // 周期采样时间
HAL_ADC_ConfigChannel(&ADC_Handler, &ADC_ChanneConf); // 初始化规则通道
// 注入通道初始化
sConfigInjected.InjectedNbrOfConversion = 1; // 注入通道转换个数
sConfigInjected.InjectedChannel = ADC_CHANNEL_5; // 通道5
sConfigInjected.InjectedRank = 1; // 注入通道执行顺序
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_56CYCLES; // 注入采样周期
sConfigInjected.InjectedOffset = 0;
sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING; // 使用上升沿触发
sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_CC4; // 使用定时器1通道4触发
sConfigInjected.AutoInjectedConv = DISABLE; // 不启用常规通道转换1次后注入自动转换
sConfigInjected.InjectedDiscontinuousConvMode = DISABLE; // 不启用注入通道连续转换
HAL_ADCEx_InjectedConfigChannel(&ADC_Handler, &sConfigInjected);
TIM1_Config(); // 定时器初始化
HAL_ADC_Start_IT(&ADC_Handler); // 开启ADC中断
HAL_ADCEx_InjectedStart_IT(&ADC_Handler); // 开启注入通道转换中断
}
- 重定义HAL_ADC_MspInit()函数,初始化ADC底层引脚并使能ADC时钟和中断。
// ADC底层引脚重定义
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_ADC1_CLK_ENABLE(); // 使能ADC1时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 开启GPIOA时钟
GPIO_Initure.Pin = GPIO_PIN_4|GPIO_PIN_5; // PA4 PA5
GPIO_Initure.Mode = GPIO_MODE_ANALOG; // 模拟
GPIO_Initure.Pull = GPIO_NOPULL; // 浮空
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
HAL_NVIC_SetPriority(ADC_IRQn, 0, 0); // 设置ADC中断优先级
HAL_NVIC_EnableIRQ(ADC_IRQn); // 使能ADC
}
- 定义ADC中断服务函数,函数中调用HAL_ADC_IRQHandler()处理中断请求。
// ADC中断服务函数
void ADC_IRQHandler()
{
HAL_ADC_IRQHandler(&ADC_Handler);
}
- 定义两个存储变量,分别用来存储规则通道和注入通道的值。
uint16_t uhADCxConvertedRegValue = 0; // 存储规则通道转换值
uint16_t uhADCxConvertedInjValue = 0; // 存储注入通道转换值
- 定义注入转换完成回调函数,函数中首先获取ADC转换数字值,然后通过获取的数字值计算出电压值,最后通过串口打印电压值。
// ADC注入通道转换值
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
float adc_voltage = 0.0f;
uhADCxConvertedInjValue = HAL_ADCEx_InjectedGetValue(AdcHandle, ADC_INJECTED_RANK_1); // 获取ADC数字值
adc_voltage = uhADCxConvertedInjValue * 3.3f / 4095; // 计算电压值
printf("Inject adc_voltage: %.2f\r\n", adc_voltage);
}
- 创建Regula_GetData()函数,用来处理规则通道中数据。同样函数中首先获取ADC转换数字值,然后通过获取的数字值计算出电压值,最后返回计算后的电压值。
// 规则通道数据计算
float Regula_GetData(void)
{
float adc_voltage = 0.0f;
uhADCxConvertedRegValue = HAL_ADC_GetValue(&ADC_Handler); // 获取ADC数字值
adc_voltage = uhADCxConvertedRegValue * 3.3f / 4095; // 计算电压值
return adc_voltage;
}
- 主函数main程序如下:
第一步:初始化系统时钟和串口。
第二步:调用ADC_InjectInit()函数初始化ADC两个通道。
第三步:在while()循环中获取规则通道转换电压值,使用printf将电压值通过串口每隔500ms打印出来。
int main()
{
CLOCK_Init(); // 时钟初始化
UART_Init(); // 串口初始化
ADC_InjectInit(); // 注入模式初始化
while(1)
{
printf("Regulation adc_voltage: %.2f\r\n", Regula_GetData());
HAL_Delay(500);
}
}
现象
将程序下载到开发板中,可以看到注入通道数据每隔1秒钟打印一次,规则通道500ms打印一次,当规则通道打印两次数据的时候,注入通道打印一次数据。(此时规则通道4连接的3.3v引脚,注入通道5连接的GND引脚)。