HC32开发笔记
记录,为日后遇到类似问题提供思路。
常见问题&解决办法
J-LINK杀电脑事件
我有一个朋友,调试电控板时严格遵循“断电操作”,结果烧录器炸了,电脑USB口也烧了。🙂
因为之前已经有同事带电调试电脑被炸,但是没人说清楚到底是什么原因,搞得人心惶惶。风水轮流转今天吃瓜吃到了兄弟家。
我们用的是两脚插头,上面有一个船型开关,插入插座时零火线方向不固定。本次操作中,插头的火线(L)恰好接到了船型开关的输入端,而零线(N)直接接到了电控板上。
朋友拨下船型开关“断电”时,实际上断开的只是零线(N)回路,而火线(L)始终通过电源线直接连在电控板的所有铜皮、GND层和芯片地脚上。
此时开发板的“地”(GND)被火线强制抬升至接近220V。由于回路被切断,板子不工作、指示灯不亮,肉眼完全看不出异常。
朋友将烧录器的GND线连接到开发板的GND上。此时:开发板GND = 220V火线电位,电脑USB口的GND = 真正的0V大地电位;
两者通过烧录器相连,等于直接将220V火线接到了电脑USB口的金属外壳和主板GND上。瞬时大电流击穿烧录器内部所有隔离元件,并长驱直入灌入电脑主板,烧毁USB控制器及南桥芯片。
还好没有被电到只是炸台电脑助助兴。。。
解决办法:可以在两脚插头上做明显极性标记,或者搞个隔离变压器,这样妈妈再也不用担心我被电了。
更新:电脑主板烧了,开不了机了。拿去电脑电修了。
RS-485通信热插拔后无法恢复
RS-485总线在通信超时或物理断开(如带电插拔)后重新连接,上层应用显示通信链路已恢复,但MCU无法再接收到来自总线的数据,完全锁死无数据,需要重启MCU才能恢复正常。
排除物理层故障(偏置电阻、收发器),因为再故障前可正常通信。考虑因为异常之后进入了一个未知状态。尝试加断点/打印,通信故障后未正常进入中断,查看UART错误标志寄存器被置起,故障清楚异常标志位后,通信可恢复。确认是清除遗漏问题,加固代码。
根因:问题根源在于UART外设进入了错误锁定状态,且未被正确清除。手册中有描述 “RS-485总线带电插拔或断开瞬间,信号线电平不稳定,极易导致接收端产生帧错误(Frame Error, FE)——即接收到的数据帧缺少有效的停止位”,“FE=1后不能继续接收数据”,“PE=1后不能继续接收数据”,一旦这些错误标志被硬件置位,即使后续总线信号恢复正常,UART模块也会停止向RXNE(接收数据寄存器非空)中断提供新数据,导致CPU无法感知新数据的到来。
解决办法:在中断结束时候或主循环定时清除故故障标志位,防止通信因故障进入死锁状态而无法恢复。
时钟初始化过程中,进入到ddl异常中断,死循环
/**
* @brief DDL assert error handle function
* @param [in] file Point to the current assert the wrong file.
* @param [in] line Point line assert the wrong file in the current.
* @retval None
*/
__WEAKDEF void DDL_AssertHandler(const char *file, int line)
{
/* Users can re-implement this function to print information */
DDL_Printf("Wrong parameters value: file %s on line %d\r\n", file, line);
for (;;) {
}
}
卡在这个for死循环里面了,当执行 CLK_SetClockDiv() 函数时进入DDL_AssertHandler死循环,通常是因为触发了芯片的断言(Assert)机制,表明配置参数存在非法情况。
解决办法:参考了deepseek的建议,开始排查,排查了是否时钟分频参数设置错误、时钟配置寄存器可能被写保护。很幸运,确实是时钟还在写保护状态导致。解锁保护后时钟配置正常执行。
延时函数不准确、串口发送数据与PC端接受到的数据不匹配。
解决办法:发现是未正确初始化时钟导致。初始化时钟后问题解决。
J-Flash 每次打开报错 device tle9863qxw20: flash bank 0x11000000: no loader specified
解决办法:C:\Users\你的用户名\AppData\Roaming\SEGGER\JLinkDevices\JLinkDevices.xml 删除问题不复现,应该是文件中芯片地址配置错误导致
jlinkdevices.xml文件本用于J-Link识别芯片型号及硬件参数,并关联对应的Flash算法文件(.FLM)。但多次修改该文件仍报异常,删除后问题不复现。主要用HC32F460系列单片机,jlink默认的有对应的芯片类型可以用,可以暂时不用额外添加
J-Flash 添加非默认芯片
以HC32F170JATA为例:
- 从华大官网找到对应的芯片包,从芯片包里找到.FLM文件,这里搜索以下网上也有别人整理好的,含芯片配置文件和烧录算法;
- 用记事本打开JLinkDevices.xml文件,根据HC32F170JATA芯片的实际情况,将支持包内JLinkDevices.xml中的描述代码段修改,因为这里之前用别人的一直报错,打算自己重新写一下。
JLinkDevices.xml文件如下:
<Device> <!-- 1. 基础信息:告诉 JLink 芯片内核是 Cortex-M0+,以及 RAM 大小和地址 --> <ChipInfo Vendor="XHSC" Name="HC32L17x" WorkRAMAddr="0x20000000" WorkRAMSize="0x4000" Core="JLINK_CORE_CORTEX_M0"/> <!-- 2. 烧录算法:告诉 JLink Flash 的地址、大小,以及最重要的算法文件 (.FLM) 的存放位置 --> <FlashBankInfo Name="Flash_128K" BaseAddr="0x0" MaxSize="0x20000" Loader="Devices/HDSC/HC32L17x_128KB.FLM" LoaderType="FLASH_ALGO_TYPE_OPEN" AlwaysPresent="1"/> </Device> - 找到你电脑上JLink的安装目录
C:\Program Files\SEGGER\JLink或者C:\Users\你的用户名\AppData\Roaming\SEGGER\JLink,将.FLM和JLinkDevices.xml拷贝进去。- 好像在J-FlashJLink 7.62后优化了手动增加新MCU型号支持方法,在用户AppData下增加芯片1
- 这样重新打开J-Flash就可以找到新增芯片
改了程序下载地址之后程序起不来
问题背景: 有客户需要定制程序,需要我们提供我们软件BSP,他们自己写应用控制逻辑。当完成业务逻辑剥离后打算测试,发现程序一直跑飞,未按照预期运行,尝试DEBUG,直接运行,发现已经跑飞了,反汇编窗口跳转到了0xF404D0F8 AAAA ADD r2, sp, #0x2A8,跑飞!!单步运行后无法看堆栈信息,提示“Could not stop Cortex-M device! Please check the JTAG cable”,然后DEBUG就自动退出了!!
首先很明显确定已经跑飞了,根据我单步调试的结果问题再初始化阶段。0xF404D0F8这块不是正常程序代码地址。
尝试重启,能够稳定复现。回顾了一下我的操作,主要是移除了应用层代码,修改了程序烧录起始地址。因为不需要bootloader了,让程序直接从0开始进入。比较了下代码,没有BSP层没有大改动,程序是批量过的,考虑暂时先排除代码层面问题。
尝试屏蔽了部分初始化程序,问题仍然存在。
向大佬沟通,考虑中断跑飞了,使用__disable_irq();在初始化后屏蔽中断,问题复现。尝试在初始化前屏蔽中断问消失!缩小范围为中断问题。
查看中断向量表,#define VECT_TAB_OFFSET (0x0UL),逻辑上看中断向量表为0,没有问题。但是发现外面套了层宏。但是全局搜索都没有找到这个宏。
/* Vector Table base offset field */
#ifndef VECT_TAB_OFFSET
#ifndef USE_OTA_FUNC
#define VECT_TAB_OFFSET (0x0UL) /*!< This value must be a multiple of 0x400. */
#else
#define VECT_TAB_OFFSET (0x4000UL) /*!< This value must be a multiple of 0x400. */
#endif
A thousand years later,仍然没找到问题,裂开了。尝试直接屏蔽此段代码,直接定义中断向量表偏移地址设置为0,故障消失。淦!
最中找到在Keil中定义了该宏,导致中断向量表错误,初始化后,进入错误中断程序地址跑飞。
跑飞常见原因:
- 栈溢出,超出了为栈分配的空间,把旁边的数据给“踩”了。当用到旁边的数据后,程序乱掉。
- 野指针,程序跑到了一个无效的地址。
- 向量表错误,CPU在收到中断请求时,查了一个错误地址,导致跳到了错误的地方去执行代码。
- 之前还遇到过供电不稳的,也又可能跑飞。
- 也有可能Flash程序读取错误,坏块之类的。导致程序跑飞。
- 。。。
AI战士的滑铁卢日常,联合体错误使用导致CAN通信异常问题
最近一个新项目,需要用到CAN通信。现在的MCU CAN模块已经非常集成,内部自带控制器,支持报文过滤、FIFO、自动收发,外部只需接一个CAN收发器,通过寄存器读写数据,稳定可靠。
按理说,CAN发送这种基础功能,代码写过无数遍了,闭着眼睛都能写。于是偷懒让AI帮忙重构老代码,抽离公用层,简化逻辑,重新生成一个发送函数。
/**
* @brief CAN发送数据
* @param id: CAN ID
* @param data: 8字节数据指针
* @retval None
*/
void CanSendData(uint32_t id, uint8_t *data)
{
stc_can_tx_frame_t stcTx;
int32_t i32Ret;
//填充发送帧
stcTx.u32ID = id;
stcTx.IDE = 1;
stcTx.DLC = 8;
stcTx.u32Ctrl = 0; // 错误,联合体内容被清空
//复制数据
if(data != NULL)
{
for(uint8_t i = 0; i < 8; i++)
{
stcTx.au8Data[i] = data[i];
}
}
//发送
CAN_FillTxFrame(CAN_UNIT, CAN_TX_BUF_PTB, &stcTx);
CAN_StartTx(CAN_UNIT, CAN_TX_REQ_PTB);
}
代码非常简洁清晰:配ID格式和数据长度,最后填数据、发送。但偏偏就是发不出去。排查接线、时钟、寄存器都没问题,甚至开始怀疑硬件本身。
尝试DEBUG单步调试,发现判断为ID错误,通过打印变量,发现整个控制字相关的配置(IDE、DLC)都没有正确写入。
追根溯源,问题出在驱动库中stc_can_tx_frame_t结构体的定义上,先配置了DLC、IDE后再清空了u32Ctrl导致。真是一个愚蠢的操作,使用联合体在操作同一块内存。
AI在老项目中看到有xxx.u32Ctrl = 0这行代码,想都没想就复制过来了。还顺手调整了顺序。也是太相信AI了,给AI反馈错误信息,AI也是胡乱分析,最后只好自己来。
/**
* @brief CAN TX frame data structure.
*/
typedef struct {
uint32_t u32ID; /*!< 11 bits standard ID or 29 bits extended ID, depending on IDE. */
union {
uint32_t u32Ctrl;
struct {
uint32_t DLC: 4; /*!< Data length code. Length of the data segment of data frame.
It should be zero while the frame is remote frame.
This parameter can be a value of @ref CAN_Data_Length_Code */
uint32_t BRS: 1; /*!< Bit rate switch. */
uint32_t FDF: 1; /*!< CAN FD frame. */
uint32_t RTR: 1; /*!< Remote transmission request bit.
It is used to distinguish between data frames and remote frames. */
uint32_t IDE: 1; /*!< Identifier extension flag.
It is used to distinguish between standard format and extended format.
This parameter can be a 1 or 0. */
uint32_t RSVD: 24; /*!< Reserved bits. */
};
};
uint8_t au8Data[8U]; /*!< TX data payload. */
} stc_can_tx_frame_t;
又尝试了一下,AI也是能发现的,不过需要把错误描述精确到CAN ID错误。如果无法有效的描述现象,只是回答CAN无法收发数据,像这种隐性问题AI分析也困难。
重构第一步就应该确保重构前功能正常,先做好环境的测试验证工作,不是撸起袖子就干!
参考资料
-
痞子衡嵌入式. (2024). 痞子衡嵌入式:从JLink V7.62开始优化了手动增加新MCU型号支持方法. https://www.cnblogs.com/henjay724/p/18203031 ↩