stm32cubemx+freertos+dma实现ADC采样和数据处理
本文将使用stm32cubemx
+freertos
+DMA
实现ADC的采样
stm32cubemx配置
此处的设置如下所示:
其他时钟方面的设置按照自己的需求来就好
freertos
的设置如下,我这开的是CMSIS_V1
,其他特别的配置就没有了
代码
找到下面的这些函数,然后按照相同的方式进行修改即可,这里只提供一个可行的范例,有其他需求可以自己摸索一下进行修改
void StartDefaultTask(void const * argument)
{
/* init code for USB_DEVICE */
MX_USB_DEVICE_Init();
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
// power_adc测量初始化
HAL_NVIC_DisableIRQ(DMA1_Channel1_IRQn);
Start_Power_ADC_DMA(&hadc1);
osThreadTerminate(NULL);
/* USER CODE END StartDefaultTask */
}
实现下面这几个函数:
// 启动DMA
void Start_Power_ADC_DMA(ADC_HandleTypeDef* hadc)
{
Start_ADC_Calibration(hadc);
HAL_ADC_Start_DMA(hadc, (uint32_t*)adc_val_buf, (ADC_CHANNEL_CNT*ADC_CHANNEL_FRE));
}
// 该函数改为在freertos的任务中手动调用,不再通过中断的方式调用
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)
{
uint32_t tmp_value=0;
for(int i = 0; i < ADC_CHANNEL_FRE; i ++ )
{
tmp_value += adc_val_buf[i];
}
adc_value = (float)ADC_CHANGE_VALUE * tmp_value / ADC_CHANNEL_FRE + 0.057; // 需要加0.057补偿一下
power_adc_power = POWER_CHANGE_VALUE * adc_value;
}
}
流程图:
StartDefaultTask(调用Start_Power_ADC_DMA) -> HAL_ADC_ConvCpltCallback ---|(一直调用该函数将数据取出来处理即可)
↑-------------|
整个代码如下:
// power_adc.h
#ifndef POWER_ADC_H
#define POWER_ADC_H
#include "stm32f1xx_hal.h"
#define ADC_CHANNEL_CNT 1 // 采样通道数
#define ADC_CHANNEL_FRE 3 // 单个通道采样次数,用来取平均值
#define ADC_CHANGE_VALUE 0.00080586 // 右对齐情况下的系数
#define POWER_CHANGE_VALUE 21 // 实际电源和ADC测量值之间的倍数
#define RING_LOW_VOLT 3.75 // 单个电芯报警电压
#define PING_CRIT_VOLT 3.64 // 当个电芯危险电压
#define POWER_S 6 // 电芯数量
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc); // DMA 转换完成时的回调函数
void Start_Power_ADC_DMA(ADC_HandleTypeDef* hadc); // 开启DMA测量
void Stop_Power_ADC_DMA(ADC_HandleTypeDef* hadc); // 关闭DMA测量
void Start_ADC_Calibration(ADC_HandleTypeDef* hadc); // 开始ADC校准
void Restart_Power_ADC_DMA(void); // 重启DMA
float Get_Power_ADC_Value(void); // 返回电源的ADC值,换算过的
float Get_ADC_Value(void); // 返回adc测量的值,没有换算过的
#endif // POWER_ADC_H
// power_adc.c
#include "power_adc.h"
static uint16_t adc_val_buf[ADC_CHANNEL_CNT*ADC_CHANNEL_FRE]; // 传递给DMA存放多通道采样值的数组
static float adc_value; // 多通道的平均采样值
static float power_adc_power; // 电源的电压值,经过计算的
static uint8_t power_low_count = 0; // 记录低电压的次数
static uint8_t power_crit_count = 0; // 记录报警电压次数
extern ADC_HandleTypeDef hadc1;
extern DMA_HandleTypeDef hdma_adc1;
extern volatile uint8_t beer_ring_mode; // 控制蜂鸣器叫的函数
// 该函数改为在freertos的任务中手动调用,不再通过中断的方式调用
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)
{
uint32_t tmp_value=0;
for(int i = 0; i < ADC_CHANNEL_FRE; i ++ )
{
tmp_value += adc_val_buf[i];
}
adc_value = (float)ADC_CHANGE_VALUE * tmp_value / ADC_CHANNEL_FRE + 0.057; // 需要加0.057补偿一下
power_adc_power = POWER_CHANGE_VALUE * adc_value;
// 加一个累计计时,防止上电和下电的时候乱叫
if(power_adc_power<PING_CRIT_VOLT * POWER_S)
{
power_crit_count ++;
power_low_count = 0;
}else if(power_adc_power<RING_LOW_VOLT * POWER_S)
{
power_low_count ++;
power_crit_count = 0;
}else
{
power_low_count = 0;
power_crit_count = 0;
}
if(power_crit_count > 30)
{
beer_ring_mode = 5;
power_crit_count = 31;
}else if(power_low_count > 30)
{
beer_ring_mode = 3;
power_low_count = 31;
}
}
}
void Start_Power_ADC_DMA(ADC_HandleTypeDef* hadc)
{
Start_ADC_Calibration(hadc);
HAL_ADC_Start_DMA(hadc, (uint32_t*)adc_val_buf, (ADC_CHANNEL_CNT*ADC_CHANNEL_FRE));
}
void Stop_Power_ADC_DMA(ADC_HandleTypeDef* hadc)
{
HAL_ADC_Stop_DMA(hadc);
}
void Restart_Power_ADC_DMA(void)
{
// 停止当前的 DMA 传输
HAL_DMA_Abort(&hdma_adc1);
// 清除 DMA 中断标志
__HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_TC1);
// 重新启动 DMA 传输
HAL_DMA_Start(&hdma_adc1, (uint32_t)&ADC1->DR, (uint32_t)adc_val_buf, (ADC_CHANNEL_CNT*ADC_CHANNEL_FRE));
}
void Start_ADC_Calibration(ADC_HandleTypeDef* hadc)
{
HAL_ADCEx_Calibration_Start(hadc);
}
float Get_Power_ADC_Value(void)
{
return power_adc_power;
}
float Get_ADC_Value(void)
{
return adc_value;
}