如何用VSCODE开发stm32 (日志输出打印)

只看如何用VSCODE开发stm32看左侧目录跳过去即可!其余内容是连续课程

公共层------日志输出打印

添加目录 加载头文件

在MDK-ARM文件夹中新建common

新建日志头文件和主类

添加GROUP组并将头文件添加到工程中

点击魔术棒-C/C++-包含路径

将common文件夹放进来,会把文件夹中的h头文件加入

熟悉vscode开发c类型项目的都知道,还需要把文件放进CMakeLists.txt中才能被编译,因此在这里也需要添加编译文件,把common文件添加为类

新建common类,点击Addfiles,把c h都包含进去

注意要把allfiles选上才会展示所有的文件。

如何在vscode开发Keil项目

我们需要现在vscode安装这个

然后配置Uv4Path的路径

按"ctrl+,"打开设置搜索keil

C51这一项不用改,MDK UV4.exe path要修改为自己的UV4地址

如何快速得到UV4的位置呢?

桌面右击Keil uVision5 然后点击打开文件所在位置

选中exe,shift+ctrl+c复制路径,然后粘贴到里面,记得去掉引号

然后下滑到这一项,将值这里改为上面地址去掉UV4/UV4.exe的父地址:

"E:\Project\stm32\keil5core\UV4\UV4.exe"-》"E:\Project\stm32\keil5core"

然后就能打开了

如果点击这个打开uVision项目还是打不开怎么办?

把版本往下调就可以了

打印HELLOWORLD

编辑commom中的debug.h文件:

cpp 复制代码
#ifndef __COM_DEBUG_H
#define __COM_DEBUG_H
//使用串口重定向视线日志输出打印
#include "usart.h"

#endif /* __COM_DEBUG_H */

在APPLICATION/USER/CORE的main.c中的USER CODE BEGIN END2之间添加日志打印:

cpp 复制代码
/* USER CODE BEGIN 2 */

//使用串口打印信息
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello World!\r\n", 13, 1000);

/* USER CODE END 2 */

点击编译

显示这个就是OK了:

bash 复制代码
*** Using Compiler 'V5.06 update 6 (build 750)', folder: 'E:\Project\stm32\keil5core\ARM\ARMCC\Bin'
Build target 'Location_hal'
compiling main.c...
compiling stm32f1xx_it.c...
compiling gpio.c...
compiling usart.c...
compiling stm32f1xx_hal.c...
compiling Com_debug.c...
compiling stm32f1xx_hal_cortex.c...
compiling stm32f1xx_hal_pwr.c...
compiling stm32f1xx_hal_msp.c...
compiling stm32f1xx_hal_dma.c...
compiling stm32f1xx_hal_rcc_ex.c...
compiling stm32f1xx_hal_flash.c...
compiling stm32f1xx_hal_gpio_ex.c...
compiling stm32f1xx_hal_gpio.c...
compiling stm32f1xx_hal_rcc.c...
compiling stm32f1xx_hal_uart.c...
compiling system_stm32f1xx.c...
compiling stm32f1xx_hal_exti.c...
compiling stm32f1xx_hal_flash_ex.c...
linking...
Program Size: Code=4512 RO-data=312 RW-data=16 ZI-data=1096  
FromELF: creating hex file...
"Location_hal\Location_hal.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed:  00:00:03
 *  终端将被任务重用,按任意键关闭。 

接上stm32,再点击烧录

显示这个就是正常:

bash 复制代码
> E:\Project\stm32\keil5core\UV4\UV4.exe -f e:\stm32\project\Location\Location_hal\MDK-ARM\Location_hal.uvprojx -j0 -t Location_hal -o e:\stm32\project\Location\Location_hal\MDK-ARM\.vscode\uv4.log

Load "Location_hal\\Location_hal.axf" 
Erase Done.
Programming Done.
Verify OK.
Application running ...
Flash Load finished at 11:24:03
 *  终端将被任务重用,按任意键关闭。

将stm32接入usb-ttl

【STM32 usb-ttl连接使用方法-哔哩哔哩】 https://b23.tv/wtD1g1Y

打开串口助手sscom

按下BOOT重启就有输出了:

封装日志打印函数

在debug.h中先添加printf的依赖------stdio.h

cpp 复制代码
#ifndef __COM_DEBUG_H
#define __COM_DEBUG_H
//使用串口重定向视线日志输出打印
#include "usart.h"
#include "stdio.h"
#endif /* __COM_DEBUG_H */

在debug.c中做重定向:

printf底层调用fputc函数,对每个字节调用一次fputc函数:

cpp 复制代码
#include "Com_debug.h"
//编写重定向方法
int fputc(int ch, FILE *f){
    //printf底层调用fputc函数,对每个字节调用一次fputc函数
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000);
    return ch;
}

然后在main.c中include一下,要写在USERCODE之间,否则每次更新会被删掉

cpp 复制代码
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "Com_debug.h"
/* USER CODE END Includes */

然后修改HELLOWORLD的方法为封装后的方法:

依然在USERCODE2之间

cpp 复制代码
/* USER CODE BEGIN 2 */

//使用串口打印信息
//HAL_UART_Transmit(&huart1, (uint8_t*)"Hello World!\r\n", 13, 1000);
printf("Hello World!\r\n");

/* USER CODE END 2 */

重新编译烧录即可(可以看出,在高波特率下有一定概率出现乱码)

优化打印函数------同时打印文件名和行号

我们不知道日志来自哪一行,因此希望加上行号和文件名

在debug.h中:

cpp 复制代码
#ifndef __COM_DEBUG_H
#define __COM_DEBUG_H
//使用串口重定向视线日志输出打印
#include "usart.h"
#include "stdio.h"
#include "string.h"
#include "stdarg.h"
//使用宏定义替换printf函数
#define debug_printf(format, ...) printf("[%s:%d]" format,__FILE__,__LINE__, ##__VA_ARGS__)
#endif /* __COM_DEBUG_H */

首先引入stdarg,这样才能使用系统自定义的FILE LINE的硬编码参数代表文件名和行号

逐部分解析

1. #define debug_printf(format, ...)
  • 定义一个名为 debug_printf 的宏;
  • format 是第一个参数,通常是格式化字符串(如 "x = %d\n");
  • ... 表示"可变参数"------你可以传 0 个、1 个或多个额外参数(就像 printf 一样)。

2. printf("[%s:%d]" format, __FILE__, __LINE__, ##__VA_ARGS__)

这是宏展开后实际执行的代码:

部分 含义
"[%s:%d]" format 字符串拼接!C 语言会自动把相邻字符串字面量合并。例如: 如果 format"value=%d\n",那么整体变成: "[%s:%d]value=%d\n"
__FILE__ 预定义宏 ,编译时自动替换为当前源文件的路径(如 "main.c""com_debug.c"
__LINE__ 预定义宏,编译时自动替换为当前代码行号(整数)
##__VA_ARGS__ ... 中传入的实际参数展开,并传递给 printf## 是 GCC/Clang/MSVC 支持的扩展,用于处理"零参数"情况)

🧪 举个例子

假设你在 main.c 第 25 行写了:

复制代码
1debug_printf("sensor value: %d, status: %s\n", 42, "OK");

经过预处理器展开后,等价于:

复制代码
1printf("[%s:%d]sensor value: %d, status: %s\n", "main.c", 25, 42, "OK");

运行时输出可能为:

复制代码
1[main.c:25]sensor value: 42, status: OK

✅ 一眼就知道这条日志是从哪个文件、哪一行打印的!

cpp 复制代码
/* USER CODE BEGIN 2 */

//使用串口打印信息
//HAL_UART_Transmit(&huart1, (uint8_t*)"Hello World!\r\n", 13, 1000);
printf("Hello World!");
debug_printf("Hello World!");
/* USER CODE END 2 */

嘉立创和淘宝的发票要报销

避免重定义宏是什么?

这里的"避免重定义"并不是指避免 #define __COM_DEBUG_H 这个宏被重复定义 ,而是通过这个宏来防止整个头文件的内容被多次包含 ,从而间接避免头文件内部的其他内容(如函数、变量、类型、其他宏)被重复定义

具体解释如下:

  1. 目的

    当多个 .c 文件或嵌套的头文件都 #include "com_debug.h" 时,预处理器会把该头文件的内容插入多次。如果没有保护,就会导致:

    • 结构体/类重定义
    • 函数重定义
    • 变量重定义
    • 其他宏重定义(如果头文件里还有 #define MAX_SIZE 100 等)
  2. 机制(Include Guard / 头文件守卫)

    • 第一次包含 com_debug.h 时,__COM_DEBUG_H 未定义 → 进入 #ifndef 块 → 定义 __COM_DEBUG_H → 编译头文件内容。
    • 第二次再包含时,__COM_DEBUG_H 已定义#ifndef 条件为假 → 跳过整个块(直到 #endif)→ 头文件内容不会被再次处理
相关推荐
Heartache boy5 小时前
野火STM32_HAL库版课程笔记-手动建立工程模板与CubeMX后续用法(重要)
笔记·stm32·单片机·嵌入式硬件
【ql君】qlexcel5 小时前
Visual Studio Code的使用,VS code常用扩展
ide·vscode·编辑器·visual studio·扩展
望眼欲穿的程序猿7 小时前
Vscode Clangd 无法索引 C++17 或者以上标准
java·c++·vscode
深念Y8 小时前
多模态技术详解:TTS、ASR、OCR
ide·ai·语音识别·agi·多模态·文字识别·实时语言
Wave8459 小时前
Freertos中PendSV与sysTick
单片机·嵌入式硬件
jghhh019 小时前
带红外抄板和LCD显示的单相电能表设计
stm32·单片机·嵌入式硬件
easyboot10 小时前
Visual Studio 2026安装Avalonia
ide·windows·visual studio
wggmrlee10 小时前
GD32 vs STM32
单片机·嵌入式硬件
czhaii10 小时前
STM32 F103 Altium一键下载PCB图
stm32·单片机·嵌入式硬件
雾削木10 小时前
基于STM32F411RET6 + 双路MB85RS2MT的铁电U盘
stm32·单片机·嵌入式硬件