使用 VSCode 给STM32配置一个串口 printf 工程

gcc 重定向 printf 和 keil 不一样。

文件准备

  1. 先从以前的工程中拷过一份串口的代码来,然后在 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 .

  2. 编译通过,但是下载进去果然不行,串口没出来任何东西。这是因为 gcc 和 Keil 关于 printf() 函数底层的实现不一样。在 Keil 中需要重定向的是 fputc() 函数,但是在 GCC 中不太一样。

重定向_Printf

  1. 已知在 GCC 中想要使用 printf() 函数是需要重定向 _write() 函数的。

  2. 想要知道在 GCC 中怎么用,最好看看官方怎么说,别管哪个官方说的总会比私人说的靠谱。先试着找一下 ST 固件库的示例工程中有没有用到 printf() 的。很幸运的是官方提供了示例工程,在固件库的 ...\STM32Cube_FW_F1_V1.8.0\Projects\STM32F103RB-Nucleo\Examples\UART\UART_Printf 目录中可以拿到它(每一种芯片的目录下应该都有对应的这个例程)。

  3. 打开示例工程的目录,可以看到里面有一个 readme.txt , 把 .txt 给它重命名为 .md 用 vscode 打开我们就可以十分清晰的看到它的介绍信息了。不难发现这个工程其实是让单片机连接超级终端用的。既然要连接超级终端那想必除了要实现 printf() 还要实现 scanf() 和其他的东东吧。不过我们目前只关心 printf(), 剩下的以后再收拾。

  4. 我们需要用到这个工程里的两个文件,main.csyscalls.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;
      }
  5. 在搞明白原理之后我们只需用 main.c 中重定向 __io_putchar(int ch) 的部分源码替换掉 usart.c 在 Keil 中重定向 printf() 的那部分代码,然后将 syscalls.c 文件添加到工程并添加到 Makefile 文件中使其编译就可以正常使用 printf() 函数了。这里其实不把 syscalls.c 整个文件拿过来只要重写 _write() 的那部分也可以,但是看在这个文件体积也不大的份上还是把他拿过来吧,万一以后用上也方便。

  6. 好了,不出意外的话现在我们就可以正常的使用 printf() 了,试验一下吧。

  7. 关于无法打印浮点数的问题。试了试好像确实没办法打印浮点数,用 sprintf() 也不行。不过问题不大,在 Makefile 文件中找到 LDFLAGS 选项然后在里面添加 -u _printf_float 参数就可以了,添加以后printf() 和 sprintf() 正常使用。

  8. Ps.日常写程序时常用的除了 printf() 还有 sprintf() 和 sscanf() 这两个数字和字符串互转的函数,前面说了 sprintf() 已经可以正常用了,那么 sprintf() 呢?其实一样的道理, sprintf() 默认也是不能转浮点数的,但是在 Makefile 里对应的加一句 -u _scanf_float 就万事大吉了。

使用_printf_需要注意的地方

  1. printf() 只有在检测到 '\n' 时才会从缓存区把数据发出去,因此在数据结尾一定要有 '\n', 否则数据是肯定传不出去的。这次滞留的数据有可能会在下次发送带 '\n' 的数据时随它一块过去,当然也有可能被覆盖,因此记得'\n'. 如果真有什么特殊需求不能用 '\n' 的话在发完数据之后就要运行一次 fflush(stdout) 强制刷新一次输出流,这样数据也是能发出去的。

  2. 编码问题。VSCode 默认使用的编码是 UTF-8 因此如果你的输出有中文的话请找一个支持 UTF-8 的串口助手查看,否则肯定会乱码,实测 Windows 应用商店里的 串口调试助手 可用。虽然 VSCode 也能改成 GB2312 编码,但我劝你还是忘记那个糟糕的东西吧。

  3. 刚才又发现 vscode 一个莫名奇妙的问题,他说我的串口句柄(一个变量)没定义,扯淡我明明定义了。后来试了一下把 main.c 中最后一项 include #include "stdio.h" 移到顶端就没问题了。C/C++这个插件也是莫名其妙,渣渣。


总结

本篇介绍在 GCC 中重定向 printf() 方法,也顺便解答了从之前的 Keil 工程中将文件移植到 GCC 项目使用的问题,总结起来步骤大概分为以下几个:

  1. 复制文件。把文件复制到工程目录下你喜欢的地方。

  2. 添加 includepath. 这一步需要分别在 Makefile 和 c_cpp_properties.json 文件中添加,已添加的就不用重复添加了。

  3. 添加 C_SOURCES . 在 Makefile 中添加你新引入的 C 文件的路径。不添加不一定出错,但添加上好。

  4. 添加外设的 HAL 库文件。虽然 CubeMX 生成的工程中包含了完整的 HAL 库,但默认这些库文件并没有全部编译,比如我们默认生成的只配置了灯的工程自然就不会去编译串口、ADC等这些不相干的库文件,因此当我们需要使用串口时就需要手动把它包含进来了。

    1. 首先确定你的工程中的确有串口相关的 HAL 库文件,一般在 \Drivers\STM32F1xx_HAL_Driver\Src 目录下。
    2. 然后去 stm32f1xx_hal_conf.h 文件中取消掉 #define HAL_UART_MODULE_ENABLED#define HAL_USART_MODULE_ENABLED 这两个宏的注释。
    3. 最后在 Makrfile 的 C_SOURCES 中添加上串口的 HAL 库 C 文件。
  5. 引入声明了初始化串口函数的 .h 文件,然后在 main() 函数中初始化串口并 printf() 一个数据试试。

  6. 为了更好的使用 printf() 和 sscanf()、sprintf() 可以在 Makefile 中添加 -u _printf_float-u _scanf_float ,这样就可以实现浮点数的转换了。

  7. 使用 printf() 记得以 '\n' 结尾或使用 fflush(stdout) .


使用 VSCode 给STM32配置一个串口 printf 工程的更多相关文章

  1. STM32 使用 printf 发送数据配置方法 -- 串口 UART, JTAG SWO, JLINK RTT

    STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK) http://home.eeworld.com.cn/my/space-uid-338727-blogid-47 ...

  2. Mac OS安装Go语言及配置VSCode开发环境:一个工具(gopls)解千愁

    前言 截止到目前为止,Go语言已经更新到1.14.1,网上的很多教程均已经过时,我在此汇总并整理一下相关的教程,提供一个适合当下的Mac OS教程. 教程中使用了Go在1.11之后推出的依赖包管理工具 ...

  3. VScode开发STM32/GD32单片机-MakeFile工程JlinkRTT配置

    本次使用开发板为STM32F401CCU6,使用CubeMX配置一个Makefile工程 配置时候为内部时钟 工程选择makefile工程类型 只生成需要的文件 用VSCode打开后显示很多波浪线 选 ...

  4. STM32 USB虚拟串口(转)

    源:STM32 USB虚拟串口 串口调试在项目中被使用越来越多,串口资源的紧缺也变的尤为突出.很多本本人群,更是深有体会,不准备一个USB转串口工具就没办法进行开发.本章节来简单概述STM32低端芯片 ...

  5. 配置一个高效快速的Git环境

    username and email editor difftool and mergetool alias 可以直接修改~/.gitconfig文件,也可以用命令配置一个可以实际使用的高效的Git环 ...

  6. STM32之模拟串口设计

    一.设计用途: 公司PCB制成板降成本,选择的MCU比项目需求少一个串口,为满足制成板成本和项目对串口需求,选择模拟一路串口. 二.硬件电路: 三.设计实现: 工具&软件:STM32F030R ...

  7. vscode 安装与配置

    vscode 安装与配置 安装 安装 vscode 从官网 [https://code.visualstudio.com/Download] 下载速度奇慢,可以找到下载的网址,如下图所示,将其中红色框 ...

  8. STM32F103VET6-keil工程配置-USART串口中断

    1.新建一个标准空白工程 2.设置时钟源为外部HSE时钟 1 #ifndef __SYSCLK_CONFIG_H 2 #define __SYSCLK_CONFIG_H 3 #include &quo ...

  9. VSCode·备份&还原配置及拓展项

    阅文时长 | 0.54分钟 字数统计 | 924字符 主要内容 | 1.引言&背景 2.备份VSCode配置 3.还原VSCode配置 4.Syncing常用命令 5.声明与参考资料 『VSC ...

随机推荐

  1. Send Excerpts from Jenkins Console Output as Email Contents

    Sometimes we need to send some excerpts from Jenkins console output (job logs) as email, such as tes ...

  2. Golang语言系列-12-网络编程

    网络编程 互联网协议介绍 互联网的核心是一系列协议,总称为"互联网协议"(Internet Protocol Suite),正是这一些协议规定了电脑如何连接和组网.我们理解了这些协 ...

  3. 不同JDK版本的流异常处理

    1.JDK7以前的流异常try-catch处理 public static void main(String[] args) { FileInputStream fis = null; try { f ...

  4. EasyExcel导入导出

    maven依赖 <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --> <dependency> & ...

  5. 一个系列搞懂YARN(1)——Yarn架构

    前言 几天前和大哥说起了Yarn,大哥问我,你知道Yarn里面怎么进行资源的动态分配回收的吗?我和诚实,说不知道,然后就有了这个系列博文.不同版本的hadoop版本对应的yarn文档会有差别,本文中选 ...

  6. mysql导出word的表结构操作

    mysql导出word的表结构操作 1.首先准备好mysql的相关插件mysql-connector-odbc和DBExportDoc 百度网盘地址: 链接:https://pan.baidu.com ...

  7. spring cloud 网管篇zuul

    1, consul 2, zuul 程序的yml 文件 server: port: 8083spring: application: name: zuulInfo # 应用名称 cloud: cons ...

  8. itoa函数递归实现

    库函数中有atoi函数,用意是将字符形式输入的数据转换成数字,而库函数有没有提供一个将数字转换成字符的函数呢?答案是有的,而且功能很是强大,那就是sprintf().snprintf()格式化转换函数 ...

  9. javacc在stanfordnlp中的应用

    总结: 这个javacc感觉比较复杂,在于stanfordnlp中 p.p1 { margin: 0; font: 11px Monaco } CoreMapExpressionExtractor这个 ...

  10. Mybatis简单应用

    Mybatis的核心组件: SqlSeeeionFactoryBuilder (构建器):它会根据配置或者代码来生成SqlSessionFactory,采用的是分布构建的Builder模式: SqlS ...