電控---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.pswp.cn/pingmian/77183.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77183.shtml
英文地址,請注明出處:http://en.pswp.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…