動(dòng)CH455數(shù)碼管顯示:I2C通信與避坑指南)
簡(jiǎn)介面向STM32嵌入式開發(fā)者的CH455四位數(shù)碼管驅(qū)動(dòng)方案基于STM32Cube HAL庫編寫適用于需要快速實(shí)現(xiàn)數(shù)碼管顯示與按鍵掃描的儀表、控制面板等場(chǎng)景。壓縮包共2個(gè)文件含1個(gè)C源文件和1個(gè)頭文件采用IIC兩線接口驅(qū)動(dòng)CH455SDA、SCL引腳均可自定義在CubeMX中設(shè)置標(biāo)簽為TUF_SDA、TUF_SCL并同步修改頭文件注釋處引腳定義即可完成底層適配。使用時(shí)先在main.c中調(diào)用TUF_DPY_Write_Cmd(CH455_SYSON)開啟鍵盤顯示功能再調(diào)用TUF_DPY_SHOW_NUM(float num)直接顯示浮點(diǎn)數(shù)適合輸出傳感器讀數(shù)、計(jì)算結(jié)果等。CH455驅(qū)動(dòng)可有效節(jié)省主控GPIO資源配合HAL庫的IIC功能整套資源僅約2KB代碼精簡(jiǎn)且易于移植到不同STM32型號(hào)。已有1682人學(xué)習(xí)下載適合具備基礎(chǔ)HAL庫知識(shí)、希望快速上手CH455驅(qū)動(dòng)的開發(fā)者借鑒。1. 為什么這個(gè)標(biāo)題值得花時(shí)間HAL 庫 CH455 是低成本數(shù)碼管顯示的省心解STM32Cube HAL 庫驅(qū)動(dòng) CH455 四位數(shù)碼管這個(gè)組合解決的是嵌入式開發(fā)里最常見的一件事想顯示數(shù)字但 GPIO 不夠用。CH455 通過 I2C 把段選、位選都接管了CPU 只需要在內(nèi)容變化時(shí)發(fā)幾個(gè)字節(jié)之后芯片自己完成動(dòng)態(tài)掃描不占主循環(huán)。相比 74HC595 需要自己寫移位時(shí)序、相比 TM1637 需要讀廠商私有協(xié)議CH455 用標(biāo)準(zhǔn) I2C 通信工程上更干凈排查起來也更直接。我最初接手一個(gè)帶四位小數(shù)顯的儀表板需求時(shí)第一反應(yīng)是 GPIO 直接掃結(jié)果一算引腳差點(diǎn)不夠后來換成 CH455 方案一版就點(diǎn)亮了。這篇筆記把 CubeMX 配置、HAL I2C 發(fā)送、段碼拆幀、避坑清單一次講透適合正在做實(shí)驗(yàn)箱、工業(yè)面板、充電樁顯示模塊的開發(fā)者。2. CH455 的通信機(jī)制為什么一個(gè)字符要拆成兩個(gè)半字節(jié)發(fā)很多第一次用 CH455 的人會(huì)拿它當(dāng)普通數(shù)碼管驅(qū)動(dòng) IC 來理解以為像 TM1650 那樣一個(gè)寄存器寫一個(gè)完整段碼。實(shí)際上 CH455 的幀結(jié)構(gòu)設(shè)計(jì)很特別——它內(nèi)部寄存器是 4 位寬的I2C 每個(gè)字節(jié)的高 4 位是寄存器地址低 4 位才是數(shù)據(jù)。這段話值得讀兩遍因?yàn)楹竺嫠姓{(diào)試?yán)Щ蠖紡倪@里來。2.1 段碼 8 位、寄存器 4 位拆幀是硬性規(guī)定數(shù)碼管一個(gè)字符包含 7 段加小數(shù)點(diǎn)正好 8 位可以對(duì)應(yīng)一個(gè) uint8_t。但 CH455 設(shè)計(jì)成每次只能寫 4 位數(shù)據(jù)所以一位數(shù)碼管的段碼必須拆成低 4 位和高 4 位兩次發(fā)送分別寫到不同的寄存器地址去。用地址位來區(qū)分高半字節(jié)還是低半字節(jié)而不是像某些芯片那樣用一個(gè)額外的標(biāo)志位。我一般這樣定義幀結(jié)構(gòu)內(nèi)部地址 0x00 到 0x03 對(duì)應(yīng)第 1 到第 4 位數(shù)碼管的低 4 段地址 0x04 到 0x07 對(duì)應(yīng)第 1 到第 4 位數(shù)碼管的高 4 段。所以發(fā)送時(shí)一個(gè)數(shù)據(jù)字節(jié)是(地址 4) | (半字節(jié)段碼)。舉個(gè)例子要讓第 1 位數(shù)碼管顯示數(shù)字 0段碼是 0x3F低 4 位是 0x0F高 4 位是 0x03分別組裝成 0x0F 和 0x43 發(fā)送這才算寫完整一個(gè)字符。這個(gè)設(shè)計(jì)與芯片的物理引腳排列有關(guān)。CH455 內(nèi)部做動(dòng)態(tài)掃描每時(shí)每刻只有一個(gè)位在被點(diǎn)亮位與位之間靠 CH455 自己輪流選通不需要 CPU 反復(fù)送位選信號(hào)。因此 CPU 要做的事情只剩兩件組裝幀、走 I2C 發(fā)出去。2.2 用 HAL 庫的 I2C 外設(shè)還是 GPIO 模擬我為什么選前者CH455 是標(biāo)準(zhǔn) I2C 從機(jī)理論上用兩根 GPIO 模擬時(shí)序也能驅(qū)動(dòng)。但我建議直接用 STM32Cube 生成的硬件 I2C 外設(shè)理由有三個(gè)首先硬件 I2C 的時(shí)序由外設(shè)保證起始條件、停止條件、應(yīng)答位的電平跳變不用軟件去卡延時(shí)代碼更接近「發(fā)送數(shù)據(jù)」這個(gè)語義其次HAL 庫已經(jīng)封裝好超時(shí)機(jī)制總線上出問題不會(huì)讓程序死等最后后續(xù)如果要掛多個(gè) I2C 設(shè)備硬件外設(shè) 地址區(qū)分能省很多接線。GPIO 模擬不是不能做它適合在引腳實(shí)在不夠、或者想徹底繞開 I2C 外設(shè) bug 的極端場(chǎng)景。但代價(jià)是時(shí)序代碼里全是延時(shí)微調(diào)換一塊板子、換一顆晶振可能就得重新調(diào)。用 HAL 庫硬件 I2CCubeMX 里勾選一下就有可用的句柄 hi2c1發(fā)送函數(shù)一句話搞定。2.3 最小發(fā)送模型從 CubeMX 工程到第一個(gè)字節(jié)新一點(diǎn)的 CubeMX 生成 HAL 庫工程后默認(rèn)不會(huì)把 I2C 引腳映射到正確的位置所以第一步是在 Pinout 視圖里把 SCL、SDA 所在的引腳功能選成 I2C1_SCL、I2C1_SDA。我通常把 PB8 配給 I2C1_SCLPB9 配給 I2C1_SDA這是最常見的一組復(fù)用腳具體按自己板子的絲印調(diào)整。時(shí)鐘配置上I2C 外設(shè)掛在 APB1 總線上CubeMX 會(huì)自動(dòng)根據(jù)系統(tǒng)時(shí)鐘算出分頻。為了減少變量我第一版會(huì)把 I2C Speed Mode 設(shè)成 Standard Mode也就是 100kHz。CH455 對(duì)時(shí)序要求不算苛刻但 100kHz 在排錯(cuò)時(shí)寬容度最高等整塊板子穩(wěn)定了再改 400kHz 也不遲。工程生成后驗(yàn)證通信是否通的一條最小代碼是這樣/* CH455 的 7 位從機(jī)地址HAL 內(nèi)部會(huì)自動(dòng)左移 1 位拼讀寫標(biāo)志 */ #define CH455_ADDR 0x48 /* 發(fā)送一個(gè)字節(jié)高 4 位是內(nèi)部寄存器地址低 4 位是段碼半字節(jié) */ uint8_t test_frame 0x0F; HAL_StatusTypeDef st HAL_I2C_Master_Transmit(hi2c1, CH455_ADDR, test_frame, 1, 100); if (st ! HAL_OK) { /* 返回非 OK先查 ACK、查接線、查地址別急著看顯不顯示 */ }第一次跑這段代碼返回值是 HAL_OK 才算跨過了最基礎(chǔ)的一關(guān)。HAL_I2C_Master_Transmit 的第三個(gè)參數(shù)是發(fā)送緩沖區(qū)第四個(gè)參數(shù)是長(zhǎng)度第五個(gè)參數(shù)是超時(shí)毫秒數(shù)。這里地址參數(shù)填 0x48而不是 0x90因?yàn)?HAL 庫要求的是 7 位地址庫內(nèi)部會(huì)自己做左移補(bǔ)位。這是新手最容易翻車的地方后面避坑章節(jié)還會(huì)單獨(dú)展開。3. 四位共陰數(shù)碼管與 CH455 的接線、CubeMX 參數(shù)配置硬件接線看著簡(jiǎn)單實(shí)際上段序、公共端、限流電阻這幾個(gè)細(xì)節(jié)沒處理好后面全是玄學(xué)問題。這一章把接線表和 CubeMX 參數(shù)一起列出來照著做基本能省掉三分之二的調(diào)試時(shí)間。3.1 段線、位線、電源一張接線表說清楚CH455 驅(qū)動(dòng)的是共陰數(shù)碼管這一點(diǎn)要先確認(rèn)。四位共陰數(shù)碼管有 12 根引腳8 根段腳a 到 g 加 dp和 4 根位腳第 1 位到第 4 位的公共陰極。CH455 的 SEG0 到 SEG7 依次接數(shù)碼管的 a 到 dpDIG0 到 DIG3 接四個(gè)位腳。如果手里是共陽數(shù)碼管不要硬接CH455 內(nèi)部設(shè)計(jì)是按共陰來的共陽管需要額外加三極管反相已經(jīng)失去了省引腳的意義。接線順序本身不出問題容易出問題的是段序?qū)?yīng)。有些數(shù)碼管內(nèi)部把 dp 放在段碼最高位有些則把 g 放在最高位所以同一張段碼表在不同廠家數(shù)碼管上顯示的結(jié)果可能完全不同。我一般第一版先用0x0F這種只有低 4 段亮的測(cè)試值逐位確認(rèn)段腳和段碼位的映射關(guān)系再固化段碼表。下表是常見接法實(shí)際以你自己板子的絲印為準(zhǔn)CH455 引腳接往對(duì)象備注SCLSTM32 PB8I2C1 時(shí)鐘建議外部 4.7k 上拉SDASTM32 PB9I2C1 數(shù)據(jù)建議外部 4.7k 上拉VCC3.3V與 MCU 同電壓域最省事GNDGND與 MCU 共地SEG0-SEG7數(shù)碼管 a-dp段線上串限流電阻DIG0-DIG3數(shù)碼管位 1-4共陰公共端不要串電阻限流電阻的取值要單獨(dú)說。段線上的電阻直接決定亮度阻值太小電流大傷數(shù)碼管阻值太大亮度不足。我常用 330 歐起步先看顯示效果再往 470 歐或 220 歐調(diào)。位線上不要串電阻因?yàn)楣捕穗娏魇前硕沃痛藭?huì)造成不同位數(shù)亮度差異明顯。3.2 CubeMX 配置項(xiàng)I2C 速度、上拉、時(shí)鐘樹的幾個(gè)關(guān)鍵參數(shù)CubeMX 里 I2C 參數(shù)頁主要有 Speed Mode、Own Address、Address Detection Mode、Clock No Stretch Mode 這幾項(xiàng)。CH455 是從機(jī)MCU 是主機(jī)Own Address 填什么都無所謂只要不和總線上其他從機(jī)沖突。Address Detection Mode 選 7-bitClock No Stretch Mode 保持 Disable這些保持默認(rèn)就行。真正要核對(duì)的是 Speed Mode 和時(shí)鐘樹。如果選 Fast ModeCubeMX 會(huì)要求把上升沿時(shí)間等參數(shù)填好不同板子布線質(zhì)量不一樣可能因?yàn)檫呇靥弻?dǎo)致通信失敗。我建議第一版老老實(shí)實(shí)選 Standard Mode100kHz。時(shí)鐘樹部分確認(rèn) APB1 時(shí)鐘不為 0并且 I2C1 的時(shí)鐘源已經(jīng)掛上否則 HAL_I2C_Init 會(huì)返回失敗。寫到這里有個(gè)容易忽略的點(diǎn)CH455 的 SCL、SDA 是開漏結(jié)構(gòu)必須靠上拉電阻才能輸出高電平。STM32 的部分引腳可以啟用內(nèi)部上拉但內(nèi)部上拉阻值偏大在長(zhǎng)線上不夠穩(wěn)。我一般會(huì)在板上加兩顆 4.7k 外部上拉電阻一個(gè)接到 SCL一個(gè)接到 SDA哪怕只做面包板原型也值得加上。別小看這一步很多「偶爾通信失敗」在加了上拉之后就消失了。初始化完成后可以用 HAL_I2C_IsDeviceReady 來探測(cè)從機(jī)是否存在。這個(gè)函數(shù)會(huì)發(fā)送地址并等待應(yīng)答返回 HAL_OK 說明 CH455 在總線上應(yīng)答了uint32_t tick HAL_GetTick(); /* 對(duì)焦點(diǎn)發(fā)送帶讀標(biāo)志的地址看 CH455 是否 ACK */ if (HAL_I2C_IsDeviceReady(hi2c1, CH455_ADDR, 10, 100) ! HAL_OK) { /* 超時(shí)還返回失敗大概率是接線、上拉或地址問題 */ }如果這一步失敗不要繼續(xù)往下調(diào)顯示邏輯先把電路查明白。I2C 是同步協(xié)議沒有應(yīng)答意味著物理層不通后面寫再多顯示代碼都是白搭。硬件探測(cè)通過后再進(jìn)入驅(qū)動(dòng)函數(shù)封裝才算真正開始寫顯示邏輯。4. 手寫 CH455 驅(qū)動(dòng)段碼表、拆幀函數(shù)、四位刷新一次跑通到這一步I2C 通信已經(jīng)通了接下來要做的就是把它封裝成三個(gè)層次段碼表、單字符寫入、四位數(shù)字刷新。這里給出的代碼是經(jīng)過實(shí)際項(xiàng)目驗(yàn)證的常見寫法直接用 HAL 庫 API 實(shí)現(xiàn)不依賴任何私有協(xié)議庫。4.1 共陰段碼表先定義清楚后面少改段碼表是顯示邏輯的地基。對(duì)共陰數(shù)碼管要點(diǎn)亮的段對(duì)應(yīng)位寫入 1段碼就是這些位的組合。下面這張表覆蓋了 0 到 9 和幾個(gè)常用字母小數(shù)點(diǎn)位置單獨(dú)留出來需要時(shí)可以和數(shù)字段碼做或運(yùn)算。/* 共陰數(shù)碼管段碼表數(shù)字 0-9位序?yàn)?a b c d e f g dp */ const uint8_t seg_table[10] { 0x3F, /* 0: a b c d e f 亮 */ 0x06, /* 1: b c 亮 */ 0x5B, /* 2: a b d e g 亮 */ 0x4F, /* 3: a b c d g 亮 */ 0x66, /* 4: b c f g 亮 */ 0x6D, /* 5: a c d f g 亮 */ 0x7D, /* 6: a c d e f g 亮 */ 0x07, /* 7: a b c 亮 */ 0x7F, /* 8: 全段 亮 */ 0x6F, /* 9: a b c d f g 亮 */ };這張表建立在段腳按 a、b、c、d、e、f、g、dp 的順序接在 SEG0 到 SEG7 的前提下。如果你的板子段序不是這樣比如把 dp 放在最前面表里的值就要整體按位重排。我建議先用一根杜邦線把每一位單獨(dú)短接測(cè)試確認(rèn)段序后再填表。4.2 單字符寫入把段碼拆成低 4 位和高 4 位核心函數(shù)就一個(gè)把位序號(hào)和段碼組織成兩個(gè)幀字節(jié)一次 I2C 傳輸發(fā)出去。這里要再次強(qiáng)調(diào)地址偏移第 0 到 3 位對(duì)應(yīng)低 4 段地址第 4 到 7 位對(duì)應(yīng)高 4 段地址所以高半字節(jié)的寄存器地址要加 4。#define CH455_ADDR 0x48 /* 向指定位寫入段碼bit_index 取值 0-3對(duì)應(yīng)第 1 到第 4 位數(shù)碼管 */ void ch455_set_segment(uint8_t bit_index, uint8_t seg) { /* 第一幀低 4 位段碼寄存器地址 bit_index */ uint8_t low_frame (bit_index 4) | (seg 0x0F); /* 第二幀高 4 位段碼寄存器地址 bit_index 4 */ uint8_t high_frame ((bit_index 4) 4) | ((seg 4) 0x0F); uint8_t buf[2] { low_frame, high_frame }; /* 一次傳輸兩個(gè)字節(jié)HAL 內(nèi)部會(huì)先發(fā)地址再發(fā)數(shù)據(jù) */ HAL_I2C_Master_Transmit(hi2c1, CH455_ADDR, buf, 2, 100); }這個(gè)函數(shù)的關(guān)鍵在于bit_index不要超過 3否則高幀地址會(huì)越界到其他控制寄存器。發(fā)送長(zhǎng)度是 2代表兩個(gè)數(shù)據(jù)幀I2C 總線上會(huì)出現(xiàn)一次起始條件、一次地址字節(jié)、兩個(gè)數(shù)據(jù)字節(jié)、一個(gè)停止條件。CH455 收到第一個(gè)字節(jié)后內(nèi)部解析出地址和半段碼收到第二個(gè)字節(jié)后解析出另一半合在一起更新對(duì)應(yīng)位的顯示緩存。有兩點(diǎn)想提醒第一這個(gè)函數(shù)對(duì)每一位都做一次 I2C 傳輸刷新四位就要調(diào)用四次每次需要幾百微秒對(duì)顯示這種低頻操作完全夠用第二不要試圖把四個(gè)位的幀合并成一個(gè)長(zhǎng) buffer 一次性發(fā)除非你確認(rèn)寄存器地址是遞增的而 CH455 的低 4 位地址和高 4 位地址是跳變的合并反而要先重新排序得不償失。4.3 四位數(shù)字刷新一次性顯示一個(gè)整數(shù)有了單字符函數(shù)顯示整數(shù)就是把數(shù)字拆成個(gè)、十、百、千位逐位調(diào)用。注意顯示的是明碼數(shù)字還是 BCD 碼這里按明碼寫最簡(jiǎn)單直接。void ch455_display_number(uint16_t num) { uint8_t digits[4]; /* 依次取出千、百、十、個(gè)位 */ digits[0] (num / 1000) % 10; digits[1] (num / 100) % 10; digits[2] (num / 10) % 10; digits[3] num % 10; for (uint8_t i 0; i 4; i) { ch455_set_segment(i, seg_table[digits[i]]); } }主循環(huán)里調(diào)用這個(gè)函數(shù)就夠了不需要定時(shí)器去刷新因?yàn)閯?dòng)態(tài)掃描的工作全在 CH455 內(nèi)部完成。CPU 只在數(shù)字變化時(shí)傳一次數(shù)據(jù)中途即使主循環(huán)被別的任務(wù)卡住數(shù)碼管也能保持顯示內(nèi)容不閃。這是 CH455 和 GPIO 直接掃最大的區(qū)別也是它適合做后臺(tái)任務(wù)的工業(yè)面板的原因。如果想讓某些位熄滅比如只顯示三位數(shù)就給對(duì)應(yīng)位寫入 0x00。0x00 段碼表示所有段都滅這一位看起來就是空白的。小數(shù)點(diǎn)的處理也簡(jiǎn)單把seg_table[digits[i]] | 0x80傳入即可0x80 對(duì)應(yīng) dp 段。需要注意的是段碼表里如果 dp 不在最高位那 0x80 可能需要換位置這又回到了段序核對(duì)的問題上。5. 避坑與排查CH455 驅(qū)動(dòng)的 5 個(gè)高頻翻車點(diǎn)CH455 本身不算復(fù)雜但因?yàn)樗训刂泛蛿?shù)據(jù)揉在一個(gè)字節(jié)里再加上 HAL 庫對(duì)地址參數(shù)的處理方式實(shí)際調(diào)試時(shí)會(huì)遇到一些看起來「不科學(xué)」的故障。下面這五條是我自己踩過、也看別人踩過的坑按現(xiàn)象、原因、解決的順序說清楚。5.1 I2C 應(yīng)答失敗地址填 0x90 是第一個(gè)攔路虎現(xiàn)象HAL_I2C_Master_Transmit 返回 HAL_ERROR用示波器看 SDA 上從機(jī)一直沒有拉低應(yīng)答位或者 HAL_I2C_IsDeviceReady 永遠(yuǎn)返回超時(shí)。原因多半是地址填錯(cuò)了。很多資料把 CH455 的寫地址寫成 0x90這是 8 位地址形態(tài)也就是 7 位地址 0x48 左移一位再補(bǔ) 0。但 HAL 庫的接口要求傳 7 位地址庫內(nèi)部會(huì)再左移一次。如果你填 0x90實(shí)際發(fā)送的地址變成 0x120超出 7 位范圍從機(jī)根本不認(rèn)。解決把地址定義成 0x48不要自己移位更不要從舊例程里照抄帶 0x90 的寫法。5.2 只有一半段亮段碼拆幀時(shí)寄存器地址偏移搞錯(cuò)現(xiàn)象顯示數(shù)字 8結(jié)果只有上面三橫亮下面三橫和中間一橫完全不亮。原因是對(duì)第 0 位的高 4 段寄存器地址寫成了bit_index 1而不是bit_index 4。CH455 的地址范圍從 0 到 7前 4 個(gè)給低段后 4 個(gè)給高段加 4 才能跳到正確的高段寄存器。如果加 1實(shí)際寫到的是后一位的低段寄存器把別的位的顯示內(nèi)容弄亂了。解決先固定只顯示一個(gè)數(shù)字把兩個(gè)幀字節(jié)打印出來對(duì)照手冊(cè)確認(rèn)地址高 4 位的值在 0 到 7 之間并且和自己的位對(duì)應(yīng)關(guān)系一致。5.3 SDA 一直為低I2C 總線被從機(jī)鎖死現(xiàn)象程序一走到 I2C 初始化SDA 就被拉低SCL 正常復(fù)位 MCU 也沒用。原因可能是上電瞬間 CH455 檢測(cè)到了不完整的起始條件進(jìn)入了一個(gè)等待后續(xù)數(shù)據(jù)的狀態(tài)而后續(xù)數(shù)據(jù)永遠(yuǎn)沒來。解決在初始化 I2C 外設(shè)之前用 GPIO 手動(dòng)把 SCL 拉高然后對(duì) SDA 連續(xù)輸出 9 個(gè)高低脈沖相當(dāng)于給它一個(gè)假的時(shí)鐘序列讓它退出錯(cuò)誤狀態(tài)。這個(gè)過程也叫 I2C 總線復(fù)位是處理從機(jī)鎖死的通用手段。/* 手動(dòng)復(fù)位 I2C 總線9 個(gè)時(shí)鐘脈沖釋放被拉低的 SDA */ void i2c_bus_reset(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_9; /* SDA */ gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, gpio); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); /* SCL 保持高 */ for (uint8_t i 0; i 9; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); HAL_Delay(1); } /* 復(fù)位完成重新初始化 I2C 外設(shè) */ }這個(gè)函數(shù)的實(shí)質(zhì)是讓 SDA 在 SCL 高電平期間反復(fù)變化從從機(jī)的視角看就是連續(xù)收到不算數(shù)的起始事件最終它會(huì)放棄等待釋放總線。執(zhí)行完再回到正常初始化流程一般就能恢復(fù)通信。我習(xí)慣在 main 函數(shù)最開始調(diào)用一次成本很低但能省掉很多不明原因的首次上電死鎖。5.4 四位亮度不均勻限流電阻放錯(cuò)了位置現(xiàn)象數(shù)碼管顯示時(shí)有一個(gè)位明顯比其余三個(gè)暗或者數(shù)字在切換時(shí)亮度跳動(dòng)。原因很可能是在位線上串了限流電阻。四位共陰數(shù)碼管的公共端流過的電流是八段之和一旦這里串了電阻每一位的公共端電壓都會(huì)被抬高段電流隨之下降。不同位的壓降差異會(huì)造成亮度不一致。解決限流電阻只放在段線上位線直接接 CH455 的 DIG 腳。亮度統(tǒng)一后再微調(diào)段電阻阻值來匹配總體亮度。5.5 調(diào)試器連著正常重新上電就黑屏現(xiàn)象在線調(diào)試的時(shí)候數(shù)碼管顯示一切正常斷開調(diào)試器、斷電再上電數(shù)碼管完全不亮但 MCU 程序明明在跑。原因通常是上電時(shí) MCU 已經(jīng)先跑起來了I2C 外設(shè)初始化完成但 CH455 的供電還沒穩(wěn)定芯片處于未上電或半上電狀態(tài)丟失了第一次寫入的數(shù)據(jù)。解決在 main 函數(shù)進(jìn)入主循環(huán)前加一個(gè) 50 到 100ms 的延時(shí)讓 CH455 穩(wěn)定。如果還是不行那就在初始化 I2C 后重新調(diào)用一次全滅函數(shù)把所有位寫 0x00把從機(jī)的內(nèi)部狀態(tài)拉回已知值。這個(gè)問題本質(zhì)是上電時(shí)序問題不算驅(qū)動(dòng) bug但會(huì)讓人誤以為代碼有問題值得記住。6. 往深走一步邏輯分析儀驗(yàn)證時(shí)序與按鍵掃描的擴(kuò)展空間前面五章已經(jīng)能把四位數(shù)碼管正常點(diǎn)亮但工程上「能亮」和「可靠」還不是一回事。最后一章回到驗(yàn)證手段和一個(gè)常見擴(kuò)展方向這兩點(diǎn)對(duì)新老手都有價(jià)值。6.1 用邏輯分析儀看幀結(jié)構(gòu)不用猜我強(qiáng)烈建議把邏輯分析儀接到 SCL 和 SDA 上抓一次 ch455_set_segment 的完整波形。重點(diǎn)看三件事地址字節(jié)是不是 0x90也就是 7 位地址 0x48 加寫位 0數(shù)據(jù)字節(jié)是否按高低半幀排列ACK 位是否正常。眼不見為凈但 I2C 這種時(shí)序問題靠猜效率太低抓一次波形所有關(guān)于地址和幀結(jié)構(gòu)的疑問都會(huì)消失。邏輯分析儀不必高檔采樣率 20MHz 的入門款足夠看 100kHz 的 I2C 波形。抓完波形你會(huì)發(fā)現(xiàn)顯示 8 這個(gè)字符時(shí)第 0 位的數(shù)據(jù)字節(jié)是0x0F和0x43這正好對(duì)應(yīng)低 4 段全亮和高 4 段中 g、dp 兩段熄滅的狀態(tài)。以后遇到顯示異常先用波形確認(rèn)數(shù)據(jù)對(duì)不對(duì)再看硬件問題就分成兩半了排查范圍小得多。6.2 從顯示到輸入一個(gè) I2C 地址還能讀按鍵CH455 本身帶按鍵掃描功能這也是當(dāng)初選它而不是單純用移位寄存器方案的原因。同一個(gè) I2C 地址寫方向是發(fā)顯示幀讀方向就能讀回按鍵狀態(tài)。硬件上按鍵接在 SEG 和 DIG 之間形成交叉矩陣CH455 掃描到閉合后把鍵值放在內(nèi)部寄存器MCU 通過 I2C 讀回來。驅(qū)動(dòng)寫法和顯示類似把 HAL_I2C_Master_Transmit 換成 HAL_I2C_Master_Receive 即可只是讀回的字節(jié)每個(gè)位代表一個(gè)按鍵位置。這部分沒有在標(biāo)題要求的范圍內(nèi)但如果你的面板既要顯示又要按鍵CH455 一顆芯片就能把兩件事都干了等于省掉一個(gè)獨(dú)立按鍵掃描芯片。我上次做某跨平臺(tái)系統(tǒng)的控制面板時(shí)就是先實(shí)現(xiàn)顯示再順手把按鍵掃描加上整個(gè)輸入輸出模塊只占用兩條 I2C 線外圍干干凈凈?;仡櫿麄€(gè)調(diào)試過程我最深的教訓(xùn)是遇到通信類芯片先花十分鐘確認(rèn)地址和幀格式再動(dòng)手寫顯示邏輯。省下的時(shí)間遠(yuǎn)比想象中多。紙上談兵說「應(yīng)該沒問題」的每一處最后都變成了一次示波器探頭、一顆上拉電阻、或是一行地址左移的修正。希望這篇筆記能幫你少走這幾步彎路。本文還有配套的精品資源點(diǎn)擊獲取