电控---printf重定向输出

在嵌入式系统开发中,printf 重定向输出是将标准输出(stdout)从默认设备(如主机终端)重新映射到嵌入式设备的特定硬件接口(如串口、LCD、USB等)的过程。


一、核心原理:标准IO库的底层机制

  1. C标准库的I/O层次结构

    • printf 属于标准IO库(libc),其输出最终依赖底层的 字符输出函数(如 fputc)和 文件流操作stdout)。
    • stdout 是指向 FILE 结构体的指针,该结构体定义了输出设备的操作接口(如写字符函数)。
  2. 重定向的本质

    • 通过 重定义底层输出函数,将 printf 的输出路径从默认设备(如主机终端)切换到目标硬件(如UART、SPI设备)。
    • 关键是让 printf 在调用 fputc 时,实际执行目标设备的写操作。

二、核心实现步骤:以UART为例

1. 准备硬件驱动(以UART为例)
  • 初始化UART硬件:配置波特率、数据位、停止位、奇偶校验等(具体代码依赖芯片型号,如STM32的HAL库或寄存器操作)。
    // 示例:STM32 HAL库初始化UART
    UART_HandleTypeDef huart1;
    void uart_init() {huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;HAL_UART_Init(&huart1);
    }
    // 发送单个字符到UART
    void uart_putchar(char c) {HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, 100); // 阻塞发送
    }
    
2. 重定义fputc(最通用方法)
  • fputc 是标准IO库中用于向流(如stdout)写入单个字符的函数,printf 通过调用它输出每个字符。
  • 重定义该函数,使其调用目标设备的写函数(如uart_putchar)。
    #include <stdio.h>
    int fputc(int ch, FILE *f) {if (f == stdout) { // 仅处理标准输出uart_putchar((char)ch); // 调用硬件发送函数// 处理换行符(可选:若设备需要\r\n,添加此逻辑)if (ch == '\n') {uart_putchar('\r');}}return ch; // 返回写入的字符
    }
    
3. 处理特殊字符(如换行符\n
  • 部分设备(如串口终端)需要 \r\n 作为换行标识,而 printf\n 仅输出 0x0A,因此需在 fputc 中手动添加 \r
    if (ch == '\n') {uart_putchar('\r'); // 先发送\r
    }
    
4. 编译器特定适配(关键差异点)
  • 不同编译器可能使用不同的底层函数名,需按编译器文档调整:
    • GCC(如ARM-GCC、GNU工具链):通常直接重定义 fputc 即可,或部分版本需重定义 __io_putchar(如STM32CubeIDE)。
    int __io_putchar(int ch) {uart_putchar(ch);return ch;
    }
    
    • Keil MDK(ARM Compiler):需重定义 __fputc,并可能需要关闭“Use MicroLIB”(若使用标准库):
    int __fputc(int ch, FILE *f) {uart_putchar(ch);return ch;
    }
    
    • IAR:重定义 fputc,并确保链接时不使用半主机模式(Semihosting)。
5. 关闭半主机模式(ARM调试常见问题)
  • 半主机模式是ARM调试时通过主机模拟IO的机制,若不关闭,printf 会默认输出到主机终端。
  • 关闭方法:
    • Keil:在工程配置中取消勾选 Use Semihosting
    • GCC:通过链接选项 -specs=nosys.specs 或定义 __ARM_ARCH_7__ 等宏(具体依工具链而定)。

三、进阶:不同场景的重定向

1. 重定向到非字符设备(如LCD、SPI/UART外设)
  • 若设备以块或帧为单位传输(如LCD显示字符串),需在 fputc 中逐字符发送,或在更高层函数(如自定义 lcd_puts)中处理缓冲。
int fputc(int ch, FILE *f) {lcd_write_char(ch); // LCD驱动的字符写入函数return ch;
}
2. 无操作系统(裸机)vs RTOS环境
  • 裸机:直接实现阻塞式 fputc,无需考虑任务同步。
  • RTOS(如FreeRTOS):若多个任务调用 printf,需添加互斥锁(如 vTaskSuspendAll()/xTaskResumeAll())防止竞态条件:
    int fputc(int ch, FILE *f) {taskENTER_CRITICAL(); // 进入临界区uart_putchar(ch);taskEXIT_CRITICAL(); // 退出临界区return ch;
    }
    
3. 重定向到多个输出设备(多流支持)
  • 若需同时输出到串口和LCD,可创建自定义 FILE 结构体并注册对应的写函数(需深入理解libc的流操作机制,较复杂):
    // 示例:定义自定义流
    FILE my_uart_stream;
    FILE my_lcd_stream;
    // 注册写函数(非标准方法,依赖编译器支持)
    my_uart_stream._write = uart_write_func;
    my_lcd_stream._write = lcd_write_func;
    // 使用:fprintf(&my_uart_stream, "UART: %d", data);
    
4. 禁用标准库缓冲(提升实时性)
  • stdout 默认使用行缓冲或全缓冲,可能导致输出延迟。通过 setvbuf(stdout, NULL, _IONBF, 0) 设置无缓冲模式:
    int main() {uart_init();setvbuf(stdout, NULL, _IONBF, 0); // 无缓冲printf("Hello, Embedded!\n"); // 立即输出return 0;
    }
    

四、关键注意事项

  1. 头文件包含

    • 必须包含 stdio.h,否则编译器可能无法识别 FILEfputc
  2. 内存占用与库选择

    • 标准IO库(如libc)可能占用较多内存,嵌入式系统通常使用轻量版本(如 newlib)。若使用 newlib,需确保配置中启用了相关组件(如 _printf_float 支持浮点输出)。
  3. 浮点输出支持

    • printf 的浮点格式(如%f)需要额外的数学库支持,可能增加代码体积。若无需浮点功能,可通过编译器选项禁用(如Keil的--no_floating_point)。
  4. 重定向失败排查

    • 检查硬件驱动是否正确初始化(如UART波特率是否匹配)。
    • 确认是否关闭半主机模式,避免输出到调试主机。
    • 调试时可先测试 fputc 单字符发送(如循环发送'A'),再测试printf
    • 编译器优化等级可能导致函数未被链接,可添加 __attribute__((used)) 强制保留重定义函数。
  5. 自定义printf(非标准库方案)

    • 若资源极度受限,可实现独立于标准库的简易printf,直接操作硬件(需解析格式字符串并实现字符转换,如itoa)。但此方法兼容性差,不建议除非必要。

五、典型代码示例(STM32 + GCC)

#include <stdio.h>
#include "stm32f4xx_hal.h"UART_HandleTypeDef huart1;// UART初始化
void uart_init() {huart1.Instance = USART1;huart1.Init.BaudRate = 115200;// 其他配置...HAL_UART_Init(&huart1);
}// 重定义fputc
int fputc(int ch, FILE *f) {if (f == stdout) {HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 100);// 处理\n到\r\n的转换if (ch == '\n') {HAL_UART_Transmit(&huart1, (uint8_t*)"\r", 1, 100);}}return ch;
}int main() {HAL_Init();uart_init();setvbuf(stdout, NULL, _IONBF, 0); // 无缓冲printf("System started at %s\n", __TIME__);while(1);
}

六、总结

嵌入式系统中printf重定向的核心是通过重定义底层字符输出函数(如fputc),将标准输出映射到目标硬件。关键步骤包括:

  1. 实现目标设备的单字符写入函数;
  2. 重定义编译器对应的底层函数(注意不同工具链的差异);
  3. 处理特殊字符、缓冲模式及调试配置;
  4. 适配裸机或RTOS环境,确保线程安全。

掌握此技术后,可灵活将printf输出到串口、LCD、网络等任意设备,极大提升嵌入式系统的调试和交互能力。注意结合具体编译器文档和硬件驱动进行适配,避免因底层差异导致的问题。


重点函数讲解

HAL_UART_Transmit 是 STM32 HAL(Hardware Abstraction Layer,硬件抽象层)库中用于通过 UART(通用异步收发传输器)发送数据的函数。

函数原型

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

参数解释

  1. UART_HandleTypeDef *huart

    • 此参数是一个指向 UART_HandleTypeDef 结构体的指针,该结构体用于保存 UART 外设的配置信息与状态。
    • HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, 100); 里,&huart1 代表的是指向 huart1 结构体的指针,这里的 huart1 通常是在初始化 UART1 外设时自动生成的句柄,借助它可以指定要使用的 UART 外设。
  2. uint8_t *pData

    • 这是一个指向要发送数据的指针,数据类型为 uint8_t(无符号 8 位整数)。
    • HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, 100); 中,(uint8_t*)&c 是将变量 c 的地址强制转换为 uint8_t* 类型。这意味着要发送的是变量 c 的值。
  3. uint16_t Size

    • 该参数表示要发送的数据的字节数。
    • HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, 100); 中,1 表明只发送 1 个字节的数据,也就是变量 c 的值。
  4. uint32_t Timeout

    • 此参数为发送操作的超时时间,单位是毫秒(ms)。
    • HAL_UART_Transmit(&huart1, (uint8_t*)&c, 1, 100); 中,100 意味着如果在 100 毫秒内无法完成数据发送,函数会提前返回,以避免程序陷入无限等待。
      在这个示例中,send_single_char 函数的作用是通过 UART1 发送一个字符。它调用 HAL_UART_Transmit 函数,将字符 c 发送出去,并且设置超时时间为 100 毫秒。如果发送失败,函数会返回一个非 HAL_OK 的状态码,这时就可以对发送失败的情况进行处理。

示例:TI的MSPM0G3507实现重定向

#include “stdio.h”
#include "string.h"// 重定向fputc函数
int fputc(int ch, FILE *f){DL_UART_transmitDataBlocking(UART_0_INST, ch);return (ch);
}
// 重定向fputs函数int fputs(const char* restrict s, FILE* restrict stream) {uint16_t i,len;len = strlen(s);for(i=0;i<len;i++){DL_UART_transmitDataBlocking(UART_0_INST, s[i]);}return len;
}
// 重定向puts函数
int puts(const char* _ptr)
{int count = fputs(_ptr,stdout);count += fputs("\n",stdout);return count;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/77183.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快速认识:数据库、数仓(数据仓库)、数据湖与数据运河

数据技术核心概念对比表 概念核心定义核心功能数据特征典型技术/工具核心应用场景数据库结构化数据的「电子档案柜」&#xff0c;按固定 schema 存储和管理数据&#xff0c;支持高效读写和事务处理。实时事务处理&#xff08;增删改查&#xff09;&#xff0c;确保数据一致性&…

【17】数据结构之图的遍历篇章

目录标题 图的遍历深度优先遍历 Depth First Search广度优先遍历 Breadth First Search 图的遍历 从图中某一个顶点出发&#xff0c;沿着一些边访遍图中所有的顶点&#xff0c;且使用每个顶点仅被访问一次&#xff0c;这个过程称为图的遍历.Graph Traversal. 其中&#xff0c…

简单接口工具(ApiCraft-Web)

ApiCraft-Web 项目介绍 ApiCraft-Web 是一个轻量级的 API 测试工具&#xff0c;提供了简洁直观的界面&#xff0c;帮助开发者快速测试和调试 HTTP 接口。 功能特点 支持多种 HTTP 请求方法&#xff08;GET、POST、PUT、DELETE&#xff09;可配置请求参数&#xff08;Query …

Git进阶操作

Git高阶操作完全指南&#xff1a;解锁专业开发工作流 前言 在当今的软件开发领域&#xff0c;掌握高级Git技能已成为区分普通开发者与专业开发者的关键因素。根据最新的GitHub数据&#xff0c;熟练应用交互式暂存和Rebase等高级功能的开发者&#xff0c;其代码审查通过率平均提…

Python结合AI生成图像艺术作品代码及介绍

为实现生成图像艺术作品&#xff0c;我选用 Stable Diffusion 库结合 Python 编写代码。下面先展示代码&#xff0c;再详细介绍其原理、模块及使用方法等内容。 生成图片代码 import torch from diffusers import StableDiffusionPipeline# 加载预训练模型 pipe StableDiffu…

Linux操作系统--静态库和动态库的生成and四种解决加载找不到动态库的四种方法

目录 必要的知识储备&#xff1a; 生成静态库&#xff1a; 生成动态库&#xff1a; 解决加载找不到动态库的四种方法&#xff1a; 第一种&#xff1a;拷贝到系统默认的库路径 /usr/lib64/ 第二种&#xff1a;在系统默认的库路径/usr/lib64/下建立软链接 第三种&#xff1…

LLM中的N-Gram、TF-IDF和Word embedding

文章目录 1. N-Gram和TF-IDF&#xff1a;通俗易懂的解析1.1 N-Gram&#xff1a;让AI学会"猜词"的技术1.1.1 基本概念1.1.2 工作原理1.1.3 常见类型1.1.4 应用场景1.1.5 优缺点 1.2 TF-IDF&#xff1a;衡量词语重要性的尺子1.2.1 基本概念1.2.2 计算公式1.2.3 为什么需…

Leetcode 3359. 查找最大元素不超过 K 的有序子矩阵【Plus题】

1.题目基本信息 1.1.题目描述 给定一个大小为 m x n 的二维矩阵 grid。同时给定一个 非负整数 k。 返回满足下列条件的 grid 的子矩阵数量&#xff1a; 子矩阵中最大的元素 小于等于 k。 子矩阵的每一行都以 非递增 顺序排序。 矩阵的子矩阵 (x1, y1, x2, y2) 是通过选择…

如何在 Ubuntu 22.04 上安装、配置、使用 Nginx

如何在 Ubuntu 22.04 上安装、配置、使用 Nginx&#xff1f;-阿里云开发者社区 更新应用 sudo apt updatesudo apt upgrade检查必要依赖并安装 sudo apt install -y curl gnupg2 ca-certificates lsb-release安装nginx sudo apt install -y nginx# 启动nginx sudo systemct…

Linux:显示 -bash-4.2$ 问题(CentOS 7)

文章目录 一、原因二、错误示例三、解决办法 一、原因 在 CentOS 7 系统中&#xff0c;如果你看到命令行提示符显示为 -bash-4.2$&#xff0c;一般是 Bash shell 正在运行&#xff0c;并且它没有找到用户的个人配置文件&#xff0c;或者这些文件有问题而未能成功加载。这个提示…

QT6 源(34):随机数生成器类 QRandomGenerator 的源码阅读

&#xff08;1&#xff09;代码来自 qrandom.h &#xff0c;结合官方的注释&#xff1a; #ifndef QRANDOM_H #define QRANDOM_H#include <QtCore/qalgorithms.h> #include <algorithm> // for std::generate #include <random> // for std::mt1993…

第二篇:linux之Xshell使用及相关linux操作

第二篇&#xff1a;linux之Xshell使用及相关linux操作 文章目录 第二篇&#xff1a;linux之Xshell使用及相关linux操作一、Xshell使用1、Xshell安装2、Xshell使用 二、Bash Shell介绍与使用1、什么是Bash Shell(壳)&#xff1f;2、Bash Shell能干什么&#xff1f;3、平时如何使…

MCP(模型上下文协议)学习笔记

学习MCP&#xff08;模型上下文协议&#xff09;的系统化路径&#xff0c;结合技术原理、工具实践和社区资源&#xff0c;帮助你高效掌握这一AI交互标准&#xff1a; 在当今人工智能飞速发展的时代&#xff0c;AI技术正以前所未有的速度改变着我们的生活和工作方式。然而&#…

MIR-2025 | 多模态知识助力机器人导航:从复杂环境到高效路径规划

作者&#xff1a;Hui Yuan, Yan Huang, Zetao Du, Naigong Yu, Ziqi Liu, Dongbo Zhang, Kun Zhang 单位&#xff1a;北京工业大学信息科学与技术学院&#xff0c;北京工业大学计算智能与智能系统北京市重点实验室&#xff0c;中科院自动化研究所模式识别国家重点实验室与多智…

javaSE.泛型界限

现在有一个新的需求&#xff0c;没有String类型成绩了&#xff0c;但是成绩依然可能是整数&#xff0c;也可能是小数&#xff0c;这是我们不希望用户将泛型指定为除数字类型外的其他类型&#xff0c;我们就需要使用到泛型的上界定义&#xff1a; 上界&#x1f447;只能使用其本…

压缩包网页预览(zip-html-preview)

zip-html-preview 项目介绍 这是一个基于 Spring Boot 开发的在线 ZIP 文件预览工具,主要用于预览 ZIP 压缩包中的 HTML 文件及其相关资源。 主要功能 支持拖拽上传或点击选择多个 ZIP 文件自动解压并提取 ZIP 文件中的 HTML 文件在线预览 HTML 文件及其相关的 CSS、JavaSc…

QML之Overlay

Overlay&#xff08;覆盖层&#xff09;是QML中用于在当前界面之上显示临时内容的重要组件。 一、Overlay基础概念 1.1 什么是Overlay&#xff1f; Overlay是一种浮动在现有界面之上的视觉元素&#xff0c;具有以下特点&#xff1a; 临时显示&#xff0c;不影响底层布局 通…

iso17025证书申请方法?iso17025认证意义

ISO/IEC 17025证书申请方法 ISO/IEC 17025是检测和校准实验室能力的国际标准&#xff0c;申请CNAS认可的流程如下&#xff1a; 1. 前期准备 标准学习&#xff1a;深入理解ISO/IEC 17025:2017标准要求。 差距分析&#xff1a;评估现有实验室管理与技术能力与标准的差距。 制…

reverse3 1(Base加密)

题目 做法 下载安装包&#xff0c;解压&#xff0c;把解压后的文件拖进Exeinfo PE进行分析 32位&#xff0c;无壳 扔进IDA&#xff08;32位&#xff09;&#xff0c;找到main&#xff0c;F5反编译 只是因为在人群中多看了你一眼——第31行的right flag&#xff0c;关键词找到…

电控---CMSIS概览

1. CMSIS库简介 CMSIS&#xff08;Cortex Microcontroller Software Interface Standard&#xff0c;Cortex微控制器软件接口标准&#xff09;是由ARM公司开发的一套标准化软件接口&#xff0c;旨在为基于ARM Cortex-M系列处理器&#xff08;如Cortex-M0/M0/M3/M4/M7/M33等&am…