之前写到stm32cubemx通过阻塞实现单通道和多通道的ADC的采集。
本文分享通过DMA实现单模块多通道和多模块多通道的ADC采集。
stm32cubemx的版本6.10.0。
一、DMA采集多通道ADC数据
阻塞采集是每次采集adc数据,cpu死等,直到采集完或者在设定时间超时没能采集,返回到cpu。
DMA采集是每次采集adc数据,不占用cpu资源。
配置sm32cubemx:
1、配置时钟源
使用的是25M无源晶振。
这里power regulator voltage scale 选择“Power Regulator Voltage Scale 0”,系统时钟能达到480MHz。查看数据手册
2、时基选择系统滴答定时器
3、配置时钟
配置时钟为480MHz,系统自动生成时钟树。
ADC的时钟是64MHz。
4、配置ADC
选择通道18和19
DMA settings 里添加,Mode 选择“Circular”,Data Width选择“Half Word”,半字16位。
Continuous Conversion Mode(连续转换模式)
在连续转换模式下,如果发生软件或者硬件触发,ADC会执行所有常规通道的转换,随后会自动重启并继续执行每一个通道的转换
Conversion Data Managerment Mode(转换数据管理模式)
选择存放转换完成的模拟量数据的地方。
时钟设置为32MHz,
采样时间为32.5 时钟周期
转换时间=采样时间+7.5ADC时钟周期
=32.5+7.5 =40个时钟周期=1.25us
5、配置串口
6、调试模式
配置成SW模式
7、项目管理
配置完成后,生成初始化代码。
代码部分
(1)添加头文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h" //添加头文件
/* USER CODE END Includes */
(2)添加打印函数
/* USER CODE BEGIN 0 */
int fputc(int c, FILE *stream) //重写fputc函数
{
/*
huart1是工具生成代码定义的UART1结构体,
如果以后要使用其他串口打印,只需要把这个结构体改成其他UART结构体。
*/
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
/* USER CODE END 0 */
(3)添加变量
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t ADC_Value[100];//定义16位的100个数据的数组,存储adc的数据
uint8_t i;
uint32_t ad1,ad2;//采集数据平均值
float ad1x,ad2x;//电压值
float ADCx;//转换后的电压值
/* USER CODE END 1 */
(4)启动ADC的DMA转换
/* USER CODE BEGIN 2 */
MX_ADC1_Init();//初始化ADC1,
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_Value,100); //直接采集到50组,通道4和通道5的数据,ADC_Value[i],偶数存的是通道4的数据,奇数存的是通道5的数据
printf("<<<<<<<ADC many channel DMA test\r\n");
HAL_Delay(2000);
/* USER CODE END 2 */
DMA每次使能,会把选择的通道都采集一次。
这里HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_Value,100);
原函数HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length);
我们看到缓冲区uint32_t *pData是32位。但是我们在配置DMA采集数据格式是HalfWord 16位。
uint16_t ADC_Value[100];//定义16位的100个数据的数组,直接调用
HAL_ADC_Start_DMA(&hadc1,ADC_Value,100);
就会报错:
../Core/Src/main.c(121): error: #167: argument of type "uint16_t *" is incompatible with parameter of type "uint32_t *"。
如果定义存放数据的数组改成32位:
uint32_t ADC_Value[100];//定义32位的100个数据的数组
再编译就不会报错了。
恭喜,就会掉入新的坑里。
调试一下,就会发现。数组前面50个有数据,后面50个没数据。
注意观察,前面50个的数据,每个数据存了两个通道的数据。
因为采样的是16位数据,数组格式是32位的。就会出现下面的情况。
因此,我们HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC_Value,100);
(5)while添加采集
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ad1=0,ad2=0;//清零
for(i=0;i<50;i++)
{
ad1+=ADC_Value[2*i]; //通道4数据累加
ad2+=ADC_Value[2*i+1];//通道5数据累加
}
ad1/=50;//求平均值
ad2/=50;//求平均值
ad1x =ad1 *3.3/65536; //转化为电压值
ad2x =ad2 *3.3/65536; //转化为电压值
printf("\r\n ****ADC DMA Example****\r\n\r\n");
printf("AD1 value = %d, AD1电压是%.2fV\r\n",ad1,ad1x);
printf("AD2 value = %d, AD2电压是%.2fV\r\n",ad2,ad2x);
HAL_Delay(1000);
}
/* USER CODE END 3 */
(6)添加adc校准函数
这里很关键!stm32cubemx初始化不包含校准函数,需要自己手动添加。
在adc.c的初始化函数void MX_ADC1_Init(void)内添加
/* USER CODE BEGIN ADC1_Init 2 */
HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); /* ADC校准 */
/* USER CODE END ADC1_Init 2 */
(7)编译
stm32H7系列编译起来时间超级长
花了十分钟多。有两个解决办法:
①、不勾选“Browse information”![](https://i-blog.csdnimg.cn/direct/d25e8d976aa645f58cb657ca23be2652.png)
②编译器选择“version 6“
两个方法都能编译速度极大的提升!
Debug一次。
能看到直接采集100个数据,保存到数组里。
运行
二、DMA多模块多通道ADC数据
和单模块多通道ADC数据一样的配置就不贴图了。
(1)ADC配置
增加ADC模块ADC3的芯片温度和内部基准电压采集,采集时间设置为850.5个周期
时钟设置为32MHz,
采样时间为810.5时钟周期
转换时间=采样时间+7.5ADC时钟周期
=810.5+7.5 =812个时钟周期=25.375us
配置完成后,生成初始化代码。
代码部分
(1)添加头文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h" //添加头文件
/* USER CODE END Includes */
(2)添加打印函数
/* USER CODE BEGIN 0 */
int fputc(int c, FILE *stream) //重写fputc函数
{
/*
huart1是工具生成代码定义的UART1结构体,
如果以后要使用其他串口打印,只需要把这个结构体改成其他UART结构体。
*/
HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);
return 1;
}
/* USER CODE END 0 */
(3)添加变量
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t ADC1_Value[100];//定义16位的100个数据的数组,存储adc的数据
uint16_t ADC3_Value[100];//定义16位的100个数据的数组,存储adc的数据
uint8_t i;
uint32_t ad1,ad2;//平均值
uint32_t ad3,ad4;//平均值
float ad1x,ad2x;//转换后的电压值
float ad3x,ad4x;//转换后的电压值
float temp; //芯片内部温度
/* USER CODE END 1 */
(4)启动ADC的DMA转换
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)&ADC1_Value,100); //直接采集到50组,通道4和通道5的数据,ADC_Value[i],偶数存的是通道4的数据,奇数存的是通道5的数据
HAL_ADC_Start_DMA(&hadc3,(uint32_t *)&ADC3_Value,100); //直接采集到50组,通道4和通道5的数据,ADC_Value[i],偶数存的是通道4的数据,奇数存的是通道5的数据
printf("<<<<<<<ADC + many module+channel DMA test\r\n");
HAL_Delay(2000);
/* USER CODE END 2 */
(5)while添加采集
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ad1=0,ad2=0;
ad3=0,ad4=0;
for(i=0;i<50;i++)
{
ad1+=ADC1_Value[2*i]; //通道4数据累加
ad2+=ADC1_Value[2*i+1];//通道5数据累加
ad3+=ADC3_Value[2*i]; //通道4数据累加
ad4+=ADC3_Value[2*i+1];//通道5数据累加
}
ad1/=50;//求平均值
ad2/=50;//求平均值
ad3/=50;//求平均值
ad4/=50;//求平均值
ad1x =ad1 *3.3/65536;
ad2x =ad2 *3.3/65536;
ad3x =ad3 *3.3/65536;
ad4x = (110.0-30.0)/(*(unsigned short*)(0x1FF1E840) - *(unsigned short*)(0x1FF1E820));//芯片内部温度计算公式
temp = ad4x*(ad4 - *(unsigned short*)(0x1FF1E820))+30;//芯片内部温度计算公式
//temp为最终的温度值
printf("\r\n ****ADC DMA Example****\r\n\r\n");
printf("AD1 value = %d, AD1电压是%.2fV\r\n",ad1,ad1x);
printf("AD2 value = %d, AD2电压是%.2fV\r\n",ad2,ad2x);
printf("AD3 value = %d, AD3内部参考电压是%.2fV\r\n",ad3,ad3x);
printf("AD4 value = %d, AD4温度是%.2f℃\r\n",ad4,temp);
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
(6)添加adc校准函数
这里很关键!stm32cubemx初始化不包含校准函数,需要自己手动添加。
在adc.c的初始化函数void MX_ADC1_Init(void)内添加
/* USER CODE BEGIN ADC1_Init 2 */
HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); /* ADC校准 */
HAL_ADCEx_Calibration_Start(&hadc3, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); /* ADC校准 */
/* USER CODE END ADC1_Init 2 */
运行
验证准确性
(1)我们查看芯片数据手册,
内部参考电压是1.216V
采集的电压是1.21V,能够对的起来。
(2)内部温度
我们运行正点原子的例程
采集的温度在41.5到42摄氏度之间。