藥丸入門到工程實(shí)戰(zhàn)與避坑)
很多工程師第一次接觸STM32都是從一塊藍(lán)色的小板子開始的。那塊板子叫STM32F103C8T6最小系統(tǒng)板江湖人稱“藍(lán)藥丸”。這個(gè)外號(hào)很傳神它既便宜又夠用像一顆能讓你快速進(jìn)入ARM嵌入式世界的神奇藥丸。無論你是學(xué)生做課程設(shè)計(jì)、創(chuàng)客做原型驗(yàn)證還是工程師做小批量產(chǎn)品大概率都繞不開這顆芯片。今天這篇就圍繞這顆“藍(lán)藥丸”把硬件資源、開發(fā)方式、實(shí)戰(zhàn)案例到高頻踩坑點(diǎn)一次講清楚。1. 為什么F103C8T6能成為“工程師標(biāo)配”硬件資源與定位分析1.1 這顆芯片的“家底”到底有多厚STM32F103C8T6屬于STM32F1系列中的“中等密度”型號(hào)采用ARM Cortex-M3內(nèi)核主頻最高72MHz。Cortex-M3雖然是ARMv7-M架構(gòu)里的老將但它的指令效率和中斷響應(yīng)放在今天的低成本嵌入式場景里依然能打。芯片內(nèi)置64KB Flash和20KB SRAM這個(gè)存儲(chǔ)組合乍看不大的但配合DMA、定時(shí)器、ADC、USART、I2C、SPI、USB等外設(shè)足以覆蓋大量工業(yè)控制和消費(fèi)電子產(chǎn)品需求。具體來說F103C8T6擁有37個(gè)GPIOLQFP48封裝多數(shù)引腳兼容5V輸入這對(duì)和舊電路銜接非常友好2個(gè)12位ADC最多10個(gè)外部輸入通道采樣時(shí)間可編程3個(gè)通用定時(shí)器TIM2/3/4加1個(gè)高級(jí)定時(shí)器TIM1支持PWM、輸入捕獲、編碼器接口3個(gè)USART、2個(gè)I2C其中I2C1支持SMBus/PMBus、2個(gè)SPIUSB 2.0全速設(shè)備控制器注意沒有USB Host1個(gè)CAN 2.0B接口這是很多汽車電子和工業(yè)總線場景選它的重要原因多個(gè)DMA通道支持外設(shè)到內(nèi)存、內(nèi)存到外設(shè)、內(nèi)存到內(nèi)存?zhèn)鬏攺男詢r(jià)比來看這塊芯片散片價(jià)格長期在幾塊錢到十幾塊錢人民幣區(qū)間對(duì)比同性能的其他MCU幾乎沒有對(duì)手。這也是為什么很多產(chǎn)品在選型評(píng)審時(shí)只要資源不是特別緊張工程師第一反應(yīng)就是“先拿F103C8T6兜底”。1.2 最小系統(tǒng)板“藍(lán)藥丸”的電路構(gòu)成與設(shè)計(jì)邏輯市面上最常見的藍(lán)色最小系統(tǒng)板以8MHz晶振作為HSE高速外部時(shí)鐘源。注意板載晶振通常配兩個(gè)20pF左右負(fù)載電容但實(shí)際有些廉價(jià)板子會(huì)省掉這兩個(gè)電容導(dǎo)致RTC或高精度通信出現(xiàn)頻偏。我的建議是拿到板子后先用邏輯分析儀或頻率計(jì)確認(rèn)一下時(shí)鐘是否準(zhǔn)確尤其是在做USART和CAN通信時(shí)。板子上的AM1117-3.3穩(wěn)壓芯片把USB或排針輸入的5V降壓到3.3V給芯片供電。這里有個(gè)細(xì)節(jié)很多藍(lán)色板子上的LDO壓差較大如果你用3.7V鋰電池直接供電輸出可能只有2.8V左右此時(shí)芯片雖然能跑但ADC參考電壓、Flash讀取可靠性都會(huì)受影響。所以用電池供電時(shí)盡量加一個(gè)低壓差LDO比如ME6211或XC6206系列。復(fù)位電路由10kΩ上拉電阻加100nF電容組成NRST引腳低電平復(fù)位。BOOT0和BOOT1引腳一般通過跳線或撥碼開關(guān)引出方便切換啟動(dòng)模式從主Flash啟動(dòng)正常模式、從系統(tǒng)存儲(chǔ)器啟動(dòng)ISP串口下載、從SRAM啟動(dòng)調(diào)試用。很多新手把BOOT0不小心撥到1然后發(fā)現(xiàn)程序下載進(jìn)去卻跑不起來就是這個(gè)原因。1.3 國產(chǎn)替代視角當(dāng)“藍(lán)藥丸”不再只有ST一家近幾年“國產(chǎn)替代”在MCU領(lǐng)域非常熱F103C8T6因?yàn)樯鷳B(tài)太成熟成為國產(chǎn)廠商對(duì)標(biāo)的首選對(duì)象。市面上主流的國產(chǎn)替代型號(hào)包括GD32F103C8T6兆易創(chuàng)新出品硬件引腳兼容但內(nèi)核是M3改進(jìn)版主頻能到108MHz功耗參數(shù)、ADC精度和ST原廠有細(xì)微差別APM32F103C8T6極海半導(dǎo)體出品兼容性做得相當(dāng)不錯(cuò)很多代碼不用改直接編譯燒錄HK32F103C8T6航順芯片出品主打性價(jià)比但某些外設(shè)寄存器和ST有差異需要仔細(xì)查手冊(cè)MM32F103C8T6靈動(dòng)微電子燒錄工具和ST-Link不完全通用選擇國產(chǎn)替代時(shí)我最直接的提醒是不要只看引腳兼容要拿自己產(chǎn)品里用到的每個(gè)外設(shè)逐個(gè)測試。我遇到過GD32的I2C從機(jī)模式下時(shí)序和ST行為不同導(dǎo)致外掛傳感器偶爾讀不到數(shù)據(jù)也遇到過HK32的ADC在低溫環(huán)境下噪聲偏大需要軟件濾波兜底。如果你做的是大批量產(chǎn)品替換前務(wù)必做完整的硬件兼容性測試和老化測試節(jié)省的那幾塊錢成本可能在售后上加倍賠回去。2. 從零搭建開發(fā)環(huán)境Keil/CubeMX/Arduino的選擇與工程創(chuàng)建細(xì)節(jié)2.1 不同開發(fā)方式的“性格差異”F103C8T6的開發(fā)方式非常多這是它生態(tài)成熟的最大紅利。我按個(gè)人經(jīng)驗(yàn)先把三種主流方式擺出來對(duì)比一下你可以根據(jù)項(xiàng)目需求選擇開發(fā)方式上手難度代碼可控性適合場景標(biāo)準(zhǔn)外設(shè)庫SPL中等比較高老項(xiàng)目維護(hù)、寄存器級(jí)理解HAL庫 CubeMX低中等快速原型、外設(shè)多、圖形化配置寄存器直接操作高最高極致優(yōu)化、資源緊張、學(xué)習(xí)底層Arduino框架極低較低快速驗(yàn)證、創(chuàng)客項(xiàng)目、教育演示我個(gè)人的習(xí)慣是凡是產(chǎn)品級(jí)代碼用CubeMX生成底層初始化再手工加業(yè)務(wù)邏輯凡是快速驗(yàn)證傳感器或通信協(xié)議直接Arduino框架幾分鐘跑通如果需要極致省電或精確時(shí)序才回頭用寄存器。2.2 CubeMX HAL庫手工創(chuàng)建一個(gè)LED工程很多教程讓你直接打開CubeMX點(diǎn)幾下生成工程但我更推薦你手工操作一次理解CubeMX背后發(fā)生了什么。這里以“點(diǎn)亮板載PC13 LED”為例藍(lán)藥丸板載LED通常接PC13但部分版本接PA1使用前看板子絲印。第一步打開STM32CubeMX選擇芯片型號(hào)STM32F103C8Tx。第二步在Pinout視圖里把PC13引腳配置為GPIO_Output然后修改GPIO標(biāo)簽比如命名為LED。如果你用的是PA1版本改成PA1即可其余操作一致。第三步配置RCC的HSE為Crystal/Ceramic ResonatorSYS的Debug設(shè)置為Serial Wire。這里Debug選項(xiàng)千萬別選No Debug否則下載器連不上芯片會(huì)卡死在“No target connected”。第四步時(shí)鐘樹配置頁面里把HCLK改為72MHzCubeMX會(huì)自動(dòng)配置PLL系數(shù)。如果芯片型號(hào)選錯(cuò)選成了F103C8T6但實(shí)際是其他密度型號(hào)時(shí)鐘配置可能會(huì)越界所以在選型階段就要確認(rèn)Flash容量。第五步Project Manager里填寫工程名、選擇工具鏈我一般選MDK-ARM。Code Generator頁面勾選“Generated peripheral initialization as a pair of .c/.h files per peripheral”這樣每個(gè)外設(shè)獨(dú)立文件維護(hù)起來清爽。生成代碼后在main.c的while(1)循環(huán)里加上HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(500);編譯后用ST-Link下載到板子LED以2Hz頻率閃爍。如果你用的是DAP-Link或J-Link在Keil的Options for Target里改調(diào)試器類型即可。2.3 Arduino開發(fā)F103C8T6的極速上手路徑Arduino框架算是“藍(lán)藥丸”出圈的最大功臣之一因?yàn)樗尣皇煜ぜ拇嫫鞯娜艘材苡脦仔写a控制STM32。核心方法是在Arduino IDE里安裝“STM32duino”支持包板卡選擇“Generic STM32F103C series”然后通過ST-Link或串口下載。使用Arduino時(shí)的映射關(guān)系是PB11是內(nèi)置LED如果板子是PA1亮燈版本則需要自己改Serial1對(duì)應(yīng)USART1PA9/PA10Serial2對(duì)應(yīng)USART2PA2/PA3。有個(gè)常見坑Arduino框架下analogWrite輸出的PWM頻率默認(rèn)是1kHz左右如果你要驅(qū)動(dòng)舵機(jī)或電機(jī)需要手動(dòng)改定時(shí)器分頻系數(shù)否則會(huì)有尖銳噪聲或電機(jī)抖動(dòng)。我測過同一個(gè)PID控制算法在Arduino框架和HAL庫下分別實(shí)現(xiàn)運(yùn)行結(jié)果幾乎一樣但前者開發(fā)效率高出3倍以上。唯一讓人頭疼的是啟動(dòng)時(shí)間稍長時(shí)鐘配置沒有HAL那么精細(xì)但在大多數(shù)創(chuàng)客場景完全夠用。3. 引腳功能深度解析這些“隱藏能力”和“默認(rèn)坑”你必須知道3.1 關(guān)鍵引腳的復(fù)用功能與電氣特性F103C8T6的LQFP48封裝只有37個(gè)GPIO但通過復(fù)用功能AF能撐起非常多的外設(shè)。比如PA9和PA10是USART1_TX/RX同時(shí)PA9也是USB的VBUS檢測引腳。很多人剛用USB功能時(shí)報(bào)錯(cuò)“Device not recognized”就是忘了在CubeMX里把PA9配成USB的VBUS功能而不只是GPIO或USART。再比如PB3、PB4、PA15這三個(gè)引腳默認(rèn)是JTAG調(diào)試口的一部分JTDO、NJTRST、JTDI如果你把它們當(dāng)普通GPIO用必須先把JTAG關(guān)閉開啟SWD。CubeMX里的操作是把Debug選項(xiàng)選為Serial Wire系統(tǒng)會(huì)自動(dòng)釋放三個(gè)引腳。如果你不用調(diào)試功能甚至可以選擇No Debug但我不建議因?yàn)楹罄m(xù)想調(diào)試就得擦除整個(gè)Flash才能重新連接。芯片支持5V容忍的引腳包括絕大多數(shù)ADC和I2C引腳但PB2BOOT1沒有引出復(fù)用PA11/PA12為USB DM/DP專用不是5V容忍。我用ADC采集12V電壓時(shí)習(xí)慣加一個(gè)電阻分壓分壓后電壓不超過3.3V也不要試圖直接往ADC引腳灌5V即使芯片標(biāo)稱容忍5V采樣線性度也會(huì)受影響。3.2 引腳布局與PCB布線時(shí)的工程經(jīng)驗(yàn)如果你自己畫F103C8T6的最小系統(tǒng)板而不是買現(xiàn)成的藍(lán)色板子有幾個(gè)布局經(jīng)驗(yàn)值得記錄8MHz晶振盡量靠近OSC_IN/OSC_OUT引腳負(fù)載電容地端走線要短粗晶振下方不要鋪地銅皮覆蓋太多寄生電容過大會(huì)導(dǎo)致起振困難VDDA/VSSA引腳要單獨(dú)接濾波電容推薦1μF和100nF并聯(lián)模擬地和數(shù)字地單點(diǎn)連接NRST引腳對(duì)地接100nF電容并加10kΩ上拉到3.3V防止復(fù)位受干擾VCAP引腳即VDD_1.8V內(nèi)部電壓調(diào)節(jié)器輸出需要外接2.2μF電容這個(gè)電容不能省否則芯片無法正常工作BOOT0引腳加10kΩ下拉電阻避免上電時(shí)電平懸空導(dǎo)致啟動(dòng)模式異常如果你的板子用于電機(jī)驅(qū)動(dòng)或繼電器控制盡量讓大電流回路不要穿過MCU區(qū)域STM32F103的GPIO灌電流/拉電流每個(gè)引腳最大約25mA但所有IO電流總和有限制不要把它當(dāng)功率驅(qū)動(dòng)芯片用。3.3 GPIO配置中常見的“鎖死”與“誤觸發(fā)”案例分析有次我調(diào)試一個(gè)用F103C8T6做的智能家居面板發(fā)現(xiàn)按鍵檢測經(jīng)常誤觸發(fā)示波器一看引腳波形非常干凈最后定位到問題出在GPIO配置成了“Analog”模式而不是“Input”模式。STM32的GPIO模式有4種輸入、輸出、復(fù)用、模擬。按鍵讀取必須用輸入模式且要配置上拉或下拉懸空輸入的浮空狀態(tài)極易受干擾。另一個(gè)“鎖死”案例是GPIO輸出頻率配得太高。CubeMX中GPIO Output Speed有Low/Medium/High三檔如果你把LED控制引腳配成High而且引腳走線較長整板EMI會(huì)很重甚至影響ADC采樣精度。我的經(jīng)驗(yàn)是數(shù)字信號(hào)10MHz以下選Medium足夠I2C和UART這類低速協(xié)議也能正常工作會(huì)減少很多噪聲。4. 經(jīng)典項(xiàng)目實(shí)戰(zhàn)一CubeMXHAL庫模擬IIC讀取MT6701磁編碼器4.1 為什么用軟件IIC而不是硬件IICMT6701是MagnTek出品的一塊磁編碼器芯片能輸出12位角度數(shù)據(jù)并且支持I2C接口和ABZ增量輸出。很多人在F103C8T6上讀取它時(shí)直接用硬件I2C容易翻車——ST的硬件I2C被廣泛詬病它有各種奇怪的時(shí)序毛刺和Busy標(biāo)志卡死問題。雖然新版本庫有所改善但為了穩(wěn)定省心我選擇用GPIO模擬IIC方案。軟件IIC的另一個(gè)好處是引腳選擇完全自由你可以把SCL/SDA放在任意兩個(gè)GPIO上方便PCB布局。對(duì)MT6701這種最高通信速率1MHz的傳感器來說軟件IIC完全跑得動(dòng)。4.2 核心代碼實(shí)現(xiàn)模擬IIC讀寫MT6701先定義GPIO引腳。我在CubeMX中把PB6配為SCLPB7配為SDA都是開漏輸出Open Drain且都使能內(nèi)部上拉。注意開漏輸出加上拉是模擬IIC的標(biāo)準(zhǔn)接法如果是推挽輸出通信時(shí)可能出現(xiàn)總線沖突。以下是關(guān)鍵代碼結(jié)構(gòu)。首先是引腳宏定義#define IIC_SCL_H HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_SET) #define IIC_SCL_L HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_RESET) #define IIC_SDA_H HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_SET) #define IIC_SDA_L HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_RESET) #define IIC_SDA_READ HAL_GPIO_ReadPin(SDA_GPIO_Port, SDA_Pin)然后是延時(shí)函數(shù)我使用DWT-CYCCNT做精確延時(shí)因?yàn)镠AL_Delay只支持毫秒級(jí)IIC時(shí)序需要微秒級(jí)。初始化DWTstatic void DWT_Delay_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static void DWT_Delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); }起始信號(hào)SCL高電平期間SDA拉低。停止信號(hào)SCL高電平期間SDA拉高。發(fā)送一個(gè)字節(jié)時(shí)從最高位開始循環(huán)SDA在SCL低電平期間改變高電平期間保持穩(wěn)定。接收字節(jié)類似在SCL高電平期間讀取SDA。MT6701在I2C通信時(shí)有一條特殊規(guī)則它的I2C地址是0x067位地址但每次讀取角度數(shù)據(jù)需要先發(fā)送寄存器地址0x00再讀兩個(gè)字節(jié)組合后取高12位作為角度值uint16_t MT6701_ReadAngle(void) { uint16_t angle 0; IIC_Start(); IIC_SendByte(0x06 1 | 0x00); // 寫地址 IIC_WaitAck(); IIC_SendByte(0x00); // 角度寄存器地址 IIC_WaitAck(); IIC_Start(); // 重復(fù)起始 IIC_SendByte(0x06 1 | 0x01); // 讀地址 IIC_WaitAck(); uint8_t high IIC_ReadByte(); IIC_SendAck(0); uint8_t low IIC_ReadByte(); IIC_SendNack(); IIC_Stop(); angle ((uint16_t)high 8) | low; return angle 0x0FFF; }4.3 濾波與校準(zhǔn)把原始角度變成穩(wěn)定可用的數(shù)據(jù)從傳感器讀到的原始角度數(shù)據(jù)有兩個(gè)問題一是帶輕微噪聲快速轉(zhuǎn)動(dòng)時(shí)相鄰兩次讀數(shù)可能跳動(dòng)幾個(gè)LSB二是安裝磁場中心偏移可能導(dǎo)致角度不是理想線性。濾波我采用“一階低通滑動(dòng)平均”組合。一階低通適合實(shí)時(shí)性要求高的場景公式是filtered_value alpha * raw_value (1 - alpha) * last_filtered_value;alpha取0.2~0.3具體看你需要多大的平滑度。如果轉(zhuǎn)速較慢可以加大滑窗比如緩存最近10次角度去掉最大最小值后取平均。但注意角度跨越0~4095時(shí)滑動(dòng)平均不能直接對(duì)數(shù)值求平均要做角度環(huán)回處理int16_t diff raw - last; if (diff 2048) diff - 4096; if (diff -2048) diff 4096;校準(zhǔn)方面我做的是一次簡易兩點(diǎn)校準(zhǔn)。把電機(jī)或轉(zhuǎn)軸轉(zhuǎn)到機(jī)械零位記錄角度值z(mì)ero_raw轉(zhuǎn)到已知角度比如90度對(duì)應(yīng)的1024碼記錄value_raw。之后實(shí)際角度計(jì)算公式為angle_deg (raw - zero_raw) * 360.0 / 4096.0;如果你的應(yīng)用要求更高精度可以采集360度均勻分布的多個(gè)點(diǎn)做線性插值校準(zhǔn)表這樣能消除磁鐵安裝偏心帶來的非線性誤差。這個(gè)“濾波校準(zhǔn)”方法論同樣適用于其他磁編碼器和電位器項(xiàng)目是通用技能包。5. 經(jīng)典項(xiàng)目實(shí)戰(zhàn)二用STM32F103C8T6做一個(gè)密碼鎖5.1 系統(tǒng)方案與硬件選型思路密碼鎖是很多新手做的第一個(gè)帶“人機(jī)交互”的項(xiàng)目但要做好也不是那么容易的。我做的方案用了4x4矩陣鍵盤、0.96寸OLED、電磁鎖驅(qū)動(dòng)電路和蜂鳴器核心控制就是F103C8T6。矩陣鍵盤掃描原理很簡單4條行線設(shè)為輸入下拉4條列線設(shè)為推挽輸出每次拉低一條列線讀取行線電平變化。為了防抖我在按鍵檢測里加了20ms延時(shí)消抖并且使用了狀態(tài)機(jī)記錄“按下”和“釋放”事件避免長按重復(fù)觸發(fā)。OLED用I2C接口不需要額外占用太多引腳在CubeMX里配置好I2C1后移植一下U8g2或SSD1306的驅(qū)動(dòng)庫即可。內(nèi)存方面F103C8T6的20KB SRAM跑SSD1306全屏緩沖128x81024字節(jié)毫無壓力。電磁鎖不能用GPIO直接驅(qū)動(dòng)需要加一個(gè)MOS管或繼電器。我用的是AO3400 N-MOS管源極接地、漏極接電磁鎖負(fù)極電磁鎖正極接12V。GPIO輸出3.3V經(jīng)過1kΩ電阻接到柵極再加一個(gè)10kΩ下拉確保上電時(shí)不會(huì)誤動(dòng)作。電磁鎖線圈兩端要并聯(lián)一個(gè)續(xù)流二極管1N4007或SS34否則斷電瞬間的反電動(dòng)勢能打壞MOS管甚至MCU。5.2 核心邏輯設(shè)計(jì)密碼校驗(yàn)流程與掉電存儲(chǔ)密碼校驗(yàn)屬于典型的狀態(tài)機(jī)應(yīng)用我設(shè)計(jì)的流程是系統(tǒng)空閑態(tài)OLED顯示輸入界面用戶輸入數(shù)字每按一個(gè)鍵動(dòng)態(tài)累加屏幕顯示*號(hào)輸入完成后按確認(rèn)鍵與存儲(chǔ)在Flash中的密碼比對(duì)匹配成功繼電器/MOS管導(dǎo)通電磁鎖開鎖3秒OLED顯示歡迎信息匹配失敗蜂鳴器報(bào)警連續(xù)錯(cuò)3次鎖定鍵盤30秒關(guān)鍵點(diǎn)在于“密碼存儲(chǔ)”。F103C8T6有64KB Flash最后一個(gè)扇區(qū)大小為1KB我利用內(nèi)部Flash模擬EEPROM。寫入密碼前先擦除整個(gè)扇區(qū)然后把密碼數(shù)據(jù)寫入到扇區(qū)起始地址。這樣即使斷電密碼也不會(huì)丟失。代碼大致如下#define PASSWORD_FLASH_ADDR 0x0800FC00 void Flash_WritePassword(uint32_t password) { HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef erase; erase.TypeErase FLASH_TYPEERASE_PAGES; erase.PageAddress PASSWORD_FLASH_ADDR; erase.NbPages 1; uint32_t pageErr 0; HAL_FLASHEx_Erase(erase, pageErr); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, PASSWORD_FLASH_ADDR, password); HAL_FLASH_Lock(); }讀取密碼時(shí)直接通過指針訪問Flash地址uint32_t password *(__IO uint32_t *)PASSWORD_FLASH_ADDR;注意Flash擦除壽命約為1萬次頻繁修改密碼會(huì)消耗壽命但對(duì)密碼鎖這種低頻寫入場景綽綽有余。5.3 實(shí)測體驗(yàn)與改進(jìn)方向?qū)崪y下來矩陣鍵盤掃描時(shí)偶爾出現(xiàn)按下沒反應(yīng)的情況后來排查出原因是行列掃描間隔太短加上按鍵內(nèi)阻較高導(dǎo)致檢測不到完整電平變化。我把每列掃描間隔從1ms加到了5ms并在讀取行線的瞬間加入一個(gè)極短的延時(shí)等待電平穩(wěn)定問題解決。蜂鳴器報(bào)警聲提醒有源蜂鳴器直接用GPIO輸出高低電平即可控制無源蜂鳴器則需要輸出頻率信號(hào)才能發(fā)聲。我用的是無源蜂鳴器用一個(gè)定時(shí)器輸出2.7kHz PWM驅(qū)動(dòng)聲音清脆不刺耳。未來的改進(jìn)方向可以加一個(gè)AT24C02外掛EEPROM存儲(chǔ)密碼也可以用ESP01S模塊做成遠(yuǎn)程開鎖。把這些擴(kuò)展組合在一起就是一個(gè)功能完整的智能門鎖原型直接對(duì)接物聯(lián)網(wǎng)應(yīng)用。6. 進(jìn)階玩法FreeRTOS移植、LVGL界面與ESP01S聯(lián)網(wǎng)6.1 FreeRTOS在F103C8T6上的移植要點(diǎn)用“藍(lán)藥丸”跑FreeRTOS能極大提升復(fù)雜項(xiàng)目的代碼組織效率。CubeMX里可以直接在Middleware中選擇FreeRTOSCMSIS_V1或V2都可以推薦選V2。創(chuàng)建任務(wù)時(shí)給每個(gè)任務(wù)分配合理的棧空間比如按鍵掃描任務(wù)512字節(jié)、OLED刷新任務(wù)1024字節(jié)、主業(yè)務(wù)邏輯任務(wù)1024字節(jié)。F103C8T6只有20KB SRAM跑FreeRTOS后堆內(nèi)存會(huì)緊張。我的經(jīng)驗(yàn)是讓代碼只在任務(wù)啟動(dòng)階段分配必要的內(nèi)存循環(huán)體內(nèi)避免動(dòng)態(tài)malloc寧可預(yù)先定義固定大小數(shù)組也不要讓堆碎片化。實(shí)測跑三個(gè)任務(wù)加一個(gè)軟件定時(shí)器在優(yōu)化等級(jí)-O2下FreeRTOS用掉約2.5KB RAM整體還有10KB多可用足夠絕大多數(shù)應(yīng)用。另外中斷里不要直接調(diào)用FreeRTOS API應(yīng)該用vTaskNotifyGiveFromISR或信號(hào)量去通知任務(wù)。這是很多新手寫編碼器計(jì)數(shù)、串口接收時(shí)容易踩的坑中斷里調(diào)用xQueueSend如果不做中斷保護(hù)會(huì)發(fā)生任務(wù)調(diào)度錯(cuò)誤。6.2 LVGL顯示界面讓藍(lán)藥丸擁有“智能屏”質(zhì)感LVGL移植到F103C8T6需要先想清楚一個(gè)問題用單色屏還是彩色屏如果是0.96寸OLED用LVGL有點(diǎn)大材小用U8g2效率更高如果外接1.8寸TFT SPI屏幕ST7735或ILI9341LVGL才能真正展現(xiàn)價(jià)值。移植LVGL的核心步驟把源碼加入工程配置lv_conf.h里分辨率、顏色深度配合TFT屏選RGB565、內(nèi)存大小。我通常給LVGL分配一個(gè)10KB的緩沖區(qū)做draw buffer刷新采用局部刷新策略否則F103的CPU和SRAM都扛不住全屏多次刷新。一個(gè)可行的demo在屏上做一個(gè)圓形儀表盤顯示電機(jī)角度外圈加一個(gè)實(shí)時(shí)更新的數(shù)字。LVGL的lv_arc控件和lv_label控件組合起來30行代碼能實(shí)現(xiàn)很炫的效果。注意LVGL的tick和任務(wù)心跳要喂好周期建議5ms調(diào)用一次lv_timer_handler否則動(dòng)畫會(huì)卡。6.3 ESP01S接入F103C8T6從AT指令到MQTT協(xié)議的實(shí)戰(zhàn)ESP01S是ESP8266的極小封裝版本出廠自帶AT固件通過串口控制非常方便。F103C8T6通過USART2連接ESP01S使用AT指令做TCP連接甚至MQTT發(fā)布訂閱是物聯(lián)網(wǎng)原型項(xiàng)目的高性價(jià)比選擇。實(shí)際通信時(shí)我的建議是不用AT指令的“透傳模式”做項(xiàng)目原型因?yàn)锳T指令是以字符串為單位的返回內(nèi)容里可能夾雜錯(cuò)誤碼和回顯解析麻煩。更好的做法是直接用ATCIPSEND發(fā)送裸數(shù)據(jù)利用STM32的串口空閑中斷把返回?cái)?shù)據(jù)按行分割然后進(jìn)入狀態(tài)機(jī)解析。已知的坑ESP01S模塊上電瞬態(tài)電流可達(dá)300mA3.3V由F103板載LDO提供時(shí)會(huì)掉電復(fù)位。解決方法是外接一個(gè)單獨(dú)3.3V電源比如AMS1117加輸入電容470μFESP01S和STM32共地但不共電源。我的實(shí)現(xiàn)里用Multiplexer做了電源控制平時(shí)模塊斷電需要聯(lián)網(wǎng)時(shí)再上電既省電又避免干擾。代碼上我是這樣初始化的void ESP01S_Init(void) { // USART2 115200 8N1 ESP_SendString(AT\r\n); DelayMs(200); ClearBuffer(); ESP_SendString(ATCWMODE1\r\n); // Station模式 DelayMs(500); ClearBuffer(); ESP_SendString(ATCWJAP\your_wifi\,\password\\r\n); DelayMs(3000); ClearBuffer(); ESP_SendString(ATMQTTUSERCFG0,1,\client_id\,\user\,\pass\,0,0,\\\r\n); DelayMs(500); ESP_SendString(ATMQTTCONN0,\broker_ip\,1883,1\r\n); }新版本ESP01S固件支持MQTT指令把協(xié)議棧全部挪到模塊內(nèi)部單片機(jī)只需要收發(fā)簡單的數(shù)據(jù)和狀態(tài)。這個(gè)方案在低復(fù)雜度場景非常好用但如果業(yè)務(wù)邏輯復(fù)雜、需要頻繁重啟和斷線重連我建議把ESP固件刷成MicroPython或Arduino框架在模塊端獨(dú)立處理協(xié)議。6.4 進(jìn)階避坑LVGLFreeRTOSESP01S組合的穩(wěn)定性優(yōu)化把FreeRTOS、LVGL、ESP01S放在同一個(gè)項(xiàng)目中時(shí)穩(wěn)定性問題會(huì)成倍放大。我遇到的一個(gè)典型死機(jī)場景是WiFi斷線重連時(shí)ESP01S返回大量數(shù)據(jù)串口中斷頻繁觸發(fā)LVGL刷新任務(wù)又占用了大量CPU時(shí)間導(dǎo)致看門狗超時(shí)復(fù)位。解決辦法有三步串口接收改為DMA空閑中斷降低CPU中斷負(fù)載LVGL刷新任務(wù)優(yōu)先級(jí)設(shè)為低給通信任務(wù)留出時(shí)間片在通信任務(wù)里做數(shù)據(jù)流控每次從緩沖區(qū)取固定字節(jié)數(shù)不讓接收緩沖溢出這個(gè)優(yōu)化套路不僅適用于F103C8T6任何帶GUI和通信的嵌入式項(xiàng)目都通用。你在設(shè)計(jì)任務(wù)優(yōu)先級(jí)時(shí)一定要想清楚哪些任務(wù)是硬實(shí)時(shí)的通信、電機(jī)控制哪些是軟實(shí)時(shí)的顯示刷新、按鍵掃描別把軟實(shí)時(shí)任務(wù)截?cái)嗔擞矊?shí)時(shí)任務(wù)的執(zhí)行窗口。7. 低功耗設(shè)計(jì)、加密保護(hù)與產(chǎn)品化前的打磨7.1 Stop模式與喚醒源配置很多F103C8T6做電池供電產(chǎn)品時(shí)我會(huì)用到它的低功耗模式。STM32F1系列低功耗能力其實(shí)一般Stop模式靜態(tài)電流在20μA左右但對(duì)比正常72MHz運(yùn)行時(shí)的幾十毫安已經(jīng)是天壤之別。CubeMX配置低功耗的關(guān)鍵是把所有沒用到的GPIO配置為模擬模式Analog關(guān)閉外設(shè)時(shí)鐘然后調(diào)用HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI)。喚醒后第一件事是用HAL_PWREx_EnableUltraLowPower和HAL_PWREx_EnableFastWakeUpF1沒有這兩個(gè)是F0/F4的F1直接SystemInit后重新配置時(shí)鐘。實(shí)際項(xiàng)目里我用EXTI外部中斷喚醒引腳接一個(gè)按鍵或霍爾傳感器。測試數(shù)據(jù)是整機(jī)待機(jī)電流26μA左右喚醒時(shí)間大約幾十微秒到百微秒級(jí)別可以滿足一般門鎖、遙控器、傳感器節(jié)點(diǎn)需求。如果對(duì)功耗要求更高可以選擇STM32L0系列這不是F103的強(qiáng)項(xiàng)。7.2 代碼加密防止固件被讀出的常規(guī)做法F103C8T6可以通過設(shè)置讀保護(hù)RDP防止調(diào)試器讀取Flash內(nèi)容。在CubeMX的Project Manager里可以設(shè)置Option Byte也可以用ST-Link Utility直接設(shè)置。Level 1的讀保護(hù)下普通調(diào)試讀取會(huì)失敗但可以通過整片擦除解除保護(hù)。Level 2則是永久鎖死設(shè)置后連Flash擦除都做不了。產(chǎn)品量產(chǎn)時(shí)建議用Level 1因?yàn)槟憧赡苓€需要留出升級(jí)通道。還想進(jìn)一步加強(qiáng)加密可以在程序里加入U(xiǎn)ID綁定。F103C8T6每顆芯片都有唯一的96位唯一標(biāo)識(shí)讀取方式是從地址0x1FFFF7E8開始讀三個(gè)32位數(shù)據(jù)。把UID和產(chǎn)品授權(quán)碼做CRC校驗(yàn)如果匹配不上則程序跳轉(zhuǎn)到死循環(huán)防止一顆芯片的固件被批量拷貝到另一顆芯片。這種“軟加密”雖然理論上可通過補(bǔ)丁繞過但已經(jīng)能擋住絕大多數(shù)普通用戶了。7.3 量產(chǎn)前要驗(yàn)證的幾個(gè)“看不見的指標(biāo)”做過產(chǎn)品化的朋友都知道樣板功能正常只是第一步。F103C8T6在做批量產(chǎn)品時(shí)有幾個(gè)指標(biāo)容易被忽略ADC線性度不同批次芯片的ADC偏移可能存在差異最好在出廠前校準(zhǔn)零點(diǎn)和滿量程Flash數(shù)據(jù)保持能力在高溫環(huán)境下Flash數(shù)據(jù)保持時(shí)間會(huì)縮短如果產(chǎn)品工作在85℃以上建議把關(guān)鍵配置頻繁備份到外部EEPROM晶振起振余量廉價(jià)板子用的晶振負(fù)載電容可能不匹配低溫環(huán)境下起振失敗的概率會(huì)上升量產(chǎn)前做-20℃低溫測試很有必要引腳焊接可靠性LQFP48引腳間距0.5mm手工焊接容易連錫生產(chǎn)線溫度曲線要調(diào)整好這些指標(biāo)不是靠代碼能解決的必須在硬件設(shè)計(jì)階段留下測試點(diǎn)在試產(chǎn)階段收集數(shù)據(jù)。用F103C8T6做產(chǎn)品第一版幾乎不會(huì)錯(cuò)但如果連這些基礎(chǔ)項(xiàng)都不驗(yàn)證后續(xù)批量翻車就是大概率事件。8. 數(shù)字信號(hào)處理入門在F103C8T6上做HX711稱重傳感器采集8.1 HX711的通信協(xié)議與F103的GPIO模擬HX711是24位高精度ADC芯片專門用于稱重傳感器電橋信號(hào)采集。它和MCU之間是一條簡化版SPI接口PD_SCK時(shí)鐘和DOUT數(shù)據(jù)。通信時(shí)序非常簡單當(dāng)DOUT拉低表示轉(zhuǎn)換完成MCU發(fā)送25個(gè)正脈沖前24位讀出數(shù)據(jù)第25個(gè)脈沖控制增益和通道選擇。我用F103C8T6的兩個(gè)GPIO模擬時(shí)序代碼如下uint32_t HX711_Read(void) { uint32_t value 0; while (HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) GPIO_PIN_SET); for (uint8_t i 0; i 24; i) { HX711_SCK_GPIO_Port-BSRR HX711_SCK_Pin; value 1; if (HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin)) value; HX711_SCK_GPIO_Port-BRR HX711_SCK_Pin; } // 第25個(gè)脈沖選擇A通道128增益 HX711_SCK_GPIO_Port-BSRR HX711_SCK_Pin; HX711_SCK_GPIO_Port-BRR HX711_SCK_Pin; return value; }注意HX711輸出的數(shù)據(jù)是二進(jìn)制補(bǔ)碼最高位是符號(hào)位。要轉(zhuǎn)成有符號(hào)數(shù)必須做符號(hào)擴(kuò)展如果第23位是1把高8位全部填1后再解釋成int32。否則負(fù)數(shù)重量會(huì)變成一個(gè)很大的正數(shù)直接導(dǎo)致顯示異常。8.2 稱重校準(zhǔn)、去皮與平均濾波的完整實(shí)現(xiàn)稱重系統(tǒng)標(biāo)定做過的人都有體會(huì)受力結(jié)構(gòu)和傳感器本身的非線性誤差會(huì)讓你頭疼。我的標(biāo)定方法是兩點(diǎn)標(biāo)定加分段線性插值空秤時(shí)讀取原始RAW值A(chǔ)0對(duì)應(yīng)重量0克放一個(gè)已知重量砝碼比如500克讀取RAW值A(chǔ)1對(duì)應(yīng)重量500克斜率 (A1 - A0) / 500重量 (RAW - A0) / 斜率為了提高測量穩(wěn)定性我在讀取前做一個(gè)“數(shù)值去抖”連續(xù)讀取20次去掉最大值和最小值剩余18次取平均。這個(gè)滑動(dòng)平均濾波器能有效抑制機(jī)械振動(dòng)和電源紋波造成的波動(dòng)。去皮功能在廚房秤、珠寶秤上非常重要。實(shí)現(xiàn)邏輯很簡單每次按下去皮鍵時(shí)把當(dāng)前均值存為tare_raw后續(xù)顯示重量 (current_raw - tare_raw) / 斜率。這個(gè)過程不需要額外硬件純軟件解決。8.3 HX711與F103C8T6聯(lián)調(diào)的常見問題我遇到過DOUT一直為高電平的問題排查后發(fā)現(xiàn)是傳感器電橋的激勵(lì)電壓沒接好。HX711的AVDD供橋電壓通常接5V或3.3V給傳感器電橋的E和E-必須正確連接否則差分輸入超出共模范圍DOUT永遠(yuǎn)不產(chǎn)生轉(zhuǎn)換完成信號(hào)。還有一個(gè)常見問題是電源干擾導(dǎo)致讀數(shù)漂移。HX711是24位高精度ADC0.1μV級(jí)別的噪聲都會(huì)被放大電源紋波稍大就會(huì)在最后幾位上產(chǎn)生劇烈跳動(dòng)。解決辦法是給HX711的供電加LC濾波并且讓MCU和HX711不要共用模擬電源平面。測試時(shí)把電機(jī)或其他大電流外設(shè)關(guān)掉再采集效果立竿見影。9. GPIO引腳功能與“藍(lán)藥丸”的部分應(yīng)用案例9.1 智能家居安防系統(tǒng)很多人的第一套完整項(xiàng)目基于F103C8T6做智能家居安防系統(tǒng)基本架構(gòu)是人體紅外傳感器PIR、煙霧傳感器、門窗磁簧、OLED顯示、蜂鳴器報(bào)警加ESP01S聯(lián)網(wǎng)上報(bào)。PIR傳感器輸出一個(gè)數(shù)字電平信號(hào)只要GPIO配置成輸入上拉檢測到人體時(shí)電平變化即可觸發(fā)中斷。煙霧傳感器MQ-2輸出模擬量需要ADC采集并在軟件里做閾值判斷。F103的ADC是12位輸入范圍0~3.3V換算關(guān)系是電壓 ADC值 * 3.3 / 4096。系統(tǒng)邏輯我建議放在FreeRTOS任務(wù)里傳感器讀取任務(wù)每100ms跑一次狀態(tài)機(jī)和報(bào)警邏輯單獨(dú)建任務(wù)顯示任務(wù)獨(dú)立刷新。這樣做了后安防系統(tǒng)長時(shí)間運(yùn)行的穩(wěn)定性可以得到顯著提升。9.2 智能交通燈控制用定時(shí)器PWM和狀態(tài)機(jī)模擬另一個(gè)很適合新手練手的項(xiàng)目是多路口交通燈控制。F103C8T6的每個(gè)定時(shí)器可以輸出多路PWM但交通燈控制其實(shí)不需要PWM只需要GPIO開關(guān)高低電平。核心難點(diǎn)在于狀態(tài)機(jī)設(shè)計(jì)紅燈亮45秒、綠燈亮30秒、黃燈閃3秒多路口之間要有相位差。我用TIM2做1ms時(shí)基中斷在主循環(huán)里維護(hù)一個(gè)時(shí)間戳狀態(tài)機(jī)根據(jù)當(dāng)前時(shí)間和路口相位切換顏色。引腳分配時(shí)把紅黃綠三色燈接在同一個(gè)端口的高、中、低bit上比如PC0、PC1、PC2這樣可以用一個(gè)掩碼操作同時(shí)改變?nèi)齻€(gè)燈效率極高。9.3 基于F103C8T6的示波器/邏輯分析儀民間方案F103C8T6的ADC最高采樣率約1MHz在12位分辨率下如果犧牲分辨率到8位配合定時(shí)器觸發(fā)采樣可以做一臺(tái)簡易示波器。雖然帶寬只有幾十kHz但用來觀察音頻信號(hào)、PWM波形、電源紋波已經(jīng)非常實(shí)用。整體思路定時(shí)器觸發(fā)ADC連續(xù)采樣DMA搬運(yùn)到內(nèi)存環(huán)形緩沖區(qū)然后通過USB虛擬串口把數(shù)據(jù)發(fā)送到上位機(jī)。上位機(jī)寫一個(gè)Python腳本用matplotlib實(shí)時(shí)繪圖。當(dāng)作一個(gè)周末娛樂項(xiàng)目很有意思也能讓你對(duì)ADC采樣率和DMA傳輸有直觀認(rèn)識(shí)。10. 我的“藍(lán)藥丸”硬件配置經(jīng)驗(yàn)從選型到焊接再到調(diào)試10.1 選型時(shí)容易被忽略的細(xì)節(jié)買“藍(lán)藥丸”板子時(shí)有幾點(diǎn)我會(huì)特別關(guān)注Flash容量很多商家把F103C8T664KB和F103CBT6128KB混賣如果只按型號(hào)篩選會(huì)買到低配版編譯出來固件超過64KB就燒不進(jìn)去晶振有些板子用的是貼片晶振加電容電氣性能更好有些板子用廉價(jià)圓柱晶振容易受溫度影響USB接口老版本板子用Mini USB現(xiàn)在多數(shù)改成Micro USB或Type-C連接器質(zhì)量差的會(huì)出現(xiàn)接觸不良排針焊盤板子上的排針如果是鍍錫的容易氧化鍍金排針成本高但更穩(wěn)定長期使用推薦自己補(bǔ)焊一遍10.2 焊接LQFP48的手工經(jīng)驗(yàn)如果你自己畫板子需要手工焊接F103C8T6我的步驟是先在焊盤上均勻涂一層助焊劑把芯片對(duì)準(zhǔn)絲印用烙鐵固定對(duì)角兩個(gè)引腳然后拖焊法處理其余引腳。拖焊的關(guān)鍵是烙鐵溫度在320~350℃焊錫絲要細(xì)松香助焊劑要足量。焊完后用放大鏡檢查有沒有連錫尤其是0.5mm間距的引腳之間很容易搭錫。如果發(fā)現(xiàn)連錫用吸錫帶配合助焊劑拖走多余焊錫不要用力刮引腳。10.3 調(diào)試?yán)鹘M合ST-Link串口邏輯分析儀調(diào)試F103C8T6我最常用的三件套是ST-Link V2、USB轉(zhuǎn)TTL模塊和24MHz 8通道邏輯分析儀。ST-Link負(fù)責(zé)下載和在線調(diào)試串口負(fù)責(zé)打印日志邏輯分析儀負(fù)責(zé)看波形時(shí)序。遇到難纏問題時(shí)的debug思路先看供電再看時(shí)鐘然后是復(fù)位最后查外設(shè)初始化。90%的“程序跑飛”問題可以通過在main函數(shù)開頭點(diǎn)燈確認(rèn)MCU有沒有啟動(dòng)如果燈都沒亮先查BOOT0和電源。用ST-Link調(diào)試時(shí)我習(xí)慣把HAL_Delay這類時(shí)間敏感函數(shù)用條件編譯包起來正式版關(guān)閉調(diào)試信息輸出避免串口打印拖慢實(shí)時(shí)邏輯。調(diào)試版和發(fā)布版分開構(gòu)建這個(gè)習(xí)慣在很多項(xiàng)目中幫我省了大量時(shí)間。11. 寫在最后F103C8T6還能陪你走多遠(yuǎn)如果只看性能參數(shù)F103C8T6放在今天確實(shí)算不上旗艦但它的生態(tài)、資料和社區(qū)積累是其他芯片很難超越的。我在做新項(xiàng)目時(shí)依然會(huì)優(yōu)先評(píng)估它資源夠用、價(jià)格夠低、資料夠全、替換夠方便。當(dāng)項(xiàng)目真的需要更強(qiáng)性能時(shí)從F103遷移到F407、H743或國產(chǎn)M4內(nèi)核芯片的路徑也極其平滑。這幾年我也用“藍(lán)藥丸”做了一些從原型到小批量的產(chǎn)品一個(gè)電梯樓層顯示器一個(gè)農(nóng)業(yè)大棚環(huán)境采集節(jié)點(diǎn)一個(gè)桌面雕刻機(jī)的控制板。每次項(xiàng)目復(fù)盤時(shí)都會(huì)感嘆這顆小芯片的穩(wěn)定性和易用性確實(shí)經(jīng)得住時(shí)間考驗(yàn)。如果你正在糾結(jié)學(xué)習(xí)STM32該選哪塊板子選“藍(lán)藥丸”大概率不會(huì)后悔。它不會(huì)陪你走最遠(yuǎn)的路但一定是你嵌入式生涯里最值得感謝的那一塊板子。