STM32之DMA让串口接收任意长度数据

编程入门 行业动态 更新时间:2024-10-27 18:20:27

STM32之DMA让<a href=https://www.elefans.com/category/jswz/34/1769224.html style=串口接收任意长度数据"/>

STM32之DMA让串口接收任意长度数据

DMA

简介

DMA(Direct Memory Access,直接存储器访问) ,DMA 传输是将数据从一个地址空间复制到另外一个地址空间。CPU 只负责初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。

原理

一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

  1. DMA请求
    CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
  2. DMA响应
    DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
  3. DMA传输
    DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
    在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
  4. DMA结束
    当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。

STM32标准库编程

DMA初始化结构体

结构体定义

typedef struct
{//指定DMAy信道的外设基址uint32_t DMA_PeripheralBaseAddr;//指定DMAy信道的内存基地址。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;//指定DMAy信道x的操作模式。uint32_t DMA_Mode;//指定DMAy信道x的软件优先级uint32_t DMA_Priority;//指定DMAy通道x是否将在内存到内存传输中使用uint32_t DMA_M2M;
} DMA_InitTypeDef;

结构体参数取值

  • DMA_DIR
    指定这个外设是作为数据传输的目的地还是数据传输的来源
//外设作为数据传输的目的地
#define DMA_DIR_PeripheralDST              ((uint32_t)0x00000010)
//外设作为数据传输的来源
#define DMA_DIR_PeripheralSRC              ((uint32_t)0x00000000)
  • DMA_PeripheralInc
    指定外设地址寄存器是否递增
//递增
#define DMA_PeripheralInc_Enable           ((uint32_t)0x00000040)
//不递增
#define DMA_PeripheralInc_Disable          ((uint32_t)0x00000000)
  • DMA_MemoryInc
    指定内存地址寄存器是否递增
//递增
#define DMA_MemoryInc_Enable               ((uint32_t)0x00000080)
//不递增
#define DMA_MemoryInc_Disable              ((uint32_t)0x00000000)
  • DMA_PeripheralDataSize
    指定外设数据宽度。
//一个字节(8位)
#define DMA_PeripheralDataSize_Byte        ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_PeripheralDataSize_HalfWord    ((uint32_t)0x00000100)
//一个字(32位)
#define DMA_PeripheralDataSize_Word        ((uint32_t)0x00000200)
  • DMA_MemoryDataSize
    指定内存数据宽度。
//一个字节(8位)
#define DMA_MemoryDataSize_Byte            ((uint32_t)0x00000000)
//半个字(16位)
#define DMA_MemoryDataSize_HalfWord        ((uint32_t)0x00000400)
//一个字(32位)
#define DMA_MemoryDataSize_Word            ((uint32_t)0x00000800)
  • DMA_Mode
    指定DMAy信道x的操作模式。
//循环模式
#define DMA_Mode_Circular                  ((uint32_t)0x00000020)
//普通模式
#define DMA_Mode_Normal                    ((uint32_t)0x00000000)
  • DMA_Priority
    指定DMAy信道x的软件优先级
//很高
#define DMA_Priority_VeryHigh              ((uint32_t)0x00003000)
//高
#define DMA_Priority_High                  ((uint32_t)0x00002000)
//中等
#define DMA_Priority_Medium                ((uint32_t)0x00001000)
//低
#define DMA_Priority_Low                   ((uint32_t)0x00000000)
  • DMA_M2M
    指定DMAy通道x是否将在内存到内存传输中使用
//是
#define DMA_M2M_Enable                     ((uint32_t)0x00004000)
//不是
#define DMA_M2M_Disable                    ((uint32_t)0x00000000)

DMA串口编程的一般步骤

  1. 配置NVIC分组
  2. 初始化串口
    1. 使能相应的串口以及对应的GPIO时钟
    2. 配置NVIC结构体并应用
    3. 配置GPIO结构体并应用
    4. 配置Usart结构体并应用
    5. 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE)
    6. 开启DMA接收
    7. 使能串口
  3. 初始化DMA
    1. 使能相应的DMA时钟
    2. 配置DMA结构体并应用
    3. 使能DMA
  4. 编写串口中断函数

示例代码

根据以上步骤书写代码,因为分文件书写这样看着会有点麻烦,所以我就将示例写在一个文件中,望各位大佬见谅

说明:
USART1与蓝牙进行连接,用于输入数据
USART2与PC进行连接,用于显示输入的数据

#include <stm32f10x.h>
#include <string.h>
#include <stdio.h>
//缓冲区的大小
#define Buff_Size 1024   
//此次接收结束的标志                  
volatile char rec_end_flag; 
//接收二级缓存中的数量               
volatile unsigned short count; 
//一级缓存,即DMA直接搬运数据的目的地            
volatile char usart1_recv_buff[Buff_Size]; 
/*二级缓存,即某次传输结束后将一级缓存的数据拷贝到此,
方便继续开启DMA使用一级缓存接收数据而数据不会被覆盖*/
volatile char recv_buff[Buff_Size];        //设置NVIC的优先级分组
void NVIC_config(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}
//初始化串口以及GPIO
void usart1_init(void)
{//用于GPIO初始化的结构体GPIO_InitTypeDef GPIO_InitStructure;//用于串口初始化的结构体USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStruct;//1. 初始化结构体NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;//应用NVIC结构体NVIC_Init(&NVIC_InitStruct);//2. 使能相应的串口以及对应的GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);//3. 配置GPIO结构体并应用//初始化Usart1的Txd脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化Usart1的Rxd脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//应用GPIO结构体GPIO_Init(GPIOA, &GPIO_InitStructure);//4. 配置Usart结构体并应用//初始化Usart1结构体USART_InitStructure.USART_BaudRate = 9600;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_Init(USART1, &USART_InitStructure);//5. 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE)USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//6. 开启DMA接收USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);//7. 使能串口USART_Cmd(USART1, ENABLE);
}
//使用Debug的显示
void usart2_init(void)
{//用于GPIO初始化的结构体GPIO_InitTypeDef GPIO_InitStructure;//用于串口初始化的结构体USART_InitTypeDef USART_InitStructure;//开启该串口以及其对应GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);//初始化Usart2的Txd脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化Usart2的Rxd脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化Usart2结构体USART_InitStructure.USART_BaudRate = 9600;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_Init(USART2, &USART_InitStructure);//使能Usart1USART_Cmd(USART2, ENABLE);
}//初始化DMA
void DMA1_init(void)
{DMA_InitTypeDef DMA_InitStructure;//1.使能DMA1时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//将DMA的通道5的寄存器重设为缺省值DMA_DeInit(DMA1_Channel5);//2. 配置DMA结构体并应用//设置DMA源地址DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & (USART1->DR);//内存地址基地址DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart1_recv_buff;//数据传输方向,从外设读取发送到内存DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_BufferSize = 1024; //DMA通道的DMA缓存的大小//外设地址寄存器不递增DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//内存地址寄存器递增DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//外设数据宽度为8位DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//内存数据宽度为8位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//工作模式为正常模式DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//DMA通道 x拥有中等优先级DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//此传输不是内存到内存传输DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//根据DMA_InitStruct中指定的参数初始化DMAy通道x。DMA_Init(DMA1_Channel5, &DMA_InitStructure);//3. 使能DMADMA_Cmd(DMA1_Channel5, ENABLE);
}
//串口1中断函数
void USART1_IRQHandler(void)
{//判断是否为空闲中断if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET){//数据接收完毕标志置1rec_end_flag = 1;//关闭DMA,准备重新配置DMA_Cmd(DMA1_Channel5, DISABLE);//clear DMA1 Channel5 global interrupt.DMA_ClearITPendingBit(DMA1_IT_GL5);//计算接收数据长度count = 1024 - DMA_GetCurrDataCounter(DMA1_Channel5);memcpy((void *)recv_buff, (void *)usart1_recv_buff, count);//重新配置DMA_SetCurrDataCounter(DMA1_Channel5, 1024);DMA_Cmd(DMA1_Channel5, ENABLE);//清除IDLE标志位USART1->SR;USART1->DR;}
}
//串口发送一个字符(调试使用)
void usart_send_ch(USART_TypeDef *USARTx, char ch)
{while (RESET == USART_GetFlagStatus(USARTx, USART_FLAG_TC));USART_SendData(USARTx, ch);
}
//串口发送字符串(调试使用)
void usart_send_str(USART_TypeDef *USARTx, char *str, int length)
{int i;for (i = 0; i < length; i++){usart_send_ch(USARTx, *(str + i));}//字符串末尾加换行\r\nchar enter_str[] = {'\r', '\n'};for (i = 0; i < 2; i++){usart_send_ch(USARTx, enter_str[i]);}
}
int main()
{//  1. 配置NVIC分组NVIC_config();//  2. 初始化串口以及GPIOusart1_init();//使用Debug的显示usart2_init();//  3. 初始化DMADMA1_init();//  4. 编写串口中断函数//见USART1_IRQHandlerwhile (1){//如此次传输完成if (rec_end_flag == 1){//将二级缓存的数据发送到串口2进行显示输出usart_send_str(USART2, (char *)recv_buff, count);//处理完数据后将标志置0等待下次传输结束rec_end_flag = 0;}}
}

结果展示

手机端发送

发送了36个字符

PC端接收

36个字符加\r\n刚好38个字符,成功
接着又发送了长一点的数据,也没有任何问题

总结

使用DMA的好处就是可以不用占用CPU的资源,另外CPU进入串口的空闲中断(USART_IT_IDLE)会比串口的接收中断(USART_IT_RXNE)的次数要少。而且使用空闲中断完美的解决了接收任意长度的数据,如若大佬们认为以上代码有任何Bug或错请在评论指出,我也会不断的完善。

更多推荐

STM32之DMA让串口接收任意长度数据

本文发布于:2024-02-07 06:11:18,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1754199.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:串口   长度   数据   DMA

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!