如何用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)→ 头文件内容不会被再次处理
相关推荐
二十画~书生2 小时前
ESP32-S3音频板
经验分享·单片机·音视频·硬件工程·pcb工艺
CodeQingqing2 小时前
vscode使用问题指南
ide·vscode·编辑器
2501_918126912 小时前
stm32四条线,红绿黑白分别对应什么
stm32·单片机·学习·个人开发
宫瑾2 小时前
vscode自定义快捷键的方法
ide·vscode·编辑器
小龙报11 小时前
【51单片机】 给单片机加 “安全锁”!看门狗 WDT:原理 + 配置 + 复位验证全拆解,让程序稳定不跑飞
驱动开发·stm32·单片机·嵌入式硬件·物联网·51单片机·硬件工程
一路往蓝-Anbo12 小时前
第 9 章:Linux 设备树 (DTS) ——屏蔽与独占外设
linux·运维·服务器·人工智能·stm32·嵌入式硬件
猪八戒1.016 小时前
ESP-IDF 新版
stm32
JavaLearnerZGQ17 小时前
在Windows上使用Ollama工具本地部署deepseek-r1:7b
ide
宁静致远202117 小时前
STM32CubeMX、MDK(Keil MDK)、git、vscode等工具中统一编码设置(UTF-8),确保中文支持,避免乱码问题
git·vscode·stm32