
1. 拿到板子先别急着点灯搞懂STM32F103到底是个啥STM32F103这块板子在嵌入式圈子里基本等同于学开车时的教练车——不是最快的不是最炫的但胜在皮实、资料多、踩坑有人陪。你手里这块开发板核心芯片大概率是STM32F103C8T6或者RCT6、ZET6这些型号C8T6是48脚RCT6是64脚ZET6是144脚资源依次递增。C8T6有64KB Flash、20KB RAMRCT6有256KB Flash、48KB RAMZET6直接拉到512KB Flash、64KB RAM。对于刚入门的人来说C8T6完全够用但如果你打算后面跑RTOS或者做带屏幕的项目RCT6会更从容一些。这块芯片的内核是ARM Cortex-M3最高主频72MHz。72MHz是什么概念大概就是每秒钟能执行7200万条左右的指令理想情况下放在2007年刚发布那会儿算是相当能打放到现在做控制类任务依然绰绰有余。它自带的东西挺全多个定时器高级定时器TIM1、TIM8通用定时器TIM2到TIM5基本定时器TIM6、TIM7、ADC12位最快1微秒转换一次、DAC部分型号有、USART、SPI、I2C、CAN、USB Device注意F103的USB只能做设备不能做Host这个后面会细说、SDIO等等。基本上你能想到的嵌入式外设它都给你塞进去了。为什么说STM32F103适合入门三个原因。第一资料多到离谱。中文的、英文的、视频的、文档的你随便搜一个外设名字能出来几十篇教程。第二标准库和HAL库两条路都能走。标准库更接近寄存器操作学起来对底层理解更深HAL库封装度高上手快ST官方主推。第三社区活跃。你遇到问题大概率别人已经遇到过了搜一下就有答案。我个人的建议是先花两周时间用标准库把GPIO、定时器、串口、中断这几个基础外设摸一遍然后再切到HAL库或者LL库去做项目。这样你对底层有感觉后面用CubeMX生成代码的时候不会一脸懵。买板子的时候通常会附带一些配件USB线注意是Mini-USB还是Type-C还是Micro-USB别买错了、杜邦线若干、可能还有OLED屏或者LCD屏、传感器模块之类的。先检查一下板子上的跳线帽是不是都在正确位置特别是BOOT0和BOOT1这两个引脚。BOOT0决定芯片从哪里启动接GND是从Flash启动正常运行模式接VCC是从系统存储器启动串口下载模式。BOOT1一般接GND就行。很多新手拿到板子发现下载不进去程序八成是BOOT0没接对。注意STM32F103的IO口大部分是5V容忍的但并不是全部。比如PA0到PA7、PB0到PB1这些带ADC功能的引脚在作为模拟输入使用时不能接5V。具体哪些引脚是FTFive-volt Tolerant的去翻对应型号的数据手册Datasheet别偷懒。2. 开发环境搭建Keil、CubeMX、VSCode三条路怎么选2.1 Keil MDK最经典但也最让人又爱又恨Keil MDK现在叫Keil MDK-ARM是绝大多数STM32教程里用的IDE。它的优点是编译器优化做得好调试器集成度高J-Link、ST-Link都能直接用而且网上教程一抓一大把。缺点是界面老旧代码补全功能约等于没有而且它是收费软件。当然很多人用的是评估版或者社区版代码大小限制在32KB以内。对于F103C8T6的64KB Flash来说32KB的限制意味着你写到一半可能就超了。不过对于学习阶段的小项目32KB基本够用。安装Keil的时候有个坑Keil5默认不包含STM32F1的器件支持包Device Family Pack你需要单独去Keil官网或者ST官网下载STM32F1xx_DFP这个包安装之后才能在新建工程时选到STM32F103的型号。另外Keil5和Keil4可以共存但要注意安装路径不要冲突。如果你之前装过Keil4做51单片机想再装Keil5做STM32建议装在不同目录下否则可能会互相干扰。还有一个常见问题Keil5的代码编辑体验太差很多人会搭配VSCode来写代码然后用Keil来编译和下载。具体做法是在VSCode里安装Keil Assistant插件配置好Keil的安装路径和工程文件路径就可以在VSCode里编辑代码然后一键调用Keil的编译和下载功能。这个方案我用了好几年体验比纯Keil好很多。2.2 STM32CubeMX HAL库ST官方主推的现代化方案CubeMX是一个图形化配置工具你只需要在界面上点一点选择芯片型号、配置时钟树、使能外设、设置引脚功能它就能自动生成初始化代码。生成的代码基于HAL库Hardware Abstraction Layer封装程度高可移植性强。比如你要用串口在CubeMX里使能USART1设置波特率115200生成代码后直接调用HAL_UART_Transmit()就能发数据不用自己去配寄存器。但HAL库也有缺点代码效率比标准库低一些因为多了一层封装有些时候HAL库的默认配置不一定符合你的需求需要手动改而且HAL库的版本更新比较频繁不同版本之间可能有API变化。我个人的建议是新手先用CubeMX HAL库快速做出东西来建立信心然后再回头用标准库或者LL库去理解底层。CubeMX生成代码时有个选项叫“Copy only necessary library files”建议勾上这样生成的工程体积小不会把整个HAL库都拷进来。另外如果你用Keil作为工具链CubeMX可以直接生成Keil工程文件.uvprojx打开就能编译。2.3 VSCode PlatformIO极客路线PlatformIO是VSCode的一个插件它把编译器、调试器、库管理都集成在一起了。你只需要在platformio.ini文件里写几行配置比如[env:bluepill_f103c8] platform ststm32 board bluepill_f103c8 framework arduino upload_protocol stlink然后PlatformIO就会自动下载对应的工具链和库编译下载一键完成。它支持Arduino框架、STM32Cube框架、CMSIS框架等多种开发方式。如果你之前玩过Arduino用PlatformIO过渡到STM32会非常顺滑。但PlatformIO的缺点是国内下载库和工具链可能比较慢需要配置镜像源而且它的调试功能虽然能用但不如Keil和IAR那么成熟。另外PlatformIO的社区虽然活跃但中文资料相对少一些。2.4 工具链选择对比方案上手难度代码效率调试体验适合人群Keil MDK中等高好学生、传统嵌入式开发者CubeMX HAL低中好新手、快速原型开发VSCode PlatformIO中等中高中等有Arduino基础、喜欢现代化工具IAR Embedded Workbench高最高最好企业级开发、对代码大小敏感我个人的建议是如果你是完全零基础先用CubeMX Keil的组合把第一个工程跑起来。等你对STM32的工程结构、启动流程、时钟配置有了基本概念之后再尝试VSCode PlatformIO或者纯寄存器开发。3. 第一个工程从点亮LED到串口打印3.1 硬件连接确认拿到板子后先别急着写代码。第一步是确认硬件连接。用USB线把板子接到电脑上观察板子上的电源指示灯是否亮起。如果不亮检查USB线是不是只能供电不能传数据的那种有些便宜的USB线只有电源线没有数据线。如果板子上有多个USB口确认哪个是供电下载口哪个是USB Device口。然后检查ST-Link或者J-Link调试器是否连接正确。如果你用的是板载ST-Link很多开发板自带直接用USB线连接电脑即可。如果你用的是外置ST-Link需要连接SWDIO、SWCLK、GND、3.3V这四根线。注意SWDIO和SWCLK不要接反GND一定要共地。注意STM32F103的SWD接口默认是启用的但如果你在代码里禁用了SWD或者JTAG引脚比如把PA13、PA14配置成了普通IO下次下载程序时可能会连不上。解决办法是按住复位键点击下载然后松开复位键。或者把BOOT0接VCC从系统存储器启动这样芯片会忽略你的代码直接进入下载模式。3.2 新建工程与时钟配置以CubeMX为例新建工程选择STM32F103C8T6或者你板子上的具体型号。然后配置时钟源在RCC选项卡里把HSE高速外部时钟设置为Crystal/Ceramic Resonator。大部分F103开发板外部都焊了8MHz的晶振。然后在Clock Configuration选项卡里把PLL Source选为HSEPLL Mul选为9倍频这样系统时钟就是8MHz × 9 72MHz。AHB Prescaler设为1APB1 Prescaler设为2APB1最高36MHzAPB2 Prescaler设为1APB2最高72MHz。为什么APB1要分频因为STM32F103的APB1总线最高只能跑36MHz而APB2可以跑72MHz。如果你把APB1也设成72MHz定时器、串口这些挂在APB1上的外设可能会工作不正常。这个细节很多新手会忽略导致串口波特率不对或者定时器周期不对。3.3 GPIO输出点亮第一颗LED在CubeMX里找到板子上LED对应的引脚。以常见的BluePill板为例板载LED接在PC13上低电平点亮。在Pinout视图里点击PC13选择GPIO_Output。然后在GPIO选项卡里把PC13的GPIO output level设为Low这样上电就亮GPIO mode设为Output Push PullGPIO Pull-up/Pull-down设为Pull-upMaximum output speed设为Low。生成代码后在main函数的while(1)循环里写HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(500);编译下载你应该能看到LED以1Hz的频率闪烁。如果没亮先检查PC13的配置是否正确再检查板子上的LED是不是接在别的引脚上。有些板子的LED接在PA5或者PB12上具体看原理图。3.4 串口配置与printf重定向串口是调试过程中最重要的工具。在CubeMX里使能USART1模式设为Asynchronous波特率1152008位数据位1位停止位无校验。然后生成代码。为了能用printf输出调试信息需要重定向fputc函数#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; }然后在Keil的Target选项里勾选“Use MicroLIB”否则printf可能无法正常工作。如果你用的是GCC工具链比如PlatformIO需要自己实现_write函数。配置好之后在main函数里写printf(Hello STM32! System Clock: %lu Hz\r\n, HAL_RCC_GetHCLKFreq());用USB转TTL模块连接PA9TX和PA10RX注意TX接RXRX接TXGND共地。打开串口助手波特率115200你应该能看到打印信息。注意STM32F103的USART1挂在APB2上USART2、USART3挂在APB1上。如果你用USART2波特率计算时的时钟源是APB1的时钟36MHz而不是72MHz。这个在配置的时候CubeMX会自动处理但如果你手动写寄存器就要注意这个区别。4. 进阶外设实战定时器、ADC、CAN和USB4.1 定时器不只是延时STM32F103的定时器功能非常强大。除了基本的定时中断还能做PWM输出、输入捕获、编码器接口等。以TIM2为例它是一个32位通用定时器挂在APB1上。假设APB1时钟是36MHzTIM2的时钟就是36MHz × 2 72MHz因为APB1预分频系数不为1时定时器时钟会倍频。要配置一个1kHz的定时中断计算过程如下定时器时钟72MHz预分频系数Prescaler设为72-171这样定时器计数频率就是1MHz。自动重装载值AutoReload Register设为1000-1999这样每1000个计数溢出一次溢出频率就是1MHz / 1000 1kHz。在CubeMX里配置TIM2Clock Source选Internal ClockPrescaler填71Counter Period填999然后使能TIM2全局中断。在中断回调函数里写void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); } }这样LED就会以500Hz的频率翻转肉眼看起来是常亮的。你可以把Counter Period改成7200-1这样溢出频率就是100HzLED闪烁就看得清了。PWM输出也很常用。比如控制舵机需要50Hz的PWM脉宽在0.5ms到2.5ms之间。配置TIM3的通道1为PWM Generation CH1Prescaler设为72-1Counter Period设为20000-1这样PWM周期就是20ms50Hz。然后设置Pulse为1500对应1.5ms脉宽舵机就会转到中位。4.2 ADC读取模拟传感器STM32F103的ADC是12位的转换范围0到4095对应电压0到3.3V。配置ADC1的通道0PA0连续转换模式使能DMA传输。这样ADC会不停地转换DMA自动把结果搬到内存里不占用CPU。uint16_t adc_value; HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_value, 1); float voltage adc_value * 3.3f / 4095.0f;如果你要读光敏电阻或者热敏电阻注意分压电路的设计。比如光敏电阻和10k电阻串联光敏电阻接VCC10k接GND中间点接PA0。光照越强光敏电阻阻值越小PA0电压越高。注意STM32F103的ADC输入阻抗不是无限大如果信号源内阻太大转换结果会不准。一般建议信号源内阻小于10kΩ。如果内阻太大可以在前面加一个电压跟随器运放。4.3 CAN通信为什么突然连不上CAN总线在汽车电子和工业控制里用得很多。STM32F103自带CAN控制器但需要外接CAN收发器比如TJA1050或者SN65HVD230。配置CAN的时候波特率计算比较复杂。以500kbps为例APB1时钟36MHzPrescaler设为4BS1设为5个时间单元BS2设为2个时间单元SJW设为1。这样波特率 36MHz / (4 × (1 5 2)) 36MHz / 32 1.125Mbps不对重新算36MHz / 4 9MHz9MHz / (1 5 2) 1.125MHz还是不对。正确的公式是波特率 APB1时钟 / (Prescaler × (1 BS1 BS2))。要得到500kbpsPrescaler × (1 BS1 BS2) 36MHz / 500k 72。如果Prescaler4那么1BS1BS218BS115BS22SJW1。这样波特率 36MHz / (4 × 18) 500kbps。CAN突然连不上的常见原因终端电阻没接CAN总线两端需要各接一个120Ω电阻、波特率不匹配、CAN收发器供电不对、或者CAN_H和CAN_L接反了。我遇到过最坑的一次是CAN收发器的STB引脚Standby悬空了导致收发器一直处于待机模式怎么都发不出数据。后来把STB接地就好了。4.4 USB DeviceF103的USB能做什么STM32F103的USB外设是USB 2.0 Full Speed12Mbps只能做Device不能做Host。也就是说你可以把STM32模拟成一个USB设备比如虚拟串口CDC、键盘、鼠标、U盘MSC等但不能接U盘或者键盘。用CubeMX配置USB Device选择Communication Device ClassCDC生成代码后电脑上会多出一个虚拟串口。你可以用这个串口和STM32通信速度比UART快很多。但要注意F103的USB和CAN共用一部分RAM如果你同时用USB和CAN可能会遇到RAM不够的问题。具体来说USB缓冲区占用了512字节的专用RAMCAN也有一部分专用RAM两者加起来可能会超过F103的RAM限制。另外F103的USB时钟必须精确为48MHz。如果外部晶振是8MHzPLL配置为8MHz × 9 72MHz作为系统时钟USB时钟需要72MHz / 1.5 48MHz。这个1.5分频是F103特有的CubeMX会自动处理。如果你用的是12MHz晶振PLL配置会不一样USB时钟也要重新算。5. 常见问题排查与避坑指南5.1 下载失败No Target Connected这是新手遇到最多的问题。排查步骤检查ST-Link驱动是否安装。在设备管理器里看有没有“STMicroelectronics STLink dongle”或者类似设备。如果没有去ST官网下载ST-Link驱动安装。检查SWDIO和SWCLK是否接对。SWDIO接PA13SWCLK接PA14GND接GND3.3V接3.3V。检查BOOT0是否接GND。如果BOOT0接VCC芯片会从系统存储器启动不运行你的代码但ST-Link应该还是能连上。如果之前下载的程序禁用了SWD引脚按住复位键点击下载然后松开复位键。检查Keil的Debug设置里是否选对了调试器ST-Link Debugger以及Port是否选为SW。5.2 串口乱码波特率不对串口乱码最常见的原因是波特率不匹配。检查以下几点代码里设置的波特率和串口助手设置的波特率是否一致。系统时钟配置是否正确。如果HSE没起振系统会默认使用HSI8MHz内部RC振荡器导致实际波特率偏差很大。用示波器或者逻辑分析仪测一下PA9的波形看看波特率是不是115200。如果用的是USB转TTL模块检查模块的晶振是不是12MHz。有些便宜的模块用的是1.5MHz的陶瓷谐振器波特率误差很大。5.3 定时器延时不准HAL_Delay()函数是基于SysTick定时器的默认1ms中断一次。如果你在中断服务函数里调用了HAL_Delay()会导致死锁因为SysTick中断优先级低于当前中断永远进不来。解决办法是在中断里不要用HAL_Delay()改用简单的循环延时或者硬件定时器。另外如果你修改了系统时钟HAL_Delay()的延时也会变。比如你把系统时钟从72MHz改成36MHzHAL_Delay(1000)实际延时会变成2秒。因为SysTick的配置是基于系统时钟的CubeMX生成的代码里会自动处理但如果你手动改了时钟配置就要重新初始化SysTick。5.4 CAN通信突然断开CAN总线在强电磁干扰环境下可能会出错。如果CAN突然连不上检查终端电阻是否接好。CAN总线两端各需要一个120Ω电阻总线上其他节点不需要接。共地问题。如果两个CAN节点之间没有共地差分信号可能超出共模范围导致通信失败。波特率是否一致。所有节点的波特率必须完全相同。CAN收发器是否损坏。用示波器看CAN_H和CAN_L的差分波形正常应该有明显的差分信号。5.5 常见问题速查表问题现象可能原因解决方法下载失败BOOT0接错BOOT0接GND下载失败SWD引脚被禁用按住复位键下载串口乱码波特率不匹配检查时钟配置和串口助手设置LED不亮引脚配置错误检查原理图和GPIO配置定时器不中断中断未使能检查NVIC配置ADC读数跳动参考电压不稳加滤波电容CAN连不上终端电阻缺失两端各接120ΩUSB识别不了时钟不对检查USB时钟是否为48MHz6. 从开发板到项目下一步怎么走板子上的外设都摸了一遍之后你可能会想接下来做什么我的建议是找一个具体的项目来做哪怕很简单。比如STM32鱼缸控制器用DS18B20测水温用继电器控制加热棒用OLED显示当前温度用按键设置目标温度。这个项目涵盖了GPIO、定时器、单总线、I2C、中断等多个外设。超声波测距用HC-SR04测距用OLED显示距离用蜂鸣器做报警。这个项目可以练习输入捕获和定时器。CAN总线数据采集用两个STM32板子通过CAN通信一个发送传感器数据一个接收并显示。这个项目可以深入理解CAN协议。USB虚拟串口把STM32做成一个USB转串口工具可以替代CH340。这个项目可以学习USB CDC协议。做项目的过程中你会遇到各种问题这些问题才是真正让你成长的东西。看教程只能让你知道“怎么做”但只有自己踩过坑才知道“为什么这么做”和“怎么做更好”。提示做项目的时候养成写文档和注释的习惯。把每个模块的接线、配置、遇到的问题和解决方法都记下来。过几个月回头看你会感谢当时的自己。最后分享一个我个人的习惯每次拿到一个新的芯片或者开发板我都会先花半天时间把数据手册和参考手册翻一遍重点看引脚定义、时钟树、电源要求和外设框图。不用全部看懂但至少知道遇到问题的时候该去翻哪一章。这个习惯让我在后面的开发中省了很多时间。STM32F103的参考手册有1000多页但你不需要从头读到尾把它当成字典用就行。