在《STM32CubeMX初识与工程创建》的基础上,首先对串口进行设置,以实现通过串口对数据的收发。STM32CubeMX生成的HAL库中,提供了三类串口数据收发的接口,分别为阻塞模式,非阻塞模式和DMA模式,文本主要对DMA模式进行了分析并依据提供的接口提出了更加实用的串口数据收发方案。通过对网上资料的查找和分析,主要存在下述两个问题:

1、在数据接收过程中,采用了串口的空闲中断实现了DMA模式下不定长数据的接收,但存在的限制是单次接收的数据长度必须小于DMA缓冲区的长度,如果接收的数据长度大于DMA缓冲区的长度,就数据丢失了。

2、在数据发送过程中,DMA在发送阶段是不能再使能DMA发送的,即调用HAL库中的HAL_UART_Transmit_DMA接口后,在DMA传输数据完成之前是不能再调用该接口的,常用的方式是通过一个标志变量来确定是否可发送,但程序中若存在等待标志变量等逻辑的话,会降低程序运行的效率。

本文通过采用DMA的循环模式配合DMA传输完成中断和串口空闲中断解决DMA缓冲区长度对串口接收数据量的限制问题。采用类递归的逻辑解决串口发送数据等待发送完成标志的问题。

1 硬件配置

首先,应用STM32CubeMX对串口进行配置,

在Connectivity中勾选usart1,具体引脚根据硬件确定。重点注意的是,勾选USART1 global interrupt 使能;将收发的DMA添加上,将USART1_RX的DMA设置为Circular模式,将USART1_TX的DMA设置为Normal模式。

2 软件实现方案

2.1 初始化准备

将程序内部做驱动层和应用层的区分,串口数据收发接口的封装属于驱动层面,应用层面调用驱动层的接口实现数据的发送和获取,通过收发两个缓冲区实现数据的交互,本文中采用一组循环FIFO实现数据的缓冲。新建一组文件提供对串口收发数据的中间件接口,在头文件中定义串口中间件属性:

typedef struct

{

UART_HandleTypeDef *handle; /*HAL库提供的串口句柄*/

int16_t TransFlag; /*数据发送标志位*/

int32_t DmaSize; /*DMA缓冲区的大小*/

int32_t DamOffset; /*获取数据在DMA缓冲区的偏移量*/

uint8_t *pReadDma; /*指向接收DMA缓冲区的首地址*/

uint8_t *pWriteDma; /*指向发送DMA缓冲区的首地址*/

CFIFO ReadCFifo; /*接受数据的循环缓冲区*/

CFIFO WriteCFifo; /*发送数据的循环缓冲区*/

}MW_UART_ATTR;

上述属性值中包括了缓冲区,DMA等参数,后续实现中具体讲述各参数的用途。本文仅提供对一个串口进行配置用作演示,实际中在属性中添加了id参数实现多串口的管理,或者采用其他方式。

#define MW_TRANS_IDLE 0

#define MW_TRANS_BUSY 1

#define MW_UART_BUFFER_LEN 1024

#define MW_UART_DMA_LEN 256

static uint8_t Uart1TxBuff[MW_UART_BUFFER_LEN] = {0};

static uint8_t Uart1RxBuff[MW_UART_BUFFER_LEN] = {0};

static uint8_t Uart1TxDma[MW_UART_DMA_LEN] = {0};

static uint8_t Uart1RxDma[MW_UART_DMA_LEN] = {0};

static MX_UART_ATTR sUartAttr;

​int8_t MW_UART_Init(UART_HandleTypeDef *handle)

{

/*为属性的参数附初值*/

MX_UART_ATTR *pUartAttr = &sUartAttr;

pUartAttr->handle = handle;

pUartAttr->DamOffset = 0;

pUartAttr->TransFlag = MW_TRANS_IDLE;

pUartAttr->DmaSize = MW_UART_DMA_LEN;

pUartAttr->pReadDma = Uart1RxDma;

pUartAttr->pWriteDma = Uart1TxDma;

CFIFO_Init(&pUartAttr->ReadCFifo, Uart1RxBuff, MW_UART_BUFFER_LEN);

CFIFO_Init(&pUartAttr->WriteCFifo, Uart1TxBuff, MW_UART_BUFFER_LEN);

/*配置DMA参数并使能中断*/

if(HAL_OK != HAL_UART_Receive_DMA(pUartAttr->handle, pUartAttr->pReadDma, MW_UART_DMA_LEN))

{

return MW_FAIL;

}

/*使能串口空闲中断*/

__HAL_UART_ENABLE_IT(handle, UART_IT_IDLE);

return MW_SUCCESS;

}

MW_UART_Init接口实现了对串口初始化的功能,HAL库提供的MX_USART1_UART_Init接口仅对硬件进行了配置,在该接口后调用MW_UART_Init对串口进行使能。因为CFIFO初始化的循环缓冲区用于对DMA接收数据的缓存,因此DMA缓冲区的长度要小于数据缓存区的长度。通过对串口空闲中断的使能和DMA的使能,使能串口数据的接收。

2.2 串口数据接收方案

本文中提供了采用串口空闲中断和DMA接收完成中实现数据接收的方案。假设DMA缓冲区的大小是16,若一次接收的数据长度小于DMA缓冲区的长度,则本次传输通过串口空闲中断获取数据;若一次接收数据的长度大于16,假设为20,则前16字节由DMA完成中断从DMA缓冲区中获取,剩下的4字节数据通过串口空闲中断从DMA缓冲区中获取。

基于上述的方案,在DMA缓冲区满后应自动重新装载缓冲区,因此采用了DMA循环模式。在循环模式中,需要注意DMA缓冲满后,再接收的数据存放的位置。举例,串口要接收的一串字符串为‘’1234567890abcdefghij‘’共20字节,当DMA缓冲区接收了16字符后,DMA缓冲区里的数据为‘’1234567890abcdef‘’并产生DMA完成中断,我们可以利用这个中断将获取数据。由于是循环模式,不需要重新配置,在DMA缓冲区满后继续接收余下的4字节数据,当接收完这四字节数据后,DMA缓冲区中的16字节数据为‘’ghij567890abcdef‘’并产生串口空闲中断,我们可以利用这个中断获取后四节的数据(“ghij”)。值得注意的是,如果此时再接收4个字节的数据(比如“ABCD”)后产生空闲中断,那DMA缓冲区内的数据应是“ghijABCD90abcdef”,需要获取的“ABCD”4字节数据距DMA缓冲区的起始位置存在4字节的偏移,获取的时候需要格外注意。因此在属性中定义了DmaOffset这个参数来声明在DMA缓冲中获取数据的偏移位置。具体实现方式如下:

HAL库提供的中断处理中,并没有对空闲中断进行处理,因此需要在中断中提供对空闲中断处理的接口:

void USART1_IRQHandler(void)

{

/* USER CODE BEGIN USART1_IRQn 0 */

/* USER CODE END USART1_IRQn 0 */

HAL_UART_IRQHandler(&huart1);

/* USER CODE BEGIN USART1_IRQn 1 */

MW_UART_IRQHandler(&huart1);

/* USER CODE END USART1_IRQn 1 */

}

在HAL库提供的USART1_IRQHandler接口中添加MW_UART_IRQHandler接口实现对串口空闲中断的处理(HAL_UART_IRQHandler为HAl库提供的对中断处理的接口)。

void32 MW_UART_IRQHandler(UART_HandleTypeDef *huart)

{

int32_t RecvNum = 0;

int32_t WriteNum = 0;

int32_t DmaIdleNum = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))

{

/*清除空闲中断标识位,重新接受串口空闲中断*/

__HAL_UART_CLEAR_IDLEFLAG(huart);

/*计算在DMA缓冲区需要获取的数据长度*/

DmaIdleNum = __HAL_DMA_GET_COUNTER(huart->hdmarx);

RecvNum = pUartAttr->DmaSize - DmaIdleNum - pUartAttr->DamOffset;

/*将获取到的数据放到数据接收缓冲区中*/

WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pUartAttr->pReadDma + pUartAttr->DamOffset,RecvNum);

if(WriteNum != RecvNum)

{

loge("Uart ReadFifo is not enough\r\n");

}

/*计算获取数据位置的偏移量*/

pUartAttr->DamOffset += RecvNum;

}

}

通过对HAL库中HAL_UART_RxCpltCallback这个弱函数的重写可以实现对DMA完成中断的处理,这个函数虽然声明在stm32f4xx_hal_uart.c中,其实DMA完成中断最后调用的是串口接收完成的回调函数。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

int32_t DmaLen = 0;

int32_t WriteNum = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

/*计算需要获取数据的长度*/

DmaLen = pUartAttr->DmaSize - pUartAttr->DamOffset;

/*将获取的数据存放到数据缓冲区中*/

WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pUartAttr->pReadDma + pUartAttr->DamOffset,DmaLen);

if(WriteNum != DmaLen)

{

loge("Uart ReadFifo is not enough\r\n");

}

/*复位DMA偏移量*/

pUartAttr->DamOffset = 0;

}

该接口中也需要计算接收数据的长度的原以为:若上次串口空闲中断接收了4个字节的数据,则再接收12个字节的数据就会产生DMA传输完成中断,因此DMA缓冲区的长度16减去串口中断为DmaOffset赋值的4,得到实际接收的数据长度为12。

两个中断里将DMA缓冲区的数据搬移到数据循环缓冲区中,以供应用去获取和处理:

int32_t MW_UART_Receive(uint8_t* buffer,int32_t len)

{

int32_t RecvNum = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

/*从数据循环缓冲区中获取数据*/

RecvNum = CFIFO_Read(&pUartAttr->ReadCFifo, buffer, len);

return RecvNum;

}

应用中,调用MW_UART_Receive接口获取数据并对数据进行处理等后续操作。上述的方案避免了DMA缓冲区长度对单次接收数据长度的限制,如果出现了“Uart ReadFifo is not enough”,说明应用中调用MW_UART_Receive接口的频率不够快,或者接收循环缓冲区不够大。总之,数据接收的完整性应取决于应用上逻辑的实现,而不是需要DMA缓冲区申请的足够大,这也是本文串口数据接收方案的原则。

2.3 串口数据发送方案

串口数据发送中,采用了接收中类似的方案,先将数据暂时保存到循环缓冲区中,然后通过DMA将数据发送出去。在应用中调用MW_UART_Transmit将数据发送出去,传统方案中一次发送的数据量必须小于DMA缓冲区的大小,本方案中一次发送的数据量应小于发送循环缓冲区的余量。

int32_t MW_UART_Transmit(uint8_t* buffer,int32_t len)

{

int32_t TransNum = 0;

int32_t TransLen = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

/*将要发送的数据先写入循环缓冲区*/

TransNum = CFIFO_Write(&pUartAttr->WriteCFifo, buffer, len);

/*如果发送DMA未在发送中,则使能发送*/

if(pUartAttr->TransFlag == MW_TRANS_IDLE)

{

TransLen = CFIFO_Read(&pUartAttr->WriteCFifo,pUartAttr->pWriteDma,pUartAttr->DmaSize);

if(TransLen > 0)

{

pUartAttr->TransFlag = MW_TRANS_BUSY;

if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransLen))

{

loge("Uart Trans_DMA failed\r\n");

}

}

}

return TransNum;

}

该接口中,若TransFlag为MW_TRANS_IDLE,则从循环缓冲区中获取最长不超过DMA缓冲区长度的数据,调用HAL库提供的HAL_UART_Transmit_DMA接口将数据发送出去,将TransFlag置为MW_TRANS_BUSY,发送完成之后响应串口发送完成中断,重写HAL库中提供的HAL_UART_TxCpltCallback弱函数接口实现串口发送完成中断的操作。

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

{

int32_t TransNum = 0;

MX_UART_ATTR *pUartAttr = &sUartAttr;

/*从发送循环缓冲区中获取数据*/

TransNum = CFIFO_Read(&pUartAttr->WriteCFifo,pUartAttr->pWriteDma,pUartAttr->DmaSize);

if(TransNum > 0)

{

if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransNum))

{

loge("Uart Trans_DMA failed\r\n");

}

}

else

{

pUartAttr->TransFlag = MW_TRANS_IDLE;

}

}

当一组数据发送完成后,继续从缓冲区中获取数据,若获取到数据,继续调用HAL_UART_Transmit_DMA将数据发送出去,若获取不到数据了,则将TransFlag置为MW_TRANS_IDLE,这样再调用MW_UART_Transmit就能使能重新使能发送。本方案中,如果正处于DMA传输过程,则将要发送的数据放到数据循环缓冲区中,在发送完成中断中去获取数据将其发送,避免了等待DMA传输完成的逻辑。

本文中提及的串口数据接收发送方案最初是因为采用的MCU的RAM很小,想尽量缩减各类缓冲区的长度,出于这样的考虑完成了整体的方案设计。

hal库串口dma卡死_HAL库版DMA循环模式串口数据收发相关推荐

  1. 串口MSComm控件五种不同校验方式对数据收发的影响

    (2008-09-10 14:50:00)   http://blog.sina.com.cn/s/blog_470eccc60100arq7.html   串口MSComm控件有五种校验方式,分别是 ...

  2. java串口设备中断_利用DMA双缓冲或半完成中断实现串口不定长数据的接收

    在<HAL版本DMA循环模式串口数据收发>中介绍了利用DMA循环模式进行串口数据的收发,STM32F4xx的DMA还提供了双缓冲的功能,采用双缓冲模式,可以在一个DMA完成接收后,对其缓冲 ...

  3. 【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全的问题)

    [STM32笔记]HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒.串口唤醒和回调无法一起使用.接收数据不全的问题) [STM32笔记]低功耗模式配置及避坑汇总 前文: blog.csd ...

  4. STM32F103操作DS1302时钟芯片串口显示(标准库和HAL库)

    目录 DS1302的性能指标 DS1302的寄存器及片内RAM 标准库实现 HAL库实现 源码链接 单片机型号:STM32F103C8T6 在日常生活中,很多情况下会需要使用时间,单片机中虽然也是有定 ...

  5. GD32F303固件库开发(11)----ADC在DMA模式下扫描多个通道

    GD32F303固件库开发.11----双ADC轮询模式扫描多个通道 概述 视频教学 csdn课程 样品申请 硬件准备 keil配置 使能串口 串口重定向 ADC通道设置 DMA设置 ADC0初始化 ...

  6. 新JSON绑定库JSON-B发布公开预览版

    JSON-B这个有望添加到Java EE 8的JSON绑定库发布了公开预览版.该库以JSON Processing(不要同JSONP或JSON-with-padding混淆)为基础构建,旨在为诸如Ja ...

  7. 树莓派UART串口编程--使用wiringPi库-C开发【1-基础应用】

    一.串口说明 莓派3B+上UART串口位于GPIO的8脚和10脚,分别为GPIO8=Tx,GPIO10=Rx.在连接外部接口时,需要交换接口,即GPIO8连接到外设的Rx,GPIO10连接到外设的Tx ...

  8. 使用STM32Cube MX为STM32 CAN总线快速配置上手教程_HAL库_CAN协议_STM32_附波特率计算工具

    使用STM32Cube MX为STM32 CAN总线快速上手教程_HAL库_CAN协议_STM32 前言 博主也是刚接触CAN协议的新手,如有不对的地方欢迎交流 本文旨在使用STM32Cube MX快 ...

  9. Python的wxpy库怎么样?(微信网页版无法登录)

    首先抛开wxpy库怎么样不说,你首先得能登录微信网页版才行.因为wxpy库就是调用的微信网页版的API.所以如果你的微信网页版登录不了,wxpy库也就用不了.

最新文章

  1. 收藏!工作中Git使用实践和常用命令流程合集
  2. 学Redis这篇就够了!
  3. 5G 智慧城市安全参考架构概述
  4. EOJ Monthly 2018.1
  5. HTML5小游戏《智力大拼图》发布,挑战你的思维风暴
  6. 【转载】可复用的FS
  7. Gradle中的buildScript,gradle wrapper,dependencies等一些基础知识
  8. 好的数据分析平台有多重要
  9. glob库:文件名的匹配
  10. WordPress 5文章编辑真难用 换回老版经典编辑器教程
  11. 数据结构-树的进阶-串联各科知识
  12. 找工作:java开发三年工作经验
  13. 华为会议终端TE10 SIP配置范例
  14. 计算机四级等级考试必备知识点总结
  15. 计算机文件丢失系统无法启动,因文件的丢失或者损坏导致系统无法启动的解决方法...
  16. java 实时监控微信扫码支付,支付成功跳转到成功页面
  17. 三芯和四芯音频接口转换
  18. 什么是数据产品,如何设计一款好用的数据产品
  19. 酒店微信直播引流获客方案
  20. Hadoop的集群搭建及HDFS命令

热门文章

  1. 2014年03月 面试总结
  2. 搭建跨平台编程环境Code::Blocks+wxWidgets
  3. HDU2029 Palindromes _easy version【入门】
  4. HDU5904 LCIS【LCIS】
  5. Bailian2726 采药【模拟】
  6. Bailian4129 变换的迷宫【BFS】
  7. Bailian2679 整数的立方和【入门】
  8. CCF201703-2 学生排队(100分)
  9. 数字敏感与数字的研究
  10. 【脑筋急转弯】—— 在网格中移动