
1. 拿到板子先别急着上电STM32F103开发板入门全景拆解STM32F103开发板到手的那一刻很多人的第一反应是插上USB线看灯亮不亮。这个动作没错但如果你打算长期玩下去最好先把几个关键问题想清楚这块板子到底能干什么、它的能力边界在哪里、你打算用哪条技术路线来学。STM32F103是ARM Cortex-M3内核的微控制器主频72MHz常见型号如C8T6有64KB Flash和20KB SRAMF103ZET6则能到512KB Flash和64KB SRAM。这个配置放在今天不算高但它的外设覆盖了GPIO、USART、SPI、I2C、ADC、DAC、CAN、USB、定时器、DMA等几乎所有嵌入式开发的核心模块用来打基础再合适不过。我见过太多人买了板子之后卡在第一步Keil装好了芯片包也下了但新建工程时不知道该选标准库还是HAL库不知道启动文件该用哪个甚至不知道芯片的第一脚在哪里。这些问题看起来琐碎但每一个都能消耗掉一个新手一整个周末的耐心。所以这篇内容我会从拿到板子之后的实际动作开始把STM32F103的学习路径、工具链选择、常见外设的实操方法、以及那些教程里不会写的坑一次性讲透。适合读这篇内容的人包括刚买STM32F103开发板的初学者、从51单片机转过来的开发者、需要做STM32相关毕业设计的学生、以及想用STM32做具体项目但不知道从哪下手的工程师。我不会只讲理论每个环节都会给出可以直接复现的操作步骤和参数依据。2. 开发环境搭建工具链选型与工程创建2.1 Keil、STM32CubeIDE还是VSCode加插件工具链的选择直接决定了你后续开发的舒适度。目前STM32F103的主流开发环境有三条路线每条都有明确的适用场景。Keil MDK是国内最流行的选择优点是资料多、教程全、编译速度快配合ST-Link或J-Link下载调试都很稳定。缺点是编辑器体验停留在十年前代码补全和跳转功能弱而且需要手动安装芯片包。如果你跟着大多数国内教程走Keil是最不容易卡住的路。STM32CubeIDE是ST官方推出的免费IDE基于Eclipse集成了CubeMX配置工具和GCC编译器。优点是免费、官方支持、图形化配置外设很方便。缺点是Eclipse底子导致启动慢、占用内存大而且自动生成的代码结构对新手来说不够透明。VSCode加STM32插件是近几年越来越流行的方案用VSCode写代码用Makefile或CMake组织工程用OpenOCD或J-Link工具链下载。优点是编辑器体验极佳、代码补全强大、工程结构清晰。缺点是需要自己配置的东西多对新手不够友好。我的建议是如果你是完全的新手先用Keil把第一个LED灯点亮点亮建立信心。等你对STM32的工程结构有了基本认识之后再迁移到VSCode方案。不要一上来就折腾VSCode配置那会让你在还没看到灯亮之前就放弃。2.2 芯片包安装与新建工程的正确姿势Keil安装好之后第一件事是安装STM32F1系列的芯片包。打开Keil点击Pack Installer图标在Devices列表中找到STMicroelectronics展开STM32F1 Series找到STM32F103点击对应的Pack进行安装。如果网络环境不好导致下载失败可以去Keil官网手动下载pack文件双击安装即可。新建工程时Keil会弹出Run-Time Environment界面这里有一个关键选择是否使用CMSIS的Core和Device Startup组件。对于标准库开发你需要勾选CMSIS的CORE和Device的Startup但不要勾选Device的StdPeriph Drivers因为标准库需要手动添加。对于HAL库开发可以勾选Device的HAL Drivers让Keil自动帮你添加HAL库文件。启动文件的选择取决于你的芯片型号和Flash大小。STM32F103C8T6用startup_stm32f10x_md.sF103ZET6用startup_stm32f10x_hd.sF103RC用startup_stm32f10x_hd.s。选错了启动文件会导致程序无法运行或者堆栈溢出。一个简单的判断方法看芯片型号中的Flash容量代码C8是64KB属于中容量RC是256KB属于大容量ZC是256KB也属于大容量。2.3 标准库与HAL库的取舍逻辑标准库和HAL库的选择是新手最纠结的问题之一。标准库直接操作寄存器代码效率高、执行速度快、资源占用小但代码可移植性差ST已经停止更新。HAL库是ST现在主推的方案抽象层次高、跨系列移植方便、配合CubeMX可以快速生成初始化代码但代码体积大、执行效率略低。对于STM32F103的学习我的建议是先用标准库入门。原因很简单标准库的代码更接近硬件本质你能清楚地看到每个外设的寄存器配置过程这对理解STM32的工作原理非常有帮助。等你把GPIO、USART、定时器、中断这些基础外设用标准库跑通之后再转HAL库会非常轻松因为底层原理你已经懂了。如果你是为了做项目而不是学习或者需要快速出原型那直接用HAL库加CubeMX更高效。CubeMX可以图形化配置引脚和外设自动生成初始化代码省去了大量查手册和写配置代码的时间。3. 核心外设实操从点灯到通信的完整路径3.1 GPIO点灯与按键输入第一个可运行的程序点灯是嵌入式的Hello World但即使是点灯也有几个细节值得注意。STM32F103的GPIO有八种模式输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、开漏复用、推挽复用。点灯用推挽输出按键输入用上拉或下拉输入。以常见的STM32F103C8T6最小系统板为例板载LED通常接在PC13上低电平点亮。配置代码如下// 使能GPIOC时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); // 点亮LED GPIO_ResetBits(GPIOC, GPIO_Pin_13);按键输入需要配置为上拉输入或下拉输入并加入软件消抖。硬件消抖可以用RC电路软件消抖通常延时10到20毫秒再读取电平。我实测下来机械按键的抖动时间一般在5到15毫秒之间延时20毫秒是比较稳妥的选择。注意PC13在STM32F103上属于备份域引脚驱动能力较弱只能用于低功耗场景。如果你要驱动大电流的LED建议换用PA或PB端口的引脚或者加三极管驱动。3.2 串口通信调试与数据交互的核心通道USART是STM32开发中最常用的外设之一用于打印调试信息、与上位机通信、连接蓝牙或WiFi模块等。STM32F103通常有3个USART和2个UARTUSART1挂在APB2总线上USART2和USART3挂在APB1总线上。配置USART1的步骤如下使能GPIOA和USART1时钟配置PA9为复用推挽输出TXPA10为浮空输入或上拉输入RX配置USART参数波特率115200、8位数据位、1位停止位、无校验、无硬件流控使能USART和接收中断。// 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX PA9 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置RX PA10 GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置USART1 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate 115200; 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);重定向printf到串口是调试利器在标准库中需要重写fputc函数int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }提示使用printf重定向时需要在Keil的Target选项中勾选Use MicroLIB否则程序会卡在fputc里出不来。这个坑我踩过不止一次。3.3 定时器与中断精确控制时间的基础STM32F103的定时器资源非常丰富包括2个高级定时器TIM1和TIM8、4个通用定时器TIM2到TIM5、2个基本定时器TIM6和TIM7。定时器可以用于精确定时、PWM输出、输入捕获测频率、编码器接口等场景。配置TIM2为1毫秒中断的步骤如下使能TIM2时钟计算预分频器和自动重装载值。系统时钟72MHz预分频器设为7199则定时器时钟为72MHz/(71991)10kHz自动重装载值设为9则中断周期为(91)/10kHz1ms。RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period 9; TIM_TimeBaseStructure.TIM_Prescaler 7199; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); // 配置NVIC NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);中断服务函数中记得清除中断标志位void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 你的代码 } }3.4 ADC采样与DMA传输数据采集的高效方案ADC用于采集模拟信号比如电位器电压、传感器输出等。STM32F103的ADC是12位逐次逼近型有18个通道最高采样率1MHz。配合DMA可以实现多通道自动扫描采集不占用CPU资源。配置ADC1的通道0PA0配合DMA的流程使能ADC1、GPIOA、DMA1时钟配置PA0为模拟输入配置DMA1通道1从ADC1数据寄存器搬运到内存数组配置ADC1为连续转换模式、扫描模式、外部触发禁用使能ADC1的DMA请求校准ADC启动转换。// 配置DMA DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_values; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE);注意ADC的采样时间需要根据信号源阻抗来调整。信号源阻抗越高需要的采样时间越长。如果采样时间不够采集到的值会偏小且不稳定。一般建议采样时间设为55.5个周期或239.5个周期。4. 进阶方向与项目实战从学会到会用4.1 CAN通信与常见连接问题排查CAN总线在工业控制和汽车电子中应用广泛STM32F103自带CAN控制器配合收发器如TJA1050即可组网。CAN通信突然连不上是常见问题排查思路如下先确认终端电阻是否接好CAN总线两端各需要120欧姆终端电阻再检查波特率是否一致双方必须相同然后确认收发器供电和使能引脚状态最后用示波器看CAN_H和CAN_L的差分信号是否正常。我遇到过一种情况两块STM32板子CAN通信正常但接入第三个节点后就频繁报错。最后发现是第三个节点的终端电阻没有断开导致总线上有三个120欧姆电阻并联等效阻抗变成40欧姆信号幅度被拉低。CAN总线的终端电阻只能接在物理总线的两个最远端中间节点不能接。4.2 USB设备开发从虚拟串口到自定义HIDSTM32F103的USB外设支持全速设备模式可以实现虚拟串口、HID键盘鼠标、大容量存储等设备。做USB设备开发时ST提供的USB库可以大大简化开发流程。以虚拟串口为例你需要配置USB时钟为48MHz使能USB外设移植ST的CDC类驱动然后在main循环中处理USB中断和收发数据。USB开发中最容易卡住的地方是时钟配置。STM32F103的USB外设要求48MHz时钟而系统时钟是72MHz需要通过PLL分频得到。如果时钟配置不对USB设备根本无法被主机识别。另外USB的DP引脚需要接1.5k欧姆上拉电阻到3.3V有些开发板已经集成有些需要自己外接。4.3 超声波测距与ILI9341屏幕驱动超声波测距模块HC-SR04配合STM32的输入捕获功能可以测量距离。原理是Trig引脚发送10微秒高电平脉冲Echo引脚输出高电平高电平持续时间就是声波往返时间。距离等于高电平时间乘以声速340米每秒再除以2。用定时器的输入捕获模式可以精确测量高电平时间精度可以达到微秒级。ILI9341是常见的2.8寸TFT屏幕驱动芯片通过SPI接口与STM32通信。读ID是0xA1A1说明通信正常。驱动ILI9341需要初始化一系列寄存器然后实现画点、画线、显示字符和图片的函数。SPI时钟建议设为18MHz到36MHz之间太快会导致显示异常太慢则刷新率低。4.4 常见问题速查表问题现象可能原因排查方法程序下载后不运行启动文件选错、时钟配置错误检查启动文件型号、用示波器看晶振是否起振串口打印乱码波特率不匹配、时钟配置错误确认双方波特率一致、检查系统时钟配置中断不触发NVIC未配置、中断标志未清除检查NVIC配置、在中断服务函数中清除标志ADC采样值跳动大采样时间不足、参考电压不稳增加采样时间、加滤波电容CAN通信失败终端电阻不对、波特率不一致检查终端电阻、确认双方波特率USB设备无法识别时钟不是48MHz、上拉电阻缺失检查USB时钟配置、确认DP上拉电阻定时器延时不准预分频器或重装载值计算错误重新计算定时器参数程序卡在延时函数中断优先级配置不当、死循环检查中断优先级、确认延时函数实现5. 学习路径与资源规划少走弯路的实操建议5.1 分阶段学习路线图STM32F103的学习可以分成四个阶段。第一阶段是基础外设包括GPIO、延时、串口、中断目标是能点亮LED、按键控制、串口收发。第二阶段是定时器和ADC包括定时中断、PWM输出、输入捕获、ADC采样目标是能做呼吸灯、测频率、采集电压。第三阶段是通信接口包括SPI、I2C、CAN、USB目标是能驱动屏幕、读写EEPROM、组网通信。第四阶段是综合项目把前面学的外设组合起来做一个完整的东西比如智能小车、数据采集器、温控系统。每个阶段建议用时两到四周不要贪快。我见过有人一周之内把教程从头到尾抄了一遍结果合上电脑什么都不会写。嵌入式的核心能力是调试能力而调试能力只能通过实际踩坑来积累。5.2 调试工具与技巧ST-Link是STM32开发必备的调试工具支持下载和在线调试。J-Link性能更好但价格更贵。调试时善用断点、单步执行、变量监视窗口可以快速定位问题。串口打印是最朴素的调试手段但在没有仿真器的情况下非常有效。逻辑分析仪是排查通信问题的利器可以抓取SPI、I2C、USART、CAN的波形直观看到时序和数据。一个几十块钱的八通道逻辑分析仪配合开源软件能解决大部分通信调试问题。提示调试时如果程序跑飞先检查堆栈是否溢出。STM32F103的默认堆栈大小在启动文件中定义如果局部变量太大或者中断嵌套太深会导致堆栈溢出。可以在启动文件中把Stack_Size从0x00000400改成0x00000800试试。5.3 项目选题与毕业设计建议如果你是为了做毕业设计选题要兼顾工作量和可实现性。基于STM32F103的常见选题包括智能家居控制系统温湿度采集、继电器控制、WiFi上传、数据采集终端多路ADC、SD卡存储、屏幕显示、电机控制系统PWM调速、编码器测速、PID闭环、CAN总线节点多板通信、数据交换。选题时注意避开两个极端太简单只点个灯和太复杂要求实时操作系统加网络协议栈加图形界面。一个合理的毕业设计应该是三到四个外设的组合加上一个完整的上位机或显示界面代码量在两千到五千行之间。5.4 常见学习误区与避坑指南第一个误区是只看不练。嵌入式是实践性极强的学科看十遍教程不如自己写一遍代码。第二个误区是追求最新最全。STM32F103虽然老但它的资料最丰富、坑最少把F103学透了再学其他系列会很快。第三个误区是忽视硬件基础。很多软件问题其实是硬件问题比如电源不稳、晶振不起振、引脚接错。学会看原理图、用万用表、用示波器会让你的调试效率翻倍。第四个误区是死磕一个问题太久。遇到卡住的地方先搜索、再问人、最后放一放换个思路。有时候睡一觉起来就发现问题所在了。第五个误区是不做笔记。把踩过的坑、验证过的配置、常用的代码片段整理成自己的知识库下次遇到同样的问题可以直接查。6. 从F103出发的扩展方向STM32F103学会之后你可以往几个方向扩展。一个是往高性能走比如STM32F4系列Cortex-M4内核带FPU和DSP指令或者STM32H7系列Cortex-M7内核480MHz主频。另一个是往无线走比如STM32WB系列带蓝牙和Zigbee或者配合ESP32做WiFi通信。还有一个是往Linux走比如全志、瑞芯微等平台的开发板跑Linux系统做应用开发。但不管往哪个方向走F103打下的基础都是通用的GPIO操作、中断处理、定时器配置、通信协议、调试方法这些能力在任何嵌入式平台上都用得上。我个人的体会是把F103的每一个外设都亲手调通一遍比泛泛地学五个平台更有价值。最后分享一个小技巧建立一个自己的代码仓库把每个外设的驱动代码整理成独立的模块加上清晰的注释和接口说明。下次做新项目时直接复制粘贴能省下大量时间。这个习惯我从学F103的第一天就开始坚持到现在已经积累了几十个可复用的驱动模块做新项目的速度比刚开始快了不止三倍。