问题和解决方法
找了块开发板玩RT-Thread,一顿骚操作之后,发现debug就卡死在Stm32_putc(不稳定,反复重新上下电,重来有时候卡死有时候不卡死),卡死情况如下图:
先最后的解决方法:取消调默认的内存分配方式,用自己定义的分散加载文件(事实上官方SDK就是这么干的,自己搞着搞着就没选回去)
好奇的小伙伴可能就会问为什么这个加载方式会影响到控制台串口打印,且看下文。
排查过程:
1.一开始我也不知道为什么,但是控制台打印肯定会先初始化对应串口,我使用usart1,于是先打断点到drv_usart.c里面。复位后重新跑发现正常进入断点,然后单步调式,发现竟然没调用HAL_UART_MspInit初始化usart的引脚和中断。
看了各个变量信息,原因是huart->gState 不等于 HAL_UART_STATE_RESET,if判断不成立。
2.接着去查为什么huart->gState == HAL_UART_STATE_READY。先采取了个蠢方法全局搜HAL_UART_STATE_READY,每个地方赋值HAL_UART_STATE_READY给huart->gState的地方都打上断点,
然后复位运行,发现一个断点都没有卡住。这就很奇怪了,也就是说上电/debug复位的时候huart->gState就自动有了值,显然怀疑huart初始化的时候有问题,于是进入下一步,查huart怎么来的。
3.逐层往上查,
HAL_UART_Init传入的是&uart->handle ,而uart-是一个struct stm32_uart的结构体,其是通过struct rt_serial_device *serial计算出来的。
计算的方式很巧妙,通过函数rt_container_of计算出struct rt_serial_device *serial所在的struct stm32_uart结构体的首地址。
当然能用rt_container_of有前提,必须有个结构体struct stm32_uart,其中包含了结构体struct rt_serial_device *serial,然后这个struct rt_serial_device *serial结构体的地址作为参数传入了函数stm32_configure
rt_container_of的详细介绍见:rt_container_of 作用和实现过程超级详解介绍-CSDN博客。
4.那我们就得去查struct rt_serial_device *serial那来的,包含这个结构体的struct stm32_uart是在哪里初始化的。过程就不再赘述,最终查到int rt_hw_usart_init(void)函数初始化的struct stm32_uart,关键代码如下:
static struct stm32_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0};
int rt_hw_usart_init(void)
{
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
stm32_uart_get_dma_config();
for (rt_size_t i = 0; i < sizeof(uart_obj) / sizeof(struct stm32_uart); i++)
{
//rt_memset(&uart_obj[i],0,sizeof(struct stm32_uart));
/* init UART object */
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config;
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR
| RT_DEVICE_FLAG_INT_RX
| RT_DEVICE_FLAG_INT_TX
| uart_obj[i].uart_dma_flag
, NULL);
RT_ASSERT(result == RT_EOK);
}
return result;
}
奇怪的是:函数中没有对uart_obj ->handle -> gState进行任何初始化操作,uart_obj声明时已经 = {0}了,理论上没有赋值的部分都应该为0。
因为uart_obj是全局静态变量,可以debug直接看其内容参数,发现其一开始就有值。
到这时候,多年的搬砖经验告诉我,应该是ZI数据段上电没有清零。
可以在初始化uart_obj[i]参数前,调用函数rt_memset(&uart_obj[i],0,sizeof(struct stm32_uart));对uart_obj[i]进行清零试一下。发现控制台串口终于不卡死,有打印数据了,但是ilde线程还是会崩溃死机。
5.去查了下ZI数据段是什么时候清零的,由谁来清零,得到的结论是编译工具MDK会自动生成出来。
决定尝试改配置,用自己的分散加载文件,对比编译出来的.map,果然用自己分散加载编译出来的程序会多一个ZI初始化的代码。
虽然当前遇到的问题解决了,控制台串口不会卡死,tilde0线程也不会崩溃卡死,但是实际上还有个大疑问没有解决,就是为什么MDK用默认内存分配方式(即用target设置的),不用分散加载文件时,没有添加zi清零操作,也许是哪些配置没有勾选,目前看不明白只能当作一个BUG来规避了。