项目日志:移相全桥逆变器通信
前言
这是我参与的第一个电气专业的项目,我负责的部分是按照给定的通讯协议实现逆变器控制板与其他部分的通讯,使用串口实现。具体来说,在USART2外连串口转CAN模块,实现串口发送CAN命令;在USART3外连串口转485模块,实现串口发送485命令。
代码部分没有太多好说的,实现起来并不困难,两个部分加在一起也只花了我一天时间。困难的地方在于,最终运行代码的地方是国产的HC32F4A0单片机。与以往的编程项目不同,嵌入式编程项目中遇到的很多问题都是我难以理解的:明明代码没有问题,但是放在板子上就时好时坏,或者根本跑不起来,需要进行很多调试才能试出来解决方案,而且这个解决方案往往也同样难以理解,稀里糊涂就好了。这块芯片应用很少,网上很难找到其他人解决问题的经验,基本只能自己对着官方文档来调试。下面记录一下调试过程中遇到的种种问题,在以后的项目中可以参考。
准备工作
修改、润色、校对项目设计报告
涉及文件:
高功率密度高频逆变器设计报告(改
三相PFC电源方案设计报告20241221
工作一:
通过串口转CAN,逆变器控制板(HC32F4A0)通过串口向绝缘监测仪发送CAN命令,
1819A1A5 0001020304050607,启动绝缘电阻监测功能(内置高压继电器闭合)。之后控制板应该会周期(每1s)收到绝缘监测仪发送来的CAN数据帧。
涉及文件:
GYID系列绝缘监测仪产品规格书V1.0.5
遇到的问题
发送命令后高压继电器没有闭合。
解决过程:
问题出在两个地方,一是小导最开始发送的数据帧的帧id是错的,使用电脑上的CAN收发工具应该发送帧id为0x1819A1A5的数据帧,之后电脑从绝缘检测仪收到的数据帧的帧id才应该是0x1819A1A4。小导弄反了,发送了帧id为0x1819A1A4的数据帧。这一点我很快就排查出来了,这个不重要。
主要问题在于,小导买的串口转CAN模块上有四个物理开关,其中开关1负责调扩展帧和标准帧,开关2、3一起负责调波特率,开关4负责调是否透传,即是否随数据上传ID。
小导的代码是直接通过串口发送一个大小为12的数组,数组里就是完整的CAN命令(12个字节)。如果开启了开关4,串口转CAN模块会自动把前四个字节识别为id,后八个字节识别为数据。小导没有开开关4,所以实际上CAN模块会认为自己收到了没有id的12字节数据。打开开关4即可解决问题。
总结:
在这一步时我既没有搞清楚项目架构,也不明白我的职责划分,小导其实到现在都没有跟我说过这些,他当时只说他来现场教学教我调CAN。我当时甚至没有弄明白真正在做的工作其实是调串口,跟CAN都没有什么关系。而且我也不知道硬件(一个单片机和一个串口转CAN模块,小导没有给我介绍)都起到什么作用。这给当时既不会调CAN也不会调串口、没用过软件也不认识硬件的我带来了极大困惑。
他当时遇到了这个问题之后,给我发送了这个绝缘监测仪的产品规格书,我读了之后弄明白了应该发送的命令和期望收到的数据,但是对整个发送接受过程是什么样的完全一窍不通。他去吃饭让我留下解决这个问题,我最初甚至以为需要手动构建完整的CAN帧并通过串口发送,去查了CAN扩展帧的格式。实际上,不管是CAN还是串口转CAN,都不需要这么做。CAN可以直接在CAN_Init函数中配置id和数据,串口转CAN也是直接发送id+数据的数组即可,转CAN模块会自动识别id和数据。
总之,尝试过手动构造完整CAN扩展帧并失败之后,我才弄明白不是用单片机的CAN外设直接发送CAN帧,而是通过串口转CAN模块来实现。而小导完全没有给我提过这个模块的事情,这个模块的详细信息还是我找小导要了购买链接去淘宝查到的。
理想情况下他应该给我讲一下要做的这个产品各模块之间的关系,以及讲一下我们正在做的工作处于整个项目的哪个部分。由于缺乏这些必要信息,我理解这个项目的过程异常吃力,在DeepSeek的帮助下才慢慢梳理出来我要做什么、我要怎么做。
工作二:
通过串口转485来实现一份通讯协议。
涉及文件:
逆变器通讯协议V04
逆变器调试指令
实现过程:
由于也没有接触过RS-485,所以先去学习了485通信的概念。
RS-485是一种串行通信标准,定义了电气特性(电压、驱动能力和接收能力),而不是通信协议(如波特率、数据格式等)。
说到底和工作一一样,工作一是单片机连串口转CAN模块,这个就是单片机连串口转485模块。
分析逆变器通讯协议文档,明确需要实现的功能:
- 接收数据:
从RS-485总线接收命令帧。
定义一个缓冲区来存储接收到的数据。
在USART的接收中断中,将接收到的数据存储到缓冲区。
在初始化USART时,使能接收中断。 - 解析命令:
根据协议文档解析接收到的命令帧。
如果收到的命令不是协议中的任意一条,丢弃命令。
如果收到的命令不包含正确的ModbusCRC12校验位,丢弃命令。 - 发送响应:
根据命令类型,生成并发送响应帧。 - 针对升级流程,使用了状态机以确保升级中的每个步骤都按照正确的顺序执行,并且在每个步骤中只接受特定的命令。在这部分还实现了双缓冲区操作,从而在其他七个短命令不浪费空间的情况下满足块数据发送命令的200字节长度的命令。
- 编写了逆变器调试指令,用于测试。
遇到的问题1:
开始时把命令解析的部分也放到了中断函数中,即收到数据时,在中断函数中将收到的数据发送到缓冲区,并解析命令和执行对应操作。
运行无反应,调试发现中断函数压根没有运行。
解决过程:
小导说不能都放在中断函数里,他把代码改成,中断函数收数据并放入缓冲区,之后将一个标志位Flag置1,而解析命令函数放在主函数的循环当中,当主函数识别到Flag = 1时,解析命令并执行对应操作。问题解决。
遇到的问题2:
按照代码,如果输入55 AA A5 55 00(即查询命令,发送时串口工具会自动添加2个字节的CRC校验码,共计7个字节),单片机应该回复55 AA A5 56 11……(即代码中写的21位响应命令+2个CRC字节),这一点已经实现,说明代码逻辑没有问题,并且重复发送查询命令会重复收到响应命令。
但是如果输入55 AA A5 57 01 01(即开机命令),单片机应该回复55 AA A5 58 01……,但是在测试中,单片机却发送了AA A5 58 01……,也就是少了第一个字节的55。并且如果重复发送开机命令,单片机不会再做出回复。
解决过程:
通过在每个命令处理函数中重置Flag,解决了重复发送命令时无法响应的问题。然而,开机和关机命令的响应仍然丢失了第一个字节 0x55,而消音命令的响应却可以正常返回。
通过进一步的测试,发现出现的错误与发送命令的字节数有关,查询命令和消音命令都是7个字节(包含2个CRC校验字节),而开关机命令则是8个字节。我尝试发送消音命令的同时在末尾也增加1个字节,响应也丢失了第1个字节0x55。这种现象表明问题可能与命令的长度或数据处理逻辑有关。
查看对应代码,在中断函数中,命令长度的判断条件是 HUM_Rx_count >= 7,这意味着当接收到的字节数达到7个时(即5个命令字节+2个CRC字节)就会触发命令处理逻辑。当命令长度大于7时就会出错。
最终通过动态判断命令长度解决了这个问题,即HUM_Rx_count >= (HMI_Rx_buf[4] + 5)。其中,固定的6个字节为起始字节+设备地址+命令类型+数据长度+两个CRC字节,HMI_Rx_buf[4]为数据长度。
遇到的问题3:
代码在115200的波特率下可以正常工作,一旦切换到协议要求的9600波特率就会出现问题:一开始发送命令会正常返回响应命令,但是发送几次之后就无响应了,必须重启设备才能重新开始有响应,而且又发送几次之后变成无响应。
解决过程:
一开始认为是中断冲突导致的问题,尝试了以下方法:
- 优化中断处理逻辑,确保其尽可能简短,具体操作为使用环形缓冲区来存储数据,中断函数中只存储数据和修改指针,其余一切操作均放在主循环中进行。
- 使用DMA(未实现)
均失败。这时DeepSeek指出问题可能出在时钟分频上,尽管理论上来说选择Div1、Div4、Div16应该都可以,但是经过验证,只有Div16可以正常工作,其余两个分频系数都无法正常工作。
总结:
问题3完全不知道出现的原因,也不清楚是怎么被解决的。很莫名其妙。
工作三:
通过串口转CAN实现一份通讯协议
涉及文件:
8KW逆变主从通讯协议
DS_HC32F4A0系列数据手册_Rev1.4
RM_HC32F4A0系列参考手册_Rev1.3
实现过程:
本质上与串口转485并无不同,过程类似,在此省略。
遇到的问题1:
向逆变器(即HC32F4A0)发送命令没有反应。通过调试得知中断函数正常运行,把寄存器收到的数据传到了缓冲区,并将标志位置1,但是主函数在标志位置1之后没有工作。
解决过程:
排查发现是Systick_Init函数没有运行,即使编译没有报错。至于根本原因是HC32没有Systick定时器还是我配置错误我没有深究。我换了种方法,通过Timer0的中断函数计次来实现周期上传信息,问题解决。
遇到的问题2:
这个是从头到尾一直在困扰我的问题,和调整分频之前的485一样,每次向串口2发送命令都有概率使其运行不正常,具体表现为串口2的中断回调函数不再运行,CAN_Rx_count和CAN_flag也不正常地停留在非0的状态,即读取数据的动作未完成就被打断。此后只有重启才会恢复,但依然是每次发送命令都有概率触发此错误,少则第一次就触发,多则十几次才触发,一旦触发只能重启。
解决过程:
- 中断函数已经被尽可能简化,只执行“把接收数据寄存器中的数据读入缓冲区并将标志位置1”的操作,其余操作均在主函数中。
- 尝试了对串口2的中断回调函数设置临界区保护或原子操作,没有作用。
- 尝试了调整串口2的分频系数和过采样倍数,没有作用。为了确保波特率没有问题,查阅HC32F4A0的参考手册,通过读取BRR寄存器的值来验证了运行的波特率的确是设置的值。
- 由于串口3转485可以正常运行,所以控制变量,把串口2的所有函数尽可能与串口3的函数接近,包括从传参数的函数改成不传参数的函数,直接从缓冲区数组中读取用得到的数据,没有作用。
- 查阅hc32_ll_dma.h和hc32_ll_usart.h,没有发现可以直接配置USART的DMA功能的库函数,所以参考HC32F4A0的数据手册和参考手册直接操作寄存器来配置 DMA 和 USART 的 DMA 功能。经验证,DMA也无法解决这个问题,DMA的传输完成中断依然会被打断。
- 此时依然没有意识到是串口2的中断直接被杀死,还以为是CAN_Rx_count和CAN_flag的不正常表现导致的中断函数运行异常。于是尝试实现超时重置机制,如果在一秒内没有接收到完整的数据帧,则自动重置接收状态(CAN_Rx_count、CAN_flag和缓冲区),避免程序因数据接收不完整而卡死。超时检测通过Timer0实现。然而,经过调试发现,如果串口2的中断被杀死,即使接收状态被重置也无法恢复正常。换句话说,CAN_Rx_count和CAN_flag的不正常表现是问题的结果而非原因。
- 此后又尝试了在超时触发后重置USART2的错误标志寄存器、重新初始化USART2等,均不奏效。
- 为了彻底验证是否是我写的程序的问题(因为之前没有过嵌入式开发的经历,所以心里真的没底),我把我写的各种函数全都删了,只留了最简单的收发,然而问题依旧存在,也就是说,连最简单的从接收数据寄存器中读数据到缓冲区都做不到,到这我都快崩溃了。
- 我又重新生成了一个空白项目,从零开始配置串口2,并编写程序实现最基础命令的接收和应答,发现完全没有问题。又把我的完整的CAN通信部分放进去,发现也完美运行。此时我放弃了优化我的代码,而是把目光放在了小导给我的程序中原本存在的那部分,问题一定是那里导致的。(此前不是没有怀疑,而是多次检查过但是没有发现问题,他的这部分又是关键控制部分,我也不敢乱改)
- 控制变量进行排查,如果在主函数中把TimerA的初始化函数注释掉,CAN通信就可以完美运行。找小导讨论后得知TimerA的作用是生成PWM波,从而触发外部中断,确定了是外部中断导致的CAN通信异常。然而外部中断必须保持最高优先级和严格的时间周期,不能调整,进度又僵住了。
- 最终是调整了SPI通信的分频系数,从Div64改到了Div32。SPI通信用于从传感器读取数据并参考数据进行控制,更改后通过Debug可以看到缓冲区中依然可以读取到数据,而这个修改似乎是给CAN通信留出了时间裕量,CAN通信可以正常运行。
遇到的问题3:
周期上传功能出现异常,第二条信息的帧ID和数据部分错位。
解决过程:
修改函数将这两条分开发送也不行,更改发送的CAN帧的构造方式也不行,最终在两条发送之间增加10ms延时,问题解决。
遇到的问题4:
在安装到设备上后运行时单片机有时会无法正常启动。
解决过程:
在主函数的初始化部分加一些延时,问题解决,原因不明。
总结:
对硬件运行的理解还很浅薄,所以大致知道是外部中断长时间、高频率、高优先级的运行导致了CAN通信的异常,但是不知道从何处下手解决,也不能理解为什么串口转485就可以正常运行。
学海无涯,共勉。