找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1310|回复: 3
收起左侧

STM32单片机串口DMA数据读取(详细代码)

[复制链接]
ID:955050 发表于 2023-4-8 16:12 | 显示全部楼层 |阅读模式
最近重新开始学STM32单片机,搞到串口 DMA 的时候, 数据读取卡了很长一段时间,最终,功夫不负有心人终于搞出来了。在此以记录一下,方便以后查询使用。
在调试的过程中也遇到了很多bug,有些简直就是低级问题,但是还是卡了很久,在此写出来给自己加深印象,同时已给后来者一个前车之鉴
1、在32的程序编写中,若使用到了中断部分,特别是中断函数一定要注意,查询清除中断标志位到底是什么。
获取中断标志位 是 USART_GetITStatus 函数,笔者就是没弄清 用成了 USART_ GetFlagStatus 函数,
USART_GetITStatus   检查指定的 USART 中断发生与否
USART_ GetFlagStatus 检查指定的 USART 标志位设置与否
就是这个问题卡了笔者很久,甚至笔者检查了很多次都没注意到

2、中断函数的使用, 这个在使用时若不注意就会用错,比如 中断串口2 ,却使用成了 中断串口1
关于中断函数的查询可以通过kile5 Startup文件夹 下的 startup_stm32f10x_md.s 进行查询

3、代码功能独立
这部分主要就是要将各函数功能进行,独立。方便理清逻辑,可能很多人觉得很麻烦,且很多人没有此习惯,但是在学习和工作中稍微花点时间,将其进行整理还是很有必要的。
笔者在调试这部分时,就出现了逻辑混乱的情况,将 GPIO, 串口,DMA 全部写在一起,导致最后混乱。最终每次只会搬运最后一个字符。具体原因笔者也没搞清楚。若有大佬能够了解,望指点一二。

—————————————————————分割线———————————————

以上废话,下为正文
DMA 串口数据读取主要就是 将串口的数据搬移到内存中
在实际代码中,笔者定义了一个char 数组进行存储。并通过重定义printf 的方式将 char 数组内的内容输出出来(后面看大家需不需要,也可以将DMA 数据发送的代码 分享出来 嘿嘿嘿)。
主要步骤如下
1、选择使用的串口(串口1)
2、查询该串口对应的引脚及DMA通道(A9,A10, DMA1chanel4, DMA1chanel5)Tip:可以通过STM32参考手册查询,后面会放的(感谢正点原子)
3、配置串口1 GPIO
4、配置串口1 USART 及 中断
5、配置串口1 对应 DMA

着重需要注意的是:这里使用的是 串口1 的 空闲中断。空闲中断在设备接收到数据后 一个字节内的时间还是为接收到数据就会产生,在清除之后,当下一次接收到数据之后的1个字节时间内才会再次产生。   说人话就是,正常情况下不会产生,只有在接收数据后,没有数据了,才会再次产生。
此处使用串口 的空闲中断可以实现 接收不定长数据,若使用的是 定长数据 就可以使用DMA 的接收完成中断(接收完成是当前数据接收完成,还需要判断接收次数是否为 0)

笔者设备 STM32F103RCT6 + window10,若其他设备可根据提供的资料进行查询对应的引脚和通道


—————————————分割线———————————————
开始放代码
配置引脚
  1. void USART1_GPIO_Init(void){//初始化 串口 1 GPIO
  2.                 //GPIO引脚功能初始化结构体
  3.                 GPIO_InitTypeDef GPIO_InitStruct;
  4.                 //1、使能串口引脚,复用功能
  5.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);
  6.                
  7.                 //2、初始化引脚功能 输入 悬空(输入高为高, 输入低为低), 输出 复用推挽
  8.                 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//初始化引脚为 A9
  9.                 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//初始化引脚功能为 复用推挽模式
  10.                 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//设置引脚传输速率为 10MHz
  11.                 GPIO_Init(GPIOA, & GPIO_InitStruct);//初始化 A9引脚 (发送)
  12.                
  13.                 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;//初始化引脚为 A10
  14.                 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//初始化引脚功能为 悬空输入
  15.                 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//设置引脚传输速率为 10MHz
  16.                 GPIO_Init(GPIOA, & GPIO_InitStruct);//初始化 A1引脚 (接收)
  17. }
复制代码
配置串口
  1. void USART1_Init(uint32_t BaudRate){               
  2.                 //串口初始化结构体
  3.                 USART_InitTypeDef USART_InitStruct;
  4.                 //串口优先级结构体
  5.                 NVIC_InitTypeDef NVIC_InitStruct;

  6.                 //1、使能串口引脚,复用功能
  7.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

  8.                 //2、串口初始化
  9.                 //2.1 初始化波特率
  10.                 USART_InitStruct.USART_BaudRate = BaudRate;//初始化串口波特率(使用形参确定)
  11.                 //2.2 初始化数据位
  12.                 USART_InitStruct.USART_WordLength = USART_WordLength_8b;//初始化串口数据位为 8 位
  13.                         //2.3 初始化停止位
  14.                 USART_InitStruct.USART_StopBits = USART_StopBits_1;//初始化串口数据位为 0 位
  15.                 //2.4 初始化奇偶校验位
  16.                 USART_InitStruct.USART_Parity = USART_Parity_No;//初始化串口奇偶校验位
  17.                 //2.5 初始化流控
  18.                 USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//初始化串口控制为无流控
  19.                 //2.6 初始化串口工作模式
  20.                 USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

  21.                 USART_Init(USART1, & USART_InitStruct);               

  22.                 //3、设置串口 接收中断 (空闲中断-搬运数据完毕 或 未搬运数据 当开始搬运数据后 一段时间未搬运下一个数据,发出此中断)
  23.                 //3.1 配置串口中断优先级通道
  24.                 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
  25.                 //3.2 使能命令
  26.                 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  27.                 //3.3 设置抢占优先级
  28.                 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
  29.                 //3.4 设置优先级
  30.                 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
  31.                 NVIC_Init( & NVIC_InitStruct);
  32.                
  33.                 //5、配置串口接收中断
  34.                 USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//串口1 空闲中断               
  35.                 //5、中断处理
  36.                 USART_DMACmd(USART1,USART_DMAReq_Rx, ENABLE);//开启串口1 DMA 接收请求
  37.                 //6、使能串口
  38.                 USART_Cmd(USART1,ENABLE);//若重载 printf ,就无需使用 DMA 进行数据搬移。(故此处应该无需使用重载)
  39. }
复制代码

配置 DMA
  1. void USART1_DMA_Init(void){
  2.         //DMA初始化结构体
  3.         DMA_InitTypeDef DMA_InitStruct;

  4.         //1、使能DMA时钟
  5.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
  6.         
  7.         //2、接收数据 DMA通道5初始化
  8.         //2.1 初始化外设地址
  9.         DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)& USART1->DR;//设置串口发送数据寄存器
  10.         //2.2 初始化内存地址
  11.         DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Get_Date;//接收内存地址
  12.         //2.3 初始化 DMA 方向
  13.         DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;//外设为源地址
  14.         //2.4 初始化 发送数据大小 ,6.2内存大小
  15.         DMA_InitStruct.DMA_BufferSize = BUFFSIZE;//此处的接收数据大小设置一个固定的,较大的
  16.         //2.5 初始化外设地址增量(向发送端写入数据,无需使用增量)
  17.         DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//无增量
  18.         //2.6 初始化内存地址增量(读取内存地址,写入外设,有增量)
  19.         DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;//有增量
  20.         //2.7 初始化外设数据宽度
  21.         DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//无增量的话应该不用设置
  22.         //2.8 初始化内存数据宽度
  23.         DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//增量为全字
  24.         //2.9 初始化DMA工作模式
  25.         DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;//正常循环模式
  26.         //2.10 初始化DMA通道优先级
  27.         DMA_InitStruct.DMA_Priority = DMA_Priority_High;//通道优先级 高
  28.         //2.11 初始化DMA存储器to存储器(内存写入外设,无需使用)
  29.         DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;//不使用 内存到内存               

  30.         DMA_Init(DMA1_Channel5, & DMA_InitStruct);//通道5 串口1 读数据

  31.         DMA_Cmd(DMA1_Channel5, ENABLE);//开启通道5 使能位
  32. }
复制代码

中断和调用函数部分
  1. void Mov_DmaDate_To_Buffer(void){//搬移函数,将DMA寄存器中的数据搬移到另一个寄存器
  2.         DMA_Cmd(DMA1_Channel5, DISABLE);//关闭 DMA 通道 5 (关闭串口1 读)
  3.                         
  4.         DMA1_Channel5->CNDTR = BUFFSIZE;//重新设置 通道 待传输数据大小
  5.                
  6.         DMA_Cmd(DMA1_Channel5, ENABLE);//开启 DMA 通道 5 (开启串口1 读)
  7.         
  8.         printf("%s\n",Get_Date);//输出接收的数据
  9. }


  10. void USART1_IRQHandler(void){//串口1 中断处理函数
  11.         if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){//若中断标志位为 空闲中断
  12.                 //调用搬移函数
  13.                 Mov_DmaDate_To_Buffer();//调用空闲中断处理函数

  14.                 USART_ReceiveData(USART1);//通过读取数据实现关闭 空闲中断
  15.                
  16.                 //清除标志位
  17.                 Recevie_State = true;//设置接收数据完毕标志位
  18.         }
  19. }
复制代码

Keil代码下载: 代码STM32_Study_Serial_DMA_Read.7z (193.55 KB, 下载次数: 39)

评分

参与人数 1黑币 +80 收起 理由
admin + 80 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:353115 发表于 2023-4-8 17:15 | 显示全部楼层
定长和不定长都可以使用DMA功能,限制最大接收数量就好了。
既然用了空闲中断,定长数据和不定长数据就失去了意义,反正空闲之后标志这一帧结束。在空闲中断中要停用串口的DMA功能,处理完数据后再开启串口的DMA功能。
回复

使用道具 举报

ID:955050 发表于 2023-4-8 20:01 | 显示全部楼层
qsssuv 发表于 2023-4-8 17:15
定长和不定长都可以使用DMA功能,限制最大接收数量就好了。
既然用了空闲中断,定长数据和不定长数据就失 ...

多谢补充,
回复

使用道具 举报

ID:291668 发表于 2023-4-8 20:28 | 显示全部楼层
学以致用,这种方法很好! 学习了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表