打印
[其他ST产品]

STM32 RS485串口DMA发送问题记录及调试解决

[复制链接]
637|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
STM32 RS485串口DMA接收及发送,问题记录及调试解决
芯片型号:STM32F767IGT6、SP3485,如图1、图2所示。

图1 主芯片型号
图2 485芯片型号
开发环境:Keil uVision5、STM32CubeMX,如图3、图4所示。

图3 Keil uVision版本信息
图4 STM32CubeMX版本信息
之前与上层设备的通讯协议是基于MODBUS TCP进行地相应开发,但因为STM32F767IG芯片只有一个物理网口,并且其也不得不接入局域网。所以当局域网内有大量数据时可能导致网络阻塞,其与上层设备的通讯会不稳定,从而无法正常工作。于是乎便考虑使用基于MODBUS RTU的协议进行通讯。在开发过程中,碰到了些许问题,花费了不少时间解决,觉得有必要记录总结下。

好了,背景阐述完毕,咱们进入正题。

其实MODBUS协议从网口改为兼容串口通讯,协议部分的处理并不棘手,只要考虑如何兼容网口的MBAP报文头的处理,以及串口的CRC校验;串口通讯主要的难点在于命令帧之间的区分、应答的及时性,同时若使用RS485通讯,需要注意485芯片数据处理方向的转换,以及转换的时机。

使用特权

评论回复
沙发
一点点0321|  楼主 | 2024-3-31 23:02 | 只看该作者
数据接收
如图5所示,在文档Modbus_over_serial_line_V1 2.5.1.1节阐述,在MODBUS RTU模式中,消息帧之间的最小间隔时间是3.5字符的时间(具体时间由串口比特率及单个字符包含的位数决定,如比特率为9600bits/s,单个字符包含1个起始位,8个数据位,0个校验位,1个停止位,则最小间隔时间具体值t3.5=1/9600*(1+8+0+1)*3.5≈3.65ms)。因此数据接收可以使用芯片自带的超时功能实现,如图6所示,除了要使能接收器超时中断外,同时也要使能接收器超时功能,并使能DMA接收的循环模式,即数据接收采用 串口接收器超时中断+DMA接收循环模式 的方式,在串口中断中判断是否是超时中断,若是则获取此次中断录入DMA的新数据长度值,数据长度值的存储采用10级的循环缓冲区实现,并在程序主循环中判断存储数据长度值的缓冲区是否为空,若不为空则根据存储的数据长度值读取DMA接收缓冲区相应的数据量,然后送入MODBUS协议解析函数中进行处理。

使用特权

评论回复
板凳
一点点0321|  楼主 | 2024-3-31 23:03 | 只看该作者

图5 MODBUS Message RTU Framing



image-20230622190810356图6 MODBUS通信

使用特权

评论回复
地板
一点点0321|  楼主 | 2024-3-31 23:03 | 只看该作者
数据发送
2.1调用HAL_UART_Transmit()进行发送
计划是准备先通过标准函数HAL_UART_Transmit()调通后,再考虑使用DMA发送。现实却是一直有接收到正确的数据,但就是没有进行正确的应答。查了很长一段时间,甚至在Debug模式下一步步运行,程序都是在按设想地运行,最后弄得都有点怀疑人生了,才猛得意识到是没有正确地转换485芯片的方向。后续是在转换为发送方向前延时一定时间,再调用HAL_UART_Transmit(),然后再延时一定时间后,转换为接收方向,整个链路总算能正常工作了。

2.2调用HAL_UART_Transmit_DMA()进行发送
DMA发送配置的是正常模式(DMA发送循环模式还没来得及研究),开始是直接把HAL_UART_Transmit()替换成HAL_UART_Transmit_DMA(),数据是能够正常发送出去,但总是比应该应答的少一两个字节,然而在Debug模式下打断点,一步步运行又能正常应答,因此猜测是某处的时序不对,可身边没有示波器直接对信号进行监测;便上网搜索相应的解决方法,果然大家也遇到过同样的问题,经过一番查找与阅读,及修改程序再实测(在调用完HAL_UART_Transmit_DMA()后,延时时间加长,再转换为接收方向)。定位到正常运行时丢应答数据的本质原因是485方向转换的时机不对。可以在调用完HAL_UART_Transmit_DMA()后,延时时间加长再转换485芯片为接收方向,来实现正常应答。但更稳妥以及更高效地处理,是通过串口发送完成中断来进行485芯片方向的转换。

使用特权

评论回复
5
一点点0321|  楼主 | 2024-3-31 23:03 | 只看该作者
待以为这样处理完后,就调通串口DMA发送了。没想到又遇到了新的问题:发送两帧才正常应答一次。当然又是一番网上搜索,发现网上大部分遇到的是只会发送一次,解决方法也是有多种,但通过仔细查阅,个人感觉只是打补丁,没有找到真正的原因。后来又是一步步仿真调试,发现是如图7标记所示的条件语句就满足了一次,后续huart->gState的值都是HAL_UART_STATE_BUSY_TX,导致后续调用HAL_UART_Transmit_DMA()实际没有正常执行。

使用特权

评论回复
6
一点点0321|  楼主 | 2024-3-31 23:04 | 只看该作者

图7 函数HAL_UART_Transmit_DMA部分代码
因此可在串口发送完成中断后重新赋值huart->gState = HAL_UART_STATE_READY就可以了。说来也是巧,之所以会遇到这个问题,是因为我刚好在调用串口中断函数HAL_UART_IRQHandler()前进行了__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC)清除串口发送完成中断的操作,不然如图8所示在HAL_UART_IRQHandler()中会调用UART_EndTransmit_IT(),如图9所示也会执行huart->gState = HAL_UART_STATE_READY操作,如此就不需要再手动赋值huart->gState = HAL_UART_STATE_READY。

使用特权

评论回复
7
一点点0321|  楼主 | 2024-3-31 23:04 | 只看该作者

图8 函数HAL_UART_IRQHandler部分代码


图9 函数UART_EndTransmit_IT部分代码

使用特权

评论回复
8
一点点0321|  楼主 | 2024-3-31 23:05 | 只看该作者
程序按照以上方法修改后,串口DMA发送也能正常应答了。但是我实际遇到的现象是发送两帧才正常应答一次,为了不给后续的开发埋下“炸*”,便决定得找到真正的原因。又是通过一系列的断点,总算定位到问题了,如图10所示,是因为DMA产生了DMA_IT_FE中断,从而导致HAL_UART_Transmit_DMA()会执行如图11的语句。


图10 函数HAL_DMA_IRQHandler部分代码

使用特权

评论回复
9
一点点0321|  楼主 | 2024-3-31 23:05 | 只看该作者


图11 函数HAL_UART_Transmit_DMA部分代码
所以就如同huart->gState一直在进行自恢复,导致一直能每发送两帧,正常应答一次。好的,问题找到了,但解决方法就困惑了,因为我根本没有使能DMA FIFO模式,为什么会出现FIFO错误了,而且还是DMA发送导致的。这次是网上搜索了几番,花费了很多时间,很久也没有搜索到想要的解惑说明。经过一段漫长的修改关键词再搜索,总算是在以下链接处找到了问题的本质,如图12所示

使用特权

评论回复
10
一点点0321|  楼主 | 2024-3-31 23:05 | 只看该作者
原来是我提前调用LL_USART_EnableDMAReq_TX(),使能了串口DMA的发送器导致的,如图13所示,技术文档AN4031 4.3节也有详细的说明。

使用特权

评论回复
11
一点点0321|  楼主 | 2024-3-31 23:05 | 只看该作者
按照以上要求进行修改后,程序终于能够按照开发的要求正常运行了。至此,MODBUS协议从网口到兼容串口通讯正式开发完成了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

35

主题

347

帖子

0

粉丝