DMA原理与实验例程

一、DMA简介

DMA(Direct Memory Access)一直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用CPU,即在传输数据的时候, CPU可以干其他的事情,好像是多线程一样。数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM或者是FLASH, DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道,这里的通道可以理解为传输数据的一种管道。要注意的是DMA2只存在于大容量的单片机中。

二、DMA功能框图

DMA控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握功能框图中的三部分内容即可,具体见图: DMA控制器的框图。
在这里插入图片描述
图 DMA功能框图

三、 DMA请求
如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求, DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。DMA有DMA1和DMA2两个控制器, DMA1有7个通道, DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,具体见DMA请求映像表。
在这里插入图片描述
图 DMA各通道请求映像表
其中ADC3, SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。

四、通道

DMA具有12个独立可编程的通道,其中DMA1有7个通道, DMA2有5个通道,每个通道对应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

五、仲裁器

当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理DMA通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA-CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0高于通道1,在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。

六、DMA数据配置

使用DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。

6.1从哪里来到哪里去

我们知道DMA传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。具体的方向DMA CCR位4DIR配置: 0表示从外设到存储器, 1表示从存储器到外设。这里面涉及到的外设地址由DMA CPAR配置,存储器地址由DMA CMAR配置。

外设到存储器

当我们使用从外设到存储器传输时,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。

存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。

存储器到存储器

当我们使用从存储器到存储器传输时,以内部FLASH向内部SRAM复制数据为例。DMA外设寄存器的地址对应的就是内部FLASH (我们这里把内部FALSH当作一个外设来看)的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA CCR位14: MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。

6.2.要传多少,单位是什么
当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA-CNDTR配置,这是一个32位的寄存器,一次最多只能传输65535个数据。要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8位的,所以我们定义的要发送的数据也必须是8位。外设的数据宽度由DMA CCRx的PSIZE[1:0]配置,可以是8/16/32位,存储器的数据宽度由DMA-CCRx的MSIZE[1:0]配置,
可以是8/16/32位。
在DMA控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由DMA-CCRx的PINC配置,存储器的地址指针由MINC配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。

6.3.什么时候传输完成
数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA通道在DMA传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考DMA中断状态寄存器DMAISR的详细描述。
传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断DMA使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由DMA CCRx寄存器的CIRC循环模式位控制。

七、 DMA初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体xxx InitTypeDef(xxx为外设名称),结构体成员用于设置外设工作参数,并由标准库函数xxx Init()调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的。结构体xxx InitTypeDef和库函数xxxInit配合使用是标准库精髓所在,理解了结构体
xxxInitTypeDef每个成员意义基本上就可以对该外设运用自如。结构体xxxInitTypeDef定
义在stm32f10x xxx.h(后面xxx为外设名称)文件中,库函数xxx Init定义在stm32fl0xxxx.c
文件中,编程时我们可以结合这两个文件内注释使用。

typedef struct
 {
 uint32_t DMA_PeripheralBaseAddr; // 外设地址
 uint32_t DMA_MemoryBaseAddr; // 存储器地址
 uint32_t DMA_DIR; // 传输方向
 uint32_t DMA_BufferSize; // 传输数目
 uint32_t DMA_PeripheralInc; // 外设地址增量模式
 uint32_t DMA_MemoryInc; // 存储器地址增量模式
 uint32_t DMA_PeripheralDataSize; // 外设数据宽度
 uint32_t DMA_MemoryDataSize; // 存储器数据宽度
 uint32_t DMA_Mode; // 模式选择
 uint32_t DMA_Priority; // 通道优先级
 uint32_t DMA_M2M; // 存储器到存储器模式
 } DMA_InitTypeDef;
  1. DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
  2. DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。
  3. DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的 DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
  4. DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值。
  5. DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
  6. DMA_MemoryInc:如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
  7. DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16位)和字(32位),它设定 DMA_CCR 寄存器的 PSIZE[1:0]位的值。
  8. DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
  9. DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR 寄存器的 CIRC 位的值。例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。
  10. DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0]位的值。DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
  11. DMA_M2M: 存储 器到存 储器 模式 , 使 用存储 器到 存储 器时 用到, 设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。

实验一、DMA 存储器到存储器模式实验
存储器到存储器模式可以实现数据在两个内存的快速拷贝。我们先定义一个静态的源数据,存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM),最后对比源数据和目标地址的数据,看看是否传输准确。

1、软件设计
1.1. 编程要点

  1. 使能 DMA 时钟;
  2. 配置 DMA 数据参数;
  3. 使能 DMA,进行传输;
  4. 等待传输完成,并对源数据和目标地址数据进行比较。

1.2代码分析
DMA 宏定义及相关变量定义

 // 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
 #define DMA_CHANNEL DMA1_Channel6
 #define DMA_CLOCK RCC_AHBPeriph_DMA1

 // 传输完成标志
 #define DMA_FLAG_TC DMA1_FLAG_TC6

 // 要发送的数据大小
 #define BUFFER_SIZE 32
 
 /* 定义 aSRC_Const_Buffer 数组作为 DMA 传输数据源
 * const 关键字将 aSRC_Const_Buffer 数组变量定义为常量类型
 * 表示数据存储在内部的 FLASH 中
 */
 const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]=
 {
 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80
 };
 /* 定义 DMA 传输目标存储器
 * 存储在内部的 SRAM 中
 */
 uint32_t aDST_Buffer[BUFFER_SIZE];

注意
1、使用宏定义设置外设配置方便程序修改和升级。
2、存储器到存储器传输通道没有硬性规定,可以随意选择。
3、aSRC_Const_Buffer[BUFFER_SIZE]定义用来存放源数据,并且使用了 const 关键字修饰,即常量类型,使得变量是存储在内部 flash 空间上。

DMA 数据配置

 void DMA_Config(void)
 {
 DMA_InitTypeDef DMA_InitStructure;
 
 // 开启 DMA 时钟
 RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
 // 源数据地址
 DMA_InitStructure.DMA_PeripheralBaseAddr =
 (uint32_t)aSRC_Const_Buffer;
 // 目标地址
 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
 // 方向:外设到存储器(这里的外设是内部的 FLASH)
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
 // 传输大小
 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
 // 外设(内部的 FLASH)地址递增
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
 // 内存地址递增
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
 // 外设数据单位
 DMA_InitStructure.DMA_PeripheralDataSize =
 DMA_PeripheralDataSize_Word;
 // 内存数据单位
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
 // DMA 模式,一次或者循环模式
 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
 // 优先级:高
 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
 // 使能内存到内存的传输
 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
 // 配置 DMA 通道
 DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
 // 使能 DMA
 DMA_Cmd(DMA_CHANNEL,ENABLE);
 }

使用 DMA_InitTypeDef 结构体定义一个 DMA 初始化变量,这个结构体内容我们之前已经有详细讲解。
调用 RCC_AHBPeriphClockCmd 函数开启 DMA 时钟,使用 DMA 控制器之前必须开启对应的时钟。
源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏 BUFFER_SIZE 决定,源和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个 DMA通道,优先级随便设置,最后调用 DMA_Init 函数完成 DMA 的初始化配置。
DMA_ClearFlag函数用于清除DMA标志位,代码用到传输完成标志位,使用之前先清 除传输完成标志位以免产生不必要干扰。DMA_ClearFlag 函数需要 1 个形参,即事件标志位,可选有传输完成标志位、半传输标志位、FIFO 错误标志位、传输错误标志位等等,非常多,我们这里选择传输完成标志位,由宏 DMA_FLAG_TC 定义。
DMA_Cmd 函数用于启动或者停止 DMA 数据传输,它接收两个参数,第一个是 DMA通道,另外一个是开启 ENABLE 或者停止 DISABLE。

存储器数据对比

 uint8_t Buffercmp(const uint32_t* pBuffer,
 uint32_t* pBuffer1, uint16_t BufferLength)
{
 /* 数据长度递减 */
 while (BufferLength--) {
 /* 判断两个数据源是否对应相等 */
 if (*pBuffer != *pBuffer1) {
 /* 对应数据源不相等马上退出函数,并返回 0 */
 return 0;
 }
 /* 递增两个数据源的地址指针 */
 pBuffer++;
 pBuffer1++;
 }
 /* 完成判断并且对应数据相对 */
 return 1;
 }

函数说明:判断指定长度的两个数据源是否完全相等,如果完全相等返回 1;只要其中一对数据不相等返回 0。它需要三个形参,前两个是两个数据源的地址,第三个是要比较数据长度。

主函数

 int main(void)
 {
 /* 定义存放比较结果变量 */
 uint8_t TransferStatus;
 
 /* LED 端口初始化 */
 LED_GPIO_Config();
 
 /* 设置 RGB 彩色灯为紫色 */
 LED_PURPLE;
 
 /* 简单延时函数 */
 Delay(0xFFFFFF);
 
 /* DMA 传输配置 */
 DMA_Config();
 
 /* 等待 DMA 传输完成 */
 while (DMA_GetFlagStatus(DMA_FLAG_TC)==RESET)
 {
 
 }
 
 /* 比较源数据与传输后数据 */
 TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
 
 /* 判断源数据与传输后数据比较结果*/
 if (TransferStatus==0)
{
 /* 源数据与传输后数据不相等时 RGB 彩色灯显示红色 */
 LED_RED;
 }
 else
 {
 /* 源数据与传输后数据相等时 RGB 彩色灯显示蓝色 */
 LED_BLUE;
 }
 
 while (1)
{
 }
 }

程序说明及实验现象:
首先定义一个变量用来保存存储器数据比较结果。RGB 彩色灯用来指示程序进程,使用之前需要初始化它,LED_GPIO_Config 定义在bsp_led.c 文件中。开始设置 RGB 彩色灯为紫色,LED_PURPLE 是定义在 bsp_led.h 文件的一个宏定义。
Delay 函数只是一个简单的延时函数。调用 DMA_Config 函数完成 DMA 数据流配置并启动 DMA 数据传输。
DMA_GetFlagStatus 函数获取 DMA 事件标志位的当前状态,这里获取 DMA 数据传输完成这个标志位,使用循环持续等待直到该标志位被置位,即 DMA 传输完成这个事件发生,然后退出循环,运行之后程序。
确定 DMA 传输完成之后就可以调用 Buffercmp 函数比较源数据与 DMA 传输后目标地址的数据是否一一对应。
TransferStatus 保存比较结果,如果为 1 表示两个数据源一一对应相等说明 DMA 传输成功;相反,如果为 0 表示两个数据源数据存在不等情况,说明 DMA传输出错。如果 DMA 传输成功设置 RGB 彩色灯为蓝色,如果 DMA 传输出错设置 RGB 彩色灯为红色。

实验二、 DMA 存储器到外设模式实验
我们先定义一个数据变量,存于 SRAM 中,然后通过 DMA 的方式传输到串口的数
据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来。

1、软件设计

  1. 配置 USART 通信功能;
  2. 设置串口 DMA 工作参数;
  3. 使能 DMA;
  4. DMA 传输同时 CPU 可以运行其他任务。

2、代码分析

USART 和 DMA 宏定义

// 串口工作参数宏定义
 #define DEBUG_USARTx USART1
 #define DEBUG_USART_CLK RCC_APB2Periph_USART1
 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
 #define DEBUG_USART_BAUDRATE 115200
 
 // USART GPIO 引脚宏定义
 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
 #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
 
 #define DEBUG_USART_TX_GPIO_PORT GPIOA
 #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
 #define DEBUG_USART_RX_GPIO_PORT GPIOA
 #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
 
 // 串口对应的 DMA 请求通道
 #define USART_TX_DMA_CHANNEL DMA1_Channel4
 // 外设寄存器地址
 #define USART_DR_ADDRESS (USART1_BASE+0x04)
 // 一次发送的数据量
 #define SENDBUFF_SIZE 5000

程序说明:
使用宏定义设置外设配置方便程序修改和升级。
USART 部分设置与 USART 章节内容相同,可以参考 USART 章节内容理解。串口的发送请求对应有固定的 DMA 通道,这里的外设的地址即串口的数据寄存器,一次要发送的数据量可以自定义,配置 SENDBUFF_SIZE 这个宏即可。

串口 DMA 传输配置

void USARTx_DMA_Config(void)
 {
 DMA_InitTypeDef DMA_InitStructure;

 // 开启 DMA 时钟
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
 // 设置 DMA 源地址:串口数据寄存器地址*/
 DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
 // 内存地址(要传输的变量的指针)
 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
 // 方向:从内存到外设
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
 // 传输大小
 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
 // 外设地址不增
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 // 内存地址自增
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
 // 外设数据单位
 DMA_InitStructure.DMA_PeripheralDataSize =
 DMA_PeripheralDataSize_Byte;
 // 内存数据单位
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
 // DMA 模式,一次或者循环模式
 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
 // 优先级:中
 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
 // 禁止内存到内存的传输
 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
 // 配置 DMA 通道
 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);
 // 使能 DMA
 DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
 }

程序说明:
首先定义一个 DMA 初始化变量,用来填充 DMA 的参数,然后使能 DMA 时钟。因为数据是从存储器到串口,所以设置存储器为源地址,串口的数据寄存器为目标地址,要发送的数据有很多且都先存储在存储器中,则存储器地址指针递增,串口数据寄存器只有一个,则外设地址地址不变,两边数据单位设置成一致,传输模式可选一次或者循环传输,只有一个 DMA 请求,优先级随便设,最后调用 DMA_Init 函数把这些参数写到DMA 的寄存器中,然后使能 DMA 开始传输。

主函数

 int main(void)
 {
 uint16_t i;
 /* 初始化 USART */
 USART_Config();
 
 /* 配置使用 DMA 模式 */
 USARTx_DMA_Config();
/* 配置 RGB 彩色灯 */
 LED_GPIO_Config();
 
 printf("\r\n USART1 DMA TX 测试 \r\n");
 
 /*填充将要发送的数据*/
 for (i=0; i<SENDBUFF_SIZE; i++)
 {
 SendBuff[i] = 'P';
 
 }
 
 /*为演示 DMA 持续运行而 CPU 还能处理其它事情,持续使用 DMA 发送数据,量非常大,
 *长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,
 *或把 DMA 配置中的循环模式改为单次模式*/
 
 /* USART1 向 DMA 发出 TX 请求 */
 USART_DMACmd(USARTx, USART_DMAReq_Tx, ENABLE);
 
 /* 此时 CPU 是空闲的,可以干其他的事情 */
 //例如同时控制 LED
 while (1)
 {
 LED1_TOGGLE
 Delay(0xFFFFF);
 }
 }

程序说明:
USART_Config 函数定义在 bsp_usart_dma.c 中,它完成 USART 初始化配置,包括GPIO 初始化,USART 通信参数设置等等,具体可参考 USART 。
USARTx_DMA_Config 函数也是定义在 bsp_usart_dma.c 中,之前我们已详细分析。
LED_GPIO_Config 函数定义在 bsp_led.c 中,它完成 RGB 彩色灯初始化配置。
使用 for 循环填充源数据,SendBuff[SENDBUFF_SIZE]是定义在 bsp_usart_dma.c 中的一个全局无符号 8 位整数数组,是 DMA 传输的源数据,在 USART_DMA_Config 函数中已
经被设置为存储器地址。
USART_DMACmd 函数用于控制 USART 的 DMA 请求的启动和关闭。它接收三个参数,第一个参数用于设置串口外设,可以是 USART1/2/3 和 UART4/5 这 5 个参数可选,第二个参数设置串口的具体 DMA 请求,有串口发送请求 USART_DMAReq_Tx 和接收请求USART_DMAReq_Rx 可选,第三个参数用于设置启动请求 ENABLE 或者关闭请求DISABLE。运行该函数后 USART的 DMA发送传输就开始了,根据配置存储器的数据会发送到串口。
DMA 传输过程是不占用 CPU 资源的,可以一边传输一次运行其他任务。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是HAL库DMA串口发送数据的程: ```c #include "stm32f4xx_hal.h" UART_HandleTypeDef huart2; DMA_HandleTypeDef hdma_usart2_tx; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_USART2_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_USART2_UART_Init(); char* message = "Hello, world!\r\n"; HAL_UART_Transmit_DMA(&huart2, (uint8_t*)message, strlen(message)); while (1) { } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 16; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4; RCC_OscInitStruct.PLL.PLLQ = 7; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } if (HAL_PWREx_EnableOverDrive() != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } } static void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } } static void MX_DMA_Init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); hdma_usart2_tx.Instance = DMA1_Stream6; hdma_usart2_tx.Init.Channel = DMA_CHANNEL_4; hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_tx.Init.Mode = DMA_NORMAL; hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(&huart2,hdmatx,hdma_usart2_tx); } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } void Error_Handler(void) { __disable_irq(); while (1) { } } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值