戰(zhàn):ARM Cortex-M0+低成本32位MCU選型與開發(fā))
最近有個(gè)實(shí)際項(xiàng)目要換主控要求功耗低、成本敏感、接口還得夠用朋友順手推了一顆料過來芯嶺技術(shù)這邊在推廣的普冉 MCU型號(hào)是 MS32C001-C32 位 ARM Cortex-M0 內(nèi)核。說實(shí)話看到這個(gè)組合第一反應(yīng)是“又是一顆國(guó)產(chǎn)替代芯片”但真正上手跑了一輪之后發(fā)現(xiàn)它在定位、資源、性價(jià)比上確實(shí)有點(diǎn)東西。這篇文章就把我從選型、硬件設(shè)計(jì)、開發(fā)環(huán)境到實(shí)際調(diào)試踩坑的全過程整理出來給正在評(píng)估或者已經(jīng)拿到樣片的工程師做個(gè)參照。我寫的所有內(nèi)容都圍繞這顆芯片的實(shí)際使用展開適合三類人看一是做低成本 IoT 設(shè)備選型的硬件工程師二是想從 8 位單片機(jī)遷移到 32 位平臺(tái)的學(xué)生或愛好者三是已經(jīng)在用 MS32C001-C 但遇到外設(shè)、燒錄、低功耗問題的開發(fā)者。整篇文章不講虛的全部以實(shí)操為主參數(shù)部分以官方手冊(cè)和實(shí)際測(cè)試為準(zhǔn)我會(huì)盡量標(biāo)注清楚哪些是手冊(cè)值、哪些是我實(shí)測(cè)值。1. 項(xiàng)目背景與選型邏輯1.1 這顆芯片到底解決什么問題先看定位。MS32C001-C 是一顆基于 32 位 ARM Cortex-M0 內(nèi)核的通用 MCU主頻做到幾十兆赫茲級(jí)別集成 Flash 和 SRAM內(nèi)置多路定時(shí)器、通信接口、ADC 等常用外設(shè)。它的目標(biāo)非常明確替代傳統(tǒng) 8 位單片機(jī)把那些正在用 51、AVR、PIC 的老方案往 32 位平臺(tái)遷移。為什么會(huì)有這種需求因?yàn)楝F(xiàn)在很多小家電、電動(dòng)工具、傳感器模組、智能家居面板算力需求比過去高了不少。比如我這次做的模擬項(xiàng)目叫“某溫控器系統(tǒng)”原來用的 8 位單片機(jī)要同時(shí)跑 PID 運(yùn)算、驅(qū)動(dòng)數(shù)碼管、做按鍵掃描和串口上報(bào)CPU 已經(jīng)滿負(fù)荷稍微加個(gè)功能就要換主控。換到 MS32C001-C 之后同樣的功能跑下來資源占用還不到一半而且擴(kuò)展余量明顯變大。成本層面32 位 M0 芯片現(xiàn)在已經(jīng)下探到和高端 8 位機(jī)差不多的價(jià)位真正做到了性價(jià)比上的對(duì)位。這顆芯片還解決了另一個(gè)問題開發(fā)體驗(yàn)。8 位平臺(tái)各家工具鏈碎片化嚴(yán)重CMSIS 標(biāo)準(zhǔn)、統(tǒng)一的調(diào)試接口、豐富的庫(kù)函數(shù)這些都是 32 位平臺(tái)的天然優(yōu)勢(shì)上手成本其實(shí)比想象中低。1.2 為什么優(yōu)先考慮 ARM Cortex-M0 而不是 M3 或者 8 位機(jī)很多人會(huì)問同樣都是 ARM為什么不直接上 M3這里有幾個(gè)關(guān)鍵考量。第一是功耗Cortex-M0 的流水線更短門數(shù)更少靜態(tài)功耗和動(dòng)態(tài)功耗都明顯低于 Cortex-M3對(duì)于電池供電的設(shè)備來說非常關(guān)鍵。第二是成本M0 內(nèi)核的授權(quán)費(fèi)和芯片面積更小芯片廠能做到更低的售賣價(jià)終端產(chǎn)品才有利潤(rùn)空間。第三是外設(shè)夠用很多場(chǎng)景確實(shí)用不到復(fù)雜的內(nèi)存管理、浮點(diǎn)運(yùn)算、DSP 指令。但 M0 也不是沒有代價(jià)。比如沒有硬件除法器復(fù)雜運(yùn)算要靠軟件模擬中斷響應(yīng)和嵌套能力也弱于 M3。所以選擇時(shí)要評(píng)估你的應(yīng)用是否真的需要高端特性。如果只是做電機(jī)控制、傳感器讀取、簡(jiǎn)單人機(jī)交互M0 足夠如果涉及大量浮點(diǎn)算法、復(fù)雜的協(xié)議棧和快速上下文切換那就老老實(shí)實(shí)上 M3 或者 M4。對(duì)比傳統(tǒng) 8 位機(jī)優(yōu)勢(shì)就更明顯了。8 位機(jī)雖然便宜但總線寬度限制在那里處理復(fù)雜邏輯時(shí)效率很低。而 M0 即使主頻只有幾十MHz實(shí)際指令執(zhí)行效率遠(yuǎn)超同頻 8 位機(jī)遇到定時(shí)器比較、中斷嵌套、多路 PWM 這種需求甩開 8 位機(jī)幾條街。1.3 MS32C001-C 在同類方案里的定位在普冉的 MCU 產(chǎn)品線里MS32C001-C 屬于基礎(chǔ)通用型主打小封裝、低成本、低功耗。和更高端的系列相比它沒有那么多高級(jí)外設(shè)比如缺少 USB、SDIO、以太網(wǎng)這類復(fù)雜接口但是常用通信接口一個(gè)不少UART、I2C、SPI、ADC、定時(shí)器、PWM 都有。這意味著它非常適合做“功能單一但需要穩(wěn)定”的設(shè)備節(jié)點(diǎn)。而且這顆芯片有幾個(gè)容易被忽略的優(yōu)點(diǎn)寬電壓供電范圍、內(nèi)部振蕩器精度可以滿足大多數(shù)串口通信場(chǎng)景、引腳兼容性做得好。我測(cè)試的時(shí)候發(fā)現(xiàn)產(chǎn)品設(shè)計(jì)時(shí)不用外掛晶振也能跑穩(wěn)串口通信很多時(shí)候能把 BOM 成本再壓一檔。整體定位可以概括成一句話用最低的成本和功耗完成 8 位機(jī)到 32 位平臺(tái)的無(wú)痛遷移同時(shí)保持足夠的接口靈活性。2. 硬件資源盤點(diǎn)與最小系統(tǒng)搭建2.1 核心資源與封裝信息先看核心資源。MS32C001-C 的 CPU 是基于 ARM Cortex-M0 內(nèi)核這意味著它天生支持 CMSIS 標(biāo)準(zhǔn)、標(biāo)準(zhǔn)調(diào)試接口SWD、SysTick 定時(shí)器、NVIC 中斷控制器。存儲(chǔ)方面Flash 容量有多個(gè)配置常見的有幾十 KB 級(jí)別RAM 也有多種容量可選具體值必須按照你買到的那一檔的型號(hào)后綴來確認(rèn)不同子型號(hào)差別挺大。外設(shè)資源方面我拿到手的是中等配置版本實(shí)際集成了多路 12 位 ADC支持多通道采集多路高級(jí)定時(shí)器和基礎(chǔ)定時(shí)器支持 PWM 輸出至少兩路 UART支持標(biāo)準(zhǔn)串口通信一路 I2C一路 SPI適合接傳感器、存儲(chǔ)、顯示模塊GPIO 數(shù)量充足支持外部中斷和內(nèi)部上拉/下拉電阻配置封裝選擇上有常見的 TSSOP、QFN、LQFP 等方便不同產(chǎn)品形態(tài)選擇。我這里用的小體積封裝做傳感器節(jié)點(diǎn)非常合適。2.2 最小系統(tǒng)硬件設(shè)計(jì)要點(diǎn)最小系統(tǒng)其實(shí)很簡(jiǎn)單只要抓住下面幾個(gè)點(diǎn)就不會(huì)出大問題。首先是電源芯片的 VDD 管腳要接高質(zhì)量去耦電容一般 100nF 靠近 VDD 管腳放另外再加上一個(gè) 10μF 左右的鉭電容或陶瓷電容做低頻退耦。電源推薦使用低噪聲 LDO如果系統(tǒng)里還有其他大功率器件地平面要分開處理。其次是復(fù)位管腳。很多工程師圖省事直接把 NRST 接 VDD不接下拉和電容。這在調(diào)試階段通常沒問題但在強(qiáng)干擾環(huán)境下容易出現(xiàn)意外復(fù)位。建議按規(guī)格書要求接標(biāo)準(zhǔn)的 RC 復(fù)位電路電阻電容的值參考官方推薦不要隨意加大電容否則上電復(fù)位時(shí)間太長(zhǎng)會(huì)導(dǎo)致系統(tǒng)啟動(dòng)異常。然后是時(shí)鐘配置。我做的這個(gè)項(xiàng)目直接使用了內(nèi)部 RC 振蕩器沒有外接晶振。實(shí)測(cè)下來在常溫環(huán)境下串口通信波特率誤差完全可以接受。如果系統(tǒng)需要更精準(zhǔn)的時(shí)鐘比如要求嚴(yán)格的時(shí)間同步或者 USB 相關(guān)功能當(dāng)然這顆芯片沒有 USB那就需要外接晶振。晶振的兩個(gè)負(fù)載電容選擇很關(guān)鍵要根據(jù)晶振的負(fù)載電容參數(shù)來配配錯(cuò)了會(huì)導(dǎo)致起振困難或者頻率偏移。2.3 供電、電平匹配與引腳保護(hù)這塊重點(diǎn)講電平問題。MS32C001-C 的 GPIO 在寬電壓范圍內(nèi)工作但不同電壓域之間做接口時(shí)還是要小心。如果外部傳感器或者通信總線是 5V 電平而芯片工作在 3.3V則需要進(jìn)行電平轉(zhuǎn)換。哪些引腳內(nèi)部有鉗位保護(hù)二極管、哪些沒有一定要看數(shù)據(jù)手冊(cè)的引腳說明不能指望所有引腳都 5V 耐壓。還有一個(gè)容易被忽視的地方引腳浮空輸入。在系統(tǒng)初始化之前所有 GPIO 處于高阻態(tài)如果這些引腳連接的是關(guān)鍵使能信號(hào)可能在復(fù)位期間被誤觸發(fā)。解決辦法是在硬件上加上下拉電阻或者在初始化代碼里盡快配置引腳模式。我遇到過復(fù)位瞬間外部繼電器被短暫吸合的情況就是因?yàn)?GPIO 高阻態(tài)導(dǎo)致電平翻轉(zhuǎn)后來加了外部下拉電阻才徹底解決。設(shè)計(jì)最小系統(tǒng)的時(shí)候還建議預(yù)留 SWD 調(diào)試接口的測(cè)試點(diǎn)不要只引出電源和地。哪怕原型階段不用調(diào)試器后期量產(chǎn)和故障分析也離不開調(diào)試口這點(diǎn)省不了。2.4 電源紋波與 PCB 布線建議這個(gè)環(huán)節(jié)比較偏實(shí)戰(zhàn)。M0 內(nèi)核的瞬態(tài)電流雖然不大但如果供電質(zhì)量差Flash 讀取會(huì)出錯(cuò)運(yùn)行時(shí)會(huì)不定期死機(jī)。用示波器測(cè) VDD 的紋波峰值不要超過 100mV否則就該考慮加大電容或者換低阻抗的電源走線。在 PCB 布線時(shí)晶體和去耦電容要盡可能靠近 MCU 管腳不要穿過大電流走線地線盡量用完整地平面不要在底層割裂。如果是兩層板特別要注意時(shí)鐘走線和高速信號(hào)SWDIO、SWCLK不要交叉不要走在板邊。我之前做過一塊板子為了走線方便把 SWCLK 拉得很長(zhǎng)結(jié)果調(diào)試器偶爾連不上后來縮短走線并在 SWCLK 上串了小電阻才穩(wěn)定。這些經(jīng)驗(yàn)雖然零碎但在實(shí)際調(diào)試中能省很多時(shí)間。3. 開發(fā)環(huán)境搭建與工程創(chuàng)建3.1 Keil MDK 環(huán)境與器件支持包開發(fā)工具推薦直接用 Keil MDK因?yàn)槠杖降墓俜綆?kù)和例程大多基于 Keil 工程提供。新建工程之前需要先確認(rèn) Keil 版本不要太老最好選用較新的版本避免出現(xiàn)編譯器兼容性問題。然后安裝對(duì)應(yīng)的器件支持包安裝之后在 Device 列表里就能找到 MS32C001-C 對(duì)應(yīng)的型號(hào)。這里有個(gè)細(xì)節(jié)有些人安裝完支持包后發(fā)現(xiàn)編譯時(shí)仍然報(bào)找不到頭文件其實(shí)是因?yàn)闆]有正確選擇芯片型號(hào)導(dǎo)致 Keil 沒有把 Device 相關(guān)的宏定義和頭文件路徑自動(dòng)加進(jìn)來。建議在工程配置里確認(rèn) C/C 選項(xiàng)卡中的 Define 和 Include Paths 已經(jīng)自動(dòng)添加不要手動(dòng)亂改。如果遇到編譯報(bào)錯(cuò)說找不到 core_cm0plus.h基本就是選擇了錯(cuò)誤的 Device 或者沒有安裝 CMSIS 支持包。3.2 官方 SDK、標(biāo)準(zhǔn)庫(kù)與寄存器操作如何選擇開發(fā)時(shí)一般有三種選擇直接用官方 SDK、用標(biāo)準(zhǔn)外設(shè)庫(kù)、完全寄存器操作。我的建議是快速原型驗(yàn)證用官方 SDK因?yàn)槔锩嬉呀?jīng)把外設(shè)驅(qū)動(dòng)封裝好了直接調(diào)用接口大部分情況不用自己挖寄存器。產(chǎn)品做穩(wěn)定版本時(shí)可以把涉及性能瓶頸的部分改用寄存器操作比如 PWM 占空比更新、ADC 多通道切換、串口高速收發(fā)。如果想深入理解芯片或者做底層移植直接用寄存器操作學(xué)習(xí)效果最好但工作量確實(shí)大。我自己做這個(gè)項(xiàng)目時(shí)初期用的是官方庫(kù)函數(shù)跑通功能后再逐步把定時(shí)器中斷和 ADC 采集部分改成寄存器操作性能有很大提升。比如 PWM 在庫(kù)函數(shù)方式下更新占空比可能需要多個(gè)函數(shù)調(diào)用合成一條寄存器寫操作后周期抖動(dòng)明顯改善。3.3 編譯與下載配置Keil 工程里的燒錄配置需要注意幾點(diǎn)。第一編程算法要和 Flash 容量匹配選錯(cuò)了或者容量超出范圍會(huì)導(dǎo)致燒錄到一半報(bào)錯(cuò)。第二下載前是否勾選“擦除整個(gè) Flash”如果只擦除部分扇區(qū)而代碼分散在多個(gè)位置可能出現(xiàn)殘留數(shù)據(jù)導(dǎo)致程序跑飛。第三復(fù)位模式一般選擇復(fù)位后運(yùn)行這樣的話下載完成后芯片會(huì)自動(dòng)復(fù)位并跑起來方便驗(yàn)證。燒錄工具我用的是兼容 CMSIS-DAP 的調(diào)試器成本低速度也夠。如果要批量生產(chǎn)建議用脫機(jī)編程器或者專用燒錄底座速度快、穩(wěn)定還能做序列號(hào)和 MAC 地址寫入。關(guān)于軟件架構(gòu)老生常談但還是要說中斷服務(wù)函數(shù)里不要寫復(fù)雜邏輯盡量只在中斷里置標(biāo)志位、讀數(shù)據(jù)主循環(huán)再處理協(xié)議和業(yè)務(wù)。這樣寫的好處是中斷延遲穩(wěn)定不容易出現(xiàn)數(shù)據(jù)丟失。比如串口接收如果直接在中斷里處理幀解析收到長(zhǎng)幀時(shí)很容易把其他中斷餓死改成中斷只往環(huán)形隊(duì)列里丟字節(jié)主循環(huán)再解析就穩(wěn)定多了。4. 外設(shè)驅(qū)動(dòng)開發(fā)與代碼實(shí)踐4.1 GPIO 與延時(shí)函數(shù)實(shí)戰(zhàn)GPIO 算是最基礎(chǔ)的外設(shè)但這里有幾個(gè)容易踩的坑。先看一段點(diǎn)燈和延時(shí)代碼#include MS32C001_C.h #define LED_PIN GPIO_PIN_5 #define LED_PORT GPIOB void delay_ms(uint32_t ms) { for (uint32_t i 0; i ms; i) { for (volatile uint32_t j 0; j 8000; j); } } int main(void) { GPIO_InitTypeDef gpio_init; gpio_init.Pin LED_PIN; gpio_init.Mode GPIO_MODE_OUTPUT_PP; gpio_init.Pull GPIO_NOPULL; gpio_init.Speed GPIO_SPEED_LOW; HAL_GPIO_Init(LED_PORT, gpio_init); while (1) { HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET); delay_ms(500); HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); delay_ms(500); } }這個(gè)延時(shí)函數(shù)里 8000 這個(gè)數(shù)字要根據(jù)實(shí)際主頻和編譯器優(yōu)化級(jí)別調(diào)整。最好的做法是不要用這種純軟件空循環(huán)來延時(shí)因?yàn)槟銚Q編譯器版本、換優(yōu)化級(jí)別時(shí)間就可能不對(duì)。項(xiàng)目里涉及到需要準(zhǔn)確時(shí)間的地方我都改用 SysTick 來實(shí)現(xiàn)。SysTick 是 Cortex-M0 內(nèi)核自帶的一個(gè) 24 位遞減計(jì)數(shù)器用它做時(shí)基非常標(biāo)準(zhǔn)。說回 GPIO。輸出模式要分清推挽和開漏驅(qū)動(dòng) LED、蜂鳴器用推挽I2C 總線這種需要外部上拉的總線一定要用開漏。輸入模式要區(qū)分上拉、下拉和浮空按鍵檢測(cè)建議內(nèi)部上拉傳感器信號(hào)建議根據(jù)傳感器輸出方式選不要盲目用浮空??傊瓽PIO 初始化時(shí)不要只設(shè)置方向還要認(rèn)真設(shè)置上下拉和速度否則會(huì)在功耗和信號(hào)完整性上出問題。4.2 串口 UART 與 printf 重定向串口是嵌入式調(diào)試的第一利器MS32C001-C 的串口模塊支持標(biāo)準(zhǔn)波特率內(nèi)部 RC 在常溫下跑 115200 基本沒問題。但要注意如果系統(tǒng)主頻有誤差波特率就會(huì)有誤差兩個(gè)設(shè)備累計(jì)誤差超過 2% 就可能丟數(shù)據(jù)。所以量產(chǎn)前一定要實(shí)測(cè)串口通信的穩(wěn)定性。使用串口之前先初始化時(shí)鐘、GPIO 復(fù)用功能和 UART 外設(shè)。GPIO 復(fù)用這塊很多人容易忽略以為初始化了 GPIO 就能直接做串口其實(shí)必須把對(duì)應(yīng)的引腳切換成復(fù)用功能否則數(shù)據(jù)完全不通。這點(diǎn)在數(shù)據(jù)手冊(cè)的引腳功能表里會(huì)有詳細(xì)說明我強(qiáng)烈建議畫原理圖的時(shí)候就把復(fù)用關(guān)系標(biāo)出來后續(xù)寫代碼就清楚了。printf 重定向是串口調(diào)試最實(shí)用的技巧。在 Keil 環(huán)境里重定向標(biāo)準(zhǔn)輸出到串口只要實(shí)現(xiàn)一下 fputc 函數(shù)int fputc(int ch, FILE *f) { // 把 ch 發(fā)送到調(diào)試串口 USART_SendData(DEBUG_UART, (uint8_t)ch); while (USART_GetFlagStatus(DEBUG_UART, USART_FLAG_TXE) RESET); return ch; }重定向之后直接用 printf 打印調(diào)試信息效率非常高。注意用微庫(kù)時(shí)要勾選 MicroLIB否則浮點(diǎn)格式化等比較耗資源。調(diào)試沒問題之后正式代碼里可以減少甚至關(guān)掉 printf避免拖慢主循環(huán)。4.3 ADC 多通道采集與參考電壓校正ADC 這塊是我在項(xiàng)目里花費(fèi)時(shí)間最多的外設(shè)因?yàn)椴杉瘻貪穸葌鞲衅鞯哪M輸出電壓時(shí)精度直接影響了 PID 控制效果。MS32C001-C 的 ADC 是 12 位精度理論上最高分辨率可以到 4096 級(jí)但實(shí)際精度受參考電壓穩(wěn)定性、PCB 噪聲和采樣時(shí)間影響很大。我的建議是ADC 參考電壓不能直接接在系統(tǒng)電源上最好用高精度基準(zhǔn)源或者至少保證 VDD 紋波足夠小。采樣時(shí)間不要設(shè)太短信號(hào)源阻抗高時(shí)采樣時(shí)間不足會(huì)導(dǎo)致充電不充分采集值偏低。我一般是盡量把采樣周期設(shè)置得比最小要求長(zhǎng)一點(diǎn)換取穩(wěn)定度。多通道采集時(shí)通道切換后要等一小段時(shí)間再啟動(dòng)轉(zhuǎn)換否則前一次采樣的殘留電荷會(huì)影響結(jié)果。如果需要精確測(cè)量必須做軟件校準(zhǔn)。方法是用一個(gè)已知精度的參考電壓輸入到某個(gè) ADC 通道然后計(jì)算增益和偏移誤差再把所有通道的讀數(shù)做線性補(bǔ)償。做電池電壓檢測(cè)時(shí)還要注意分壓電阻的阻值選擇。分壓電阻太大漏電流小但采樣會(huì)變慢太小采樣快但會(huì)白白耗電。在低功耗設(shè)備里一般用 100K 量級(jí)的分壓電阻并且用 MOS 管或者 GPIO 控制分壓網(wǎng)絡(luò)的通斷只在測(cè)量瞬間才上電測(cè)量完立刻斷電。4.4 PWM 輸出與定時(shí)器使用經(jīng)驗(yàn)PWM 輸出在電機(jī)調(diào)速、燈光調(diào)光、蜂鳴器音調(diào)控制上非常常用。MS32C001-C 的定時(shí)器可以輸出多路帶死區(qū)的 PWM直接驅(qū)動(dòng)半橋電路或者做燈光控制很方便。我做的溫控器里就用 PWM 控制加熱棒的功率占空比由 PID 運(yùn)算結(jié)果更新溫控穩(wěn)定度比傳統(tǒng)的繼電器通斷方式高很多。配置 PWM 有幾個(gè)規(guī)律分頻和自動(dòng)重裝載值決定頻率預(yù)分頻決定計(jì)數(shù)時(shí)鐘重裝載值決定周期。占空比由比較值決定更新比較值的時(shí)機(jī)最好放在定時(shí)器中斷的底部并且一次更新完成避免多個(gè)寄存器寫入過程中產(chǎn)生中間狀態(tài)。電機(jī)驅(qū)動(dòng)等應(yīng)用要配置死區(qū)避免上下橋同時(shí)導(dǎo)通。如果對(duì)頻率精度要求高建議用外部晶振做定時(shí)器時(shí)鐘源內(nèi)部 RC 在不同溫度下會(huì)有偏移。我在實(shí)際調(diào) PWM 時(shí)就遇到過一個(gè)現(xiàn)象占空比輸出偶爾跳變一開始以為是芯片問題后來發(fā)現(xiàn)是軟件里更新比較值時(shí)被更高優(yōu)先級(jí)的中斷打斷導(dǎo)致兩個(gè)寄存器寫入不同步。解決辦法是先把比較值算好在關(guān)閉定時(shí)器中斷的情況下同時(shí)更新或者使用影子寄存器機(jī)制一次更新整個(gè)周期。5. 低功耗、可靠性設(shè)計(jì)與量產(chǎn)注意事項(xiàng)5.1 低功耗模式選型與實(shí)測(cè)這顆芯片最大的賣點(diǎn)之一就是功耗Cortex-M0 內(nèi)核本身很省電。我在做項(xiàng)目時(shí)把待機(jī)功耗做到了很低具體數(shù)值受外設(shè)和 GPIO 狀態(tài)影響很大。芯片支持多種低功耗模式其實(shí)用選擇只有兩個(gè)原則需要被外部中斷喚醒的就用睡眠模式不需要任何喚醒源的或者在極端低功耗場(chǎng)景下才考慮停機(jī)模式。先講睡眠模式。睡眠模式的特點(diǎn)是 CPU 停住但定時(shí)器和串口等外設(shè)還在跑喚醒后可以繼續(xù)執(zhí)行。這種模式適合做定時(shí)喚醒類的應(yīng)用比如每隔幾秒采集一次傳感器然后繼續(xù)睡覺。進(jìn)入睡眠前把不需要的外設(shè)時(shí)鐘關(guān)掉GPIO 配置為低功耗狀態(tài)這個(gè)低功耗狀態(tài)很有講究懸空的引腳會(huì)帶來非常大的漏電所以要么啟用內(nèi)部上拉要么配置成輸出低電平總之不能讓引腳懸空。再講停機(jī)模式。在停機(jī)模式里主要時(shí)鐘源停止只有少數(shù)喚醒源可以喚醒喚醒后系統(tǒng)相當(dāng)于復(fù)位重啟要從頭初始化。這個(gè)模式適合電池供電、大部分時(shí)間都在休眠的設(shè)備。實(shí)測(cè)待機(jī)電流比睡眠模式低一個(gè)到兩個(gè)數(shù)量級(jí)。我最開始測(cè)試低功耗時(shí)電流一直偏高排查了很久才發(fā)現(xiàn)是 GPIO 的問題。有個(gè)引腳接的傳感器模塊模塊內(nèi)部有上拉電阻主控停機(jī)時(shí)模塊仍然從引腳灌電流進(jìn)來導(dǎo)致待機(jī)電流下不去。后來在傳感器模塊的供電線上加了 MOS 管主控睡眠的時(shí)候把整個(gè)模塊斷電才達(dá)到理想待機(jī)電流。這個(gè)屬于硬件設(shè)計(jì)和軟件聯(lián)動(dòng)的經(jīng)驗(yàn)遇到功耗問題首先要查的往往不是睡眠模式配置而是外部電路在睡眠時(shí)的漏電路徑。5.2 Flash 擦寫壽命與掉電保存參數(shù)工程上有時(shí)候需要把設(shè)備參數(shù)存在 Flash 里比如校準(zhǔn)系數(shù)、設(shè)備地址、用戶設(shè)置。Flash 的擦寫次數(shù)是有限的你需要規(guī)劃好數(shù)據(jù)存儲(chǔ)策略。大多數(shù) M0 芯片 Flash 的擦寫壽命在 1 萬(wàn)次到 10 萬(wàn)次量級(jí)具體看手冊(cè)。如果你的設(shè)備經(jīng)常寫參數(shù)一天寫幾百次那很快就到壽命上限了。解決思路是盡量只在參數(shù)變化時(shí)寫 Flash不要周期性重復(fù)寫同樣的值。使用多備份存儲(chǔ)交替寫入多個(gè)扇區(qū)一邊寫壞了就用另一塊。寫入時(shí)先寫數(shù)據(jù)到臨時(shí)扇區(qū)校驗(yàn)成功后更新正式扇區(qū)防止寫入中途掉電造成數(shù)據(jù)損壞。關(guān)鍵參數(shù)加校驗(yàn)和或 CRC讀取時(shí)校驗(yàn)校驗(yàn)不通過就用默認(rèn)值。掉電保存是這個(gè)方向里最容易出問題的點(diǎn)。如果系統(tǒng)正在擦寫 Flash 時(shí)斷電Flash 內(nèi)容可能是未定義狀態(tài)。所以要做掉電檢測(cè)加了電壓監(jiān)測(cè)功能當(dāng)檢測(cè)到電壓低于某個(gè)閾值時(shí)先完成當(dāng)前寫入并停止新的寫入。光靠軟件延時(shí)做不到這一點(diǎn)必須在設(shè)計(jì)時(shí)就預(yù)留分壓檢測(cè)網(wǎng)絡(luò)和中斷引腳。5.3 批量燒錄、代碼保護(hù)與工廠測(cè)試量產(chǎn)燒錄首先要考慮效率。用調(diào)試器逐臺(tái)燒錄太慢建議上脫機(jī)編程器或者下載底座一次性燒多臺(tái)。脫機(jī)燒錄前把固件、配置、序列號(hào)規(guī)則都準(zhǔn)備好燒錄時(shí)再校驗(yàn)一遍。芯片的讀保護(hù)功能要記得開啟開啟之后別人沒法直接通過調(diào)試器讀出固件內(nèi)容防止方案被輕易抄走。我建議量產(chǎn)流程里加一個(gè)“燒錄后自檢”環(huán)節(jié)程序加上自檢邏輯上電后先檢查按鍵、LED、傳感器等關(guān)鍵外設(shè)自檢通過后通過串口打印測(cè)試結(jié)果工廠用掃碼槍掃描產(chǎn)品條碼配合自動(dòng)化測(cè)試工裝把結(jié)果存檔。這樣產(chǎn)線就能自動(dòng)篩掉焊接不良和芯片本身有問題的板子。這里還有一個(gè)經(jīng)驗(yàn)芯片的 GPIO 在復(fù)位瞬間是高阻態(tài)如果產(chǎn)品里直接驅(qū)動(dòng)了大功率負(fù)載上電瞬間會(huì)引起負(fù)載誤動(dòng)作。量產(chǎn)測(cè)試時(shí)要特別留意通電順序必要時(shí)在軟件里做“上電安全狀態(tài)”在所有 GPIO 初始化完成后才允許驅(qū)動(dòng)外部大功率設(shè)備防止燒壞后面電路。6. 實(shí)際項(xiàng)目里踩過的坑與排查手冊(cè)6.1 調(diào)試器連不上芯片怎么辦SWD 連接失敗是個(gè)經(jīng)典問題。常見原因有好幾種我按出現(xiàn)頻率排序一下現(xiàn)象可能原因排查方法連接失敗提示 No target connected目標(biāo)板沒有供電先測(cè)量 VDD/GND確認(rèn)電壓連接失敗但供電正常SWDIO/SWCLK 接反檢查杜邦線或排針順序連接時(shí)斷時(shí)續(xù)調(diào)試線太長(zhǎng)、接觸不良縮短線纜降低 SWD 時(shí)鐘頻率燒錄時(shí)報(bào) Erase Failed讀保護(hù)已開啟先用工具執(zhí)行整片擦除解鎖程序運(yùn)行后連接不上代碼里把調(diào)試引腳復(fù)用成普通 GPIO檢查 GPIO 復(fù)用配置預(yù)留調(diào)試恢復(fù)入口我自己遇到的坑是芯片的 BOOT 引腳狀態(tài)和復(fù)位引腳電平導(dǎo)致下載失敗。當(dāng)時(shí)為了省成本BOOT 引腳直接懸空結(jié)果在某些環(huán)境下進(jìn)入異常模式。后來按照手冊(cè)要求接了外部下拉電阻下載恢復(fù)正常。所以在做板子的時(shí)候就把 BOOT、NRST 引腳的默認(rèn)狀態(tài)設(shè)計(jì)好不要等調(diào)試時(shí)再來補(bǔ)電阻。6.2 串口數(shù)據(jù)亂碼或丟字節(jié)串口亂碼一般不是芯片壞而是波特率或者時(shí)鐘配置問題。排查順序檢查系統(tǒng)主頻配置主頻本身與預(yù)期不符那波特率一定會(huì)偏。檢查串口模塊的時(shí)鐘源是內(nèi)部 RC 還是晶振內(nèi)部 RC 在高溫下精度會(huì)下降。檢查雙方邏輯電平TTL 電平要一致RS232 和 TTL 不能直接對(duì)接。檢查是否共地不共地會(huì)導(dǎo)致噪聲大、亂碼。檢查發(fā)送緩沖區(qū)溢出提示信息刷屏導(dǎo)致程序卡頓。我實(shí)測(cè)過內(nèi)部 RC 在低溫環(huán)境下波特率誤差會(huì)變大所以如果你做的是戶外設(shè)備串口長(zhǎng)時(shí)間通信時(shí)建議用外部晶振。如果是設(shè)備之間短距離調(diào)試內(nèi)部 RC 完全可以接受。6.3 低功耗模式待機(jī)電流偏大這個(gè)問題的排查順序應(yīng)該是先量電流確認(rèn)是跳動(dòng)還是穩(wěn)定偏高。逐個(gè)關(guān)外設(shè)定位是哪個(gè)外設(shè)還在耗電。檢查 GPIO 浮空。檢查外部模塊的靜態(tài)電流。檢查 LDO 自身功耗和泄漏。我發(fā)現(xiàn)大多數(shù)低功耗問題都不是芯片本身的問題而是板子上的外圍電路。比如一個(gè) LED 指示燈的限流電阻計(jì)算錯(cuò)誤一個(gè)上拉電阻忘去掉一個(gè)外部傳感器在睡眠時(shí)仍然保持工作狀態(tài)這些都會(huì)輕松吃掉幾十微安電流。做低功耗產(chǎn)品時(shí)硬件原理圖評(píng)審要非常嚴(yán)格每一路電阻、每一個(gè)芯片模塊在睡眠模式下的狀態(tài)都要畫清楚不能只靠軟件。6.4 ADC 采集值跳動(dòng)大、誤差大ADC 跳字問題是嵌入式里面的高頻問題。先排除是不是信號(hào)源本身波動(dòng)再用鑷子短路輸入引腳到地如果讀數(shù)不歸零說明 ADC 內(nèi)部或基準(zhǔn)有問題。還有可能是采樣保持時(shí)間太短讀取速度過快導(dǎo)致前一次充電電荷干擾。另外電源紋波會(huì)讓 ADC 參考電壓晃動(dòng)上下幾 mV 的波動(dòng)在 12 位分辨率下就會(huì)產(chǎn)生明顯的跳碼。我的做法是在 ADC 前加一級(jí) RC 低通濾波器時(shí)間常數(shù)根據(jù)采樣率來定既可以濾掉高頻噪聲又能防止混疊。如果信號(hào)變化很慢比如溫度傳感器讀數(shù)可以在軟件上做多次采樣取平均值或者用中值濾波去掉毛刺。但濾波代碼要寫得隱蔽不能讓整個(gè)采集流程因?yàn)闉V波而變得響應(yīng)遲鈍。6.5 芯片復(fù)位不規(guī)律、程序跑飛芯片復(fù)位不規(guī)律有兩種大方向一是硬件上電源和干擾問題二是軟件上的看門狗、棧溢出和異常中斷。先測(cè)電壓排除供電跌落再看復(fù)位引腳是否有毛刺最后查代碼看看門狗有沒有被意外觸發(fā)。如果代碼里開了中斷但沒有把所有中斷入口都補(bǔ)齊被觸發(fā)后程序跳到異常向量表最高優(yōu)先級(jí)的地方表現(xiàn)就是看起來復(fù)位了實(shí)際是崩潰了。在這里提供個(gè)實(shí)用建議可以在主循環(huán)的低優(yōu)先級(jí)位置加入關(guān)鍵變量和調(diào)用深度的校驗(yàn)發(fā)現(xiàn)異常時(shí)打印關(guān)鍵信息后主動(dòng)重啟。這個(gè)思路可以幫你快速定位跑飛原因。中斷優(yōu)先級(jí)和臨界區(qū)保護(hù)也要好好檢查M0 的中斷控制器雖然簡(jiǎn)單但優(yōu)先級(jí)分組和使能順序很容易被忽略特別是在初始化多個(gè)外設(shè)時(shí)。寫在最后的一點(diǎn)經(jīng)驗(yàn)從拿到 MS32C001-C 到完成整個(gè)模擬項(xiàng)目我最深的體會(huì)是這顆芯片的“上手門檻”比想象低但上限完全取決于你對(duì)細(xì)節(jié)的把握。Cortex-M0 平臺(tái)降低了編程難度可是硬件電路設(shè)計(jì)上的坑比如 GPIO 浮空、電源紋波、低功耗漏電路徑、Flash 寫入可靠性一個(gè)都不會(huì)少。真正花時(shí)間去解決的很多時(shí)候并不是復(fù)雜的外設(shè)框架而是一個(gè)下拉電阻沒加、一個(gè)采樣時(shí)序沒等夠這類小問題。如果你正在評(píng)估這顆芯片我建議先去認(rèn)真讀一遍數(shù)據(jù)手冊(cè)的引腳功能表和電氣特性表畫原理圖前把每個(gè)引腳的默認(rèn)狀態(tài)、復(fù)用關(guān)系、耐壓要求都標(biāo)出來再做開發(fā)。芯片本身是一個(gè)不錯(cuò)的低成本 32 位方案剩下的就看你的系統(tǒng)設(shè)計(jì)功力了。希望這篇文章能幫你少走幾步彎路尤其在低功耗和量產(chǎn)這兩個(gè)環(huán)節(jié)做到了就能穩(wěn)定交付。