使用HAL库实现STM32 ADC电压采样(轮询、中断、DMA)
《DMA配置的一些知识》
DMA的模式:
- Normal模式(不循环模式):当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次。
- Circular模式(循环模式):传输完成后又重新开始继续传输,不断循环永不停止(一般用于处理循环缓冲区和连续的数据传输(如ADC的扫描模式))。
当开启循环模式之后,数据传输的数目变为0时,将会自动地恢复成配置通道时设置的初值,DMA操作将会继续进行。
data width:数据宽度
byte:字节,通用8位,与u8相同
word:字长,与硬件的位数相同,STM32是32位,所以对应是u32
Half Word:半个字长,所以对应是u16
一、ADC单通道+轮询模式
ADC单通道+单次转换模式(只转换一次)+不扫描模式(只有一个通道)
int main()
{
// 初始化ADC
MX_ADC1_Init();
/* 开启 ADC自动校准 */
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
Error_Handler();
}
while (1)
{
/* The ADC1 starts collecting the voltage of the supercapacitor */
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
{
adcValue = HAL_ADC_GetValue(&hadc1);
supercapV = (uint32_t) adcValue * 3300 / 0xFFFF;
}
HAL_ADC_Stop(&hadc1);
}
}
二、ADC单通道+中断
uint16_t ADC_Values;
int main()
{
// 初始化ADC
MX_ADC1_Init();
// 开启ADC自动校准
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
Error_Handler();
}
//开启ADC使能中断
HAL_ADC_Start_IT(&hadc1);
while (1)
{
}
}
//ADC转换完成后自动调用ADC中断回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
//获取ADC值并存储
ADC_Values=HAL_ADC_GetValue(hadc);
}
三、ADC单通道+DMA采集数据
ADC的连续转换模式 + ADC的不扫描模式 + DMA的Normal模式
为什么使用不循环模式呢?因为数据搬运到内存之后,需要先对内存进行一个操作,进行LCD显示结果之后,才进行下一轮采样。
方法一(只采样1次)
uint32_t adc_value = 0;
float supercar_voltage = 0.0;
int mian()
{
HAL_ADC_Start_DMA(&hadc1, adc_value, 1); // 采样一个值
while (1)
{
HAL_ADC_Start(&hadc1); // 启动ADC转换
HAL_ADC_PollForConversion(&hadc1, 10); // 等待转换完成
adc_value = HAL_ADC_GetValue(&hadc1);
xsprintf(adcString1, "adc:%u ", adc_value);
LCD_ShowString(4, 22, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString1);
xsprintf(adcString2, "adcV:%.4f ", (float)adc_value * (3.3 / 4096));
LCD_ShowString(4, 42, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString2);
//设置的是Normal模式,下一次ADC和DMA采集要重新开启
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10);
}
}
方法二(连续采样10次取平均值)
int main()
{
int i;
uint32_t sum;
uint32_t ADC_Value[10] = {0};
char adcString1[10] = {0};
char adcString2[10] = {0};
// 触发ADC转换,使用DMA传输数据,设置源地址、目标地址、传输数量
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10); // 采样10个值存储在ADC_Value[10]数组中
while (1)
{
if (HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1, HAL_ADC_STATE_REG_EOC)))
{
sum = 0;
for (= 0; i < 10; i++)
{
sum += ADC_Value[i];
}
sum /= 10;
// LCD显示结果
xsprintf(adcString1, "adc:%u ", sum);
LCD_ShowString(4, 22, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString1);
xsprintf(adcString2, "adcV:%.4f ", (float)sum * (3.3 / 4096));
LCD_ShowString(4, 42, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString2);
//设置的是Normal模式,下一次ADC和DMA采集要重新开启
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10);
}
}
}
四、ADC多通道+DMA采集数据:
ADC的连续转换模式 + ADC的扫描模式(多通道)+ DMA的Normal模式
以下例子是使用6个通道,每个通道采集10次取平均值来减少误差:
int main()
{
uint32_t ADC_Value[60] = {0};
uint8_t dmaFlag = 0; // 检查中断服务函数里面DMA搬运完成的标志位
char adcString1[10] = {0};
char adcString2[10] = {0};
// 触发ADC转换,使用DMA传输数据,设置源地址、目标地址、传输数量
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10); // 采样10个值存储在ADC_Value[10]数组中
while (1)
{
if (1 == dmaFlag)
{
for (int j = 0; j < 6; j++) // 遍历6个通道,轮流取值+显示
{
sum = 0;
if (int i = 0; i < 60; i++)
{
sum + = ADC_Value[(6 * i) + j]; // 每个通道采集l0次数据,进行10次累加
}
sum = sum / 10; // 取平均值
// LCD显示结果
xsprintf(adcString1, "adc:%u ", sum);
LCD_ShowString(4, 22, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString1);
xsprintf(adcString2, "adcV:%.4f ", (float)sum * (3.3f / 4096)); // 计算12位分辨率的实际电压值
LCD_ShowString(4, 42, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString2);
}
dmaFlag = 0; // 清除DMA采集完成标志位
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10); // 开启下一次ADC和DMA采集
}
}
}
五、ADC单通道+DMA采集数据(把12位转成16位分辨率)
/***************************************************************************************************
* 使用过采样和求均值的方式提高ADC的分辨率:
* 例如:F1单片机最高只能采样12位的分辨率数据,但是现在需要采集16位的,为了节省成本而不想
* 更换成H7单片机来采集16位的分辨率数据。此时,可以使用过采样的方法来提高分辨率。
*
* 公式:fos=(4^w)*fs —>fos是过采样频率,w是希望增加的分辨率位数,fs是初始采样频率要求
* 例如:本来是只有12位的分辨率:
* 若提高1位分辨率,4fs,即采样4次后,把4次结果求和,再右移1位,就能得到13位分辨率的结果
* 若提高2位分辨率,4^2fs=16fs,即采样16次后,把16次结果求和,再右移2位,就能得到14位分辨率的结果
* 若提高3位分辨率,4^3fs=64fs,即采样64次后,把64次结果求和,再右移3位,就能得到15位分辨率的结果
* 若提高4位分辨率,4^4fs=256fs,即采样256次后,把256次结果求和,再右移4位,就能得到16位分辨率的结果
* 注意:提示n位分辨率,需要右移n位
*
* 以下例程是:
* ADC单通道+DMA采集数据(把12位转成16位分辨率):
* ADC的连续转换模式 + ADC的不扫描模式 + DMA的Normal模式
****************************************************************************************************/
int main()
{
uint32_t ADC_Value[10] = {0};
unit32_t sum;
int i;
char adcString1[10] = {0};
char adcString2[10] = {0};
// 触发ADC转换,使用DMA传输数据,设置源地址、目标地址、传输数量
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10); // 采样10个值存储在ADC_Value[10]数组中
while (1)
{
if (1 == dmaFlag)
{
sum = 0;
for (i = 0; i < 256 * 10; i++) // 用12位采集256次相当于16位的1次
{
sum += ADC_Value[i];
}
sum /= 10;
sum >> = 4; // 得到一个16位分辨率的值
// LCD显示结果
xsprintf(adcString1, "adc:%lu ", sum);
LCD_ShowString(4, 22, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString1);
xsprintf(adcString2, "adcV:%.4f ", (float)sum * (3.3 / 65536)); // 计算16位分辨率的实际电压值
LCD_ShowString(4, 42, ST7735Ctx.Width, LCD_FONT_SIZE, LCD_FONT_SIZE, adcString2);
dmaFlag = 0; // 清除DMA采集完成标志位
HAL_ADC_Start_DMA(&hadc1, ADC_Value, 10); // 开启下一次ADC和DMA采集
}
}
}
五、相关回调函数
HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
当 ADC 完成一次转换时,会调用这个回调函数。
HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
当 ADC 完成半个转换缓冲区的转换时,会调用这个回调函数。注意这个回调函数只在 DMA 转换模式下有效,且需要启用 DMA 半传输完成中断。
HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
当 ADC 遇到错误时,会调用这个回调函数。
HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc)
当 ADC 的转换结果超出窗口阈值时,会调用这个回调函数。
HAL_ADC_EndOfConversionCallback(ADC_HandleTypeDef* hadc)
当 ADC 完成所有的转换序列时,会调用这个回调函数。
HAL_ADC_InjConvCpltCallback(ADC_HandleTypeDef* hadc)
当注入组转换完成时,会调用这个回调函数。
作者:LiMinyu_IoT