實(shí)戰(zhàn):從環(huán)境搭建到外設(shè)驅(qū)動(dòng)開(kāi)發(fā))
1. 從零到一我的STM32F10x初階學(xué)習(xí)心路第一次拿到STM32F103C8T6這塊“藍(lán)色小藥丸”開(kāi)發(fā)板時(shí)我和很多初學(xué)者一樣腦子里充滿(mǎn)了問(wèn)號(hào)。51單片機(jī)玩得挺溜但面對(duì)這塊基于ARM Cortex-M3內(nèi)核的32位MCU看著密密麻麻的引腳和陌生的開(kāi)發(fā)環(huán)境確實(shí)有點(diǎn)發(fā)怵。網(wǎng)上資料浩如煙海標(biāo)準(zhǔn)庫(kù)、HAL庫(kù)、LL庫(kù)讓人眼花繚亂更別提還有各種IDE和調(diào)試器。我當(dāng)時(shí)的想法很簡(jiǎn)單不追求一步登天搞什么復(fù)雜項(xiàng)目先得讓這塊板子“活”起來(lái)點(diǎn)個(gè)燈、調(diào)個(gè)串口把最基礎(chǔ)的通路跑通。這就是STM32F10x初階學(xué)習(xí)的核心——建立信心打通任督二脈理解從芯片上電到程序運(yùn)行的全過(guò)程。無(wú)論你是電子專(zhuān)業(yè)的學(xué)生還是想從8位機(jī)升級(jí)的工程師或是創(chuàng)客愛(ài)好者這篇總結(jié)希望能幫你避開(kāi)我踩過(guò)的坑用最直白的方式走穩(wěn)STM32入門(mén)的第一步。2. 學(xué)習(xí)路徑規(guī)劃與環(huán)境搭建實(shí)戰(zhàn)2.1 核心工具鏈選型為什么是Keil MDK STM32CubeMX工欲善其事必先利其器。STM32開(kāi)發(fā)環(huán)境組合眾多對(duì)于初學(xué)者我強(qiáng)烈推薦Keil MDK-ARM配合STM32CubeMX這套組合拳。這不是唯一解但絕對(duì)是“痛苦”最少、最易上手的路徑。Keil MDK-ARM是ARM官方的老牌IDE生態(tài)成熟資料最多。它的編譯器、調(diào)試器與STM32芯片契合度極高特別是對(duì)于STM32F1系列這種經(jīng)典產(chǎn)品支持完美。網(wǎng)上絕大多數(shù)教程、例程都基于Keil遇到問(wèn)題幾乎都能搜到解決方案。雖然它的界面看起來(lái)有些“復(fù)古”但穩(wěn)定性遠(yuǎn)超許多花哨的新工具。STM32CubeMX則是ST官方推出的圖形化配置工具是初學(xué)者的“救星”。它通過(guò)可視化界面配置時(shí)鐘樹(shù)、引腳功能、外設(shè)參數(shù)如串口波特率、定時(shí)器分頻并能一鍵生成初始化代碼框架。這解決了兩個(gè)大問(wèn)題一是手動(dòng)編寫(xiě)大量底層寄存器配置代碼容易出錯(cuò)且枯燥二是能直觀(guān)理解芯片資源分配比如為什么這個(gè)引腳不能同時(shí)用作USART1_TX和SPI1_MISO。注意Keil MDK是商業(yè)軟件但有32KB代碼限制的免費(fèi)版本對(duì)于學(xué)習(xí)和小型項(xiàng)目完全足夠。切勿使用來(lái)路不明的破解版以免引入安全風(fēng)險(xiǎn)或法律問(wèn)題。安裝順序也有講究。建議先安裝Keil MDK再安裝STM32CubeMX。最后在CubeMX里生成工程時(shí)選擇“MDK-ARM V5”作為T(mén)oolchain/IDE這樣生成的工程可以直接用Keil打開(kāi)無(wú)縫銜接。2.2 固件庫(kù)抉擇標(biāo)準(zhǔn)庫(kù)、HAL庫(kù)還是LL庫(kù)這是新手面臨的第一個(gè)關(guān)鍵選擇直接決定了你寫(xiě)代碼的風(fēng)格和難度。標(biāo)準(zhǔn)庫(kù)Standard Peripheral LibraryST早期推出的庫(kù)直接操作外設(shè)寄存器但通過(guò)一層封裝簡(jiǎn)化了操作。代碼效率高對(duì)硬件理解深刻但已經(jīng)停止更新官方主推HAL庫(kù)。對(duì)于F1系列資料極多很多老教程和項(xiàng)目都用它。如果你想深入理解硬件并從長(zhǎng)遠(yuǎn)看想維護(hù)一些老項(xiàng)目可以學(xué)但不建議作為唯一選擇。HAL庫(kù)Hardware Abstraction LayerST現(xiàn)在主推的硬件抽象層庫(kù)。最大特點(diǎn)是跨STM32系列兼容性好。用HAL庫(kù)寫(xiě)的驅(qū)動(dòng)代碼在F1、F4、F7等不同系列間移植修改量很小。它封裝程度更高提供了更友好的API如HAL_UART_Transmit()但代價(jià)是代碼體積稍大執(zhí)行效率略低且有時(shí)過(guò)于抽象不利于理解底層。LL庫(kù)Low-Layer接近寄存器級(jí)別的輕量級(jí)庫(kù)是HAL庫(kù)的底層補(bǔ)充。它提供了更簡(jiǎn)潔、高效的API但需要開(kāi)發(fā)者對(duì)外設(shè)有一定了解。通常與HAL庫(kù)混合使用在關(guān)鍵性能路徑上用LL庫(kù)優(yōu)化。給初學(xué)者的建議直接從HAL庫(kù)開(kāi)始。理由如下第一官方持續(xù)維護(hù)生態(tài)是未來(lái)第二CubeMX完美支持生成的都是HAL庫(kù)代碼事半功倍第三雖然抽象但大大降低了入門(mén)門(mén)檻讓你能快速實(shí)現(xiàn)功能建立正反饋。等用熟了HAL再回頭研究LL庫(kù)或寄存器對(duì)硬件的理解會(huì)更深。2.3 調(diào)試器準(zhǔn)備ST-LINK vs J-LINK vs 串口要讓程序跑進(jìn)去還得能調(diào)試調(diào)試器必不可少。ST-LINK/V2ST官方出品性?xún)r(jià)比之王。價(jià)格低廉山寨版僅二三十元完全支持STM32全系列調(diào)試和燒錄。通過(guò)SWD接口連接只需要四根線(xiàn)VCC, GND, SWDIO, SWCLK。對(duì)于初學(xué)者這是最推薦、最沒(méi)有懸念的選擇。購(gòu)買(mǎi)時(shí)注意選擇口碑好的賣(mài)家避免驅(qū)動(dòng)無(wú)法安裝的坑貨。J-LINKSEGGER公司的產(chǎn)品性能強(qiáng)大支持更多ARM內(nèi)核調(diào)試功能豐富。但價(jià)格昂貴。除非有特殊調(diào)試需求如高速跟蹤否則學(xué)習(xí)階段ST-LINK完全夠用。串口燒錄通過(guò)芯片內(nèi)置的Bootloader配合USB轉(zhuǎn)TTL串口工具可以不用調(diào)試器就下載程序。但這只能下載不能進(jìn)行單步調(diào)試、斷點(diǎn)等操作不適合學(xué)習(xí)調(diào)試技巧。實(shí)操連接以ST-LINK連接STM32F103C8T6核心板為例ST-LINK的3.3V、GND連接核心板對(duì)應(yīng)引腳。ST-LINK的SWDIO連接核心板的PA13SWDIOSWCLK連接PA14SWCLK。打開(kāi)Keil在Options for Target - Debug中選擇Use: ST-Link Debugger點(diǎn)擊Settings確認(rèn)SWD接口被識(shí)別并能讀到芯片ID0x1BA01477 for F103C8T6。3. 第一個(gè)工程從CubeMX配置到LED閃爍3.1 使用STM32CubeMX創(chuàng)建新工程打開(kāi)STM32CubeMX點(diǎn)擊New Project。在芯片選擇器里輸入STM32F103C8在列表中選擇STM32F103C8Tx。注意區(qū)分C864KB Flash和CB128KB Flash我們常用的“藍(lán)色pill”核心板一般是C8T6。芯片選好后進(jìn)入主配置界面。你會(huì)看到一個(gè)芯片的圖形化引腳圖這是最強(qiáng)大的功能之一。系統(tǒng)核心SYS配置在System Core-SYS里將Debug改為Serial Wire。這非常重要它使能了SWD調(diào)試接口否則燒錄一次程序后調(diào)試口可能被禁用導(dǎo)致芯片“鎖死”此時(shí)需用串口ISP方式擦除才能恢復(fù)。時(shí)鐘RCC配置在System Core-RCC里將High Speed Clock (HSE)選擇為Crystal/Ceramic Resonator。我們的核心板外部一般有一個(gè)8MHz的晶振這是系統(tǒng)主時(shí)鐘的來(lái)源。時(shí)鐘樹(shù)Clock Configuration配置這是難點(diǎn)也是重點(diǎn)。點(diǎn)擊頂部的Clock Configuration選項(xiàng)卡。CubeMX會(huì)自動(dòng)給出一個(gè)推薦配置。對(duì)于F103常見(jiàn)配置是HSE8MHz經(jīng)過(guò)PLL倍頻得到72MHz的系統(tǒng)時(shí)鐘SYSCLK。你需要確保PLL源選擇HSE。將PLL倍頻因子PLL Mul設(shè)置為x9因?yàn)?MHz * 9 72MHz。系統(tǒng)時(shí)鐘源選擇PLLCLK。檢查APB1總線(xiàn)時(shí)鐘PCLK1是否自動(dòng)分頻為36MHz最高只能72MHz的一半APB2總線(xiàn)時(shí)鐘PCLK2是否為72MHz。 時(shí)鐘是芯片運(yùn)行的“心跳”配置錯(cuò)誤會(huì)導(dǎo)致所有定時(shí)相關(guān)功能延時(shí)、串口波特率等全部失常。GPIO配置我們的目標(biāo)是點(diǎn)亮LED。假設(shè)LED連接在PC13引腳很多核心板如此。在引腳圖上找到PC13左鍵點(diǎn)擊選擇GPIO_Output。然后在左側(cè)System Core-GPIO里點(diǎn)擊剛配置的PC13可以設(shè)置用戶(hù)標(biāo)簽User Label為L(zhǎng)ED方便代碼閱讀。還可以設(shè)置初始輸出電平、上下拉模式、速度等這里默認(rèn)即可。生成工程點(diǎn)擊Project Manager選項(xiàng)卡。Project Name輸入LED_Blink。Project Location選擇一個(gè)干凈的路徑。Toolchain / IDE選擇MDK-ARM V5。在Code Generator里強(qiáng)烈建議勾選Generate peripheral initialization as a pair of .c/.h files per peripheral這會(huì)把每個(gè)外設(shè)的代碼生成獨(dú)立的文件結(jié)構(gòu)清晰。再勾選Copy all used libraries into the project folder這樣工程可以獨(dú)立移植。 最后點(diǎn)擊GENERATE CODE同意覆蓋生成Keil工程。3.2 Keil中的代碼編寫(xiě)與編譯用Keil打開(kāi)生成的工程。在左側(cè)Project欄展開(kāi)Application/User組打開(kāi)main.c。CubeMX生成的代碼結(jié)構(gòu)很清晰。/* USER CODE BEGIN */和/* USER CODE END */之間的區(qū)域是安全區(qū)你在這里寫(xiě)的代碼在重新生成工程時(shí)不會(huì)被覆蓋。其他地方不要手動(dòng)修改。我們?cè)谥餮h(huán)while (1)里添加閃爍邏輯/* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 翻轉(zhuǎn)PC13電平 HAL_Delay(500); // 延時(shí)500毫秒 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */這里用到了HAL庫(kù)的GPIO翻轉(zhuǎn)和延時(shí)函數(shù)。LED_GPIO_Port和LED_Pin是我們?cè)贑ubeMX中設(shè)置用戶(hù)標(biāo)簽后自動(dòng)生成的宏定義指向GPIOC和GPIO_PIN_13。點(diǎn)擊工具欄的BuildF7按鈕編譯。第一次編譯會(huì)稍慢因?yàn)橐⒄麄€(gè)工程。編譯成功后在下方Build Output窗口會(huì)看到LED_Blink.axf - 0 Error(s), 0 Warning(s)。3.3 燒錄、調(diào)試與問(wèn)題排查將ST-LINK通過(guò)SWD連接好核心板核心板單獨(dú)供電或由ST-LINK供電需確認(rèn)ST-LINK跳線(xiàn)帽設(shè)置。燒錄點(diǎn)擊LoadF8按鈕程序會(huì)自動(dòng)燒錄到芯片F(xiàn)lash??吹絃oad .\Objects\LED_Blink.axf completed即成功。此時(shí)核心板上的LED可能在PC13應(yīng)該開(kāi)始閃爍?;A(chǔ)調(diào)試設(shè)置斷點(diǎn)在HAL_GPIO_TogglePin那一行左側(cè)灰色欄點(diǎn)擊出現(xiàn)紅色圓點(diǎn)即設(shè)置斷點(diǎn)。開(kāi)始調(diào)試點(diǎn)擊Debug - Start/Stop Debug SessionCtrlF5進(jìn)入調(diào)試模式。運(yùn)行與停止按F5全速運(yùn)行程序會(huì)在斷點(diǎn)處暫停。你可以按F10單步執(zhí)行按F11跳入函數(shù)內(nèi)部。查看變量/外設(shè)在View - Watch Windows可以添加變量觀(guān)察。在View - System Viewer可以圖形化查看外設(shè)寄存器狀態(tài)非常直觀(guān)。常見(jiàn)問(wèn)題1程序無(wú)法燒錄提示“No ST-LINK detected”或“No target connected”。檢查ST-LINK驅(qū)動(dòng)是否安裝設(shè)備管理器應(yīng)出現(xiàn)STMicroelectronics STLink dongle。檢查SWD四根線(xiàn)是否連接正確、牢靠。檢查芯片供電是否正常3.3V。嘗試按住核心板復(fù)位鍵再點(diǎn)擊Keil的Load按鈕在燒錄瞬間松開(kāi)復(fù)位鍵針對(duì)某些接線(xiàn)不良情況。常見(jiàn)問(wèn)題2程序燒錄成功但LED不亮。確認(rèn)LED硬件連接F103的PC13引腳驅(qū)動(dòng)能力較弱且有些板子PC13連接的是板載LED陰極低電平點(diǎn)亮。嘗試將代碼中的Toggle改為HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET)輸出低電平試試。檢查時(shí)鐘配置確認(rèn)系統(tǒng)時(shí)鐘是否正確配置為72MHz??梢栽趍ain()函數(shù)初始化后調(diào)用SystemCoreClock變量查看或在調(diào)試時(shí)查看SysTick相關(guān)寄存器。使用萬(wàn)用表測(cè)量PC13引腳電壓看是否在高低電平間變化。4. 核心外設(shè)入門(mén)與代碼框架解析4.1 GPIO深度使用按鍵輸入與中斷點(diǎn)亮LED是輸出接下來(lái)實(shí)現(xiàn)按鍵輸入。假設(shè)按鍵接在PA0按下為低電平。輪詢(xún)方式在main.c中先配置PA0為輸入模式上拉或浮空輸入根據(jù)硬件電路決定。在while循環(huán)中不斷讀取引腳電平。// CubeMX中配置PA0為GPIO_Input模式GPIO_PULLUP如果外部有上拉電阻則選NOPULL if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { HAL_Delay(50); // 簡(jiǎn)單消抖 if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { // 按鍵確認(rèn)按下 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); while(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET); // 等待釋放 } }這種方式簡(jiǎn)單但占用CPU資源且響應(yīng)不及時(shí)。外部中斷方式這才是更高效的做法。在CubeMX中配置PA0為GPIO_EXTI0并設(shè)置觸發(fā)邊沿如下降沿觸發(fā)。然后在NVIC嵌套向量中斷控制器設(shè)置中使能EXTI line0中斷。 生成代碼后你需要自己實(shí)現(xiàn)中斷回調(diào)函數(shù)。HAL庫(kù)為外部中斷提供了一個(gè)弱定義的回調(diào)函數(shù)HAL_GPIO_EXTI_Callback我們?cè)趍ain.c的用戶(hù)代碼區(qū)重寫(xiě)它/* USER CODE BEGIN 4 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin KEY_Pin) { // 消除抖動(dòng)需要在中斷里快速標(biāo)記在主循環(huán)里處理或者用硬件消抖電路 // 這里簡(jiǎn)單演示實(shí)際需考慮消抖 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } /* USER CODE END 4 */注意中斷服務(wù)函數(shù)里執(zhí)行時(shí)間要盡可能短避免復(fù)雜操作。像延時(shí)HAL_Delay()這種阻塞函數(shù)是絕對(duì)禁止在中斷中調(diào)用的。通常做法是在中斷里設(shè)置一個(gè)標(biāo)志位在主循環(huán)中查詢(xún)并處理。4.2 串口通信打印調(diào)試信息的生命線(xiàn)串口USART是單片機(jī)與外界通信最基礎(chǔ)、最重要的方式也是調(diào)試的“printf”大法。CubeMX配置假設(shè)使用USART1引腳是PA9TX和PA10RX。在Connectivity-USART1中將模式設(shè)為Asynchronous異步通信。參數(shù)設(shè)置波特率115200字長(zhǎng)8 Bits停止位1無(wú)校驗(yàn)無(wú)硬件流控。NVIC設(shè)置中使能USART1全局中斷如果要用中斷接收。重定向printf為了能用printf函數(shù)需要重寫(xiě)fputc函數(shù)。在main.c的/* USER CODE BEGIN 4 */區(qū)域添加#ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); // 使用你的串口句柄如huart1 return ch; }并在main.c開(kāi)頭包含#include stdio.h。然后在main()函數(shù)初始化后就可以直接調(diào)用printf(Hello STM32!\r\n);了。中斷接收配置好中斷后在main()初始化部分啟動(dòng)接收中斷HAL_UART_Receive_IT(huart1, rx_buffer, 1);。當(dāng)收到一個(gè)字節(jié)后會(huì)進(jìn)入中斷并調(diào)用回調(diào)函數(shù)HAL_UART_RxCpltCallback。在該回調(diào)函數(shù)中處理數(shù)據(jù)并再次啟動(dòng)接收中斷以持續(xù)接收。串口調(diào)試心得波特率一定要和電腦端串口助手設(shè)置一致。發(fā)送和接收的引腳TX、RX要交叉連接單片機(jī)的TX接USB轉(zhuǎn)TTL的RX單片機(jī)的RX接USB轉(zhuǎn)TTL的TX。printf在調(diào)試時(shí)非常方便但會(huì)占用較多資源和時(shí)間在正式產(chǎn)品中慎用或使用條件編譯宏控制。4.3 定時(shí)器基礎(chǔ)精準(zhǔn)延時(shí)與PWM輸出HAL庫(kù)的HAL_Delay()依賴(lài)于SysTick系統(tǒng)滴答定時(shí)器有時(shí)我們需要更精準(zhǔn)或更靈活的定時(shí)?;径〞r(shí)器TIM6/TIM7主要用于產(chǎn)生定時(shí)中斷。在CubeMX中配置TIM6設(shè)置預(yù)分頻器PSC和自動(dòng)重載值A(chǔ)RR以決定定時(shí)周期。定時(shí)器時(shí)鐘一般為72MHzAPB1總線(xiàn)。定時(shí)時(shí)間 (ARR1)*(PSC1) / TimerClock。使能定時(shí)器更新中斷生成代碼后在stm32f1xx_it.c中找到TIM6_IRQHandler它會(huì)調(diào)用HAL_TIM_IRQHandler最終觸發(fā)回調(diào)函數(shù)HAL_TIM_PeriodElapsedCallback。我們重寫(xiě)這個(gè)回調(diào)函數(shù)來(lái)實(shí)現(xiàn)定時(shí)任務(wù)。PWM輸出這是控制LED亮度、電機(jī)速度的基礎(chǔ)。以TIM1的通道1PA8為例。在CubeMX中配置TIM1選擇一個(gè)通道如Channel1為PWM Generation CH1。關(guān)鍵參數(shù)Prescaler (PSC)時(shí)鐘預(yù)分頻決定計(jì)數(shù)器的計(jì)數(shù)頻率。Counter Period (ARR)自動(dòng)重載值決定PWM的頻率。PWM頻率 定時(shí)器時(shí)鐘 / ((PSC1)*(ARR1))。Pulse脈沖寬度即CCR1寄存器的值決定占空比。占空比 Pulse / (ARR1)。生成代碼后在main.c中啟動(dòng)PWMHAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1);。動(dòng)態(tài)修改占空比__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, pulseValue);。注意高級(jí)定時(shí)器如TIM1/TIM8和通用定時(shí)器如TIM2/TIM3/TIM4功能有差異。高級(jí)定時(shí)器支持互補(bǔ)輸出、死區(qū)插入等電機(jī)控制高級(jí)功能配置更復(fù)雜。5. 項(xiàng)目進(jìn)階與調(diào)試技巧實(shí)錄5.1 模塊化編程與工程管理當(dāng)代碼超過(guò)一個(gè)文件就必須考慮模塊化。在Keil工程中右鍵Source Group選擇Add New Item to Group可以創(chuàng)建新的.c和.h文件。頭文件.h規(guī)范#ifndef __LED_H #define __LED_H #ifdef __cplusplus extern C { #endif #include main.h // 包含必要的HAL頭文件 // 函數(shù)聲明 void LED_Init(void); void LED_On(void); void LED_Off(void); void LED_Toggle(void); #ifdef __cplusplus } #endif #endif /* __LED_H */源文件.c規(guī)范#include led.h // 私有全局變量用static修飾 static GPIO_TypeDef* LED_PORT LED_GPIO_Port; static uint16_t LED_PIN LED_Pin; void LED_Init(void) { // 初始化代碼實(shí)際上CubeMX已生成這里可能為空或進(jìn)行一些額外配置 } void LED_On(void) { HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); } void LED_Off(void) { HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET); } void LED_Toggle(void) { HAL_GPIO_TogglePin(LED_PORT, LED_PIN); }這樣在主函數(shù)中只需包含#include led.h調(diào)用LED_On()即可實(shí)現(xiàn)了高內(nèi)聚低耦合。5.2 常見(jiàn)問(wèn)題排查與調(diào)試技巧程序跑飛或硬件錯(cuò)誤HardFault這是最令人頭疼的問(wèn)題。通常原因有數(shù)組越界、棧溢出、訪(fǎng)問(wèn)非法內(nèi)存地址、未初始化的指針、中斷服務(wù)函數(shù)處理不當(dāng)。定位方法進(jìn)入調(diào)試模式當(dāng)發(fā)生HardFault后程序會(huì)停在HardFault_Handler。查看Call Stack Locals窗口找到你代碼中最后調(diào)用的函數(shù)。檢查該函數(shù)內(nèi)的數(shù)組、指針操作。檢查棧大小在Keil的Options for Target - Target選項(xiàng)卡中可以設(shè)置IRAM1的起始地址和大小。默認(rèn)棧Stack大小是0x4001KB對(duì)于使用了較多局部變量或遞歸的程序可能不夠可以適當(dāng)增大到0x800或0x1000。中斷無(wú)法進(jìn)入檢查CubeMX中是否使能了對(duì)應(yīng)外設(shè)的中斷以及NVIC里是否勾選并設(shè)置了優(yōu)先級(jí)。檢查中斷服務(wù)函數(shù)名稱(chēng)是否正確。HAL庫(kù)的中斷向量表在啟動(dòng)文件startup_stm32f103xe.s中定義服務(wù)函數(shù)名是固定的如USART1_IRQHandler我們一般不需要直接修改它HAL庫(kù)已經(jīng)處理好我們只需實(shí)現(xiàn)回調(diào)函數(shù)。確保在main()中調(diào)用了外設(shè)的中斷啟動(dòng)函數(shù)如HAL_UART_Receive_IT()。外設(shè)初始化順序問(wèn)題有些外設(shè)依賴(lài)時(shí)鐘或另一個(gè)外設(shè)先初始化。一個(gè)基本原則是先初始化時(shí)鐘和GPIO再初始化具體外設(shè)。CubeMX生成的代碼順序一般是正確的但如果你手動(dòng)調(diào)整代碼需要注意。使用邏輯分析儀或示波器對(duì)于時(shí)序問(wèn)題如PWM波形不對(duì)、串口數(shù)據(jù)異常軟件仿真和printf可能力不從心。一個(gè)幾十元的簡(jiǎn)易邏輯分析儀配合上位機(jī)軟件能直觀(guān)顯示引腳的電平變化時(shí)序是排查硬件時(shí)序問(wèn)題的利器。5.3 從學(xué)習(xí)到項(xiàng)目一個(gè)小型綜合練習(xí)為了鞏固知識(shí)可以嘗試做一個(gè)“呼吸燈串口控制”的小項(xiàng)目。功能上電后LED以PWM呼吸燈模式運(yùn)行。通過(guò)串口發(fā)送指令如‘S’可以停止呼吸燈發(fā)送‘R’恢復(fù)呼吸發(fā)送‘B X’X為0-100可以設(shè)置亮度。所需知識(shí)GPIO輸出、定時(shí)器PWM、串口收發(fā)中斷或輪詢(xún)、簡(jiǎn)單的命令解析。實(shí)現(xiàn)思路CubeMX配置一個(gè)定時(shí)器如TIM3的PWM通道驅(qū)動(dòng)LED配置USART1。主循環(huán)中不斷檢查串口接收緩沖區(qū)解析命令。收到‘S’調(diào)用HAL_TIM_PWM_Stop收到‘R’調(diào)用HAL_TIM_PWM_Start收到‘B’解析后面的數(shù)字計(jì)算對(duì)應(yīng)的CCR值用__HAL_TIM_SET_COMPARE設(shè)置。呼吸燈效果可以通過(guò)在定時(shí)器中斷中不斷修改CCR值來(lái)實(shí)現(xiàn)需開(kāi)啟定時(shí)器更新中斷。這個(gè)項(xiàng)目雖小但涵蓋了輸入、輸出、通信、中斷、控制等多個(gè)核心概念是檢驗(yàn)初階學(xué)習(xí)成果的很好方式。學(xué)習(xí)STM32的過(guò)程就像在搭積木。最開(kāi)始CubeMX幫你搭好了最底層、最復(fù)雜的時(shí)鐘和引腳框架。你要做的就是在這個(gè)穩(wěn)固的基礎(chǔ)上用HAL庫(kù)這些“積木塊”一層層構(gòu)建你的功能邏輯。別怕出錯(cuò)每一個(gè)無(wú)法點(diǎn)亮的LED每一行無(wú)法打印的printf都是你理解這個(gè)系統(tǒng)最寶貴的經(jīng)驗(yàn)。當(dāng)你成功讓串口打印出你設(shè)定的信息當(dāng)你用PWM讓LED平滑地明暗變化時(shí)那種對(duì)硬件的掌控感正是嵌入式開(kāi)發(fā)最原始的樂(lè)趣所在。記住所有復(fù)雜的項(xiàng)目都是由無(wú)數(shù)個(gè)GPIO_TogglePin這樣的基礎(chǔ)操作組合而成的。