HC32开发笔记

记录,为日后遇到类似问题提供思路。

常见问题&解决办法

armgcc固件烧录后无法运行(Keil编译能跑,gcc编译不跑)

计划设计my-libs代码库,建立Keil(AC5)+ armgcc(GNU 13.2)双构建通道。两通道编译同一份source/ 代码,仅构建工具不同。

但是发现Keil构建可以正常运行,但是armgcc构建程序上板子运行跑飞,并且无法再次烧录,需要Keil强擦才能恢复。

排查向量表、连接脚本、断言、CMAKE脚本,都未发现问题,与能跑的工程逐文件比对后发现根因

根因在于hc32f4xx_conf.h两项配置与能跑工程恰好相反,LL_ICG_ENABLE没有置起来,DDL源码明确要求该宏必须设置为DDL_ON,以确保芯片正常工作。可能运行中操作ICG配置区失败,导致跑飞,并导致Jlink锁死。但是明明keil编译用的也是没有置为的hc32f4xx_conf.h,却能正常运行。就很奇怪。难道是因为编译器差异?

ICG硬件配置。 芯片复位时,硬件会读取固定Flash地址中的配置。如果这里的数据不符合芯片要求,可能导致芯片无法按预期启动或运行。

缺失启动文件(.s)编译异常

.\Objects\hc32f460demo.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.
Not enough information to list image symbols.
Not enough information to list load addresses in the image map.
Finished: 2 information, 0 warning and 1 error messages.
".\Objects\hc32f460demo.axf" - 1 Error(s), 0 Warning(s).
Target not created.

点击跳转到hc32f460demo.sct文件:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)  ← 第7行: 把RESET段放在Flash最前面
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x1FFF8000 0x0002F000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

RESET段?查了一些好像是启动文件?发现确实没有将启动文件加到工程当中!?

HC32F460时钟初始化断言死锁问题

首次板子调试:程序死在 CLK_SetSysClockSrc() 内部的 DDL_AssertHandler(SDK的参数断言处理函数,故意 for(;;) 钉死故障点)。

Call Stack回溯,定位至SetSysClockSrc(),内部共三条 DDL_ASSERT:

  • IS_CLK_SYSCLK_SRC(u8Src) 参数合法性
  • IS_CLK_UNLOCKED() 时钟写保护已解
  • (CM_PWC->FCG0PC & PWC_FCG0PC_PRT0) == PWC_FCG0PC_PRT0 FCG0?

PWC_FCG0_REG_Lock():仅被 hc32_ll.c 的 LL_PERIPH_WP() 调用(在初始化尾部,案发时还没执行)

实际只有这里解锁,突然发现自己好像没有开启FCG。。

#if (LL_FCG_ENABLE == DDL_ON)
    if (u32Peripheral & LL_PERIPH_FCG) {
        PWC_FCG0_REG_Unlock();
    }
#endif

根本原因:FCG功能裁剪为OFF,使 LL_PERIPH_WE(LL_PERIPH_ALL) 的FCG解锁成为空操作;而切换系统时钟到PLL的SDK断言要求FCG0写保护已解锁;导致断言死锁

EIRQ共用引脚问题

HC32F460这款MCU基本每个引脚都支持外部中断,要注意所有端口的同号引脚共用同一个EIRQ通道,共15个通道。比如PA13、PB13、PC13、PD13、PE13全都归EIRQ13管。如果多个引脚挂在同一个中断上,会导致任何一个边沿都会触中断。

EIRQ通道 ≠ NVIC中断号,EIRQ通道与NVIC中断号之间存在一层挂号机制,这正是INTC_IrqSignIn()这类函数的核心作用。其本质是在芯片内部的中断路由表中,建立 “外设中断源 → NVIC中断号 → 回调函数” 的映射关系。

  • INTC_IrqSignIn(enIntSrc, enIRQn, pfnCallback);
  • INTC_IrqSignIn(发货人, 快递单号, 收件人),实际上就是把这三个绑定到一起,一旦触发了中断就可以执行回调函数;
  • 快递单号是索引,向量表是地址表,中断向量是表里存的地址。因为中断号终究是软件层面,要转换成中断向量,硬件才能理解并执行;
  • 如果多个引脚挂在同一个中断上,就相当于两个快递到你家,谁来都得开门收。

每个I/O端口都具有外部中断输入功能。当PCRxy.INTE位设为1时,此I/O将作为外部中断源EIRQy被允许输入。每个EIRQy可配置的I/O不止一个,使用时每个EIRQy不要同时允许多个I/O输入。

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为例:

  1. 从华大官网找到对应的芯片包,从芯片包里找到.FLM文件,这里搜索以下网上也有别人整理好的,含芯片配置文件和烧录算法;
  2. 用记事本打开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>
    
  3. 找到你电脑上JLink的安装目录C:\Program Files\SEGGER\JLink或者C:\Users\你的用户名\AppData\Roaming\SEGGER\JLink,将.FLM和JLinkDevices.xml拷贝进去。
    • 好像在J-FlashJLink 7.62后优化了手动增加新MCU型号支持方法,在用户AppData下增加芯片1
  4. 这样重新打开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分析也困难。

重构第一步就应该确保重构前功能正常,先做好环境的测试验证工作,不是撸起袖子就干!

参考资料

  1. 痞子衡嵌入式. (2024). 痞子衡嵌入式:从JLink V7.62开始优化了手动增加新MCU型号支持方法. https://www.cnblogs.com/henjay724/p/18203031 ↩

results matching ""

    No results matching ""