基于clion的野火stm32标准库开发(printf打印已解决)
三哥与林雨
2021年07月19日 17:14

1、教程说明

        最近花了一些时间,重新学习野火stm32f4的1.8标准库视频,跟着火哥一起用keil5软件敲代码,还是用不习惯,就想着用clion折腾下标准库开发,顺便学习下重温cmake的用法。

        这里只说明基于nucleo f4板子标准库开发的一些注意事项,使用野火教程的模板。

        下面是成品图:

1_成品图

2、教程准备

        这里参考稚晖君的教程,把clion的环境配好。

        配置CLion用于STM32开发 【优雅の嵌入式开发】​

        以及陈伟国AE​up主的标准库  Clion实现Stm32标准库开发 ​

3、注意使用

    (1)外设驱动库文件

        这里我们参考标准外设库STM32F4xx_DSP_StdPeriph_Lib_V1.8.0里面的SW4STM32的模板工程,找到f446RE的工程并打开,SW4STM32同样是GCC环境的。

2_SW4STM32

        参考STM32F4xx_StdPeriph_Driver中的c文件调用,把f446RE不支持的外设删除,相对应的头文件也要删除。然后复制到clion工程里面。

3_删除无用c文件

    (2)Cmake文件修改

        1)第一个是头文件路径修改,就是需要用到的头文件,都要把路径写到cmake文件里面,编译器才能识别到。剩下的板级支持包分开写,就是为了跟着火哥上课学习外设时,另外一个一个加,比如LED的头文件。

        这里我们说下这个,(复制跳转)include_directories的用法

代码块
text/html
自动换行
复制代码
https://cmake.org/cmake/help/latest/command/include_directories.html?highlight=include_directories#command:include_directories
复制成功

5_头文件命令

        2)第二个是c文件,以及启动文件路径的修改,把相关源文件和启动文件链接到编译器。使用GLOB命令使用通配符模式匹配来查找文件。file(GLOB SOURCES "src/*.*")使用这个通配符,表示所有".*"结尾的文件都会包含到这个SOURCES变量。他的用法也可以参考上面的教程网址,在快速搜索哪里搜索关键词file或者GLOB。

6_源文件命令

        3)第三个是宏定义文件的修改。我们在使用keil5编译工程前都会添加两个头文件。

7_宏定义使用

        而在clion上面,当我们用cubemx生成代码的时候,我们会发现,在stm32f4xx.h这个头文件上面,添加两个宏定义#define USE_STDPERIPH_DRIVER 和 #​include STM32F446xx。编译后,编译器会提示该头文件STM32F446xx重定义了,经过一番研究发现。原来在cubemx软件和clion生成代码的时候,已经在cmake文件上添加了相关的宏定义。我们按照图片修改就是了。

8_宏定义的使用

        下面是add_definitions命令的使用方法和解释。

9_宏定义的意思

(3)Printf串口打印

        1)主要问题

        我们一般使用printf打印log,用来调试程序。要解决的问题是将printf的输出重定向到串口,然后通过串口将数据发送出去。但是clion的printf打印函数,并不像keil5那样,他们对应的函数并不相同。所以用野火的keil5重定向函数,在clion并没有输出,反而会编译报错。

        通过查看编译器的报错提示,原来printf打印函数对应的底层调用函数,并没有定义。但是查看1.8标准库的SW4STM32例子,也没有看到相关的重定义函数和文件。进而找到HAL库的printf例子,发现工程里多了一个syscalls.c文件--该文件已经定义好了相关的函数。我们把这个文件复制到工程的User目录下,并编译,报错已经解决。

10_printf重定义

        虽然报错已经解决但是printf还是没有输出。与在keil5中重定义fputs()函数不一样,在GCC编译器中(stm32标准库)需要重定义的是__io_putchar(int ch)。

11_对应函数弱定义

         为什么可以确定是这个函数呢?我们通过查看stm官方的stm32f4的1.8标准库中的例程查到printf的使用方式。我们要学会使用官方的例程和文档,这种方式是一手途径,效率是最高的。

12_文件解释

        所以我们按照官方的做法,在串口定义的.c文件哪里,添加重定义函数。

13_添加相对重定义函数

        接着重写函数,这两个函数的写法是和野火教程的一模一样的。

14_野火格式printf

        到这里为止,我们一般就可以愉快的玩耍printf函数了,但是还有一些小问题要解决。

        2)次要问题

        第一个问题--printf打印不加\n,没有输出。

        解决方式,在#include "bsp_debug_usart.h"开头包含头文件#include <stdio.h>,然后在串口初始化时调用setvbuf(stdout, NULL, _IONBF, 0),设置buffer缓存为0,这样一有数据就发送,不然会等到缓存满或有回车换行符才发送。如果没有这句,你的printf又没\n,log就会打不出来。

15_解决问题1

        第二个问题,半主机模式,(这个模式存疑,网上很多教程)

        我没有禁用半主机模式,也是可以正常使用printf的,不知道这个原因是什么。

        半主机重定向    http://www.mculover666.cn/posts/2251182441/

        hal库的设置    https://shawnhymel.com/1873/how-to-use-printf-on-stm32/

        下面的内容都是存疑的:

        printf 函数使用了半主机模式,所以直接使用标准库会导致程序无法运行,因此必须提前告知编译器不使用半主机模式--在syscalls.c文件下添加其中一个。

        第一种方法(参考):

        不使用半主机模式 /* 告知连接器不从C库链接使用半主机的函数 */

        #pragma import(__use_no_semihosting)

        第二种方式:

        #if !defined(OS_USE_SEMIHOSTING) //如果定义了OS_USE_SEMIHOSTING

        中间是代码

        #endif //#​if !defined(OS_USE_SEMIHOSTING)

16-解决问题2

        个人结论:既然官方例程在SW4STM32例子中和syscalls.c文件中没有撰写这个,还有本人在GCC环境编译也没有出现问题。我觉得这个可以暂时忽略掉,就是不用考虑半主机问题。

(4)测试效果

        printf输出没有问题,浮点数打印也没有问题。

结果

(4)F446RET6工程文件

链接:https://pan.baidu.com/s/1310oFNVDqsHYru2nMPkjHA

提取码:w747

17_结尾图