試顯示模塊:從驅(qū)動(dòng)移植到printf式接口實(shí)現(xiàn))
1. 項(xiàng)目概述為什么需要一個(gè)OLED調(diào)試工具在嵌入式開(kāi)發(fā)尤其是STM32這類(lèi)MCU的項(xiàng)目中調(diào)試信息的輸出是貫穿整個(gè)開(kāi)發(fā)周期的核心環(huán)節(jié)。早期我們可能依賴(lài)串口打印通過(guò)USB轉(zhuǎn)TTL模塊連接到電腦的串口助手查看日志。但這種方式有幾個(gè)明顯的痛點(diǎn)首先它嚴(yán)重依賴(lài)上位機(jī)設(shè)備一旦脫離電腦就成了“黑盒”現(xiàn)場(chǎng)調(diào)試極其不便其次串口通信本身會(huì)占用一個(gè)硬件資源并且在一些對(duì)時(shí)序要求苛刻或引腳資源緊張的應(yīng)用中額外引出TX/RX線可能帶來(lái)干擾或布局困難最后對(duì)于一些需要實(shí)時(shí)觀察的變量比如電機(jī)轉(zhuǎn)速、傳感器原始值、系統(tǒng)狀態(tài)機(jī)頻繁的串口打印會(huì)影響主循環(huán)性能甚至可能因?yàn)榇蛴⊙舆t而錯(cuò)過(guò)關(guān)鍵瞬態(tài)數(shù)據(jù)。于是一個(gè)集成在設(shè)備本身的、低成本的、實(shí)時(shí)性高的顯示輸出方案就顯得非常必要。OLED顯示屏特別是0.96寸或1.3寸的I2C/SPI接口小屏以其高對(duì)比度、自發(fā)光、低功耗、體積小巧和接口簡(jiǎn)單的特點(diǎn)成為了嵌入式設(shè)備“人臉”的絕佳選擇。將OLED打造成一個(gè)專(zhuān)屬的調(diào)試工具意味著我們可以隨時(shí)隨地在設(shè)備上查看關(guān)鍵變量、系統(tǒng)狀態(tài)、錯(cuò)誤代碼甚至繪制簡(jiǎn)單的波形圖這極大地提升了開(kāi)發(fā)效率和現(xiàn)場(chǎng)問(wèn)題排查能力。這個(gè)項(xiàng)目筆記就是記錄如何為STM32項(xiàng)目構(gòu)建一個(gè)靈活、高效的OLED調(diào)試顯示模塊。它不僅僅是一個(gè)顯示驅(qū)動(dòng)更是一套用于輸出調(diào)試信息的“框架”。我們會(huì)從最基礎(chǔ)的驅(qū)動(dòng)移植講起逐步構(gòu)建字符、字符串、數(shù)字、圖形乃至菜單的顯示功能并最終將其封裝成類(lèi)似printf風(fēng)格的調(diào)試接口讓你在代碼中輕松調(diào)用像在電腦上打印日志一樣在OLED上看到實(shí)時(shí)信息。2. 核心方案設(shè)計(jì)與硬件選型2.1 OLED模塊選型與接口對(duì)比市面上常見(jiàn)的OLED模塊主要基于SSD1306或SH1106驅(qū)動(dòng)芯片尺寸以0.96寸128x64像素和1.3寸128x64或132x64像素為主。從接口上分主要有I2C和SPI兩種。I2C接口優(yōu)點(diǎn)接線簡(jiǎn)單僅需兩根線SCL SDA節(jié)省IO口。通常模塊還自帶電源和復(fù)位引腳但核心通信就這兩根。協(xié)議簡(jiǎn)單編程方便。缺點(diǎn)通信速度相對(duì)較慢在需要全屏刷新或高速動(dòng)態(tài)顯示時(shí)可能成為瓶頸。通常模塊的I2C地址是0x78寫(xiě)或0x7A讀但有些模塊可以通過(guò)電阻配置更改。適用場(chǎng)景顯示內(nèi)容更新不頻繁如參數(shù)顯示、狀態(tài)指示、簡(jiǎn)易菜單。對(duì)IO口資源緊張的項(xiàng)目非常友好。SPI接口4線或3線優(yōu)點(diǎn)通信速率高可以實(shí)現(xiàn)更快的刷屏速度適合需要?jiǎng)討B(tài)刷新、動(dòng)畫(huà)或簡(jiǎn)單圖形繪制的場(chǎng)景。缺點(diǎn)占用IO口較多4線SPISCK MOSI DC CS 3線SPI可省去DC線但需要驅(qū)動(dòng)支持。接線和驅(qū)動(dòng)編寫(xiě)稍復(fù)雜。適用場(chǎng)景需要較高刷新率的應(yīng)用如波形模擬、游戲、復(fù)雜動(dòng)畫(huà)。對(duì)于調(diào)試工具這個(gè)定位我們的顯示內(nèi)容以文本、數(shù)字和靜態(tài)圖標(biāo)為主更新頻率通常在幾百毫秒到秒級(jí)對(duì)極致刷屏速度要求不高。因此I2C接口的0.96寸OLED模塊是一個(gè)性?xún)r(jià)比和易用性俱佳的選擇也是本筆記主要采用的硬件。它通常有4個(gè)引腳VCC3.3V/5V GND SCL SDA。有些模塊還帶有RESET和DC引腳但I(xiàn)2C模式下一般不需要接。2.2 STM32基礎(chǔ)工程與驅(qū)動(dòng)層設(shè)計(jì)在開(kāi)始驅(qū)動(dòng)OLED之前需要一個(gè)可運(yùn)行的STM32基礎(chǔ)工程。你可以使用STM32CubeMX快速生成也可以基于標(biāo)準(zhǔn)庫(kù)或HAL庫(kù)手動(dòng)搭建。這里以STM32F103C8T6藍(lán)色pill開(kāi)發(fā)板和HAL庫(kù)為例。第一步硬件連接將OLED模塊連接到STM32OLED VCC - 3.3V 注意部分模塊支持5V但STM32的IO是3.3V電平為確保安全建議統(tǒng)一使用3.3VOLED GND - GNDOLED SCL - PB6 (STM32的I2C1_SCL默認(rèn)引腳也可重映射)OLED SDA - PB7 (STM32的I2C1_SDA默認(rèn)引腳)第二步使用STM32CubeMX配置打開(kāi)CubeMX選擇你的芯片型號(hào)。在Pinout Configuration標(biāo)簽頁(yè)找到I2C1將其模式設(shè)置為I2C。配置I2C參數(shù)在Parameter Settings子標(biāo)簽I2C Speed Mode: Standard Mode (100kHz) 對(duì)于OLED調(diào)試顯示完全足夠如果想更快可以選Fast Mode (400kHz)。其他參數(shù)如時(shí)鐘源等保持默認(rèn)即可。配置一個(gè)調(diào)試用的串口如USART1方便在OLED驅(qū)動(dòng)調(diào)試不成功時(shí)有備用的調(diào)試輸出。配置系統(tǒng)時(shí)鐘如使用外部8MHz晶振通過(guò)PLL倍頻到72MHz。生成代碼選擇MDK-ARM或你使用的IDE。第三步移植OLED底層驅(qū)動(dòng)網(wǎng)絡(luò)上有很多針對(duì)SSD1306的驅(qū)動(dòng)代碼我們需要將其適配到自己的工程。核心是完成兩個(gè)最底層的函數(shù)寫(xiě)命令和寫(xiě)數(shù)據(jù)。// OLED.h 中定義 #define OLED_I2C_ADDRESS 0x78 // SSD1306的I2C寫(xiě)地址通常是0x78 (0x3C 1) // OLED.c 中實(shí)現(xiàn) /** * brief 向OLED寫(xiě)入一個(gè)命令 * param cmd: 要寫(xiě)入的命令字節(jié) * retval None */ void OLED_Write_Cmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; // 控制字節(jié)0x00表示后續(xù)是命令 HAL_I2C_Master_Transmit(hi2c1, OLED_I2C_ADDRESS, buf, 2, HAL_MAX_DELAY); } /** * brief 向OLED寫(xiě)入一個(gè)數(shù)據(jù)字節(jié) * param data: 要寫(xiě)入的數(shù)據(jù)字節(jié) * retval None */ void OLED_Write_Data(uint8_t data) { uint8_t buf[2] {0x40, data}; // 控制字節(jié)0x40表示后續(xù)是數(shù)據(jù) HAL_I2C_Master_Transmit(hi2c1, OLED_I2C_ADDRESS, buf, 2, HAL_MAX_DELAY); }注意這里使用了HAL_MAX_DELAY在實(shí)際產(chǎn)品代碼中建議使用合理的超時(shí)時(shí)間并檢查HAL_I2C_Master_Transmit的返回值以確保通信成功。調(diào)試階段可以用MAX_DELAY簡(jiǎn)化。有了這兩個(gè)函數(shù)我們就可以根據(jù)SSD1306的數(shù)據(jù)手冊(cè)編寫(xiě)初始化序列、設(shè)置顯示區(qū)域、清屏等基礎(chǔ)函數(shù)了。一個(gè)完整的初始化序列通常包括關(guān)閉顯示、設(shè)置時(shí)鐘分頻和振蕩頻率、設(shè)置多路復(fù)用比例、設(shè)置顯示偏移、設(shè)置起始行、開(kāi)啟電荷泵、設(shè)置內(nèi)存地址模式、設(shè)置對(duì)比度、設(shè)置預(yù)充電周期、設(shè)置VCOMH電平、開(kāi)啟顯示等。3. 核心功能實(shí)現(xiàn)從點(diǎn)陣到“printf”3.1 顯存管理與基本繪圖函數(shù)SSD1306內(nèi)部有一個(gè)GDDRAM圖形顯示數(shù)據(jù)RAM對(duì)于128x64的屏幕其顯存結(jié)構(gòu)是“頁(yè)式”的。整個(gè)屏幕分為8頁(yè)P(yáng)age0-Page7每頁(yè)有128列每列8個(gè)像素即1字節(jié)數(shù)據(jù)。所以總顯存大小為 128 * 8 1024字節(jié)。我們可以在STM32的RAM中開(kāi)辟一個(gè)同樣大小的緩沖區(qū)uint8_t OLED_GRAM[128][8]所有的繪圖操作都先在這個(gè)緩沖區(qū)中進(jìn)行最后通過(guò)一個(gè)OLED_Refresh()函數(shù)一次性將整個(gè)緩沖區(qū)刷到OLED的GDDRAM中。這種方式避免了頻繁的I2C通信提高了效率也方便實(shí)現(xiàn)局部刷新等高級(jí)功能?;陲@存緩沖區(qū)我們可以實(shí)現(xiàn)最基礎(chǔ)的像素操作函數(shù)/** * brief 在緩沖區(qū)中設(shè)置一個(gè)像素點(diǎn) * param x: 橫坐標(biāo) (0~127) * param y: 縱坐標(biāo) (0~63) * param mode: 1-點(diǎn)亮 0-熄滅 */ void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t mode) { if(x 128 || y 64) return; // 邊界檢查 uint8_t page y / 8; uint8_t bit y % 8; if(mode) { OLED_GRAM[x][page] | (1 bit); } else { OLED_GRAM[x][page] ~(1 bit); } }有了畫(huà)點(diǎn)函數(shù)就可以衍生出畫(huà)線、畫(huà)矩形、畫(huà)圓等基本圖形函數(shù)。這些是構(gòu)建更復(fù)雜顯示內(nèi)容的基礎(chǔ)。3.2 字庫(kù)制作與字符顯示OLED顯示字符的本質(zhì)是顯示一個(gè)特定大小的點(diǎn)陣。我們需要一個(gè)“字庫(kù)”來(lái)存儲(chǔ)每個(gè)字符對(duì)應(yīng)的點(diǎn)陣數(shù)據(jù)。對(duì)于英文和數(shù)字常用的有6x8 8x16等字體對(duì)于中文則需要16x16的點(diǎn)陣。ASCII字符顯示以8x16字體為例取模使用PC端軟件如PCtoLCD2002生成字庫(kù)數(shù)組。設(shè)置取模方式為“列行式”即從上到下從左到右逐列取模每列8個(gè)點(diǎn)1字節(jié)一個(gè)8x16的字符需要16字節(jié)數(shù)據(jù)。存儲(chǔ)將生成的數(shù)組通常包含ASCII碼從32到126的可打印字符保存在一個(gè)const數(shù)組中例如const uint8_t Font8x16[][16]。顯示函數(shù)void OLED_ShowChar(uint8_t x, uint8_t y, char chr, uint8_t size) { uint8_t c chr - ; // 計(jì)算在字庫(kù)中的索引 if(size 16) { // 8x16字體 for(uint8_t i0; i16; i) { uint8_t data Font8x16[c][i]; for(uint8_t j0; j8; j) { if(data (0x80 j)) { OLED_DrawPoint(xj, yi, 1); } else { OLED_DrawPoint(xj, yi, 0); } } } } // 可以擴(kuò)展其他字體大小 }中文字符顯示原理類(lèi)似但一個(gè)中文字符是16x16點(diǎn)陣需要32字節(jié)。取模時(shí)注意選擇正確的編碼如GB2312。顯示函數(shù)需要一次處理16行x16列的數(shù)據(jù)。實(shí)操心得將不同大小的字庫(kù)分開(kāi)存放并設(shè)計(jì)一個(gè)統(tǒng)一的OLED_ShowChar函數(shù)通過(guò)size參數(shù)選擇字體這樣代碼更清晰。字庫(kù)會(huì)占用大量Flash只添加項(xiàng)目需要的字符可以節(jié)省空間。對(duì)于固定不變的界面文字可以考慮直接使用圖片取模的方式。3.3 格式化字符串輸出實(shí)現(xiàn)OLED_printf這是將OLED升級(jí)為“調(diào)試工具”的關(guān)鍵一步。我們希望像使用串口printf一樣在指定位置格式化輸出變量。由于標(biāo)準(zhǔn)庫(kù)的printf通常重定向到串口且其內(nèi)部實(shí)現(xiàn)復(fù)雜我們實(shí)現(xiàn)一個(gè)輕量級(jí)的、基于vsprintf或自己編寫(xiě)的簡(jiǎn)單版本。方案一利用標(biāo)準(zhǔn)庫(kù)稍占資源但功能全在工程中啟用MicroLIBKeil中或newlib-nano其他工具鏈它們提供了較小的printf實(shí)現(xiàn)。實(shí)現(xiàn)fputc函數(shù)但我們不重定向到串口而是重定向到一個(gè)緩沖區(qū)。自定義OLED_printf函數(shù)#include stdarg.h #include stdio.h char oled_print_buf[128]; // 足夠大的緩沖區(qū) void OLED_printf(uint8_t x, uint8_t y, const char *fmt, ...) { va_list args; va_start(args, fmt); vsnprintf(oled_print_buf, sizeof(oled_print_buf), fmt, args); va_end(args); // 調(diào)用字符串顯示函數(shù)在(x,y)位置顯示oled_print_buf OLED_ShowString(x, y, oled_print_buf); }OLED_ShowString函數(shù)內(nèi)部循環(huán)調(diào)用OLED_ShowChar。方案二自制簡(jiǎn)易格式化函數(shù)資源極度緊張時(shí)如果連vsnprintf都覺(jué)得占用太多ROM/RAM可以自己實(shí)現(xiàn)一個(gè)只支持%d%u%x%s%c等少數(shù)幾種格式的轉(zhuǎn)換函數(shù)。這需要自己編寫(xiě)整數(shù)轉(zhuǎn)字符串的代碼。使用示例int adc_value 1234; float temperature 25.6; OLED_printf(0, 0, ADC:%d, adc_value); // 在(0,0)顯示ADC:1234 OLED_printf(0, 16, Temp:%.1fC, temperature); // 在(0,16)顯示Temp:25.6C3.4 高級(jí)調(diào)試功能實(shí)時(shí)曲線與菜單框架實(shí)時(shí)曲線繪制 在調(diào)試傳感器信號(hào)、觀察波形時(shí)圖形比數(shù)字更直觀。我們可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的曲線繪制函數(shù)。定義一個(gè)顯示區(qū)域的寬度如100像素和高度如40像素。開(kāi)辟一個(gè)數(shù)組int32_t data_buffer[100]用于存儲(chǔ)最近100個(gè)數(shù)據(jù)點(diǎn)。每次得到新數(shù)據(jù)將數(shù)組整體左移一位新數(shù)據(jù)放入最右側(cè)。在OLED上根據(jù)data_buffer中的值映射到顯示高度范圍內(nèi)用OLED_DrawPoint或OLED_DrawLine將相鄰點(diǎn)連接起來(lái)??梢约由献鴺?biāo)軸和刻度使其更專(zhuān)業(yè)。簡(jiǎn)易菜單框架 當(dāng)需要顯示和設(shè)置多個(gè)參數(shù)時(shí)一個(gè)菜單系統(tǒng)非常有用。一個(gè)簡(jiǎn)單的兩級(jí)菜單可以這樣設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)定義一個(gè)菜單項(xiàng)結(jié)構(gòu)體包含項(xiàng)目名稱(chēng)、類(lèi)型菜單、數(shù)值、開(kāi)關(guān)等、當(dāng)前值、最大值、最小值、步進(jìn)值、子菜單指針、回調(diào)函數(shù)等。typedef struct { const char* name; MenuType type; int32_t value; int32_t min; int32_t max; int32_t step; void (*action)(void); // 執(zhí)行的回調(diào) struct MenuItem* parent; struct MenuItem* child_list; struct MenuItem* next; // 同級(jí)鏈表 } MenuItem;導(dǎo)航邏輯使用幾個(gè)按鍵上、下、確定、返回來(lái)瀏覽菜單。當(dāng)前選中的項(xiàng)高亮顯示。顯示邏輯根據(jù)當(dāng)前菜單層級(jí)和選中項(xiàng)刷新OLED顯示內(nèi)容。交互邏輯對(duì)于數(shù)值項(xiàng)按“確定”進(jìn)入編輯模式用“上/下”鍵增減數(shù)值。實(shí)現(xiàn)一個(gè)完整的菜單框架需要一定的代碼量但對(duì)于復(fù)雜的調(diào)試和參數(shù)設(shè)置場(chǎng)景它能提供極佳的用戶(hù)體驗(yàn)。4. 工程整合與優(yōu)化技巧4.1 驅(qū)動(dòng)封裝與接口設(shè)計(jì)一個(gè)好的驅(qū)動(dòng)應(yīng)該易于使用和移植。我們將OLED功能封裝成獨(dú)立的模塊提供清晰的API接口。oled.h 頭文件設(shè)計(jì)#ifndef __OLED_H #define __OLED_H #include main.h // 包含必要的HAL或標(biāo)準(zhǔn)庫(kù)頭文件 /* 初始化與基礎(chǔ)控制 */ void OLED_Init(void); void OLED_Clear(void); void OLED_Refresh(void); // 刷新顯存到屏幕 void OLED_SetContrast(uint8_t contrast); /* 基本繪圖API */ void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t mode); void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); void OLED_DrawRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode); void OLED_DrawCircle(uint8_t x0, uint8_t y0, uint8_t r, uint8_t mode); /* 顯示API */ void OLED_ShowChar(uint8_t x, uint8_t y, char chr, uint8_t size); void OLED_ShowString(uint8_t x, uint8_t y, const char *str, uint8_t size); void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size); void OLED_ShowSignedNum(uint8_t x, uint8_t y, int32_t num, uint8_t len, uint8_t size); void OLED_ShowHexNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size); void OLED_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t int_len, uint8_t frac_len, uint8_t size); void OLED_printf(uint8_t x, uint8_t y, const char *fmt, ...); /* 高級(jí)功能 */ void OLED_PlotWaveform(uint8_t x, uint8_t y, uint8_t w, uint8_t h, int32_t *buffer, uint8_t len); // 菜單相關(guān)API... #endif這樣在主程序中只需要#include “oled.h”調(diào)用OLED_Init()然后就可以隨意使用各種顯示函數(shù)了。4.2 性能優(yōu)化與資源管理局部刷新全屏刷新1024字節(jié)通過(guò)I2C傳輸需要時(shí)間。如果只修改了屏幕上一小部分內(nèi)容如一個(gè)數(shù)字可以只刷新對(duì)應(yīng)的“頁(yè)”和“列”區(qū)域而不是整個(gè)GDDRAM。這需要修改OLED_Refresh()函數(shù)使其能夠根據(jù)一個(gè)“臟矩陣”標(biāo)志位來(lái)選擇性刷新。這能顯著提高顯示效率。雙緩沖開(kāi)辟兩個(gè)顯存緩沖區(qū)。一個(gè)用于后臺(tái)繪制BackBuffer另一個(gè)是當(dāng)前顯示的前臺(tái)緩沖區(qū)FrontBuffer。當(dāng)后臺(tái)繪制完成后通過(guò)一個(gè)原子操作如指針交換將前后臺(tái)緩沖區(qū)切換然后刷新新的前臺(tái)緩沖區(qū)。這可以避免繪制過(guò)程中的屏幕閃爍實(shí)現(xiàn)更流暢的動(dòng)畫(huà)效果但會(huì)占用雙倍RAM2KB。字庫(kù)存儲(chǔ)優(yōu)化將不常用的字庫(kù)存放在外部SPI Flash或SD卡中需要時(shí)再加載到RAM。或者使用壓縮字庫(kù)在顯示時(shí)解壓。對(duì)于固定界面直接使用圖片取模減少運(yùn)行時(shí)字符組合的計(jì)算。使用DMA對(duì)于SPI接口的OLED可以使用DMA來(lái)傳輸顯存數(shù)據(jù)極大解放CPU。對(duì)于I2C接口部分STM32系列也支持I2C DMA可以探索使用。4.3 常見(jiàn)問(wèn)題與調(diào)試心得OLED不亮或白屏檢查電源首先確認(rèn)VCC和GND連接正確電壓是否穩(wěn)定3.3V。可以用萬(wàn)用表測(cè)量。檢查初始化序列最可能的原因是初始化命令序列有誤或順序不對(duì)。務(wù)必對(duì)照SSD1306數(shù)據(jù)手冊(cè)逐條命令檢查。特別是“開(kāi)啟電荷泵Charge Pump”的命令0x8D, 0x14沒(méi)有它OLED可能無(wú)法正常工作。檢查I2C通信用邏輯分析儀或示波器抓取SCL和SDA波形看是否有起始信號(hào)、地址應(yīng)答、數(shù)據(jù)。也可以先在初始化代碼里加入HAL_Delay(100)給OLED足夠的上電復(fù)位時(shí)間。顯示亂碼或錯(cuò)位取模方式錯(cuò)誤這是最常見(jiàn)的原因。確保取模軟件設(shè)置如橫向/縱向取模、字節(jié)順序、掃描方式與你的OLED_ShowChar函數(shù)中的像素點(diǎn)映射邏輯完全匹配。一個(gè)簡(jiǎn)單的測(cè)試方法是顯示一個(gè)全滿(mǎn)的字符如0xFF或一個(gè)簡(jiǎn)單的圖案看OLED上顯示的點(diǎn)陣是否符合預(yù)期。坐標(biāo)計(jì)算錯(cuò)誤檢查OLED_ShowChar和OLED_ShowString函數(shù)中的坐標(biāo)計(jì)算特別是跨頁(yè)每頁(yè)8行時(shí)的處理。y坐標(biāo)除以8得到頁(yè)取余得到頁(yè)內(nèi)位。顯示內(nèi)容殘影或刷新不正常清屏邏輯在刷新新內(nèi)容前是否正確清空了顯存緩沖區(qū)是全部清零還是只清了局部確保OLED_Clear()函數(shù)正確工作。刷新時(shí)機(jī)是否在繪制了所有內(nèi)容后才調(diào)用OLED_Refresh()避免在繪制中途刷新。I2C速率過(guò)快如果I2C時(shí)鐘設(shè)置過(guò)快如Fast Mode Plus 1MHz而OLED模塊或?qū)Ь€質(zhì)量不佳可能導(dǎo)致通信錯(cuò)誤。嘗試降低I2C時(shí)鐘速度到100kHz或400kHz測(cè)試。使用OLED_printf后程序卡死或進(jìn)入HardFault緩沖區(qū)溢出檢查oled_print_buf數(shù)組是否足夠大。vsnprintf的第二個(gè)參數(shù)是緩沖區(qū)大小要確保它足夠容納格式化后的字符串。??臻g不足printf類(lèi)函數(shù)可能會(huì)使用較多??臻g。在啟動(dòng)文件或鏈接腳本中適當(dāng)增大棧Stack的大小。浮點(diǎn)數(shù)支持如果使用了%f格式化浮點(diǎn)數(shù)需要確保鏈接了支持浮點(diǎn)數(shù)打印的庫(kù)如uprintf-float或nano版的特定設(shè)置。一個(gè)實(shí)用的調(diào)試技巧在OLED驅(qū)動(dòng)開(kāi)發(fā)初期務(wù)必保留一個(gè)可靠的串口調(diào)試通道??梢詫LED的初始化步驟、關(guān)鍵函數(shù)的執(zhí)行狀態(tài)、I2C通信的錯(cuò)誤標(biāo)志通過(guò)串口打印出來(lái)。這樣當(dāng)OLED顯示不正常時(shí)你至少能知道程序執(zhí)行到哪一步出錯(cuò)了而不是面對(duì)一個(gè)沉默的屏幕和單片機(jī)。