一、DMA
直接存储器存取(Direct Memory Access,DMA),直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
二、ADC连续模式和扫描模式
ADC单通道时,只进行一次ADC转换,关闭连续转换模式,扫描模式关闭,这样ADC通道转换完成后停止,等待ADC转换的下一次启动。进行连续ADC转换时,ADC配置为连续转换模式,扫描模式关闭,ADC通道,转换一次后,接着进行下一次转换,不断连续。
ADC多通道时,要使能扫描模式。如果关闭连续转换模式,ADC多个通道,按照配置的顺序依次转换一次完成后,就停止转换。使能连续转换模式,ADC多个通道按照配置的顺序依次转换完成后,接着继续从头按照配置顺序依次转换,不断连续。
当多通道开启扫描模式时,不能直接读取AD转换数据,需要通过DMA方式读取
三、STM32CubeMx配置
声音传感器模块
光传感器模块
1.将PA4和PA6配置成ADC模式
2.打开ADC1的通道9和通道11,发现PA4和PA6变成绿色,GPIO模式也自动变成ADC转换模式
3.使能外部高速时钟
4.然后是配置时钟点击Clock Configuration,由于先打开了ADC而没有配置时钟,点到时钟配置时会显示
点No就好了,继续配置
5.使能DMA,增量地址选择内存,数据长度为字,连续传输模式
6.配置ADC参数
ADC 要完成对输入电压的采样需要若干个 ADC_CLK 周期,计算公式:
TCONV = 采样时间 + 12.5,所以ADC采样周期 = (2.5+12.5)= 15个周期,ADC完成采样的时间 = 1/fadc_clk * (ADC采样周期)。
7.使能ADC中断
8.使能串口
生成代码
四、Keil编写程序
在uart.c中做printf、scanf的重定向以便于串口通信
#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch;
HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,0xfff);
return ch;
}
在adc.c中void MX_ADC1_Init(void)是ADC的初始化,与STM32CubeMX中配置的一样,也可以在此处修改
第一种ADC多通道采样方法:
1.在main.c定义一个数组作为转换数据缓存数组
/* USER CODE BEGIN 1 */
uint32_t ADC_Value[100];
uint8_t i ;
uint32_t ad1,ad2 ;
/* USER CODE END 1 */
2.在while(1)前函数去开启ADC转换和DMA传输
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value,100);
/* USER CODE END 2 */
3.DMA采用了连续传输的模式,ADC采集到的数据会不断传到到存储器中(此处即为数组ADC_Value)。数组里面的数据会不断被刷新。由于ADC顺序先是通道9(声音传感器)再是通道11(环境光传感器),所以存到ADC_Value[0]的是声音传感器ADC采集到的数据,ADC_Value[1]是环境光传感器ADC采集的数据,依此类推数组偶数存的是通道9(声音传感器)ADC采样值,数组级数存的是通道11(环境光传感器)ADC采样值。
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(500);
for(i = 0,ad1 = 0, ad2 = 0; i<100;)
{
ad1 += ADC_Value[i++] ;
ad2 += ADC_Value[i++] ;
}
ad1 /=50 ;
ad2 /=50 ;
printf("\r\n******** ADC DMA Example ********\r\n\r\n");
printf("adc1 = %d\r\n",ad1);
printf("adc2 = %d\r\n",ad2);
printf("Voice Voltage: %.3fV\r\n",ad1*3.3f/4096);
printf("Light Voltage: %.3fV\r\n",ad2*3.3f/4096);
printf("\r\n********End ADC Example ********\r\n");
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
将数组偶数下标数据加起来求平均值得到通道9(声音传感器)ADC采样值,将数组偶数下标数据加起来求平均值得到通道11(光传感器)ADC采样值,减小误差,再将数据转换为电压值。
ADC采样值到电压的转换:
ADC12位分辨率,也就是说ADC模块读到的数据是12位的数据。。二进制的12位可表示0-4095个数。 电压值 = ADC采样值*(3.3V/4096)
第二种ADC多通道采样方法:
ADC的几个回调函数:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc);
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc);
void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc);
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc);
ADC中断、DMA传输、看门狗超过阀值、发生Adc错误,这些函数返回前都调用了Callback函数。用来在非中断模式下处理Adc数据,如果想进行一些操作可以直接修改Callback函数。
查看HAL_ADC_Start_DMA()函数
在HAL_ADC_Start_DMA()里会设置函数ADC_DMAConvCplt
在ADC_DMAConvCplt里有回调函数HAL_ADC_ConvCpltCallback()
HAL_ADC_ConvCpltCallback()是虚函数,需要重新定义。
因为ADC转换和DMA传输很快,采样到的值可能还没读出来就被新的覆盖掉,可设置标志位当采样到的值传输到DMA之后停止,等到把DMA中的值读出来再启动,这就需要用到回调函数
在main函数
1.设置全局变量标志位
/* USER CODE BEGIN 0 */
int flag = 0 ;
/* USER CODE END 0 */
2.定义一个数组作为转换数据缓存数组,因为每次转换传输之后都会暂停等待数据被读出来所以可以设置数组小一点
/* USER CODE BEGIN 1 */
uint32_t ADC_Value[2];
/* USER CODE END 1 */
3.ADC转换和DMA数组传输开始
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value,2);
/* USER CODE END 2 */
4.在main函数外定义虚函数HAL_ADC_ConvCpltCallback回调函数,当ADC转换和DMA传输完成后,将标志位置为1并停止ADC转换和DMA传输
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
flag=1;
HAL_ADC_Stop_DMA(hadc);
}
/* USER CODE END 4 */
5.在main函数while(1)里,当标志位为1的时候,将标志位置0并将采样值读出来,然后启动ADC转换和DMA传输。
/* USER CODE BEGIN WHILE */
while (1)
{
if(flag==1)
{
flag=0;
HAL_Delay(200);
printf("\r\n******** ADC DMA Example ********\r\n\r\n");
printf("\r\nch1 = %d\r\n",ADC_Value[0]);
HAL_Delay(200);
printf("ch2 = %d\r\n",ADC_Value[1]);
printf("Voice Voltage: %.3fV\r\n",ADC_Value[0]*3.3f/4096);
printf("Light Voltage: %.3fV\r\n",ADC_Value[1]*3.3f/4096);
printf("\r\n********End ADC Example ********\r\n");
HAL_Delay(1000) ;
}
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_Value, 2);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
1.4秒采样一次,这个多通道采集的方法误差比较大
五、测试
用手机手电筒照在环境光传感器上测试
用手机对着声音传感器放音乐
参考博客:https://blog.csdn.net/qq_42908042/article/details/86775013