找回密码
 立即注册
查看: 1520|回复: 40

4组串口UART使用DMA收发 @STC32G,易用,高效,稳定 !精品实战代码

[复制链接]

该用户从未签到

19

主题

517

回帖

1634

积分

荣誉版主

积分
1634
发表于 2023-11-24 12:00:52 | 显示全部楼层 |阅读模式
4组串口UART使用DMA收发, 精品实战代码, 易用,高效,稳定, 源自实际系统 @STC32G

非常容易使用, 将C文件添加至项目, 初始化后, 即可用.

尽3个函数, 包含一切UART串口操作, 适用90%以上场景, 从此告别串口驱动代码开发.
void UART1_Init(u32 btl);               //初始化串口            
u8 UART1_Send(void *pt, u16 Size);      //发送数据
u16 UART1_Receive(u8 *buf, u16 Size);   //接收数据

main() 函数演示 4个串口同时使用 DMA 收发数据, 收到数据后原路返回, 不限数据长度, 持续收发.

主要收发函数使用说明:
/**
  * 原型: u8 UART1_Send( void *pt, u16 Size);
  * @功能  串口发送数据. 写数据至发送缓冲区(循环池), 写完立即返回, 由DMA管理数据流向串口,
                用户无须关心. 只要缓冲区有足够的空间, 可持续写入数据
  * @参数  pt: 发送数据指针
  * @参数  Size: 发送数量(字节)
  * @返回值 当缓冲区没有足够的空间装入数据时返回1, 其它时候返回0
  */
  

/**
  * 原型: u16 UART1_Receive(u8 *buf, u16 Size);
  * @功能  读串口数据, 从缓冲区内读取数据. (DMA接收数据后存放至接收缓冲区,
        应用代码必须定时查询读取, 否则循环池发生数据覆盖, 会丢失一部分数据, 没有提示, 但不影响后续收发)
  * @参数  buf: 接收数据指针
  * @参数  Size: Size期盼接收的字节数
  * @返回值 实际接收字节数. 缓冲区空时(没有数据可读)返回0, 返回值<Size说明本次读取完成后,缓冲区已空.
        返回值==Size说明本次读取完成后,缓冲区仍有数据可读. 任何时候,返回值不会大于Size
  */
  


4串口 DMA 实例代码 STC32G.rar (251.12 KB, 下载次数: 89)






1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复 送花

使用道具 举报

  • TA的每日心情
    开心
    昨天 05:23
  • 签到天数: 153 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1285

    积分

    荣誉版主

    积分
    1285
    发表于 2023-11-27 07:46:10 | 显示全部楼层
    还没有用过串口UART使用DMA收发,下载尝试一下。谢谢楼主!
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    难过
    昨天 06:47
  • 签到天数: 147 天

    [LV.7]常住居民III

    134

    主题

    187

    回帖

    1596

    积分

    金牌会员

    积分
    1596
    发表于 2023-11-27 09:07:25 | 显示全部楼层
    本帖最后由 QQ624353765 于 2023-11-27 09:11 编辑

    S%5]OQ[41`G@LTQ0ZE%G]7L.png


    你写的好臃肿,我就11行代码,实现串口不定长自动收发功能,下面两个函数是DMA中断函数,这是最高效的用法,整个收发过程只产生三次中断

    向设备发送数据时,第一个数据使用串口中断接收,然后在中断中开启DMA准备接收接下来的数据,接收完所有数据DMA中断标记以接收完成

    发送串口数据函数只需要两个参数,数据结构体的首地址和结构体的长度,数据发送完成DMA中断标记可接收下一次串口数据


    而且效率极高,几乎不占用CPU时间,不需要查询串口,只需要处理接收完成标志位和串口数据发送函数,其他都是全自动的

    点评

    你喜欢是你自己的事情 当你做过大程序以后,你就会明白实验室的代码和产品应用的代码之间的区别了. 实验室的代码在于讲清楚怎么使用一个设备, 应用代码要综合考虑整体的性能. 我可以持续写入数据,上一帧没发送完就可  详情 回复 发表于 2023-11-27 09:49
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    19

    主题

    517

    回帖

    1634

    积分

    荣誉版主

    积分
    1634
     楼主| 发表于 2023-11-27 09:49:33 | 显示全部楼层
    本帖最后由 tzz1983 于 2023-11-27 10:06 编辑
    QQ624353765 发表于 2023-11-27 09:07
    你写的好臃肿,我就11行代码,实现串口不定长自动收发功能,下面两个函数是DMA中断函数,这是最高效的用 ...

    你喜欢是你自己的事情
    当你做过大程序以后,你就会明白实验室的代码和产品应用的代码之间的区别了. 实验室的代码在于讲清楚怎么使用一个设备, 应用代码要综合考虑整体的性能.
    我可以持续写入数据,上一帧没发送完就可以接着写下一帧数据,不需要标记数据, 不包含任何协议, 这是底层, 不是针对哪一个应用, 这就是区别, 串口是个低速设备, 产品级代码一般不会等它做完一个动作然后下一个动作. 写入缓存后立即返回, 可以接着写,也可以做别的事情.
    接收也是这样, 不管有没有收到数据, 都是立即返回, 绝不会等.
    查询? 你做的标志不用查询? 当然了,如果你用了RTOS可以发送信号量, 但是你没有.

    点评

    支持分享源码的大神  发表于 2023-12-30 22:36
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    难过
    昨天 06:47
  • 签到天数: 147 天

    [LV.7]常住居民III

    134

    主题

    187

    回帖

    1596

    积分

    金牌会员

    积分
    1596
    发表于 2023-11-27 14:19:23 | 显示全部楼层
    tzz1983 发表于 2023-11-27 09:49
    你喜欢是你自己的事情
    当你做过大程序以后,你就会明白实验室的代码和产品应用的代码之间的区别了. 实验室 ...

    $_7}D]LD3K$X(6$YL]`~QIC.png


    就拿你这个发送函数来说,发个数据这么多代码不知道干什么用的,发送数据包每个包一个字节,你这代码一秒能发几次

    还要把数据包复制到指定内存,不知道这一步有什么意义,还要判断有没有数据长度,你发数据时你不知道有没有数据吗。

    K1$LSR5F5O$DD)%C{J]$I(H.png


    把数据包准备好,我的只要取结构体地址和长度

    }6IKI93HEQR)NN0TS1HCSUT.png

    有了这两个参数我就能发送数据了,我这里的地址只有一个字节,DMA的高地址也就没用了

    哪像你发个数据那么多if else if else ,这还不够还要memcpy来memcpy去的

    就这样的代码还敢说是高效,跟我的没法比,我这项目都是高动态的,如果用你的代码发送一次数据就要几百毫秒,等上位机收到数据这一贞数据都报废了

    还有别跟我说实验不实验的,我这串口函数已经写了好多个项目了,没有任何问题,我这代码也是旧项目上复制过来的

    点评

    [attachimg]28151[/attachimg] [attachimg]28152[/attachimg] 你的代码有问题, 我说说, 你看看: 1.你定义的那个结构体局部变量Q , 是个局部变量哦, 当退出函数时生命周期就结束了, 然面你的DMA还在使用这个变  详情 回复 发表于 2023-11-28 10:28
    再说就是和你争了,没有意思, 我知道你做了什么. 你说的几个问题我可以回答你: 1.这是一个无协议循环容池, 和你那东西不一样, 这个容池的数据是不做收到多少字节标记的. (你自己的代码, 自已做个标记, 当然是整得明  详情 回复 发表于 2023-11-27 15:46
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    19

    主题

    517

    回帖

    1634

    积分

    荣誉版主

    积分
    1634
     楼主| 发表于 2023-11-27 15:46:58 | 显示全部楼层
    本帖最后由 tzz1983 于 2023-11-27 15:51 编辑
    QQ624353765 发表于 2023-11-27 14:19
    就拿你这个发送函数来说,发个数据这么多代码不知道干什么用的,发送数据包每个包一个字节,你这代码 ...

    再说就是和你争了,没有意思, 我知道你做了什么.

    你说的几个问题我可以回答你:
    1.这是一个无协议循环容池, 和你那东西不一样, 这个容池的数据是不做收到多少字节标记的.  (你自己的代码, 自已做个标记, 当然是整得明明白白的.) 但这不通用, 你后级链接一个协议, 是不是要重新做一下代码?
    2.为什么要做长度判断?你不明白为什么我自己不知道我自己发多少数据, 说实话, 我也不知道, 因为我发的不尽有我要发的数据, 还有别人发给我的. 或者说如果这里不判断,那就要在应用层上判断. 这里判断就最保险的.
    3.通常都是发数据包, 而不是一个字节一个字节的发. 你说的效率问题是没有的. 又或者说你不会整个包然后再一起发?  还是说你发送数据前不用准备数据?  
    4.为什么要用一个循环容池?  应用层的数据应该要和底层驱动的数据隔离的, (而不是像你那样直接发一个应用层的数据包, 然后告诉对方我发了多少字节)  
    我现在发了一帧数据, 就是写入容池, 然后就不用管了, 下一段代码(或另一个任务)在上一帧数据没发完前, 仍然是是可以送数据的.  比如我可以发送一个包给设备B, 紧接着又发一个包给设备C. 这对于通信效率来说是提高的. 而不是降低.
    5. 你说的那个电机控制, 也就是说, A  B 两设备通信,  你的高效是狭义的, 在特定的条件下, 谁都可以优化一些. 比如说你不需要用地址高字节或数量高字节, 是指在特定的条件下, 是你在特定的情况下不需要用, 而不是所有人都不需要用, 这区别很大. 请问你规定了我不允许发大于256字节的数据包了吗

    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    19

    主题

    517

    回帖

    1634

    积分

    荣誉版主

    积分
    1634
     楼主| 发表于 2023-11-28 10:28:00 | 显示全部楼层
    QQ624353765 发表于 2023-11-27 14:19
    就拿你这个发送函数来说,发个数据这么多代码不知道干什么用的,发送数据包每个包一个字节,你这代码 ...

    截图202311281017255157.jpg
    截图202311281017539895.jpg

    你的代码有问题,  我说说, 你看看:

    1.你定义的那个结构体局部变量Q  ,  是个局部变量哦, 当退出函数时生命周期就结束了,  然面你的DMA还在使用这个变量.  这有两种可能, 一是编绎器的变量覆盖刚好放过了你, 运气,  另一种可能是你禁用了变是覆盖.
    第一种可能是BUG, 对于第二种可能来说没有必要禁用变是覆盖, 前面加个static啥都解决.



    截图202311281018417266.jpg


    2. 这张你看看, 你用收到的第一个字节映射一个包长度, 这种做法非常危险, 如果收到的数据受到干扰, 那会给DMA一个错误的长度,(你不会告诉我你是固定长度吧) 然后DMA的写会超出范围, 和指针越界差不多.

    不要说你实际运行一点问题也没有, 硬件直接对连出干扰的情况确实比较少, 但不代表没有.  而且指针越界危害无穷.  也就是说你一个串口代码可能影响整机.

    如果你这个代码放在无线模块上, 收不到头字节都很正常 ,何况是收到一个错的数据, 那再正常不过了.   那些成熟的通信代码, 比如IP报文, USB报文等, 不但整包有CRC, 连报头都有CRC,  见过用头字节变象用作接收长度的吗?

    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    难过
    昨天 06:47
  • 签到天数: 147 天

    [LV.7]常住居民III

    134

    主题

    187

    回帖

    1596

    积分

    金牌会员

    积分
    1596
    发表于 2023-11-28 19:39:34 | 显示全部楼层
    本帖最后由 QQ624353765 于 2023-11-28 19:59 编辑
    tzz1983 发表于 2023-11-28 10:28
    你的代码有问题,  我说说, 你看看:

    1.你定义的那个结构体局部变量Q  ,  是个局部变量哦, 当退出函数 ...

    这些情况我都考虑全了,没有问题
    首先发送的数据包是在pdata中,pdata只用来存零时数据包,数据包长度不超过256字节,退出函数是不影响pdata中的数据
    接收的数据在xdata中,接收数据包的地址是固定的,是互不影响的

    data,bdata,idata,pdata,xdata这些内存我都有很合理的分配,这么多年的编程经验可以说能做到互不影响

    接收的首字节不是长度,是指令号,每个指令都是能查询到长度的,这个项目对数据传输有要求,我这里用的是校验和,能快速的校验指令是否是错误的,当然有一定的机率是无效的,一般自然形成的错误都能过滤掉,除非你非要发送错误的指令,那就没办法了
    再说发送错误的指令CRC也检测不出来。我倒是觉得这里使用校验和以足够

    如果发送的数据包超出长度通过校验和验证后舍弃,如果发送的数据短了通过定时器监控退出DMA并作废本次传输

    我这里没用定时器监控,也没去处理,因为接收到长度比指令短的数据直接让他一直等待,等下一批数据到来时DMA就能重置,这里就当线路上有干扰误触发的,直接忽略
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    奋斗
    前天 08:45
  • 签到天数: 99 天

    [LV.6]常住居民II

    0

    主题

    81

    回帖

    590

    积分

    高级会员

    积分
    590
    发表于 2023-11-30 11:06:11 | 显示全部楼层
    向高手学习
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    昨天 02:35
  • 签到天数: 132 天

    [LV.7]常住居民III

    37

    主题

    868

    回帖

    3940

    积分

    荣誉版主

    积分
    3940
    发表于 2023-11-30 14:49:05 | 显示全部楼层
    两位大师, 帮忙看看, 如何解决不同类型的数据, 统一转换成 long类型.

    对 char, int, long, 指针类 p, 都可以前面加 (long)强制转换成 long 类型.

    例如:  long dat = (long) a;  // a 可为 char, int, long 变量 和 各种 指针类 数据, 同时支持 整型常数.



    怎样将 浮点数 转换成 long 类型?

    目前已知的方法是 用指针, 强制转换,

    例如:  long dat =  *(long*) &f; // 仅支持浮点数变量, 不支持 浮点数常数.



    现在的问题是, 希望能用一种相同的 强制转换 方式, 可将 各种 不同类型的数据, 统一转换成 long类型.

    点评

    直接赋值即可,例如: long a=11.0592; 这样的语句是合法的, 可显式转换,也可隐式转换, 舍去小数部分, 整数部分赋值给a, 赋值后a=11; 但是float可表示的数值范围比long大, 这是一种不安全的转换, 当数值大到无法用l  详情 回复 发表于 2023-11-30 18:53
    回复 支持 反对 送花

    使用道具 举报

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

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-4-29 01:50 , Processed in 0.073827 second(s), 69 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

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