1. 涉及的软硬件工具
(1)MCU为STM32F407,自带FPU浮点数运算
(2)keil工程使用正点原子STM32F407探索者工程,包含动态内存分配mymalloc功能
2. 问题说明
之前的工程中,下面操作是可以直接打印浮点数的:
float num = 11.965;
printf("num = %.3f\r\n", num);
//打印:num = 11.965
后面添加一堆功能之后,发现同样的操作,无法正常打印浮点数,打印效果如下:
num = 11\0965 //原本的小数点'.'变成'\0'
num = 11?965 //原本的小数点'.'变成'?'
3. 问题排查与定位
3.1 排查1
由于最直接的现象是printf()函数打印浮点数是有问题的,所以直接开始了解STM32F407打印浮点数失败的原因(实际上该解决问题的思路有问题,因为原本就可以浮点数打印,不过也当做顺便了解其他知识)。
STM32F407有自带的FPU浮点数运算,但不一定能直接使用,可能需要使能啥的,在CSDN上了解到,具体如下:
(1)是否优化等级开太大了。并没有优化
(2)工程是否选择为Single Precision。已选择
(3)__FPU_PRESENT和__FPU_USED宏是否都为1
__FPU_PRESENT确定为1
__FPU_USED为0,可能有问题,手动改为1,会产生很多报错,应该不能直接改
后来发现原来在启动文件下已经帮我们做好了,直接通过汇编使能了浮点运算,跟上面的__FPU_PRESENT和__FPU_USED宏是否为1应该已经关系不大。
(4)小尝试
由于printf()是标准库函数,是否是由于标准库函数有点问题,需要使用keil自带的小型的标准库。
勾选"Use MicroLIB"选项使能,结果,发现居然能解决问题!神奇。具体原因后续进行说明,这里虽然解决了,但大概率是治标不治本的,先标记一下。
排查到这里,大概知道不是FPU不使能的问题,毕竟之前工程已经能正常打印浮点数,所以肯定是因为自己加了什么代码导致的。
3.2 排查2
目前新的工程添加了挺多局部大数组,如static uint8_t rx_buf[1024]; (留意这里的static),是否因为由于RAM不够导致的一些程序上的异常。好像也不是,编译工程:
Program Size: Code=71162 RO-data=1358 RW-data=9852 ZI-data=88268
当前的RAM占用如下:
RW Data + ZI Data = 9852 + 88268 = 98120
占用了98kB,而STM32F407的RAM有128kB,因此不是RAM不够导致。
3.3 排查3
由于之前添加的代码有点多,所以通过屏蔽代码的方式,最终定位到如下代码(伪代码):
void test_func (void)
{
uint8_t rx_buf[1024] = {0};
//对rx_buf数组进行一些操作...
}
将这个函数屏蔽了,其他的什么都没改动,就能正常的打印浮点数了,跟之前的工程效果一样。
为什么函数内静态的static uint8_t rx_buf[1024]可以,而非静态的uint8_t rx_buf[1024]就不可以?
这里又涉及到不同变量存储位置不同的问题,具体如下:
(1)函数内的局部静态变量static rx_buf[1024]和局部非静态变量uint8_t rx_buf[1024],两者都是占用了RAM,但更进一步的分区不同
(2)一般单片机的RAM,分为栈区、堆区、全局静态区、常量区、代码区,这5个区域的划分(具体可参考其他文章,可能叫法上有小的差异)
局部静态变量static rx_buf[1024],是存储在全局静态区,而局部非静态变量uint8_t rx_buf[1024]是存储在栈区
(3)当前是栈区的变量uint8_t rx_buf[1024]有问题,所以需要看看当前工程定义的栈大小是多少。再次看到启动文件,可以发现当前工程分配的栈大小为0x400,也就是1024字节。所以直接在函数内部定义一个非静态的大数组1024字节,栈区就会直接溢出,导致一些奇奇怪怪的问题出现。如printf()打印浮点数出现问题。
4. 问题解决与总结
最终定位到是由于局部变量大数组导致的栈溢出问题,进而程序异常导致的printf()函数打印浮点数出现乱码的问题。
解决
解决方法也很简单:
(1)将栈空间定义大一点,如10kB
(2)不使用栈空间,仍然定义局部静态变量,加上static即可
(3)使用动态内存分配的方式
总结
(1)回到3.1排查1的(4)小尝试,为什么勾选"Use MicroLIB"后就能正常打印浮点数?猜测是由于使用了比较小型的标准库,在调用printf("%f")打印浮点数时,没有访问到出错的位置,运气不错避开了,所以能够正常打印浮点数。
(2)假如经常用到大数组,最好还是用动态内存分配,需要用到时就分配,用完就释放,保证RAM空间是足够使用且安全可控的。对于动态内存的分配和释放,会增加代码的复杂度,而且RAM的释放时机,需要比较好的把握。是否使用需要进行权衡。
(3)栈空间定义多大才足够使用,实际上是未知的,不确定未来还会使用多大的空间(不过栈用起来确实挺方便的,每次都是重新初始化,相当于自动清理之前的数据)。
(4)栈溢出的情况,可能会比当前遇到的现象更加严重,目前应该算运气好只是打印浮点数有异常,实际上还会导致程序跑飞。因为栈溢出了,开始出现一些随机性,就看程序访问到什么位置,访问到非法位置,那程序就必然会跑飞。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_41724510/article/details/141262397
|