优秀的编程知识分享平台

网站首页 > 技术文章 正文

STM32单片机从零开始使用教程(五) DMA方法实现双通道ADC采样

nanyue 2025-05-23 19:00:46 技术文章 2 ℃


这次我们改成连续转换模式并且给它添加DMA


DMA

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

数据可以不在CPU的干预下通过DMA快速移动,节省CPU的资源。

STM32F10xxx系列的DMA框图如图所示,不过DMA2是大容量和互联型产品才有。


在X86架构中,DMA运作时会占用系统总线周期的一部分时间,但是在STM32控制器的Cortex-M3架构中,DMA占用另外的总线,不会和cpu的系统总线发生冲突。


ADC转换后的规则组的数据都放在规则数据寄存器(ADC_DR)中,注入组的数据放在注入数据寄存器(ADC_JDRx)中。为了防止不同通道转换的数据互相覆盖,就要在转换完成后把数据取走(之前的非DMA方式)或者直接把数据传输到内存中(这次的DMA方式)。


在手册的ADC部分可以看到


扫描模式的ADC当设置了DMA位后,在每次EOC后DMA控制器会把规则组通道的转换数据传输到SRAM中。


程序编写

以两个通道的ADC为例,每次转换的过程应该是

通道1转换得到数据->通过DMA写入->通道2转换得到数据->通过DMA写入

这样循环往复,所以获得数据是两个通道采样结果交替的。

我们预先定义一个数组

uint32_t ADC_buffer[bufferlen];

来存放采样结果,然后在程序中先打开ADC和DMA

HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_buffer,bufferlen); //使用DMA传输

这里的第二个参数传入的是存放数据的地址,DMA便会不断的把数据放到这里,第三个参数则是这个区域的大小。


接下来在while循环里面实现数据的读取,我们只需交替的从ADC_buffer中读取通道1和通道2的数据即可。数据转换为电压的方式和之前相同。

for(uint8_t i=0;i<bufferlen;){

printf("Channel 1=%1.4f\r\n",ADC_buffer[i++]*3.3f/4096);

printf("Channel 2=%1.4f\r\n",ADC_buffer[i++]*3.3f/4096);


}


(这里是把printf重定向为了串口打印)


我将缓冲区的长度设为10,可以看到每轮发来的数据有10个,分别是5个channel1和5个channel2,他们交替出现,一个连接GND一个连接VCC

DMA已经把数据写满了的话就会从头开始写(最开始我们选了circular嘛),那我就有一个好奇了,如果我的bufferlen不是通道数的整数倍,会不会出现错位。

比如我把bufferlen设置为9


虽然我仍然是channel1接的GND,channel2接的VCC,但是很明显数据已经乱了

因为不再是第2*i个位置存放channel1,第2*i+1个位置存放channel2

所以bufferlen应当为通道数的整数倍。


printf的重定向

为了方便的使用串口打印,可以直接把printf重定向为串口发送字符串

首先在keil中启用微库,就是Use MicroLIB


然后在keil工程中进行引用

#include "stdio.h"

并添加重定向代码。

/* USER CODE BEGIN PFP */


/* re-code printf */

#ifdef __GNUC__

#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)

#else

#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)

#endif /* __GNUC__ */


/* USER CODE END PFP */


/* Private user code ---------------------------------------------------------*/

/* USER CODE BEGIN 0 */


PUTCHAR_PROTOTYPE

{

HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);

return ch;

}


/* USER CODE END 0 */

Tags:

最近发表
标签列表