1. 拿到板子先别急着点灯:STM32F103开发板入门全景拆解
STM32F103这块板子,大概是国内嵌入式圈子里保有量最大的一块入门开发板了。不管你是电子专业的学生、刚转行做嵌入式的软件工程师,还是想给自家小项目加个控制核心的硬件爱好者,手里捏着一块F103开发板,基本就等于拿到了进入ARM Cortex-M3世界的门票。我前后带过不少人入门,也见过太多人板子买回来吃灰——不是学不会,而是第一步就走歪了。这篇文章就把STM32F103开发板从开箱到跑通第一个工程的全流程拆开讲,顺带把新手最容易踩的坑、工具链怎么选、标准库和HAL库怎么取舍这些事一次说清楚。适合零基础但有一点C语言底子的人,也适合之前玩过51单片机、想升级到32位平台的朋友。
STM32F103的核心是ARM Cortex-M3内核,主频常见72MHz,片上Flash从16KB到512KB不等,SRAM从6KB到64KB。市面上常见的开发板型号有STM32F103C8T6(蓝板/最小系统板)、STM32F103ZET6(大板,引脚多、外设全)、STM32F103RCT6等。C8T6便宜、够用,适合入门;ZET6资源丰富,适合做毕业设计或者需要接很多外设的项目。你买的是哪种,直接决定了后面能玩多深。
提示:买板子的时候一定要确认芯片型号和封装,C8T6是LQFP48封装,ZET6是LQFP144封装,两者的引脚数量和可用外设差别很大,别买回来发现引脚不够用。
2. 工具链怎么选:Keil、VS Code还是其他
2.1 Keil MDK:最省心的选择,但别装错版本
新手入门,我一般直接推荐Keil MDK。原因很简单:生态成熟、教程多、调试方便,遇到问题搜一下基本都有答案。但Keil有个坑,就是版本和芯片包的问题。Keil5和Keil4的工程结构不一样,现在主流是Keil5(MDK-ARM V5)。装完Keil5之后,还需要单独安装STM32F1系列的Device Family Pack(DFP),也就是常说的“芯片包”。没有这个包,新建工程的时候找不到STM32F103的型号。
芯片包的安装方式有两种:一种是在Keil里通过Pack Installer在线安装,另一种是去官网下载.pack文件双击安装。在线安装有时候会因为网络问题失败,建议直接下载离线包。安装完成后,在Keil的Project -> Manage -> Pack Installer里能看到STM32F1系列就说明成功了。
还有一个常见问题:有人电脑上同时装了Keil C51和Keil MDK,想让它俩共存。这个是可以的,但要注意安装路径不要覆盖,而且注册的时候要分别注册。不过我个人建议,如果只是学STM32,就别折腾C51了,分开装或者干脆用两台环境,省得互相干扰。
2.2 VS Code + 插件:适合喜欢折腾的人
这两年用VS Code开发STM32的人越来越多。核心思路是用VS Code做编辑器,底层调用arm-none-eabi-gcc编译,用OpenOCD或者ST-Link Utility烧录。这套方案的好处是免费、跨平台、编辑器体验好,坏处是配置麻烦,新手容易在环境搭建上卡住。
典型的配置流程是:安装VS Code,装Cortex-Debug插件、STM32 VS Code Extension插件,然后配置tasks.json和launch.json。编译工具链用arm-none-eabi-gcc,调试用OpenOCD + ST-Link。这套配置我配过好几次,每次都要花一两个小时调,主要是路径和参数容易写错。
注意:如果你在VS Code里编译成功但烧录不进去,大概率是烧录配置里的接口类型或者目标芯片型号写错了。ST-Link的接口有SWD和JTAG两种,F103一般用SWD,四根线:VCC、GND、SWDIO、SWCLK。
2.3 标准库 vs HAL库:入门阶段怎么选
这是新手最纠结的问题之一。STM32的标准库(Standard Peripheral Library)是ST早期推出的,代码直接操作寄存器,结构清晰,适合理解底层原理。HAL库(Hardware Abstraction Layer)是后来推的,抽象层次更高,跨系列移植方便,但代码量大、执行效率略低。
我的建议是:入门阶段用标准库。原因很实际——标准库的代码更接近寄存器操作,你写一遍GPIO初始化,能大概知道CRL、CRH、ODR这些寄存器是干什么的。HAL库把这些都封装了,你调个HAL_GPIO_Init就完事,学完一圈下来可能连GPIO的模式寄存器长什么样都不知道。等你把标准库玩熟了,再转HAL库或者LL库,那时候你已经有底层概念了,学起来很快。
当然,如果你是为了快速做项目、不关心底层,直接上HAL库也行,STM32CubeMX图形化配置工具能帮你省很多事。但CubeMX生成的代码有时候比较臃肿,新手看不太懂。
3. 从零新建一个标准库工程:手把手操作
3.1 工程目录结构怎么搭
标准库工程不是新建一个Keil工程就完事了,你需要把ST官方提供的库文件组织好。一个典型的工程目录结构是这样的:
Project/ ├── CMSIS/ # 内核相关文件 │ ├── core_cm3.h │ ├── core_cm3.c │ └── stm32f10x.h ├── FWLIB/ # 外设库文件 │ ├── inc/ # 头文件 │ └── src/ # 源文件 ├── USER/ # 用户代码 │ ├── main.c │ ├── stm32f10x_it.c │ └── stm32f10x_conf.h ├── HARDWARE/ # 硬件驱动 │ ├── led.c │ └── led.h └── OBJ/ # 编译输出这个结构不是固定的,但核心思想是:内核文件、外设库文件、用户代码、硬件驱动分开存放。这样工程清晰,后面加外设的时候不会乱。
CMSIS目录下需要放core_cm3.h、core_cm3.c和stm32f10x.h。FWLIB目录下放ST提供的标准外设库,比如stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_usart.c等。USER目录下放main.c和中断服务文件。HARDWARE目录下放你自己写的驱动。
3.2 Keil工程的关键配置项
新建Keil工程后,有几个配置必须做对,否则编译或者烧录会出问题。
第一个是Target选项里的晶振频率。STM32F103外部晶振一般是8MHz,经过PLL倍频到72MHz。在Keil的Options for Target -> Target里,Xtal填8.0MHz。这个值影响的是Keil的软件仿真,实际硬件运行靠的是代码里的时钟配置。
第二个是Output选项里勾选Create HEX File。如果你用ST-Link Utility或者串口烧录,需要HEX文件。用Keil直接下载的话不勾也行。
第三个是C/C++选项里的Define。标准库工程一般需要定义USE_STDPERIPH_DRIVER和STM32F10X_MD(根据芯片容量选择,C8T6是中容量,用STM32F10X_MD;ZET6是大容量,用STM32F10X_HD)。这个宏定义决定了stm32f10x.h里包含哪些外设的头文件。
第四个是Debug选项。选ST-Link Debugger,然后点Settings,确认能识别到芯片。如果识别不到,检查接线和驱动。
实操心得:Keil工程里添加文件组的时候,建议按目录分组,比如CMSIS组、FWLIB组、USER组、HARDWARE组。这样在左侧工程树里一目了然,找文件方便。我见过有人把所有.c文件堆在一个组里,几十个文件排下来,找个main.c都要翻半天。
3.3 时钟配置:72MHz是怎么来的
STM32F103默认用的是内部8MHz的HSI振荡器,但大多数开发板外部焊了8MHz晶振。要让芯片跑在72MHz,需要配置PLL。具体流程是:使能HSE(外部高速时钟),等待HSE稳定,配置PLL倍频系数为9(8MHz × 9 = 72MHz),使能PLL,切换系统时钟源到PLL。
这段代码在标准库里的写法是:
void RCC_Configuration(void) { RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while(RCC_GetSYSCLKSource() != 0x08); }这段代码看起来简单,但有几个细节容易出错。一是HSE起振需要时间,如果晶振质量不好或者负载电容不匹配,可能会卡在while循环里。二是PLL倍频系数不能乱设,F103的PLL输出范围是16MHz到72MHz,超过72MHz可能不稳定。三是切换系统时钟源之后要确认切换成功,否则后面外设的时钟频率全错。
如果你发现程序卡在while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET)这一行,先检查晶振有没有焊好,再检查负载电容是不是匹配。实在不行,可以先用HSI跑,虽然精度差一点,但至少能跑起来。
4. GPIO操作:从点亮一个LED开始
4.1 GPIO的八种模式,别只会推挽输出
STM32的GPIO有八种模式:输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、推挽复用输出、开漏复用输出。新手最常用的是推挽输出,用来点LED、驱动数码管。但其他模式什么时候用,得心里有数。
输入浮空:外部信号直接进,没有上下拉。适合外部已经有上下拉电阻的场景。输入上拉/下拉:芯片内部提供上下拉,省外部电阻。按键输入一般用上拉或下拉。模拟输入:ADC采集的时候用,这时候GPIO不再作为数字口。开漏输出:输出高电平时是高阻态,需要外部上拉电阻。I2C的SDA和SCL就用开漏复用输出。推挽输出:能输出强高低电平,驱动LED、继电器等。
配置GPIO的步骤是:使能GPIO时钟、定义GPIO_InitTypeDef结构体、设置引脚、设置模式、设置速度、调用GPIO_Init。以PA5接LED为例:
void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_5); // 默认熄灭 }这里有个细节:GPIO_Speed设置的是输出驱动电路的响应速度,不是输出电平的高低。50MHz适合高速信号,2MHz适合低功耗场景。点LED用2MHz就够了,但设50MHz也没问题。
4.2 按键输入:消抖不是可选项
按键输入比LED输出麻烦一点,因为机械按键有抖动。抖动时间一般在5ms到20ms之间,如果不处理,按一次键可能会被识别成多次。消抖有两种方式:硬件消抖和软件消抖。硬件消抖是在按键两端并联电容,软件消抖是检测到按键按下后延时10ms再检测一次。
软件消抖的典型代码:
u8 Key_Scan(void) { if(KEY_PIN == 0) { Delay_ms(10); if(KEY_PIN == 0) { while(KEY_PIN == 0); return 1; } } return 0; }这段代码里,while(KEY_PIN == 0)是等待按键释放。如果没有这一句,按键按住不放的时候会一直被识别为按下。但这一句也有个问题:如果按键卡住,程序会死在这里。实际项目里一般用状态机或者定时器扫描来替代。
注意:按键的硬件连接方式决定了检测逻辑。如果按键一端接GPIO、一端接GND,GPIO配置为上拉输入,那么按下时读到0,松开时读到1。如果按键一端接GPIO、一端接VCC,GPIO配置为下拉输入,按下时读到1,松开时读到0。别搞反了。
4.3 LED和按键的电路设计要点
开发板上的LED一般有两种接法:共阳极和共阴极。共阳极是LED正极接VCC,负极接GPIO,GPIO输出低电平点亮。共阴极是LED负极接GND,正极接GPIO,GPIO输出高电平点亮。天祥、普中这些开发板的数码管和LED接法不一样,用之前一定要看原理图。
限流电阻不能省。LED的压降一般是1.8V到2.2V(红色),工作电流5mA到10mA就够了。如果GPIO输出3.3V,限流电阻用220Ω到1kΩ之间。我见过有人直接把LED接在GPIO和GND之间,结果LED烧了,GPIO也挂了。STM32的GPIO最大输出电流是25mA,但长期跑在极限值对芯片不好。
5. 烧录与调试:程序下不进去怎么办
5.1 ST-Link的接线和驱动
ST-Link是ST官方的调试器,支持SWD和JTAG两种接口。F103开发板一般用SWD,四根线:3.3V、GND、SWDIO、SWCLK。有些板子还有RST线,接上更稳定。
接线的时候注意:SWDIO和SWCLK不要接反,VCC不要接5V。STM32F103的IO电压是3.3V,接5V可能烧芯片。ST-Link的驱动在官网能下载,装完之后设备管理器里能看到STLink Debug Interface。
如果Keil里识别不到ST-Link,先检查驱动,再检查USB线。有些USB线只有充电功能,没有数据线,插上电脑没反应。换一根线试试。
5.2 常见烧录失败原因排查
烧录失败是新手遇到最多的问题,没有之一。我把常见原因整理成一张表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| Keil提示No ST-Link detected | 驱动未装或USB线问题 | 检查设备管理器,换USB线 |
| 提示Cannot access target | 芯片未供电或接线错误 | 测量3.3V引脚电压,检查SWD接线 |
| 提示Flash Download failed | 芯片被读保护或Flash算法未选 | 用ST-Link Utility解除读保护,检查Flash算法配置 |
| 烧录成功但程序不跑 | 启动模式错误或时钟配置错误 | 检查BOOT0/BOOT1跳线,检查晶振 |
| 程序跑一会儿就死 | 看门狗未喂或堆栈溢出 | 检查看门狗配置,增大堆栈 |
BOOT0和BOOT1的跳线经常被忽略。STM32F103的启动模式由BOOT0和BOOT1决定:BOOT0=0时从主Flash启动,BOOT0=1、BOOT1=0时从系统存储器启动(串口下载),BOOT0=1、BOOT1=1时从SRAM启动。正常跑程序的时候BOOT0要接GND。
还有一个坑:有些开发板上的ST-Link是板载的,但用的是STM32F103的另一个型号做调试器。这种板载ST-Link有时候需要先给调试器固件升级,否则Keil识别不到。升级方法是用ST-Link Utility连接,然后Firmware Upgrade。
5.3 用串口下载程序:没有ST-Link的替代方案
如果你手头没有ST-Link,可以用USB转TTL模块通过串口下载。STM32F103的串口下载需要把BOOT0接VCC、BOOT1接GND,然后复位,芯片进入系统存储器启动模式。用FlyMcu或者STM32CubeProgrammer选择串口,加载HEX文件,点击下载。
串口下载的速度比ST-Link慢,而且每次下载都要手动切换BOOT0,比较麻烦。但作为应急方案是可行的。下载完之后记得把BOOT0接回GND,再复位,程序才会从Flash启动。
实操心得:我一般会在板子上焊一个跳线帽或者拨码开关来控制BOOT0,这样切换启动模式不用每次都插拔杜邦线。成本几毛钱,省事很多。
6. 外设入门:定时器、串口和中断
6.1 定时器:不只是延时
STM32F103的定时器资源很丰富,有高级定时器TIM1、TIM8,通用定时器TIM2到TIM5,基本定时器TIM6、TIM7。定时器能做延时、PWM输出、输入捕获、编码器接口等。
用定时器做精确延时的思路是:配置定时器预分频和自动重装载值,算出定时周期,然后在中断里计数。比如72MHz时钟,预分频72-1,得到1MHz计数频率,自动重装载值1000-1,就是1ms中断一次。
void TIM3_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period = 1000 - 1; TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); TIM_Cmd(TIM3, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }中断服务函数里记得清除中断标志位,否则会一直进中断。标准库的写法是TIM_ClearITPendingBit(TIM3, TIM_IT_Update)。
PWM输出是定时器的另一个常用功能。配置PWM输出需要设置定时器的ARR和CCR,ARR决定PWM周期,CCR决定占空比。比如ARR=1000-1,CCR=500,占空比就是50%。PWM可以用来调LED亮度、控制电机速度、驱动舵机。
6.2 串口:和电脑通信的基本手段
串口是调试和通信最常用的外设。STM32F103一般用USART1,接PA9(TX)和PA10(RX)。配置串口的步骤是:使能USART和GPIO时钟、配置GPIO为复用推挽输出(TX)和浮空输入(RX)、配置USART参数(波特率、数据位、停止位、校验位)、使能USART。
void USART1_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); }串口发送数据用USART_SendData,接收数据用USART_ReceiveData。接收中断里记得清除标志位。如果要用printf输出调试信息,需要重定向fputc函数:
int fputc(int ch, FILE *f) { while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); USART_SendData(USART1, (u8)ch); return ch; }重定向之后,在Keil的Options for Target -> Target里勾选Use MicroLIB,否则printf可能不工作。
6.3 中断优先级:别让中断打架
STM32F103的中断优先级分抢占优先级和响应优先级。抢占优先级高的可以打断抢占优先级低的中断,响应优先级只在同时发生时起作用。NVIC的分组方式决定了抢占优先级和响应优先级各占几位。
新手常见的问题是:串口接收中断和定时器中断同时开,结果串口数据丢了。这通常是中断优先级配置不当,或者中断服务函数里执行时间太长。中断服务函数应该尽量短,把耗时的处理放到主循环里。
注意:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)表示2位抢占优先级、2位响应优先级。这个分组在程序里只能设置一次,一般在main函数开头调用。多次设置会导致优先级混乱。
7. 常见问题速查与避坑指南
7.1 编译报错:undefined symbol
这个错误一般是头文件没包含或者源文件没加到工程里。比如用了GPIO_Init但没包含stm32f10x_gpio.h,或者包含了头文件但没把stm32f10x_gpio.c加到工程里。检查方法是:在Keil左侧工程树里确认对应的.c文件在不在,在C/C++选项的Include Paths里确认头文件路径加没加。
还有一种情况是宏定义没写对。比如用了STM32F10X_MD但实际芯片是大容量的,stm32f10x.h里就不会包含对应的外设定义,编译的时候找不到符号。
7.2 程序卡在延时函数里
delay函数卡死是新手常见问题。如果是用循环实现的软件延时,卡死一般是循环条件写错了。如果是用SysTick实现的延时,卡死可能是SysTick配置错误或者中断优先级问题。
我见过一个典型情况:在中断服务函数里调用了delay_ms,而delay_ms依赖SysTick中断,结果SysTick中断优先级比当前中断低,永远进不去,死锁。解决办法是不要在中断里用依赖中断的延时函数,或者把SysTick优先级设到最高。
7.3 串口乱码:波特率对不上
串口乱码最常见的原因是波特率不匹配。STM32的波特率由USART_BRR寄存器决定,计算公式是:波特率 = fPCLK / (16 × USARTDIV)。fPCLK是USART所在总线的时钟频率。USART1挂在APB2上,时钟是72MHz;USART2挂在APB1上,时钟是36MHz。如果时钟配置错了,算出来的波特率就不对。
还有一个原因是晶振频率和代码里配置的不一致。比如板子上焊的是8MHz晶振,但代码里按12MHz算,波特率就会偏。用示波器或者逻辑分析仪量一下TX引脚的实际波特率,能快速定位问题。
7.4 程序下载后不运行
烧录成功但程序不跑,先检查BOOT0跳线。BOOT0接GND是从Flash启动,接VCC是从系统存储器启动。如果BOOT0接VCC,程序烧到Flash里也不会跑。
再检查复位电路。有些开发板的复位按键常闭,导致芯片一直处于复位状态。用万用表量一下NRST引脚的电压,正常应该是3.3V。
还有可能是时钟配置问题。如果程序里配置了HSE但板子上没有晶振,程序会卡在等待HSE稳定的循环里。这时候可以改用HSI,或者检查晶振电路。
7.5 常见问题速查表
| 问题 | 排查方向 | 快速解决 |
|---|---|---|
| Keil找不到芯片型号 | 芯片包未安装 | 安装STM32F1 DFP |
| 编译报错找不到头文件 | Include路径未配置 | 添加CMSIS和FWLIB路径 |
| 烧录提示Flash超时 | ST-Link接触不良 | 检查SWD接线,降低SWD速度 |
| LED不亮 | GPIO模式或电路问题 | 检查LED极性,测量GPIO电平 |
| 按键不灵敏 | 消抖未处理或上拉未配置 | 加10ms延时,配置上拉输入 |
| 串口收不到数据 | 波特率或接线错误 | 检查TX/RX是否交叉连接 |
| 定时器不中断 | 中断未使能或标志未清 | 检查NVIC配置和清除标志 |
| 程序跑飞 | 堆栈溢出或野指针 | 增大堆栈,检查数组越界 |
8. 进阶方向:学完基础之后往哪走
8.1 做几个完整项目练手
光看教程不动手,学完就忘。我建议学完GPIO、定时器、串口之后,至少做三个完整项目:一个是按键控制LED加串口输出状态,一个是定时器PWM调光加串口调速,一个是超声波测距加串口显示距离。这三个项目覆盖了输入、输出、定时、通信,做完之后对STM32的基本用法就有感觉了。
超声波测距模块HC-SR04很便宜,用法也简单:Trig引脚给10us高电平,Echo引脚会输出高电平,高电平持续时间就是距离对应的时间。距离 = 高电平时间 × 340m/s / 2。用定时器的输入捕获功能测Echo高电平时间,精度能到厘米级。
8.2 毕业设计选题参考
如果你是用STM32做毕业设计,选题方向可以往这几个方向靠:智能家居控制(温湿度采集、继电器控制、WiFi通信)、环境监测(PM2.5、甲醛、温湿度)、电机控制(步进电机、伺服电机、PID调速)、数据采集(ADC多通道、SD卡存储、LCD显示)。
选题的时候注意工作量。纯STM32的题目工作量可能不够,最好加上上位机、手机APP或者云平台。比如STM32采集数据,通过ESP8266传到手机APP显示,这样工作量就上来了。
8.3 从标准库转到HAL库和CubeMX
标准库玩熟了之后,可以试试STM32CubeMX + HAL库。CubeMX图形化配置引脚、时钟、外设,生成初始化代码,省去手动配置寄存器的麻烦。HAL库的API更统一,跨系列移植方便。但HAL库的代码效率比标准库低,中断处理也更复杂,适合项目开发,不太适合学习底层原理。
转HAL库的时候,重点理解这几个概念:句柄(Handle)、回调函数(Callback)、阻塞和非阻塞模式。HAL库的UART接收有阻塞、中断、DMA三种模式,用熟了之后开发效率确实高。
实操心得:我一般用CubeMX生成初始化代码,然后自己写业务逻辑。CubeMX生成的代码里,用户代码要写在/* USER CODE BEGIN/和/USER CODE END */之间,这样重新生成代码的时候不会被覆盖。这个习惯能省很多事。
8.4 硬件设计入门:从开发板到自己画板
用开发板做项目,最终还是要落到自己画板子上。画STM32最小系统板需要这些东西:STM32芯片、晶振电路(8MHz主晶振 + 32.768kHz RTC晶振)、复位电路、电源电路(3.3V稳压)、BOOT跳线、SWD调试接口。画板子用Altium Designer或者KiCad,新手建议从KiCad开始,免费且功能够用。
画板子的时候注意几点:晶振尽量靠近芯片,负载电容按晶振手册选(一般20pF左右),电源引脚加去耦电容(100nF),SWD接口留出VCC、GND、SWDIO、SWCLK四根线。第一次画板子建议把引脚都引出来,方便后面扩展。
9. 一些零散但重要的经验
开发板买回来,第一件事不是写代码,是看原理图。原理图告诉你LED接在哪个引脚、按键接在哪个引脚、晶振频率是多少、电源怎么供。不看原理图直接写代码,等于闭着眼睛开车。
Keil的工程模板建议自己整理一份。把CMSIS、FWLIB、USER、HARDWARE这几个目录固定下来,以后新建工程直接复制模板,改改main.c就能用。我自己的模板用了好几年,省了无数新建工程的时间。
遇到问题先搜再问。STM32的生态太成熟了,你遇到的问题99%别人都遇到过。搜索关键词用英文,比如“STM32F103 GPIO not working”、“Keil ST-Link cannot access target”,搜出来的结果质量比中文高很多。
最后说一个很多人忽略的点:学会看数据手册和参考手册。数据手册(Datasheet)讲的是电气特性和引脚定义,参考手册(Reference Manual)讲的是外设的工作原理和寄存器定义。这两个文档是STM32开发的根本,遇到不确定的地方翻手册,比在网上搜半天靠谱。
我个人在实际操作中的体会是,STM32入门最难的不是写代码,而是环境搭建和问题排查。代码写错了编译不过,至少知道错在哪;环境配错了,可能连错在哪都不知道。所以新手阶段,把工具链配好、把烧录流程跑通、把调试手段用熟,比急着写业务代码重要得多。板子买回来了就赶紧动手,别让它吃灰。