ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103开发板入门实战:从环境搭建到外设驱动全解析

STM32F103开发板入门实战:从环境搭建到外设驱动全解析 1. 拿到板子先别急着点灯STM32F103开发板入门全景拆解STM32F103这块板子大概是国内嵌入式爱好者手里保有量最大的一块开发板了。不管你是电子专业的学生、刚转行嵌入式的工程师还是想给毕业设计找个靠谱平台的准毕业生F103基本都是绕不开的起点。我前后用过不下十种F103的开发板从最小系统板到带一堆外设的综合板踩过的坑能写满一个笔记本。这次借着新板子到手的机会把从开箱到跑通第一个工程的完整思路捋一遍顺便把那些新手最容易卡住的地方提前给你标出来。STM32F103属于意法半导体STM32家族里的经典款基于ARM Cortex-M3内核主频72MHz片上Flash从16KB到1MB不等SRAM从6KB到96KB。市面上最常见的型号是STM32F103C8T6和STM32F103ZET6前者是48脚的小容量版后者是144脚的大容量版。你买到的开发板大概率是这两种之一或者是以它们为核心做的扩展板。这块芯片能做什么简单说从简单的LED闪烁、按键读取到串口通信、定时器PWM、ADC采样、I2C/SPI外设驱动再到USB设备、CAN总线、FreeRTOS多任务它都能扛。对于初学者来说它的资料丰富程度是其他任何芯片都比不了的你遇到的几乎每一个问题网上都有人已经问过并解决了。这篇文章适合谁看如果你刚拿到一块STM32F103开发板装好了Keil或者VSCode但不知道从哪下手或者你之前玩过51单片机想升级到32位平台再或者你正在做基于STM32的毕业设计需要快速搭建工程框架那接下来的内容就是为你准备的。我会从开发环境搭建、工程模板创建、外设驱动编写、调试烧录、常见问题排查这几个维度把整个入门路径讲透。不会只给你步骤还会告诉你每一步为什么这么做以及我实际踩过的坑。2. 开发环境怎么选Keil、VSCode还是STM32CubeIDE2.1 三套主流方案的取舍逻辑刚拿到板子的人第一个纠结的问题往往是我该用什么软件写代码目前STM32开发的主流环境有三套Keil MDK、STM32CubeIDE、以及VSCode加插件组合。这三套我都深度用过各有各的脾气。Keil MDK是国内最流行的选择原因很简单资料多、教程多、学校教的就是这个。它的优势在于编译器成熟、调试器支持广泛、对标准库和HAL库的兼容性都很好。但缺点也很明显界面老旧、代码补全弱、免费版有32KB代码限制。不过对于F103C8T6这种64KB Flash的芯片来说32KB限制其实挺要命的一个带USB和串口的工程很容易就超了。所以如果你用Keil要么用注册机不推荐有法律风险要么考虑用社区版或者切换到其他工具。STM32CubeIDE是ST官方推出的免费IDE基于Eclipse集成了CubeMX配置工具和GCC编译器没有代码大小限制。它的优势是官方支持、免费、配置外设方便。缺点是Eclipse底子导致界面响应慢代码补全不如VSCode流畅而且生成的工程结构比较臃肿。如果你不想折腾环境想开箱即用CubeIDE是最省心的选择。VSCode加插件是近几年越来越流行的方案核心组合是VSCode Cortex-Debug STM32CubeMX生成Makefile工程 arm-none-eabi-gcc工具链。这套方案的优势是编辑器体验极佳、代码补全强大、跨平台、完全免费。缺点是需要手动配置的东西多新手容易在launch.json和tasks.json上卡住。但一旦配好开发效率是最高的。我的建议是如果你完全零基础先用Keil或者CubeIDE把第一个工程跑通建立信心。等你对工程结构有感觉了再迁移到VSCode。不要一上来就折腾VSCode很容易在环境配置上耗尽耐心。2.2 Keil5兼容C51和STM32的安装要点很多人电脑里已经装了Keil C51用于51单片机开发想再装Keil MDK做STM32。这两个可以共存但安装顺序有讲究。正确的做法是先装C51再装MDK并且安装到不同的目录。如果顺序反了或者装到同一个目录会出现编译器冲突、器件包加载异常等问题。安装完MDK后还需要安装对应的器件支持包Device Family Pack。STM32F1系列的包叫Keil.STM32F1xx_DFP可以从Keil官网下载离线包手动安装也可以在Pack Installer里在线安装。我实测下来在线安装经常因为网络问题失败建议直接下载离线包双击安装稳得多。还有一个常见问题Keil5的代码编辑界面默认字体和配色很难看而且中文注释容易乱码。解决办法是在Edit - Configuration里把Encoding改成UTF-8字体换成Consolas或者Source Code Pro字号12到14之间比较舒服。这些小设置看似不重要但每天对着的界面舒服了学习效率真的会高不少。2.3 VSCode配置STM32开发环境的实操路径如果你决定用VSCode核心步骤是这样的先安装arm-none-eabi-gcc工具链并添加到系统PATH然后安装VSCode插件Cortex-Debug和C/C接着用STM32CubeMX生成Makefile工程最后配置launch.json和tasks.json。launch.json里最关键的是这几个字段executable指向编译输出的elf文件servertype选openocd或者jlinkdevice填STM32F103C8svdFile填对应的svd文件路径用于查看寄存器。tasks.json里配置build任务调用makeclean任务调用make clean。这些配置我建议直接从一个能用的工程里复制然后改路径比从头写快得多。有个坑要注意VSCode里编译成功但烧录不进去十有八九是launch.json里的executable路径写错了或者OpenOCD的配置文件选错了。F103C8T6对应的OpenOCD配置文件是stm32f1x.cfg如果你用的是ST-Linkinterface选stlink-v2.cfg如果用J-Linkinterface选jlink.cfg。这些细节后面讲调试的时候还会展开。3. 工程模板搭建从零创建一个可复用的STM32工程3.1 标准库还是HAL库新手的第一道选择题STM32的代码库有两套标准外设库Standard Peripheral Library和HAL库Hardware Abstraction Layer。标准库是ST早期推出的直接操作寄存器代码效率高、结构清晰但ST已经停止维护了。HAL库是现在主推的抽象层次高、跨系列移植方便但代码体积大、执行效率略低。对于F103入门来说我建议先用标准库。原因有三第一标准库的代码更接近硬件你能更清楚地看到每个操作对应哪个寄存器对理解STM32的工作原理有帮助第二网上F103的教程和例程绝大多数是基于标准库的遇到问题更容易找到参考第三标准库编译出来的代码更小F103C8T6的64KB Flash能装下更多功能。等你把标准库玩熟了再转HAL库会很快因为底层概念是相通的。反过来先学HAL再回头看标准库反而会觉得标准库太底层、太繁琐。3.2 手动创建标准库工程模板的完整步骤创建一个标准库工程模板核心是把必要的文件组织好。你需要从ST官网下载STM32F10x标准外设库解压后找到Libraries文件夹里面有两个关键目录CMSIS和STM32F10x_StdPeriph_Driver。CMSIS目录下需要的是CM3/CoreSupport里的core_cm3.c和core_cm3.h以及DeviceSupport/ST/STM32F10x里的stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h还有startup/arm里的启动文件startup_stm32f10x_md.smd对应中等容量C8T6属于这一类。STM32F10x_StdPeriph_Driver目录下需要的是inc和src两个文件夹的全部内容。然后在工程根目录下建几个文件夹Startup放启动文件Library放标准库源文件User放main.c和stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.hOutput放编译输出。在Keil里新建工程后把这些文件按分组添加进去然后在Options for Target里设置头文件包含路径勾选Create HEX File选择正确的器件型号。编译一下如果没有报错模板就建好了。这个过程看起来繁琐但做一次之后就可以复制给以后的所有工程用一劳永逸。3.3 工程模板的目录结构与文件作用说明一个清晰的项目目录结构能让你在工程变大之后依然保持条理。我习惯的布局是这样的Startup/启动文件负责在复位后初始化堆栈指针、调用SystemInit、跳转到main函数。这个文件一般不需要改但要知道它的存在。Library/标准外设库的源文件每个外设一个.c文件比如stm32f10x_gpio.c、stm32f10x_usart.c。你用到哪个就编译哪个不用的可以在Keil里排除掉以加快编译速度。User/你的应用代码。main.c是入口stm32f10x_it.c是中断服务函数的集中地stm32f10x_conf.h用来裁剪标准库的包含关系。Hardware/我自己加的一层用来放具体外设的驱动代码比如led.c、key.c、oled.c。这样应用逻辑和硬件驱动分离换板子的时候只需要改Hardware层。Output/编译生成的.hex、.axf、.map文件都放这里方便管理和烧录。这个结构不是唯一的但核心思想是分层底层库、硬件驱动、应用逻辑分开。新手最容易把所有代码堆在main.c里一开始功能少还行等代码超过500行就会乱得没法维护。4. 第一个工程GPIO点灯与按键输入4.1 点灯之前必须搞清楚的时钟使能很多新手写的第一行GPIO代码是这样的配置GPIO模式设置引脚然后发现LED不亮。排查半天最后发现是忘了使能GPIO的时钟。STM32的外设默认时钟是关闭的用之前必须通过RCC_APB2PeriphClockCmd开启对应端口的时钟。这个设计是为了省电但对手新手来说就是个必踩的坑。以PA5接LED为例标准库的代码顺序是先调用RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)然后定义GPIO_InitTypeDef结构体设置GPIO_Pin为GPIO_Pin_5GPIO_Mode为GPIO_Mode_Out_PP推挽输出GPIO_Speed为GPIO_Speed_50MHz最后调用GPIO_Init(GPIOA, GPIO_InitStructure)。之后就可以用GPIO_SetBits和GPIO_ResetBits控制亮灭了。这里有个细节GPIO_Speed的设置影响的是引脚翻转速率和电磁兼容性不是输出电平。50MHz适合需要快速翻转的场合如果只是点灯2MHz就够了。速度设高了会增加功耗和EMI虽然点灯场景无所谓但养成按需设置的习惯是好的。4.2 按键输入上拉、下拉与消抖的工程实践按键比LED麻烦的地方在于机械抖动。你按一下按键电平并不是干净地从高变低而是在几毫秒内快速跳变多次。如果不处理一次按下可能被识别成多次触发。硬件上按键通常一端接地另一端接GPIO并配上拉电阻。这样按键松开时引脚是高电平按下时是低电平。STM32的GPIO可以配置内部上拉所以外部上拉电阻可以省掉直接配置GPIO_Mode_IPU输入上拉。但内部上拉电阻阻值较大约40K欧抗干扰能力不如外部4.7K或10K上拉如果按键引线较长建议还是加外部上拉。软件消抖有两种常用方法延时法和定时器扫描法。延时法简单粗暴检测到低电平后延时10到20毫秒再检测一次如果还是低电平就确认按下。缺点是阻塞CPU只适合按键数量少、实时性要求不高的场景。定时器扫描法是用一个定时器每5到10毫秒中断一次在中断里读取按键状态并做状态机判断不阻塞主循环适合多按键和复杂交互。我建议新手先用延时法把功能跑通然后尽快过渡到定时器扫描法因为实际项目里阻塞式消抖迟早会出问题。4.3 用位带操作提升GPIO读写效率标准库的GPIO_SetBits和GPIO_ResetBits函数调用有一定开销在需要频繁翻转引脚的场合比如软件模拟SPI直接操作寄存器会快很多。STM32的位带Bit-Band特性可以把每个比特映射到一个32位地址上让你像操作普通变量一样操作单个引脚。具体做法是定义宏#define PAout(n) *(volatile uint32_t *)(0x42000000 ((GPIOA_BASE 0x0C - 0x40000000) * 32) (n * 4))然后就可以用PAout(5) 1来直接置位。这个技巧在F103上很好用但要注意位带区域只覆盖了外设寄存器和SRAM的一部分不是所有地址都支持。用之前查一下参考手册的位带映射表确认目标地址在位带范围内。5. 串口通信调试信息输出的生命线5.1 USART初始化与重定向printf串口是嵌入式开发中最重要的调试手段没有之一。有了串口你可以在代码任何位置打印变量值、打印状态信息比单步调试直观得多。STM32F103通常有3个USART加2个UARTUSART1挂在APB2上USART2和3挂在APB1上时钟频率不同配置时要注意。初始化USART1的步骤使能GPIOA和USART1时钟配置PA9为复用推挽输出TXPA10为浮空输入或上拉输入RX然后配置USART_InitTypeDef结构体设置波特率115200、8位数据、1位停止位、无校验、无硬件流控最后调用USART_Init和USART_Cmd使能。重定向printf是为了能用printf函数往串口输出。标准库环境下需要重写fputc函数int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }。然后在Keil的Options for Target里勾选Use MicroLIB否则printf会卡死。这个MicroLIB的坑我见过太多人踩了现象就是程序编译烧录都正常但一调用printf就死机。5.2 串口接收中断、空闲中断与环形缓冲区串口发送简单接收才是难点。最简单的接收方式是轮询USART_GetFlagStatus检查RXNE标志但这样会阻塞主循环。进阶做法是用接收中断每收到一个字节进一次中断把数据存入缓冲区。但字节中断在波特率高、数据量大的时候CPU开销很大。更好的方案是DMA加空闲中断。DMA负责把串口数据搬到内存空闲中断在总线空闲时触发告诉你一帧数据收完了。这样CPU几乎不参与接收过程效率极高。F103的DMA1通道5对应USART1_RX配置好DMA后在USART1_IRQHandler里检测IDLE标志清除标志后计算收到的数据长度然后处理。环形缓冲区是串口接收的好搭档。定义一个数组和读写指针中断里往写指针位置存数据并移动写指针主循环从读指针位置取数据并移动读指针。当读写指针相等时缓冲区为空当写指针加一等于读指针时缓冲区满。这个结构能有效解耦接收和处理的速度差异避免数据丢失。5.3 串口通信常见故障排查串口调不通是最常见的问题之一排查思路可以按这个顺序来先确认硬件连接TX接RX、RX接TX、GND接GND这三根线缺一不可。然后确认波特率一致两边都是115200或者都是9600差一点都不行。接着用示波器或者逻辑分析仪看TX引脚有没有波形输出如果没有说明代码里USART没使能或者GPIO配置错了。如果有波形但收到的都是乱码检查时钟配置SystemInit里默认是72MHz如果你改了晶振频率但没改PLL配置波特率就会偏。还有一个隐蔽的问题有些USB转串口模块的TX电平是3.3V有些是5V。STM32的IO是3.3V耐压的接5V的TX长期可能损坏引脚。用之前量一下模块的TX空闲电平如果是5V加个电平转换或者分压电阻。6. 定时器从延时到PWM到输入捕获6.1 定时器模式选择与参数计算STM32F103的定时器资源很丰富TIM1和TIM8是高级定时器TIM2到TIM5是通用定时器TIM6和TIM7是基本定时器。高级定时器多了互补输出、死区控制、刹车输入等功能适合电机控制。通用定时器适合PWM输出、输入捕获、编码器接口。基本定时器只能做定时中断。定时器的核心参数是预分频器PSC和自动重装载值ARR。定时周期计算公式是T (PSC 1) * (ARR 1) / TIMx_CLK。假设TIM2挂在APB1上时钟是72MHz你想要1毫秒中断一次可以设PSC71ARR999这样(711)*(9991)/72000000 0.001秒。PSC和ARR都是16位寄存器最大值65535所以单次最大定时周期有限需要更长定时就靠软件计数累加。6.2 PWM输出控制LED亮度和电机速度PWM是定时器最常用的功能之一。配置步骤使能定时器和GPIO时钟配置GPIO为复用推挽输出配置定时器时基结构体设置PSC和ARR配置OC结构体设置PWM模式1、输出使能、占空比初值最后使能定时器和对应通道的输出。占空比的计算假设ARR999CCR300则占空比为300/100030%。改变CCR值就能改变占空比。用PWM控制LED亮度时频率要选在人眼不觉得闪烁的范围一般1kHz以上就够了。控制电机时频率要匹配电机驱动器的要求太低会有噪音太高开关损耗大通常10kHz到20kHz比较合适。我实测下来F103的TIM1和TIM8做PWM时要注意高级定时器的BDTR寄存器必须设置MOE位才能使能输出否则PWM出不来。这个坑很多人踩过明明配置都对了但引脚就是没波形最后发现是忘了TIM_CtrlPWMOutputs(TIM1, ENABLE)。6.3 输入捕获测频率与占空比输入捕获用来测量外部信号的频率和脉宽。原理是当引脚上出现指定边沿时定时器把当前计数值锁存到捕获寄存器通过两次捕获值的差就能算出时间间隔。配置步骤GPIO设为浮空输入定时器时基配置好输入捕获结构体设置通道、边沿、分频、滤波然后使能捕获中断。在中断里读取CCR值根据两次捕获的差值计算频率。如果要同时测频率和占空比可以用PWMI模式一个通道捕获上升沿另一个通道捕获下降沿或者用从模式复位计数器的方案。测频率时有个精度问题定时器时钟72MHzPSC71时计数频率1MHz测1kHz信号一个周期1000个计数精度0.1%。但如果测10kHz信号一个周期只有100个计数精度就降到1%了。所以测高频信号时要减小PSC让计数频率更高。但PSC太小的话低频信号又会溢出。实际项目中要根据信号频率范围动态调整PSC或者用两个定时器配合。7. 调试与烧录ST-Link、J-Link与串口ISP7.1 三种烧录方式对比与选择STM32F103支持三种主要烧录方式ST-Link、J-Link和串口ISP。ST-Link是ST官方的调试器价格便宜山寨版十几块钱支持SWD和JTAG配合Keil和CubeIDE都很好用。J-Link是SEGGER的性能更强、支持芯片更多但正版价格高山寨版稳定性参差不齐。串口ISP是通过USART1的Bootloader烧录不需要额外调试器但只能烧录不能调试而且需要手动切换BOOT0引脚。对于F103入门我建议用ST-Link。SWD模式只需要三根线SWDIO、SWCLK、GND加上3.3V供电一共四根。接线简单不容易出错。J-Link虽然性能好但山寨版驱动问题多新手容易被驱动安装卡住。7.2 Keil里配置ST-Link调试的详细步骤在Keil里打开Options for Target - Debug选择ST-Link Debugger点Settings。在Debug标签页里Port选SWMax Clock可以拉到4MHzF103支持的最高SWD时钟。在Flash Download标签页里确认Programming Algorithm里有STM32F10x Medium-density Flash如果没有就点Add添加。有一个常见问题Keil提示“No Cortex-M Device found”或者“Cannot access target”。排查顺序是先确认ST-Link驱动装好了设备管理器里能看到ST-Link Debug Interface然后确认接线正确SWDIO对SWDIOSWCLK对SWCLK再确认目标板供电正常3.3V电压稳定最后检查复位引脚有没有被拉低。有时候ST-Link固件版本太老也会导致连接失败用ST-Link Utility升级一下固件就好。7.3 烧录成功但程序不运行的排查思路烧录成功但程序不跑这个现象比烧录失败更让人头疼。常见原因有几个第一启动文件选错了比如C8T6应该用startup_stm32f10x_md.s如果用了hd或者ld的中断向量表就对不上程序一跑就飞。第二SystemInit里时钟配置有问题比如外部晶振起振失败但代码里还等着HSE就绪就会卡在while循环里。第三中断服务函数名写错了导致中断触发时跳到了默认的死循环。排查方法先用调试器单步执行看能不能走到main函数。如果走不到问题在启动阶段如果能走到main但后面跑飞问题在时钟或外设初始化。还可以在main函数第一行翻转一个LED用示波器看有没有输出快速判断程序有没有跑起来。8. 常见问题速查与避坑经验8.1 编译、烧录、运行三类问题速查表问题现象可能原因排查方法编译报错undefined symbol源文件没添加到工程或头文件路径不对检查Keil工程分组和Include Paths编译通过但axf文件过大优化等级低或包含了不用的库文件提高优化等级排除未用源文件烧录提示Flash Download failed算法文件没选或芯片读保护添加正确算法用ST-Link Utility解除读保护烧录成功但LED不亮时钟没使能或引脚配置错误检查RCC使能和GPIO模式串口无输出MicroLIB没勾选或波特率不对勾选Use MicroLIB核对波特率程序跑一段时间死机堆栈溢出或中断优先级冲突增大栈空间检查NVIC优先级分组定时器中断不触发没使能更新中断或NVIC没配置检查TIM_ITConfig和NVIC_InitI2C通信失败上拉电阻缺失或时序不对加4.7K上拉用逻辑分析仪看波形8.2 那些教程里不会写的实操心得第一个心得买板子的时候尽量选带ST-Link的套餐虽然贵十几块钱但省去的麻烦远不止这个价。我见过太多人为了省这点钱在串口ISP烧录上折腾一整天。第二个心得开发板的原理图一定要找到并打印出来。很多问题最后都是靠查原理图解决的比如LED接在哪个引脚、按键有没有外部上拉、晶振频率是多少。没有原理图就像蒙着眼睛修车。第三个心得新建工程时先把串口调通。有了串口输出后面所有调试都事半功倍。我现在的习惯是任何新板子到手第一件事是点灯第二件事就是调通串口printf这两件事做完后面就顺了。第四个心得不要迷信教程里的代码。教程的代码是针对特定板子写的引脚定义、晶振频率、外设连接可能和你的板子不一样。抄代码之前先确认硬件差异改好引脚定义再编译。8.3 从点灯到毕业设计学习路径建议如果你是为了做毕业设计才学STM32时间有限我建议按这个路径走第一周搞定GPIO和串口能点灯能打印第二周搞定定时器和中断能做PWM和定时任务第三周搞定一个通信接口I2C或SPI能驱动OLED或传感器第四周把FreeRTOS跑起来学会多任务编程。这四步走完大部分毕业设计的技术栈就覆盖了。具体到毕业设计常见的题目比如“基于STM32的超声波测距系统”核心就是定时器输入捕获测回波时间加上串口或OLED显示距离。再比如“基于STM32的鱼缸控制器”核心是DS3231时钟读取、继电器控制、温度传感器读取。这些功能拆开看都不难难的是把它们稳定地整合在一起这就需要在工程结构上下功夫把驱动层和应用层分开每个模块单独测试通过后再集成。9. 外设扩展I2C、SPI与传感器驱动9.1 I2C驱动OLED的完整流程I2C是连接传感器和小型显示屏最常用的总线。F103的硬件I2C有已知的稳定性问题很多老手宁愿用软件模拟I2C。软件I2C的好处是引脚随便选、时序可控、调试方便缺点是速度慢、占CPU。对于OLED这种数据量不大的外设软件I2C完全够用。驱动OLED的步骤先写I2C起始、停止、发送字节、接收字节这几个底层函数延时用微秒级。然后写OLED初始化序列按照SSD1306的数据手册发送配置命令。接着写显示函数把显存数据写入OLED的GDDRAM。最后写字符和数字的显示函数用取模软件生成字模数组。这里有个细节软件I2C的延时不能太短否则时序不满足器件要求。SSD1306的I2C最高支持400kHz对应周期2.5微秒半周期1.25微秒。用72MHz的STM32一个__NOP()大约14纳秒所以延时函数里循环几次就够了。但如果你开了编译优化__NOP()可能被优化掉要用volatile变量或者内联汇编来保证延时。9.2 SPI驱动与DMA传输SPI比I2C快得多适合驱动TFT屏幕、Flash芯片、无线模块等。F103的SPI最高18MHz配合DMA可以实现高速数据传输。配置SPI的步骤使能SPI和GPIO时钟配置SCK、MISO、MOSI为复用推挽配置SPI结构体设置主模式、数据宽度、时钟极性相位、分频系数最后使能SPI。用DMA传输时把SPI的DR寄存器地址作为DMA外设地址内存缓冲区地址作为DMA内存地址设置传输方向为内存到外设使能DMA通道。SPI的TX对应DMA1通道3RX对应通道2。传输完成后DMA会产生中断在中断里处理后续逻辑。我实测下来SPI加DMA驱动ILI9341屏幕刷一屏320x240的16位色数据大概20毫秒左右比软件模拟快了几十倍。但要注意DMA传输期间不能修改缓冲区内容否则会花屏。9.3 传感器驱动中的时序陷阱驱动传感器最容易出问题的地方是时序。比如DS3231的I2C时序、DHT11的单总线时序、超声波模块的触发和回波时序都有严格的时间要求。DHT11要求主机拉低至少18毫秒然后释放传感器会在80微秒后拉低响应。如果你用延时函数要确保延时精度足够而且不能被中断打断。我的做法是对时序敏感的驱动在关键延时期间关中断__disable_irq()延时完成后再开中断。但关中断时间不能太长否则会影响其他中断的响应。DHT11的18毫秒拉低如果全程关中断可能会影响串口接收所以更好的方案是用定时器硬件产生精确延时或者用状态机在中断里分步处理。10. 进阶方向USB、CAN与RTOS10.1 STM32做USB设备的入门路径F103自带USB 2.0全速设备控制器可以做HID、CDC、MSC等设备。做USB设备的门槛比串口高不少因为涉及USB协议栈。ST提供了USB FS Device Library里面有针对不同设备类的例程。入门建议从CDC虚拟串口开始因为它和串口用法最像容易理解。把USB配置成CDC设备后电脑上会多出一个虚拟串口STM32通过USB发送的数据会出现在这个串口里。配置步骤包括使能USB时钟48MHz来自PLL配置USB中断初始化USB设备库在中断里调用USB_Istr处理事件。有个坑要注意F103的USB和CAN共用SRAM如果同时用USB和CAN需要配置缓冲区描述表否则会冲突。另外USB的DP引脚需要1.5K上拉电阻告诉主机设备已连接有些开发板已经内置了有些需要外部加。10.2 CAN通信的初始化与调试CAN总线在汽车电子和工业控制中用得很多。F103的CAN挂在APB1上最高1Mbps。初始化步骤使能CAN和GPIO时钟配置CAN_RX为浮空输入、CAN_TX为复用推挽配置CAN结构体设置波特率、工作模式、自动重传等配置过滤器最后使能CAN。波特率计算稍微复杂CAN波特率 APB1时钟 / (分频系数 * (1 BS1 BS2))。APB1是36MHz想要500kbps可以设分频系数4BS18BS27这样36M/(4*(187))562.5k不太对。重新算36M/(4*(187))562.5k要500k的话分频系数4BS19BS2836M/(4*(198))500k。这些参数在CubeMX里可以直接选波特率自动算但用标准库就得自己算。CAN调试最常见的问题是终端电阻。CAN总线两端各需要120欧姆终端电阻如果开发板上没有通信会不稳定甚至完全不通。用万用表量CANH和CANL之间的电阻应该是60欧姆左右两个120欧并联。10.3 FreeRTOS在F103上的移植要点FreeRTOS是嵌入式最流行的实时操作系统F103的资源跑FreeRTOS绰绰有余。移植的核心是提供几个底层接口任务切换PendSV中断、系统节拍SysTick中断、临界区保护关中断。移植步骤把FreeRTOS源码里的Source文件夹复制到工程添加必要的.c文件在FreeRTOSConfig.h里配置时钟频率、堆大小、优先级数量等参数然后实现vPortSVCHandler、xPortPendSVHandler、xPortSysTickHandler这三个中断函数。最后在main里创建任务并调用vTaskStartScheduler。我建议新手先跑一个最简单的双任务例程一个任务闪LED一个任务通过串口打印。跑通之后再逐步增加任务和通信机制队列、信号量、互斥量。不要一上来就搞复杂项目RTOS的调试比裸机麻烦任务栈溢出、优先级反转这些问题都需要经验才能快速定位。11. 项目实战从需求到成品的完整思路11.1 需求拆解与技术选型拿到一个STM32项目需求第一步不是写代码而是拆解。比如“做一个基于STM32的报站系统”拆解下来需要语音播报需要音频解码和功放、站点显示需要屏幕、按键控制需要GPIO输入、数据存储需要Flash或SD卡。然后针对每个子功能选技术方案语音用WT588D还是SYN6288显示用OLED还是LCD存储用W25Q64还是SD卡。选型的依据是成本、开发难度、资料丰富度。新手尽量选资料多的模块比如OLED用SSD1306、语音用SYN6288这些网上例程多遇到问题好查。不要为了省几块钱选冷门芯片调试花的时间成本远高于物料差价。11.2 模块化开发与联调策略项目开发最忌讳的是把所有功能写在一起然后一起调试。正确的做法是每个模块单独写驱动、单独测试确认没问题后再集成。比如先写OLED驱动显示几个字符确认正常再写语音驱动播报一段固定语音确认正常最后把两者放到一个工程里通过主循环协调。集成的时候最容易出问题的是资源冲突两个模块用了同一个定时器、同一个中断优先级、同一个引脚。所以在选引脚的时候就要规划好画个表格把每个外设用的引脚、定时器、中断列出来避免冲突。如果冲突了要么换引脚要么用软件模拟要么分时复用。11.3 代码版本管理与文档习惯即使是个人项目也建议用Git做版本管理。每次完成一个功能就提交一次写清楚提交信息。这样改出问题的时候可以回退到上一个可用版本不用从头重写。Git仓库可以放在本地也可以用私有仓库托管。另外要养成写文档的习惯。每个模块的引脚定义、通信协议、注意事项都记下来。过一个月回头看自己的代码没有文档的话很多细节都想不起来了。文档不用很正式一个Markdown文件列清楚每个模块的接口和用法就行。12. 我个人的学习体会与建议STM32F103这块板子说难不难说简单也不简单。它的外设丰富寄存器多刚开始看参考手册会觉得头大。但只要你把GPIO、串口、定时器这三个基础外设吃透后面的I2C、SPI、ADC、DMA都是类似的套路使能时钟、配置结构体、使能外设、处理中断。掌握了这个模式换任何外设都能快速上手。我自己的经验是学STM32最快的路径不是看视频教程而是找一个具体的项目做。哪怕是一个很简单的小项目比如“按键控制LED亮度”你在做的过程中会遇到各种问题解决问题的过程就是学习的过程。看教程是被动接收做项目是主动探索效果完全不一样。还有一点很重要不要怕看英文数据手册和参考手册。中文资料虽然方便但翻译质量参差不齐有些关键参数翻译错了会误导你。遇到不确定的地方直接查英文原版手册虽然慢一点但准确。看多了之后你会发现手册里的英文其实就那么些固定表达看习惯了比看中文还快。最后说一个心态问题嵌入式开发入门阶段会遇到很多挫折代码不跑、板子发烫、调试器连不上这些都是常态。不要觉得自己不适合每个老手都是从这一步过来的。遇到问题先冷静分析用排除法一步步缩小范围实在搞不定就放一放睡一觉起来可能就有思路了。坚持过前三个月后面就会越来越顺。
返回列表