当前位置: 首页 > news >正文

STM32 重定向 printf 到串口的 GCC 方法与 Keil 方法对比

前言

我在查看他人的项目源码时,发现了一个名为 syscalls.c 的文件。查阅资料后了解到,这个文件是 STM32CubeMX 针对 arm-none-eabi-gcc(即嵌入式版的 GCC)的底层实现。平时在网上搜索重定向 printf 到串口的方法时,大多数教程都是针对 ARM 环境的。本文将介绍 GCC 环境下如何重定向 printf 到串口,以及两者的实现细节差异。

image-20251103174937634

不同开发环境使用的编译器和 C 标准库对比如下:

IDE/工具链编译器C 标准库
Keil MDK (AC5)ARMCCARM C Library / MicroLib
Keil MDK (AC6)ARMCLANGARM C Library / MicroLib
STM32CubeIDEGCC (arm-none-eabi-gcc)Newlib
System WorkbenchGCC (arm-none-eabi-gcc)Newlib
IAR EWARMICCARMDLIB / CLIB

printf 重定向的原理

在上位机(自己的电脑)编写 C 语言程序时,会使用很多标准库函数:

printf("Hello");        // 打印
malloc(100);           // 分配内存  
strcpy(a, b);         // 字符串复制

不同编译器对标准库都有各自的底层实现,但实现效果相同,因为它们遵循同一套标准。

各编译器使用的 C 标准库如下:

编译器使用的 C 标准库
Keil AC5ARM C Library 或 MicroLib
GCCNewlib
电脑上的 GCCglibc(太大,嵌入式不适用)

调用 printf 时的执行流程如下:

用户代码:printf("Hello %d", 123)↓
【标准库层】处理格式化字符串 → "Hello 123"↓
【底层接口】逐字符输出↓
【硬件层】UART 发送数据

不同的标准库在"底层接口"这一层使用不同的函数名和机制,这就是问题的根源。两者的主要区别如下:

  • Keil (ARMCC):实现 fputc() 函数
  • GCC (Newlib):实现 _write()__io_putchar() 函数

Keil MDK 实现方法

使用 MicroLib(最简单)

MicroLib 是 ARM C Library 的精简版本,去除了文件操作等复杂功能。可以在 Keil 中直接启用,只需勾选相应选项即可。

image-20251103185232306

启用 MicroLib 后,只需实现相应的底层接口即可,这里是 fputc 函数(串口可自行更改):

#include <stdio.h>
#include "stm32xxx_hal.h"extern UART_HandleTypeDef huart1;int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}

使用标准库

使用标准库需要定义的内容较多,但都是固定的。可以参考正点原子的教程:

#include <stdio.h>
#include "stm32f0xx_hal.h"  // 假设已定义 UART 句柄
extern UART_HandleTypeDef huart1;/*** @brief  重定向 fputc 函数,实现 printf 到 UART* @param  ch 要输出的字符* @param  f 文件指针(这里用不到)* @retval 输出的字符*/
int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}/*** @brief  重定向 fgetc 函数,实现 scanf 从 UART 接收* @param  f 文件指针* @retval 接收到的字符*/
int fgetc(FILE *f)
{uint8_t ch;HAL_UART_Receive(&huart1, &ch, 1, HAL_MAX_DELAY);return ch;
}// 避免使用半主机模式(semihosting)
#pragma import(__use_no_semihosting)// 标准库需要的支持函数
struct __FILE 
{ int handle; 
};FILE __stdout;
FILE __stdin;void _sys_exit(int x) 
{ x = x; 
}

GCC (Newlib) 实现方法

GCC 的实现流程如下:printf() → 格式化 → _write() → __io_putchar() → UART 硬件。通过 _write() 系统调用实现输出。

直接实现 _write

#include <unistd.h>
#include "stm32xxx_hal.h"extern UART_HandleTypeDef huart1;int _write(int file, char *ptr, int len)
{HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);return len;
}

使用 syscalls.c

这是 STM32CubeIDE 和 System Workbench 的标准做法。由于这个文件是自动生成的,这里不再赘述。

/*** File        : syscalls.c** Abstract    : System Workbench Minimal System calls file**               For more information about which c-functions*               need which of these lowlevel functions*               please consult the Newlib libc-manual** Environment : System Workbench for MCU** Distribution: The file is distributed "as is," without any warranty*               of any kind.*//* Includes */
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>/* Variables */
extern int errno;
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));register char * stack_ptr asm("sp");char *__env[1] = { 0 };
char **environ = __env;/* Functions */
void initialise_monitor_handles()
{
}int _getpid(void)
{return 1;
}int _kill(int pid, int sig)
{errno = EINVAL;return -1;
}void _exit (int status)
{_kill(status, -1);while (1) {}		/* Make sure we hang here */
}__attribute__((weak)) int _read(int file, char *ptr, int len)
{int DataIdx;for (DataIdx = 0; DataIdx < len; DataIdx++){*ptr++ = __io_getchar();}return len;
}__attribute__((weak)) int _write(int file, char *ptr, int len)
{int DataIdx;for (DataIdx = 0; DataIdx < len; DataIdx++){__io_putchar(*ptr++);}return len;
}caddr_t _sbrk(int incr)
{extern char end asm("end");static char *heap_end;char *prev_heap_end;if (heap_end == 0)heap_end = &end;prev_heap_end = heap_end;if (heap_end + incr > stack_ptr){errno = ENOMEM;return (caddr_t) -1;}heap_end += incr;return (caddr_t) prev_heap_end;
}int _close(int file)
{return -1;
}int _fstat(int file, struct stat *st)
{st->st_mode = S_IFCHR;return 0;
}int _isatty(int file)
{return 1;
}int _lseek(int file, int ptr, int dir)
{return 0;
}int _open(char *path, int flags, ...)
{/* Pretend like we always fail */return -1;
}int _wait(int *status)
{errno = ECHILD;return -1;
}int _unlink(char *name)
{errno = ENOENT;return -1;
}int _times(struct tms *buf)
{return -1;
}int _stat(char *file, struct stat *st)
{st->st_mode = S_IFCHR;return 0;
}int _link(char *old, char *new)
{errno = EMLINK;return -1;
}int _fork(void)
{errno = EAGAIN;return -1;
}int _execve(char *name, char **argv, char **env)
{errno = ENOMEM;return -1;
}

附录

名词解释

本文涉及的一些专业术语解释如下。

半主机模式(Semihosting)

半主机模式是让单片机通过调试器(比如 ST-Link、J-Link)借用电脑的功能(比如在电脑屏幕上打印信息、读写电脑的文件)。当程序需要脱离调试器独立运行时(比如烧录后拔掉调试器),必须禁用半主机模式,否则在运行 printf 时,程序会一直等待调试器,导致卡死。

arm-none-eabi-gcc

这是嵌入式 ARM 版本的 GCC。none 表示没有操作系统(裸机运行),eabi 是 Embedded Application Binary Interface(嵌入式应用程序二进制接口)的缩写。

Newlib

一个专为嵌入式系统设计的 C 标准库,适用于 GCC 工具链(STM32CubeIDE、PlatformIO 等)。syscalls.c 就是为 Newlib 准备的底层接口文件。

AC5 和 AC6

Keil MDK 使用的两代编译器:

项目AC5AC6
全名ARM Compiler 5ARM Compiler 6
核心ARMCCARMCLANG(基于 LLVM)
年代老版本(已停止更新)新版本(推荐使用)
C 库ARM C LibraryARM C Library

两者都不使用 Newlib,所以都不需要 syscalls.c。

glibc

Linux 电脑上使用的标准 C 库(体积较大,嵌入式系统不适用)。

总结对比

项目KeilGCC
函数名fputc()_write() / __io_putchar()
syscalls.c不需要推荐使用
半主机模式处理#pragma 禁用syscalls.c 已处理
http://www.dtcms.com/a/566430.html

相关文章:

  • 网站建设主题怎么定深圳营销外深圳网络营销公司
  • 网页制作与网站设计论文阳江房产网春天尚院
  • 广州seo网站推广优化四川省建设招标网站
  • 内核的“瘦身”艺术:从臃肿BSP到精悍映像的雕刻之路
  • 网站建设公司-跨界鱼科技网站返回503的含义是
  • 泉州找工作哪个网站好手机版网站建设价格
  • 网站制作费用遵义网上房地产
  • pc门户网站是什么意思建设银行济宁分行网站
  • 免费网站建设策划宿迁东岸网络技术有限公司
  • 南昌有哪些做网站的公司58同城网站建设推广
  • 啥时候用抽象类,啥时候用接口【示例】
  • GB/T 4857.3:运输包装静载堆码试验要点
  • 【Windows】Windows 11 添加IE浏览器 解决浏览器兼容问题
  • 高职大数据技术专业需要的基础
  • 织梦网站建设asp.net 微信网站
  • 时尚网站电子商城网站开发
  • Spark专有名词
  • wordpress 创建子主题外链seo招聘
  • 广告公司网站设计策划设计ui是什么意思
  • 怎么在Adobe Photoshop中调用banana与flux、即梦4.0等AI图像模型?AI+工作流已成为平面设计师必备技术手段!不用等着被淘汰吧!
  • 免费的网站域名查询565wccwordpress交互式地图
  • 先建网站还是先做app好特产网站源码
  • 轻云服务器 多个网站wordpress表白模板下载
  • 腾冲做兼职的网站专业企业展厅设计公司
  • 前端导出大量数据到PDF方案
  • 全自动分液站在实验室自动化中的关键作用与性能解析
  • C2S-Scale 27B 模型: AI 解码 “细胞语言“,发现癌症疗法新途径
  • 学做网站需要文化嘛seo网站优化推广教程
  • 电介质的主要电气特性:液体电介质的损耗--与温度和频率的关系
  • 求最大连续bit数