使用 VSCode 给STM32配置一个串口 printf 工程
使用 VSCode 给STM32配置一个串口 printf 工程
gcc 重定向 printf 和 keil 不一样。
文件准备
先从以前的工程中拷过一份串口的代码来,然后在 main 函数中初始化串口并 print 一个数据吧。
新添加的文件需要添加到 Markfile 文件中,否则编译肯定会报错的。同时为了 vscode 不报错也把 include 路径在 c_cpp_properties.json 中放一份。
.h文件路径 ->Makefile+c_cpp_properties.json
.c文件 ->Makefile之后还需要在 stm32f1xx_hal_conf.h 删除 ...uart.h 和 ...usart.h 的注释,然后把 HAL 库里串口的 .c 文件添加到 Makefile .

编译通过,但是下载进去果然不行,串口没出来任何东西。这是因为 gcc 和 Keil 关于 printf() 函数底层的实现不一样。在 Keil 中需要重定向的是 fputc() 函数,但是在 GCC 中不太一样。
重定向_Printf
已知在 GCC 中想要使用 printf() 函数是需要重定向 _write() 函数的。
想要知道在 GCC 中怎么用,最好看看官方怎么说,别管哪个官方说的总会比私人说的靠谱。先试着找一下 ST 固件库的示例工程中有没有用到 printf() 的。很幸运的是官方提供了示例工程,在固件库的
...\STM32Cube_FW_F1_V1.8.0\Projects\STM32F103RB-Nucleo\Examples\UART\UART_Printf目录中可以拿到它(每一种芯片的目录下应该都有对应的这个例程)。打开示例工程的目录,可以看到里面有一个 readme.txt , 把 .txt 给它重命名为 .md 用 vscode 打开我们就可以十分清晰的看到它的介绍信息了。不难发现这个工程其实是让单片机连接超级终端用的。既然要连接超级终端那想必除了要实现 printf() 还要实现 scanf() 和其他的东东吧。不过我们目前只关心 printf(), 剩下的以后再收拾。

我们需要用到这个工程里的两个文件,
main.c和syscalls.c. main.c 的重要性没什么可说的,而 syscalls 翻译过来是系统调用的意思,因此我觉得所有的底层应该都是在这里实现的。首先看他的 main.c 文件,把所有干扰视线的注释删除掉可以发现其中除了各模块初始化等我们熟悉的代码外就多了以下两段内容。
...... /* 在我们的工程中 __GNUC__ 肯定是定义了的,因此这一段其实就只有 #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) 生效了 */
/* Private function prototypes -----------------------------------------------*/
#ifdef __GNUC__
/* With GCC, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */ ...... /* 这里写了 PUTCHAR_PROTOTYPE ,这不就是上面定义的那个宏吗,也就是说这里重写了 int __io_putchar(int ch) 这个函数,但是这跟 printf() 有什么关系呢? */
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
} ......
接下来打开 syscalls.c 文件,这个文件在
\Examples\UART\UART_Printf\SW4STM32目录下(SW4STM32 基于GCC的STM32的编译调试工具链,看到 GCC 就觉得它很可爱)。打开后果然发现这里面重写了 _write() 函数,而且其内容就是上文重写的 __io_putchar(int ch) 那个函数,到这里一切疑问就迎刃而解了。__attribute__((weak)) int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}
在搞明白原理之后我们只需用 main.c 中重定向 __io_putchar(int ch) 的部分源码替换掉 usart.c 在 Keil 中重定向 printf() 的那部分代码,然后将 syscalls.c 文件添加到工程并添加到 Makefile 文件中使其编译就可以正常使用 printf() 函数了。这里其实不把 syscalls.c 整个文件拿过来只要重写 _write() 的那部分也可以,但是看在这个文件体积也不大的份上还是把他拿过来吧,万一以后用上也方便。
好了,不出意外的话现在我们就可以正常的使用 printf() 了,试验一下吧。

关于无法打印浮点数的问题。试了试好像确实没办法打印浮点数,用 sprintf() 也不行。不过问题不大,在 Makefile 文件中找到 LDFLAGS 选项然后在里面添加
-u _printf_float参数就可以了,添加以后printf() 和 sprintf() 正常使用。
Ps.日常写程序时常用的除了 printf() 还有 sprintf() 和 sscanf() 这两个数字和字符串互转的函数,前面说了 sprintf() 已经可以正常用了,那么 sprintf() 呢?其实一样的道理, sprintf() 默认也是不能转浮点数的,但是在 Makefile 里对应的加一句
-u _scanf_float就万事大吉了。
使用_printf_需要注意的地方
printf() 只有在检测到 '\n' 时才会从缓存区把数据发出去,因此在数据结尾一定要有 '\n', 否则数据是肯定传不出去的。这次滞留的数据有可能会在下次发送带 '\n' 的数据时随它一块过去,当然也有可能被覆盖,因此记得'\n'. 如果真有什么特殊需求不能用 '\n' 的话在发完数据之后就要运行一次
fflush(stdout)强制刷新一次输出流,这样数据也是能发出去的。编码问题。VSCode 默认使用的编码是 UTF-8 因此如果你的输出有中文的话请找一个支持 UTF-8 的串口助手查看,否则肯定会乱码,实测 Windows 应用商店里的 串口调试助手 可用。虽然 VSCode 也能改成 GB2312 编码,但我劝你还是忘记那个糟糕的东西吧。
刚才又发现 vscode 一个莫名奇妙的问题,他说我的串口句柄(一个变量)没定义,扯淡我明明定义了。后来试了一下把 main.c 中最后一项 include
#include "stdio.h"移到顶端就没问题了。C/C++这个插件也是莫名其妙,渣渣。
总结
本篇介绍在 GCC 中重定向 printf() 方法,也顺便解答了从之前的 Keil 工程中将文件移植到 GCC 项目使用的问题,总结起来步骤大概分为以下几个:
复制文件。把文件复制到工程目录下你喜欢的地方。
添加 includepath. 这一步需要分别在 Makefile 和 c_cpp_properties.json 文件中添加,已添加的就不用重复添加了。
添加 C_SOURCES . 在 Makefile 中添加你新引入的 C 文件的路径。不添加不一定出错,但添加上好。
添加外设的 HAL 库文件。虽然 CubeMX 生成的工程中包含了完整的 HAL 库,但默认这些库文件并没有全部编译,比如我们默认生成的只配置了灯的工程自然就不会去编译串口、ADC等这些不相干的库文件,因此当我们需要使用串口时就需要手动把它包含进来了。
- 首先确定你的工程中的确有串口相关的 HAL 库文件,一般在
\Drivers\STM32F1xx_HAL_Driver\Src目录下。 - 然后去
stm32f1xx_hal_conf.h文件中取消掉#define HAL_UART_MODULE_ENABLED和#define HAL_USART_MODULE_ENABLED这两个宏的注释。 - 最后在 Makrfile 的 C_SOURCES 中添加上串口的 HAL 库 C 文件。
- 首先确定你的工程中的确有串口相关的 HAL 库文件,一般在
引入声明了初始化串口函数的 .h 文件,然后在 main() 函数中初始化串口并 printf() 一个数据试试。
为了更好的使用 printf() 和 sscanf()、sprintf() 可以在 Makefile 中添加
-u _printf_float和-u _scanf_float,这样就可以实现浮点数的转换了。使用 printf() 记得以 '\n' 结尾或使用 fflush(stdout) .
使用 VSCode 给STM32配置一个串口 printf 工程的更多相关文章
- STM32 使用 printf 发送数据配置方法 -- 串口 UART, JTAG SWO, JLINK RTT
STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK) http://home.eeworld.com.cn/my/space-uid-338727-blogid-47 ...
- Mac OS安装Go语言及配置VSCode开发环境:一个工具(gopls)解千愁
前言 截止到目前为止,Go语言已经更新到1.14.1,网上的很多教程均已经过时,我在此汇总并整理一下相关的教程,提供一个适合当下的Mac OS教程. 教程中使用了Go在1.11之后推出的依赖包管理工具 ...
- VScode开发STM32/GD32单片机-MakeFile工程JlinkRTT配置
本次使用开发板为STM32F401CCU6,使用CubeMX配置一个Makefile工程 配置时候为内部时钟 工程选择makefile工程类型 只生成需要的文件 用VSCode打开后显示很多波浪线 选 ...
- STM32 USB虚拟串口(转)
源:STM32 USB虚拟串口 串口调试在项目中被使用越来越多,串口资源的紧缺也变的尤为突出.很多本本人群,更是深有体会,不准备一个USB转串口工具就没办法进行开发.本章节来简单概述STM32低端芯片 ...
- 配置一个高效快速的Git环境
username and email editor difftool and mergetool alias 可以直接修改~/.gitconfig文件,也可以用命令配置一个可以实际使用的高效的Git环 ...
- STM32之模拟串口设计
一.设计用途: 公司PCB制成板降成本,选择的MCU比项目需求少一个串口,为满足制成板成本和项目对串口需求,选择模拟一路串口. 二.硬件电路: 三.设计实现: 工具&软件:STM32F030R ...
- vscode 安装与配置
vscode 安装与配置 安装 安装 vscode 从官网 [https://code.visualstudio.com/Download] 下载速度奇慢,可以找到下载的网址,如下图所示,将其中红色框 ...
- STM32F103VET6-keil工程配置-USART串口中断
1.新建一个标准空白工程 2.设置时钟源为外部HSE时钟 1 #ifndef __SYSCLK_CONFIG_H 2 #define __SYSCLK_CONFIG_H 3 #include &quo ...
- VSCode·备份&还原配置及拓展项
阅文时长 | 0.54分钟 字数统计 | 924字符 主要内容 | 1.引言&背景 2.备份VSCode配置 3.还原VSCode配置 4.Syncing常用命令 5.声明与参考资料 『VSC ...
随机推荐
- Redisson实战-BloomFilter
1. 简介 布隆过滤器是防止缓存穿透的方案之一.布隆过滤器主要是解决大规模数据下不需要精确过滤的业务场景,如检查垃圾邮件地址,爬虫URL地址去重, 解决缓存穿透问题等. 布隆过滤器:在一个存在一定数量 ...
- 解决SecureCRT中删除会话后无法重建问题
SecureCRT和SecureFX中创建一个会话(例如名为"10.0.2.47")后,如果删除这个会话,然后再创建一个同样名字的会话,就会报错,原因是SecureCRT在C盘上创 ...
- 关于shell脚本——条件测试、if语句、case语句
目录 一.条件测试 1.1.表达说明 1.2.test命令 文件测试 1.3.整数值比较 1.4.字符串比较 1.5.逻辑测试 二.if语句 2.1.单分支结构 2.2.双分支结构 2.3.多分支结构 ...
- 2021 年 iOS 应用程序开发七种最佳语言
移动应用程序现在几乎是每个在线业务的必备品.最新的 StatCounter 数据显示,多达56% 的在线连接是通过移动设备建立的,这使它们高于平板电脑和计算机.更重要的是,同一个消息来源说,其中27% ...
- Linux UDP服务器编程
UDP主要使用sendto()和recvfrom() recvfrom() 函数原型如下: #include <sys/types.h> #include <sys/socket.h ...
- COM笔记-包容与聚合
COM不支持实现继承的原因在于这种继承方式将使得一个对象的实现同另外一个对象的实现紧紧地关联起来.在这种情况下,当基类的实现被修改后,派生类将无法正常运行而必须被修改.这就是为什么一些用C++编写大型 ...
- 【springboot】自动装配原理
摘自:https://mp.weixin.qq.com/s/ZxY_AiJ1m3z1kH6juh2XHw 前言 Spring翻译为中文是"春天",的确,在某段时间内,它给Java开 ...
- ASP net core面试题汇总及答案
在dot net core中,我们不需要关心如何释放这些服务, 因为系统会帮我们释放掉.有三种服务的生命周期. 单实例服务, 通过add singleton方法来添加.在注册时即创建服务, 在随后的请 ...
- C#基础知识---?为何物
一. 可空类型修饰符(?)引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空.例如:string str=null; 是正确的,int i=null; 编译器就会报错.可空类型的出现, ...
- Qt元对象和属性系统详解
Qt 是一个用标准 C++ 编写的跨平台开发类库,它对标准 C++ 进行了扩展,引入了元对象系统.信号与槽.属性等特性,使应用程序的开发变得更高效. 本节将介绍 Qt 的这些核心特点,对于理解和编写高 ...