的IC卡讀寫控制器設(shè)計(jì)與時(shí)序可靠性實(shí)踐)
做 IC 卡讀寫控制器這個(gè)選題放在今天的硬件圈里聽起來有點(diǎn)復(fù)古但它一直在真實(shí)產(chǎn)線上活著?,F(xiàn)場一批老終端用了十幾年主板燒了買不到備件原廠早就停止維護(hù)能做的只有照原邏輯重畫一塊板、重寫一份固件然后塞進(jìn)原來的機(jī)柜里接口、時(shí)序、卡型一樣不能變。這類活兒的技術(shù)難度不在算法而在于IC 卡讀寫控制器要在 51 單片機(jī)這種資源緊到摳門的平臺(tái)上把供電、時(shí)序、協(xié)議、掉電保護(hù)、插拔耐受全部做穩(wěn)一塊板子裝上去得扛住三年不斷電、卡座插拔十萬次不虛接。這篇就按我實(shí)際交付過的思路從前期的架構(gòu)選型、硬件原理圖與 PCB 的關(guān)鍵網(wǎng)絡(luò)、軟件分層怎么切、時(shí)序參數(shù)怎么算一直講到聯(lián)調(diào)時(shí)示波器該看哪幾根線、量產(chǎn)前必須補(bǔ)的校驗(yàn)和與老化測(cè)試。適合手上正接這類改造項(xiàng)目、或者剛接觸 IC 卡底層通信的同行參考讀完之后你至少能畫出一版能過 EMC 預(yù)測(cè)試的板子同時(shí)寫出一份半年后自己還看得懂的固件。1. IC卡讀寫控制器的整體設(shè)計(jì)與選型拆解1.1 為什么這個(gè)場景下還在用51內(nèi)核先把為什么不用 ARM這個(gè)問題正面回答掉因?yàn)槊看卧u(píng)審都會(huì)被問。答案是三件事BOM 成本、存量生態(tài)、供貨確定性。一塊控制器的核心成本里MCU 只占很小一部分但如果你換成 32 位平臺(tái)隨之而來的穩(wěn)壓要求、PCB 層數(shù)、固件工具鏈、燒錄工裝、產(chǎn)線檢測(cè)治具全部要重做一次改造攤到幾百臺(tái)設(shè)備上單價(jià)立刻失控。51 內(nèi)核的 8 位機(jī)在 5V 供電下 IO 直接兼容絕大多數(shù) IC 卡接口芯片和邏輯器件省掉一堆電平轉(zhuǎn)換這在老機(jī)柜里是巨大優(yōu)勢(shì)。第二個(gè)理由是代碼規(guī)模和實(shí)時(shí)性需求匹配。一個(gè) IC 卡讀寫控制器要做的事情其實(shí)很少上電自檢、輪詢卡座、識(shí)別卡型、讀寫指定扇區(qū)或地址、串口上報(bào)、異常記錄。這些邏輯加起來也就幾 KB 代碼中斷響應(yīng)要求是毫秒級(jí)不是微秒級(jí)。用 51 內(nèi)核跑起來主循環(huán)加一個(gè)定時(shí)器節(jié)拍足夠干凈。反而是上 32 位平臺(tái)之后很多人會(huì)不自覺地引入 RTOS、文件系統(tǒng)、動(dòng)態(tài)內(nèi)存最后把一件簡單的事做復(fù)雜穩(wěn)定性還不如原來。第三個(gè)理由最實(shí)在舊固件的時(shí)序特征是和舊硬件綁定的。上位機(jī)軟件里可能硬編碼了命令響應(yīng)時(shí)間、超時(shí)重試次數(shù)、上電延時(shí)你換了主頻和控制周期上位機(jī)會(huì)偶發(fā)判超時(shí)。沿用同主頻的 51 內(nèi)核平臺(tái)比如經(jīng)典的 89C52 系列配 11.0592MHz 晶振串口波特率誤差、定時(shí)器節(jié)拍、延時(shí)循環(huán)都能對(duì)上原來的量級(jí)改造風(fēng)險(xiǎn)最低。我一般會(huì)明確告訴對(duì)方這次改造的目標(biāo)是無縫替換不是技術(shù)升級(jí)兩條路線不要混著走。提示如果項(xiàng)目是從零開始的新設(shè)計(jì)沒有存量上位機(jī)約束那選擇面可以放寬。但只要涉及替換舊板主頻、晶振、串口參數(shù)這三項(xiàng)盡量與舊方案保持一致能省掉大量的聯(lián)調(diào)扯皮。1.2 控制器要解決的四類核心問題把需求拆開看一個(gè) IC 卡讀寫控制器本質(zhì)上要同時(shí)解決四類問題任何一類沒處理好現(xiàn)場都會(huì)表現(xiàn)為偶發(fā)讀不出卡。類別具體內(nèi)容典型失效表現(xiàn)供電與保護(hù)卡座供電、限流、熱插拔浪涌、反接保護(hù)插卡瞬間復(fù)位、芯片發(fā)熱時(shí)序與協(xié)議時(shí)鐘、數(shù)據(jù)建立保持時(shí)間、應(yīng)答超時(shí)偶發(fā) NAK、讀到全 FF 或全 00數(shù)據(jù)可靠性校驗(yàn)、寫周期等待、掉電保護(hù)寫一半斷電后數(shù)據(jù)塊損壞人機(jī)與上報(bào)指示燈、蜂鳴、串口協(xié)議、日志現(xiàn)場無法判斷故障歸因這張表是我做方案評(píng)審時(shí)必?cái)[的一頁。很多人一上來就盯著協(xié)議層摳時(shí)序結(jié)果板子插拔幾十次之后接觸電阻變化波形已經(jīng)歪了還在懷疑代碼。供電和接插件永遠(yuǎn)應(yīng)該排在第一優(yōu)先級(jí)因?yàn)樗鼈儧Q定了后面所有軟件努力能不能生效。1.3 軟硬件一體的分層模型我把整個(gè)控制器切成四層這個(gè)分層從畫原理圖那一天就開始生效而不是等寫代碼時(shí)才想起來。最底層是硬件抽象層只做一件事把引腳操作封裝成函數(shù)比如bus_start()、bus_write_byte()、card_power_on()、card_power_off()。這一層不允許出現(xiàn)任何協(xié)議語義只允許出現(xiàn)電平。往上是器件驅(qū)動(dòng)層負(fù)責(zé)具體卡型的時(shí)序比如存儲(chǔ)卡的頁寫、邏輯加密卡的密碼校驗(yàn)、非接觸卡的尋卡與防沖突。這一層只知道怎么和這顆芯片說話不知道業(yè)務(wù)要存什么。再往上是業(yè)務(wù)邏輯層負(fù)責(zé)卡型識(shí)別、權(quán)限判斷、余額或工號(hào)的讀出寫入、異常計(jì)數(shù)。這一層是純邏輯理論上可以脫離硬件在 PC 上跑單元測(cè)試。最頂上是通信與運(yùn)維層負(fù)責(zé)串口或總線上報(bào)、命令解析、日志落盤。這樣切的好處很直接換卡座供應(yīng)商只改第一層換卡型只改第二層上位機(jī)改協(xié)議只改第四層。我見過太多把四個(gè)層揉成一個(gè)main.c的項(xiàng)目改一個(gè)引腳定義要翻八百行最后沒人敢動(dòng)只能推倒重寫。2. 硬件設(shè)計(jì)從電源到卡座的每一個(gè)坑2.1 電源架構(gòu)與去耦的實(shí)操取舍IC 卡控制器一般是 5V 系統(tǒng)卡座供電也吃 5V但卡座是熱插拔部件插拔瞬間的浪涌和靜電是最大的敵人。我的常規(guī)做法是主板輸入 5V 先過一顆自恢復(fù)保險(xiǎn)絲再進(jìn) LC 濾波然后分兩路——一路直供 MCU一路經(jīng)限流電阻供卡座卡座這路再并一顆 TVS 管到地。限流電阻的取值要算不能拍腦袋。假設(shè)卡型工作電流最大 10mA卡座接觸瞬間可能短路你希望短路電流不超過 100mA那么 R 5V/100mA 50Ω。但電阻串在供電回路里會(huì)產(chǎn)生壓降10mA 時(shí)壓降 0.5V卡端只剩 4.5V多數(shù)卡還能工作但已經(jīng)在邊緣。所以更穩(wěn)的做法是用 22Ω 到 33Ω 之間配合自恢復(fù)保險(xiǎn)絲讓保險(xiǎn)絲承擔(dān)長時(shí)間短路的保護(hù)電阻只負(fù)責(zé)削掉瞬態(tài)尖峰。去耦這塊我固定放三顆MCU 電源腳旁邊 0.1uF 瓷片卡座電源腳旁邊 0.1uF 加 10uF 鉭電容另外整板入口放一顆 100uF 電解。這里有個(gè)常被忽略的細(xì)節(jié)——卡座的 10uF 電容一定要靠近卡座焊盤不要放在板子另一頭。我遇到過一次插卡復(fù)位查了兩天最后發(fā)現(xiàn)是 10uF 電容離卡座 4 厘米插卡瞬間的電流突變?cè)谧呔€電感上產(chǎn)生的壓降直接把 MCU 拉進(jìn)欠壓復(fù)位。挪到卡座旁邊 5 毫米問題消失。注意TVS 管的選型要看結(jié)電容高速數(shù)據(jù)線上串的 TVS 結(jié)電容過大會(huì)把上升沿拖慢導(dǎo)致時(shí)序不滿足。電源線用大結(jié)電容的沒關(guān)系數(shù)據(jù)線一定要選低結(jié)電容型號(hào)或者干脆只在電源側(cè)做保護(hù)。2.2 卡座選型與接觸可靠性設(shè)計(jì)卡座是這類項(xiàng)目里故障率最高的機(jī)械件沒有之一。選型時(shí)我會(huì)重點(diǎn)關(guān)注四項(xiàng)觸點(diǎn)鍍層、插拔壽命標(biāo)稱值、防插反結(jié)構(gòu)、檢測(cè)開關(guān)。觸點(diǎn)鍍層優(yōu)先選鍍金厚度至少要 0.3 微米以上鍍錫的便宜但氧化快南方潮濕環(huán)境下半年就開始出問題。插拔壽命標(biāo)稱值一般寫 10 萬次實(shí)際現(xiàn)場能撐到 5 萬次就算不錯(cuò)所以軟件上必須做讀到異常就重試的兜底硬件上則要在結(jié)構(gòu)上給它留出更換空間——?jiǎng)e把卡座焊死在板子正中間四周留出鑷子能伸進(jìn)去的間隙維修時(shí)能單獨(dú)換。防插反這件事光靠卡座自帶的塑料限位不夠。我的做法是在 PCB 上把卡座的插入方向做成非對(duì)稱布局卡座一側(cè)緊貼板邊另一側(cè)留出元件區(qū)用戶反插時(shí)物理上插不到底。這個(gè)細(xì)節(jié)在打樣階段用 3D 打印一個(gè)假卡套試一下就能驗(yàn)證成本幾乎為零但能省掉大量現(xiàn)場投訴。帶檢測(cè)開關(guān)的卡座貴一點(diǎn)點(diǎn)但非常值。檢測(cè)開關(guān)接一個(gè) IO 并在外部加 10k 上拉插卡時(shí)產(chǎn)生電平跳變MCU 用中斷或輪詢捕捉。這樣控制器平時(shí)可以不給卡座供電插卡才上電既省電又減少觸點(diǎn)氧化。沒有檢測(cè)開關(guān)的方案只能靠一直上電一直輪詢觸點(diǎn)長期帶電氧化速度明顯更快。2.3 電平匹配、上拉與熱插拔保護(hù)51 內(nèi)核單片機(jī)的 IO 是準(zhǔn)雙向口輸出高電平時(shí)實(shí)際上是靠內(nèi)部弱上拉灌電流能力很弱。這意味著凡是需要輸出高電平驅(qū)動(dòng)的地方都不能直接接。驅(qū)動(dòng)指示燈、蜂鳴器、卡座電源開關(guān)一律用三極管或者專用的驅(qū)動(dòng)芯片IO 只負(fù)責(zé)給控制信號(hào)。I2C 這類開漏總線上拉電阻必須外部加不能指望內(nèi)部弱上拉。上拉電阻的取值要算我在第 4 章會(huì)給出完整計(jì)算過程這里先說結(jié)論5V 系統(tǒng)、總線電容 200pF 以內(nèi)、速率 100kHz 的條件下4.7k 是最穩(wěn)妥的通用值2.2k 更快但功耗高10k 在長走線時(shí)上升沿會(huì)明顯變緩。熱插拔保護(hù)除了前面說的 TVS 和限流還有一個(gè)細(xì)節(jié)所有接到卡座的信號(hào)線串一顆 100Ω 到 220Ω 的電阻。這顆電阻的作用不是限流而是隔離防止卡座上積累的靜電通過信號(hào)線打進(jìn) MCU 內(nèi)部保護(hù)二極管。串了這顆電阻之后上升沿會(huì)變緩所以要重新核算時(shí)序余量通常 100kHz 的 I2C 完全沒問題400kHz 就要謹(jǐn)慎。2.4 時(shí)鐘、復(fù)位與看門狗的配置取舍晶振選 11.0592MHz 還是 12MHz這是老話題但仍然值得說清楚。11.0592MHz 的優(yōu)點(diǎn)是串口波特率可以做到零誤差——11059200 除以 12 得 921600再除以 16 和 6 正好是 9600。12MHz 在 9600 波特率下誤差約 8.5%短幀還能忍長幀連續(xù)通信就容易出問題。所以只要涉及串口我一律選 11.0592MHz。代價(jià)是定時(shí)器節(jié)拍不是整數(shù)。12MHz 時(shí)機(jī)器周期正好 1 微秒算延時(shí)非常舒服11.0592MHz 時(shí)機(jī)器周期是 1.085 微秒做 1 毫秒定時(shí)器要裝 922 而不是 1000。這個(gè)換算必須提前算好寫進(jìn)注釋否則后人接手會(huì)以為初值算錯(cuò)了。復(fù)位電路我用經(jīng)典的阻容復(fù)位加一顆專用復(fù)位芯片的組合。純阻容復(fù)位在電源緩慢上升時(shí)可能不可靠加一顆帶延時(shí)的復(fù)位監(jiān)控芯片成本幾毛錢能保證上電時(shí) MCU 一定在穩(wěn)定電壓之后才開始跑??撮T狗必須開而且要養(yǎng)成主循環(huán)里定時(shí)喂狗、任何可能阻塞的地方都要評(píng)估喂狗時(shí)機(jī)的習(xí)慣。我踩過一次坑某個(gè)等待卡應(yīng)答的循環(huán)里用了長延時(shí)結(jié)果看門狗把板子復(fù)位了現(xiàn)場表現(xiàn)為插卡就重啟查了很久才發(fā)現(xiàn)是喂狗點(diǎn)設(shè)計(jì)不合理。2.5 非接觸方案的線圈與鋪地注意事項(xiàng)如果卡型是非接觸的硬件部分還要多考慮一層讀卡芯片的天線匹配和鋪地。常見做法是天線和芯片之間串匹配電容和阻尼電阻具體參數(shù)必須按芯片手冊(cè)給的參考值來不要自己算因?yàn)樘炀€電感跟 PCB 走線形狀強(qiáng)相關(guān)算出來往往對(duì)不上。鋪地上有一條硬規(guī)矩天線投影區(qū)域內(nèi)層和底層不允許鋪銅也不允許走任何信號(hào)線。鋪了銅相當(dāng)于給天線并聯(lián)了一個(gè)短路線圈讀卡距離會(huì)斷崖式下降。我見過一塊板子讀卡距離只有 1 厘米翻來覆去改代碼最后發(fā)現(xiàn)是底層鋪了一整片地。把天線區(qū)域的地挖空距離立刻回到 5 厘米。另外讀卡芯片和 MCU 之間的 SPI 線要盡量短超過 10 厘米就要考慮串電阻做阻抗匹配。51 內(nèi)核跑 SPI 速率不高一般 1MHz 以內(nèi)這個(gè)速率下短走線問題不大但走線長了之后時(shí)鐘和數(shù)據(jù)之間的偏移會(huì)引起偶發(fā)讀錯(cuò)寄存器表現(xiàn)是讀出來的版本號(hào)偶爾不對(duì)。3. 軟件分層從能跑到能維護(hù)3.1 四個(gè)層次的職責(zé)邊界與目錄結(jié)構(gòu)軟件分層最怕的是名義上分了實(shí)際上還在互相調(diào)。我的做法是用目錄結(jié)構(gòu)強(qiáng)制約束每個(gè)目錄只允許向上依賴不允許橫向亂調(diào)。/project /bsp 硬件抽象引腳、延時(shí)、電源控制 /driver 器件驅(qū)動(dòng)各類卡的時(shí)序?qū)崿F(xiàn) /app 業(yè)務(wù)邏輯卡型識(shí)別、數(shù)據(jù)讀寫、權(quán)限 /comm 通信協(xié)議串口幀解析、上報(bào) /cfg 配置與常量時(shí)序參數(shù)、地址映射 main.c 只做初始化和調(diào)度/bsp里只有寄存器和引腳操作絕不允許出現(xiàn)if (card_type xxx)這種判斷。/driver里可以有卡型相關(guān)的時(shí)序差異但不能出現(xiàn)余額不足這種業(yè)務(wù)詞匯。/app里全部是純邏輯理論上可以編譯到 PC 上跑測(cè)試。這套約束聽起來很教條實(shí)際收益很大。有一次客戶臨時(shí)要求把接觸式卡換成另一種邏輯加密卡我只改了/driver下一個(gè)文件、/cfg里幾個(gè)參數(shù)/app一行沒動(dòng)半天就交付了。3.2 時(shí)間基準(zhǔn)用一個(gè)節(jié)拍器替掉所有長延時(shí)新手寫 51 程序最容易犯的錯(cuò)是到處delay_ms(5)。這在 demo 里沒問題在真實(shí)控制器里是災(zāi)難按鍵響應(yīng)變慢、串口丟字節(jié)、看門狗被餓死。正確做法是建立一個(gè) 1 毫秒的定時(shí)器節(jié)拍所有跟時(shí)間相關(guān)的邏輯都掛在節(jié)拍上。定時(shí)器初值的算法要寫清楚/* 11.0592MHz 晶振定時(shí)器0 方式11ms 節(jié)拍 機(jī)器周期 12 / 11.0592MHz ≈ 1.085us 1ms 需要計(jì)數(shù) 1000 / 1.085 ≈ 922 初值 65536 - 922 64614 0xFC66 */ TH0 0xFC; TL0 0x66;節(jié)拍中斷里只做一件事給若干個(gè)軟件計(jì)數(shù)器加一。主循環(huán)里判斷計(jì)數(shù)器到?jīng)]到到了就執(zhí)行任務(wù)沒到就去干別的。這樣整個(gè)系統(tǒng)沒有阻塞點(diǎn)插卡響應(yīng)、串口收發(fā)、指示燈閃爍全部并行推進(jìn)。對(duì)于確實(shí)需要等待的場合比如等卡內(nèi)部寫周期完成不要用死等而是設(shè)一個(gè)超時(shí)計(jì)數(shù)器每毫秒減一減到零就判失敗。這個(gè)模式統(tǒng)一之后代碼里幾乎看不到while(!flag);這種死循環(huán)穩(wěn)定性立刻上一個(gè)臺(tái)階。3.3 命令-響應(yīng)模型與超時(shí)重試策略卡和 MCU 之間的交互本質(zhì)上是發(fā)命令、等響應(yīng)。這中間有三個(gè)參數(shù)必須明確命令發(fā)出后的等待上限、單次響應(yīng)失敗后的重試次數(shù)、整體失敗的判定條件。等待上限我是這么定的查器件手冊(cè)給出的最大響應(yīng)時(shí)間乘以 2 作為軟件超時(shí)。比如某類卡寫的最大內(nèi)部寫周期是 5 毫秒那軟件超時(shí)設(shè) 10 毫秒。乘 2 是留余量因?yàn)閷?shí)際器件的響應(yīng)時(shí)間會(huì)隨溫度、電壓、批次波動(dòng)。重試次數(shù)不要多3 次足夠。重試太多會(huì)掩蓋真實(shí)故障讓偶發(fā)問題變成看起來能用。3 次全敗就上報(bào)錯(cuò)誤碼讓上位機(jī)或者運(yùn)維去處理。這里有個(gè)反直覺的經(jīng)驗(yàn)重試次數(shù)多的系統(tǒng)現(xiàn)場故障率反而高因?yàn)樗言缙谡髡兹客痰袅说鹊綇氐讐牡臅r(shí)候已經(jīng)積累了一堆劣化數(shù)據(jù)。重試之間要有間隔一般 2 到 5 毫秒讓總線電容放電、讓卡內(nèi)部狀態(tài)機(jī)復(fù)位。間隔太短等于連續(xù)發(fā)同樣的命令卡還沒準(zhǔn)備好必然繼續(xù)失敗。3.4 掉電保護(hù)與關(guān)鍵數(shù)據(jù)的寫入策略這類控制器經(jīng)常要存一些關(guān)鍵數(shù)據(jù)卡的黑名單、設(shè)備參數(shù)、累計(jì)交易筆數(shù)。一旦寫到一半斷電數(shù)據(jù)塊就可能損壞。51 內(nèi)核沒有硬件 ECC必須靠軟件策略。我的策略是雙備份 序號(hào) 校驗(yàn)和。同一份數(shù)據(jù)在存儲(chǔ)區(qū)里占兩個(gè)槽位每個(gè)槽位包含數(shù)據(jù)本體、一個(gè)遞增的寫入序號(hào)、一個(gè) CRC8 校驗(yàn)和。啟動(dòng)時(shí)讀兩個(gè)槽位校驗(yàn)通過的才算有效兩個(gè)都有效就取序號(hào)大的那個(gè)一個(gè)有效就用那個(gè)都無效就加載默認(rèn)值并置故障標(biāo)志。寫入順序也要講究先寫備用槽寫完校驗(yàn)通過再把主槽標(biāo)記為舊。任何一個(gè)時(shí)刻斷電至少有一個(gè)槽是完整的。這套機(jī)制占用的空間不多但能救命。我之前有個(gè)項(xiàng)目現(xiàn)場經(jīng)常直接拉閘斷電用單槽方案時(shí)一個(gè)月壞了三次數(shù)據(jù)換成雙槽之后半年零故障。另外累加類數(shù)據(jù)比如總計(jì)數(shù)不要每次都寫存儲(chǔ)。改成在 RAM 里累加每滿 10 次或者每 10 秒落盤一次能把寫入次數(shù)降低一個(gè)數(shù)量級(jí)。存儲(chǔ)器的擦寫壽命是有限的典型存儲(chǔ)芯片標(biāo)稱 100 萬次但那是理想條件實(shí)際算上溫度和電壓偏差留 10 倍余量比較穩(wěn)妥。4. 關(guān)鍵時(shí)序與協(xié)議實(shí)現(xiàn)細(xì)節(jié)4.1 I2C總線上拉電阻的完整計(jì)算過程很多資料直接給用 4.7k但不給理由導(dǎo)致?lián)Q了個(gè)板子就不靈。這里把計(jì)算過程寫全。上拉電阻有上限和下限兩個(gè)約束。上限由上升沿時(shí)間決定。I2C 標(biāo)準(zhǔn)模式規(guī)定上升沿最長時(shí)間 1000 納秒上升時(shí)間近似為tr ≈ 0.8473 × R × Cb其中 Cb 是總線總電容包括走線電容、器件引腳電容、卡座電容。一塊小板子上Cb 一般在 100 到 300pF取 200pF 計(jì)算R ≤ tr / (0.8473 × Cb) ≤ 1000ns / (0.8473 × 200pF) ≈ 5900 Ω所以上拉不能大于約 5.9k4.7k 剛好在這個(gè)范圍內(nèi)。下限由灌電流能力決定。I2C 規(guī)范要求低電平時(shí)器件能拉低到 0.4V 以下標(biāo)準(zhǔn)模式灌電流按 3mA 算R ≥ (Vcc - Vol) / Iol ≥ (5 - 0.4) / 0.003 ≈ 1533 Ω所以 1.5k 到 5.9k 都合規(guī)取中間值 4.7k 兼顧功耗和速度。如果要跑 400kHz 快速模式上升沿要求收緊到 300 納秒上限變成約 1.8k這時(shí)候就得用 2.2k 甚至 1.5k同時(shí)要嚴(yán)格控制總線電容。注意很多人只加一組上拉結(jié)果總線上掛了多個(gè)器件后等效上拉變小。器件內(nèi)部如果自帶弱上拉外部再并 4.7k實(shí)際等效可能只有 2k 出頭功耗上去了但沒人注意。做低功耗產(chǎn)品時(shí)要逐個(gè)確認(rèn)。4.2 存儲(chǔ)類卡的頁寫與寫周期等待存儲(chǔ)卡比如常見的串行 EEPROM 類有兩個(gè)關(guān)鍵參數(shù)必須搞準(zhǔn)頁大小和內(nèi)部寫周期。頁大小決定了單次連續(xù)寫不能跨頁。以頁大小 8 字節(jié)的型號(hào)為例如果你從地址 0x06 開始連寫 4 個(gè)字節(jié)寫到 0x08 時(shí)會(huì)回卷到頁首 0x00把原本的數(shù)據(jù)覆蓋掉。這個(gè)坑極其隱蔽因?yàn)樽x回來的前兩個(gè)字節(jié)是對(duì)的你以為寫成功了。穩(wěn)妥寫法是先算頁內(nèi)剩余空間/* 從 addr 開始寫 len 字節(jié)自動(dòng)處理頁邊界 */ while (len 0) { uint8_t page_left PAGE_SIZE - (addr % PAGE_SIZE); uint8_t chunk (len page_left) ? len : page_left; bus_start(); bus_write_byte(DEV_ADDR_W); bus_write_byte(addr 8); bus_write_byte(addr 0xFF); for (i 0; i chunk; i) { bus_write_byte(buf[i]); } bus_stop(); wait_write_done(10); /* 超時(shí) 10ms手冊(cè)典型 5ms */ addr chunk; buf chunk; len - chunk; }寫周期等待有兩種做法固定延時(shí)和應(yīng)答輪詢。固定延時(shí)簡單但要按最壞情況取值浪費(fèi)時(shí)間應(yīng)答輪詢更快但實(shí)現(xiàn)稍復(fù)雜。控制器的場景下一次操作省幾毫秒沒有意義我一般直接用固定延時(shí)代碼簡單不容易出錯(cuò)只在批量寫的場合才用輪詢。4.3 邏輯加密卡的密碼校驗(yàn)與錯(cuò)誤計(jì)數(shù)處理邏輯加密卡這類器件有一個(gè)非常危險(xiǎn)的特性密碼連續(xù)錯(cuò)誤若干次后會(huì)永久鎖死鎖死之后卡就廢了不可恢復(fù)。這個(gè)特性必須寫進(jìn)軟件設(shè)計(jì)的第一條注釋。防護(hù)措施有三層。第一層密碼在本地先做格式校驗(yàn)長度、字符集不對(duì)直接拒絕不發(fā)到卡上。第二層每次校驗(yàn)前先讀卡內(nèi)的剩余嘗試次數(shù)多數(shù)型號(hào)支持這個(gè)讀取剩余次數(shù)低于 2 就拒絕操作并上報(bào)需管理員介入。第三層錯(cuò)誤次數(shù)在本地做一份影子記錄存到掉電保存區(qū)這樣即使換一臺(tái)設(shè)備也能知道這張卡已經(jīng)錯(cuò)過幾次。現(xiàn)場最常見的誤操作是運(yùn)維人員反復(fù)試密碼。所以人機(jī)界面上必須有明確提示不能只亮個(gè)紅燈。我的做法是錯(cuò)誤一次就蜂鳴長響、指示燈快閃兩次之后直接鎖定界面要求走管理流程。4.4 非接觸卡的尋卡流程與防沖突實(shí)現(xiàn)非接觸方案里MCU 和讀卡芯片之間是對(duì)接讀卡芯片和卡之間是射頻。MCU 側(cè)的關(guān)鍵是寄存器讀寫時(shí)序和命令流控制。尋卡的典型流程是軟復(fù)位、配置定時(shí)器和調(diào)制參數(shù)、發(fā)送尋卡命令、判斷是否有卡、有多張卡則進(jìn)入防沖突讀序列號(hào)、選卡、認(rèn)證、讀寫。防沖突這一步經(jīng)常被簡化掉因?yàn)楝F(xiàn)場通常一次只放一張卡。但只要有兩張卡同時(shí)進(jìn)入場區(qū)沒有防沖突邏輯的固件就會(huì)讀到混亂的序列號(hào)表現(xiàn)為讀到一張不存在的卡。穩(wěn)妥做法是保留完整的防沖突流程即使最終只取第一張卡。還有一個(gè)細(xì)節(jié)尋卡命令的間隔。如果主循環(huán)里每圈都發(fā)一次尋卡命令讀卡芯片會(huì)一直處于發(fā)射狀態(tài)功耗高、發(fā)熱大長期運(yùn)行還可能影響芯片壽命。正確做法是每 100 到 200 毫秒發(fā)一次中間留出空閑。5. 聯(lián)調(diào)實(shí)錄示波器上該看什么5.1 三個(gè)必須上示波器的測(cè)量點(diǎn)軟件寫完燒進(jìn)去能跑不代表跑得穩(wěn)。我聯(lián)調(diào)時(shí)必看三個(gè)點(diǎn)每次都能提前發(fā)現(xiàn)隱患。第一個(gè)點(diǎn)是卡座供電腳的上電波形??磧杉律仙赜袥]有臺(tái)階有臺(tái)階說明限流電阻和電容形成了 RC 拐點(diǎn)可能讓卡上電時(shí)序不滿足、插卡瞬間有沒有跌落超過 0.3V跌太多會(huì)導(dǎo)致卡內(nèi)部復(fù)位。這個(gè)波形只有在實(shí)際插拔時(shí)才能抓到用單次觸發(fā)模式觸發(fā)條件設(shè)成下降沿。第二個(gè)點(diǎn)是時(shí)鐘線的上升沿。把時(shí)間基準(zhǔn)放到 200 納秒一格看上升時(shí)間。如果超過 1 微秒說明上拉偏大或者總線電容偏大長線場合必然出問題。第三個(gè)點(diǎn)是應(yīng)答等待時(shí)間。觸發(fā)設(shè)在命令發(fā)出的最后一個(gè)時(shí)鐘下降沿看響應(yīng)回來的延遲。這個(gè)延遲如果在臨界值附近晃說明軟件超時(shí)設(shè)置得太緊換個(gè)批次或者溫度變了就會(huì)失敗。5.2 通信失敗的排查路徑表現(xiàn)場出問題的時(shí)候最忌諱上來就改代碼。我一般按這張表往下走八成的問題在前三步就能定位?,F(xiàn)象首要懷疑快速驗(yàn)證方法完全無響應(yīng)供電、卡座接觸、復(fù)位萬用表量卡座電壓插拔看是否跳變有時(shí)能讀有時(shí)不能卡座氧化、限流電阻偏大換一張新卡對(duì)比看是否與卡有關(guān)讀到全 FF數(shù)據(jù)線未接、上拉缺失示波器看數(shù)據(jù)線是否有拉低動(dòng)作讀到全 00數(shù)據(jù)線被拉死、器件未上電斷電測(cè)數(shù)據(jù)線對(duì)地阻抗寫入后讀回錯(cuò)亂頁邊界跨界、寫周期等待不足單字節(jié)寫測(cè)試逐字節(jié)驗(yàn)證長線通信偶發(fā)錯(cuò)上升沿過緩、串阻過大縮短走線或減小串阻后復(fù)測(cè)插卡就復(fù)位電源跌落、地彈示波器看 MCU 電源腳瞬態(tài)這張表我一般打印出來貼在工位上新來的同事按著走能省掉大量瞎猜的時(shí)間。要強(qiáng)調(diào)的是先物理后邏輯確認(rèn)供電、接觸、地線沒問題再去懷疑協(xié)議和代碼。我見過太多人抱著邏輯分析儀摳時(shí)序最后發(fā)現(xiàn)是卡座彈片變形。5.3 一次典型的偶發(fā)讀卡失敗定位過程講一個(gè)實(shí)際案例過程比結(jié)論有價(jià)值。某批控制器在現(xiàn)場出現(xiàn)偶發(fā)讀卡失敗比例大概 5%重啟就好過幾天又犯。第一步先確認(rèn)是不是卡的問題。拿十張同型號(hào)卡在新板子上跑全部正常說明卡沒問題。第二步把故障板拿回來在實(shí)驗(yàn)室里連續(xù)插拔 500 次復(fù)現(xiàn)了兩次失敗。第三步示波器掛在數(shù)據(jù)線上做長時(shí)間錄制抓到失敗瞬間的波形——數(shù)據(jù)線的低電平只到 1.2V沒到 0.4V 以下。低電平下不去無非三個(gè)原因上拉太強(qiáng)、灌電流能力不足、地線阻抗大。量了一下上拉是 4.7k正常。查器件手冊(cè)低電平灌電流能力 3mA符合。最后量地線發(fā)現(xiàn)卡座的地是通過一段細(xì)走線連到主地的這段走線電阻有 0.4Ω加上插拔后接觸電阻上升到 2Ω 左右形成分壓把低電平抬起來了。處理方式很簡單卡座地改成大面積鋪銅直連主地另外在卡座地腳旁邊加一個(gè)過孔到地平面。改完之后連續(xù)插拔 2000 次零失敗。這個(gè)案例的教訓(xùn)是低速總線上的問題八成不是時(shí)序算錯(cuò)而是回流路徑不通。6. 常見問題速查與工程避坑清單6.1 硬件類高頻問題問題一插卡時(shí)指示燈閃一下就滅。大概率是電源跌落導(dǎo)致 MCU 復(fù)位。檢查卡座電容位置、限流電阻是否過大、地線是否夠?qū)挕?梢韵扰R時(shí)把限流電阻短接驗(yàn)證如果現(xiàn)象消失就是供電能力問題。問題二同一張卡在 A 板能用 B 板不能用。先對(duì)比兩塊板的上拉電阻和串阻再對(duì)比卡座型號(hào)。不同批次卡座的彈片壓力和鍍層不一樣接觸電阻能差好幾倍。問題三板子放一段時(shí)間就壞。潮濕環(huán)境下的電化學(xué)遷移。檢查是否有長期帶電的密集走線、是否有未做阻焊開窗的裸銅。三防漆能解決大部分問題但要避開卡座觸點(diǎn)區(qū)域。問題四讀卡距離忽遠(yuǎn)忽近。非接觸方案專屬。檢查天線附近有沒有金屬件、電池、螺絲。人體靠近也會(huì)影響測(cè)試時(shí)要模擬實(shí)際安裝狀態(tài)。6.2 軟件類高頻問題問題一串口偶爾收到亂碼。先算波特率誤差。11.0592MHz 配 9600 是零誤差如果用的是 12MHz誤差 8.5%連續(xù)收長幀必錯(cuò)。另外檢查晶振負(fù)載電容是否匹配負(fù)載電容不對(duì)會(huì)導(dǎo)致實(shí)際頻率偏移。問題二寫入成功但讀回是舊數(shù)據(jù)。緩存沒刷新。很多驅(qū)動(dòng)里讀操作會(huì)走緩存寫完必須主動(dòng)清緩存或者延時(shí)后重讀。我一般寫完立刻讀回校驗(yàn)校驗(yàn)不過就重寫一次。問題三長時(shí)間運(yùn)行后死機(jī)。看門狗配置問題或者棧溢出。51 內(nèi)核的??臻g很小中斷嵌套加上局部數(shù)組很容易溢出。檢查原則中斷服務(wù)函數(shù)里不放數(shù)組、不調(diào)用多層函數(shù)。問題四換一批芯片就不工作。時(shí)序余量不足。不同批次的器件響應(yīng)速度有差異如果你的超時(shí)是卡著手冊(cè)最小值設(shè)的換批次必然出問題。所有時(shí)序參數(shù)至少留 2 倍余量。6.3 量產(chǎn)前必須補(bǔ)的三件事第一件每塊板做一次全流程自檢。上電后自動(dòng)跑讀卡座空載檢測(cè)、寫一個(gè)測(cè)試字節(jié)到保留區(qū)、讀回校驗(yàn)、清測(cè)試標(biāo)志。這個(gè)自檢可以在產(chǎn)線治具上觸發(fā)也可以在每次上電時(shí)跑成本很低但能攔住大部分焊接不良。第二件固件里加版本號(hào)和構(gòu)建時(shí)間。通過一條專用命令讀出來?,F(xiàn)場排查時(shí)第一句問的就是這塊板子跑的哪個(gè)版本沒有版本號(hào)根本沒法定位。第三件做高低溫循環(huán)和反復(fù)插拔老化。抽 5% 的板子在 0 度和 60 度之間循環(huán) 10 次每次循環(huán)后跑一遍自檢插拔測(cè)試做 5000 次。這一步能提前暴露九成的接觸和溫漂問題。我吃過一次虧一批板子在常溫下全部合格到了北方冬天現(xiàn)場低溫下晶振起振慢上電復(fù)位失敗幾百臺(tái)返修。從那之后老化測(cè)試變成了必做項(xiàng)。6.4 關(guān)于代碼可維護(hù)性的一點(diǎn)私貨最后說點(diǎn)不那么技術(shù)但很重要的。這類改造項(xiàng)目的生命周期往往比預(yù)期長交付三年后可能還有人來找你改一個(gè)功能。所以代碼里一定要留夠信息函數(shù)命名要能自解釋card_power_on()比pwr(1)強(qiáng)一百倍。所有魔數(shù)時(shí)序參數(shù)、地址、密碼位置必須定義成宏并寫明來源手冊(cè)第幾頁、實(shí)測(cè)值。每個(gè)驅(qū)動(dòng)文件頭部寫清楚適用的器件型號(hào)范圍和已知限制。關(guān)鍵分支加注釋說明為什么這么寫而不是寫了什么。我接手過一個(gè)前人留下的項(xiàng)目里面有一行delay_us(37);沒有任何注釋。查了三天最后通過對(duì)比手冊(cè)才知道那是某個(gè)器件的等待時(shí)間而且 37 這個(gè)值是因?yàn)楫?dāng)時(shí)用的晶振是 12MHz 算出來的后來晶振換成了 11.0592MHz這個(gè)延時(shí)其實(shí)已經(jīng)不準(zhǔn)了只是恰好還在容差范圍內(nèi)。這種坑全靠注釋才能避免重演。我個(gè)人在這些項(xiàng)目里最深的體會(huì)是IC 卡讀寫控制器這類東西技術(shù)難度從來不是門檻把每一個(gè)偶發(fā)當(dāng)成必然會(huì)發(fā)生去設(shè)計(jì)才是門檻?,F(xiàn)場插拔十萬次5% 的失效概率就是五千次投訴溫度跨 60 度任何卡著臨界值的參數(shù)都會(huì)翻車。所以每次做方案我都會(huì)問自己一句這個(gè)余量如果是最差批次、最差溫度、最臟的卡座還夠不夠。答案不夠就再加一倍。