ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32F103C8T6开源项目:代码、原理图、仿真三件套一致性评价与实操指南

STM32F103C8T6开源项目:代码、原理图、仿真三件套一致性评价与实操指南 1. 项目缘起与整体设计思路STM32 项目开源这件事我前前后后做过好几轮。最早是自己在大学里做平衡小车后来带新人做毕业设计再后来接一些工业控制的小单子每次都要重新搭一遍工程模板、重新画一遍最小系统板、重新配一遍仿真环境。时间长了就发现与其每次从零开始不如把一套经过验证的完整方案沉淀下来代码、原理图、仿真三件套打包开源谁需要谁拿去改。这个项目就是在这种背景下攒出来的。所谓“评价”这个标题其实包含两层意思。第一层是对项目本身的评价——这套开源方案到底值不值得用它的边界在哪里适合什么场景不适合什么场景。第二层是对 STM32 开源生态的评价——现在网上 STM32 的开源项目多如牛毛但真正能做到代码、原理图、仿真三者对齐、拿来就能跑通的比例并不高。很多项目要么只有代码没有硬件图纸要么原理图和代码里的引脚定义对不上要么仿真模型和实际硬件行为差异巨大。这个项目要解决的核心问题就是三件套的一致性。先说说整体设计思路。我选择的是STM32F103C8T6这颗芯片作为核心原因很直接它是 STM32 家族里资料最全、价格最低、供货最稳的型号之一LQFP48 封装72MHz 主频64KB Flash20KB SRAM对于大多数入门到中级的嵌入式项目来说完全够用。网上有人叫它“单片机界的五菱宏光”我觉得这个比喻挺贴切——不豪华但皮实、好修、配件遍地都是。代码层面我没有用 HAL 库而是选择了标准外设库SPL加上部分寄存器操作。这个选择可能会让一些人觉得“过时”但我的考虑是标准库的代码结构更贴近硬件初学者看的时候能直接看到寄存器级别的操作对理解 STM32 的运行机制有帮助。HAL 库虽然抽象层次高、移植方便但很多初学者用 HAL 库做项目做完之后连 GPIO 的推挽输出和开漏输出有什么区别都说不清楚。当然这不是说 HAL 库不好只是在这个项目里我希望代码本身能承担一部分教学功能。原理图部分我用的是嘉立创 EDA绘制。选它的理由也很实际免费、云端存储、元件库丰富、可以直接对接打样。对于开源项目来说别人拿到你的原理图之后能不能快速修改、能不能直接下单打板这很重要。Altium Designer 虽然功能强大但正版授权费用高不是每个学习者都负担得起。Cadence 就更不用说了那是做高速板子的工具用来画一块 STM32 最小系统板属于杀鸡用牛刀。仿真部分我采用的是Proteus Keil 联合仿真的方案。Proteus 里搭建外围电路模型Keil 里编译代码生成 hex 文件然后在 Proteus 里加载运行。这个方案的好处是可以在没有实物硬件的情况下验证代码逻辑和电路连接的正确性。当然Proteus 的仿真精度有限比如 ADC 的噪声特性、PWM 的实际纹波、通信接口的时序细节这些它模拟不了。但对于验证“代码能不能跑通”“引脚接得对不对”“外设初始化有没有问题”这些基础问题它足够用了。提示Proteus 仿真通过不代表实物一定能跑通。仿真里没有接触不良、没有电源纹波、没有电磁干扰实物调试时这些问题一个都不会少。整个项目的文件结构我做了清晰的划分/firmware放 Keil 工程和源码/hardware放原理图源文件和 PDF 导出/simulation放 Proteus 工程文件/docs放引脚定义表、外设配置说明和常见问题记录。这样别人拿到之后想改代码的去 firmware想改硬件的去 hardware想先仿真验证的去 simulation各取所需。2. 核心细节解析与实操要点2.1 代码架构的分层设计代码这块我采用的是三层结构硬件抽象层HAL_LAYER、驱动层DRIVER_LAYER、应用层APP_LAYER。虽然用的是标准库但我自己在标准库之上又包了一层轻量的硬件抽象目的是让应用层的代码不直接依赖具体的寄存器操作。硬件抽象层里我把 GPIO、USART、SPI、I2C、TIM、ADC 这些外设的初始化封装成独立的函数每个函数只做一件事。比如bsp_gpio_init()负责所有 GPIO 的时钟使能和模式配置bsp_usart1_init()负责 USART1 的波特率、数据位、停止位、校验位配置。这样做的好处是如果以后要换芯片型号比如从 F103 换到 F407只需要改硬件抽象层驱动层和应用层基本不用动。驱动层里放的是具体外设的驱动逻辑。比如 OLED 屏幕的驱动、DHT11 温湿度传感器的驱动、HC-SR04 超声波模块的驱动。这些驱动调用硬件抽象层提供的接口不直接操作寄存器。驱动层的函数命名我遵循了统一的规范drv_xxx_init()、drv_xxx_read()、drv_xxx_write()、drv_xxx_deinit()。这样看名字就知道这个函数是干什么的。应用层就是具体的业务逻辑了。比如主循环里读取传感器数据、更新屏幕显示、通过串口上报数据、根据阈值控制继电器。应用层的代码应该尽量简单因为复杂的逻辑应该下沉到驱动层或者单独的业务模块里。// 硬件抽象层示例GPIO 初始化 void bsp_gpio_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); // LED 引脚PA5 推挽输出 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); // 按键引脚PB0 上拉输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, GPIO_InitStructure); }这里有个细节值得展开说GPIO 速度配置。很多人初始化 GPIO 的时候随手就写GPIO_Speed_50MHz觉得越快越好。但实际上GPIO 速度配置影响的是引脚翻转时的驱动能力速度越高EMI 辐射越大功耗也越高。对于 LED 控制、按键读取这种低速场景用GPIO_Speed_2MHz就够了。只有在驱动 SPI、SDIO 这种高速接口时才需要开到 50MHz。我在项目里对不同的引脚做了区分配置低速外设用 2MHz高速通信用 50MHz。2.2 原理图设计中的关键决策原理图这块我想重点讲几个容易被忽视但非常关键的细节。第一是电源部分。STM32F103C8T6 的供电范围是 2.0V 到 3.6V典型值是 3.3V。我在原理图里用了 AMS1117-3.3 这颗 LDO 来做 5V 到 3.3V 的转换。选它的原因是便宜、外围电路简单、最大输出电流 1A带一块 STM32 加几个外设绰绰有余。但要注意AMS1117 的压差大约是 1.1V也就是说输入电压至少要 4.4V 才能保证输出稳定的 3.3V。如果你用 5V 供电没问题如果用 3.7V 锂电池供电那就要换低压差的 LDO 了比如 RT9013 或者 XC6206。去耦电容的配置也是很多人容易忽略的地方。STM32 的每个电源引脚旁边都应该放一个 100nF 的陶瓷电容越近越好。另外在电源入口处放一个 10uF 的钽电容或者电解电容做储能。我见过有人画板子的时候只在电源入口放了一个 100nF结果芯片一跑起来就复位查了半天才发现是电源纹波太大。第二是晶振电路。STM32F103 的外部高速晶振HSE通常用 8MHz配合内部 PLL 倍频到 72MHz。晶振旁边要放两个 20pF 的负载电容这个值不是随便选的要根据晶振的负载电容参数来算。公式是CL (C1 * C2) / (C1 C2) Cstray其中 Cstray 是 PCB 走线的寄生电容一般取 3pF 到 5pF。如果晶振的负载电容是 20pF那么 C1 和 C2 大约取 36pF 到 39pF。但实际中很多人直接用 20pF 也能起振这是因为 PCB 寄生电容和芯片内部电容的容差比较大。不过如果你发现晶振起振困难或者频率偏差大就要重新算一下负载电容了。第三是 BOOT 引脚。BOOT0 和 BOOT1 决定了芯片的启动模式。BOOT0 接 10K 下拉电阻到地BOOT1 接 10K 下拉电阻到地这是最常用的配置芯片从主 Flash 启动。如果你要用串口下载程序需要把 BOOT0 接高电平下载完再接回低电平。我在板子上留了一个跳线帽方便切换。第四是 SWD 调试接口。我引出了 SWDIO、SWCLK、GND、3.3V 四根线用的是 4Pin 的排针。SWD 比 JTAG 少两根线对于 F103C8T6 这种引脚本来就不多的芯片来说能省两个引脚是很有意义的。而且 ST-Link 和 J-Link 都支持 SWD通用性没问题。2.3 仿真环境的搭建与配置Proteus 仿真 STM32 这件事说简单也简单说坑多也坑多。我踩过的坑包括Proteus 版本和芯片模型不匹配、hex 文件路径包含中文导致加载失败、仿真速度太慢导致看门狗误触发、虚拟终端不显示串口输出等等。先说版本选择。Proteus 8.9 之后的版本对 STM32 的支持比较好我用的是 Proteus 8.13。芯片模型选STM32F103C8注意不是STM32F103C8T6Proteus 的元件库里的命名和实际型号可能不完全一致。如果找不到这个型号可以试试STM32F103C6两者的外设基本兼容只是 Flash 和 RAM 大小不同。Keil 这边需要设置一下输出 hex 文件。在Options for Target-Output里勾选Create HEX File然后编译。编译成功后在 Proteus 里双击 STM32 芯片在Program File里选择刚才生成的 hex 文件。注意路径不要有中文和空格否则 Proteus 可能加载失败。时钟配置是仿真里最容易出问题的地方。Proteus 里的 STM32 模型默认使用 8MHz 的外部晶振如果你在代码里配置 PLL 倍频到 72MHz需要在 Proteus 的芯片属性里把Crystal Frequency改成 8MHz。如果代码里用的是内部时钟HSI那就要把Crystal Frequency改成 8MHz 并且配置RCC_CFGR寄存器选择 HSI 作为 PLL 输入。我建议在仿真阶段先用内部时钟跑通再切换到外部时钟这样可以排除晶振电路的问题。注意Proteus 仿真 STM32 的串口输出时需要在虚拟终端里设置正确的波特率。如果代码里配置的是 115200虚拟终端也要设成 115200否则显示乱码。还有一个坑是仿真速度。Proteus 是软件仿真不是硬件加速所以运行速度比实物慢很多。如果你的代码里有延时函数比如delay_ms(1000)在仿真里可能要等好几秒才能过去。我的做法是把仿真用的延时缩短比如实物用 1000ms 的地方仿真里改成 100ms等验证完逻辑再改回去。另外看门狗在仿真里很容易误触发因为仿真速度慢喂狗不及时。建议在仿真阶段先关闭看门狗等实物调试时再打开。3. 实操过程与核心环节实现3.1 从零搭建 Keil 工程的完整流程很多人拿到开源代码之后不知道怎么在自己的电脑上跑起来这里我把从零搭建工程的步骤完整走一遍。第一步安装 Keil MDK。版本建议用 5.36 或以上太老的版本可能不支持新的芯片包。安装完之后需要单独安装 STM32F1 的芯片包Device Family Pack。芯片包可以从 Keil 官网下载也可以从一些国内镜像站下载。安装芯片包的时候要注意如果之前装过其他系列的芯片包可能会有冲突建议先卸载再安装。第二步新建工程。Project-New uVision Project选择保存路径然后在弹出的芯片选择窗口里找到STM32F103C8。如果找不到说明芯片包没装好。选好芯片之后Keil 会问你要不要加载Startup文件选“是”。第三步添加文件分组。我一般建四个分组Startup、CMSIS、StdPeriph_Driver、User。Startup里放启动文件startup_stm32f10x_md.sCMSIS里放core_cm3.c和system_stm32f10x.cStdPeriph_Driver里放标准外设库的源文件User里放自己的应用代码。第四步配置头文件路径。Options for Target-C/C-Include Paths把CMSIS、StdPeriph_Driver/inc、User这些目录都加进去。另外在Define里加上USE_STDPERIPH_DRIVER, STM32F10X_MD。这两个宏定义很关键USE_STDPERIPH_DRIVER告诉编译器使用标准外设库STM32F10X_MD告诉库文件当前芯片是中容量型号64KB 或 128KB Flash。第五步配置调试器。Options for Target-Debug选择ST-Link Debugger或者J-LINK / J-TRACE Cortex。然后点Settings在Flash Download里勾选Reset and Run这样下载完程序之后芯片会自动复位运行。第六步编译下载。按F7编译按F8下载。如果一切正常你应该能看到 LED 闪烁或者串口输出信息。# 编译输出示例 Build started: Project: STM32_OpenSource *** Using Compiler V5.06 update 7 (build 960), folder: C:\Keil_v5\ARM\ARMCC\Bin Build target Target 1 compiling main.c... compiling bsp_gpio.c... compiling bsp_usart.c... linking... Program Size: Code5120 RO-data320 RW-data48 ZI-data1024 FromELF: creating hex file... .\Objects\STM32_OpenSource.axf - 0 Error(s), 0 Warning(s). Build Time Elapsed: 00:00:033.2 原理图绘制与 PCB 打样要点原理图绘制这块我用的是嘉立创 EDA 专业版。整个绘制流程分为元件选型、原理图绘制、ERC 检查、PCB 布局布线、DRC 检查、导出 Gerber 文件、下单打样。元件选型的时候我尽量选嘉立创基础库里的元件因为这些元件打样的时候是免费的而且库存充足。STM32F103C8T6 在基础库里就有价格大概十几块钱。电阻电容用 0603 封装的比 0805 小一点布局更紧凑但手工焊接难度稍大。如果你是自己手工焊接建议用 0805 甚至 1206好焊很多。原理图绘制的时候我习惯按功能模块分区电源区、MCU 区、通信区、外设区。每个区之间用网络标签连接而不是直接拉线这样图纸看起来清爽很多。网络标签的命名要有规律比如USART1_TX、USART1_RX、I2C1_SCL、I2C1_SDA一看就知道是什么信号。ERC 检查电气规则检查一定要做。嘉立创 EDA 的 ERC 会检查出未连接的引脚、电源和地短路、输出引脚直接相连等问题。我见过有人画完原理图直接就去画 PCB 了结果打样回来发现 VCC 和 GND 接反了板子直接报废。ERC 检查花不了几分钟但能避免很多低级错误。PCB 布局布线的时候有几个原则要遵守。晶振尽量靠近芯片走线尽量短晶振下面不要走其他信号线。去耦电容尽量靠近电源引脚电源走线先经过电容再到引脚。SWD 接口放在板子边缘方便插拔调试器。电源走线要加粗至少 20mil大电流路径要更粗。信号线避免直角走线用 45 度或者圆弧过渡。提示打样之前一定要导出 Gerber 文件用 Gerber 查看器检查一遍。我遇到过好几次在 EDA 软件里看着没问题导出 Gerber 之后发现丝印压焊盘、阻焊开窗不对的情况。3.3 仿真验证的完整操作记录仿真验证我分三步走先验证最小系统再验证外设驱动最后验证整体逻辑。最小系统验证就是确认芯片能正常启动、时钟能正常配置、GPIO 能正常翻转。我在 Proteus 里搭了一个最简单的电路STM32 芯片、8MHz 晶振、复位电路、一个 LED 接在 PA5 上。代码里只做一件事配置 PA5 为推挽输出然后在主循环里翻转 PA5。运行仿真如果 LED 闪烁说明最小系统没问题。外设驱动验证就是逐个测试 USART、SPI、I2C、TIM、ADC 这些外设。以 USART 为例我在 Proteus 里加了一个虚拟终端Virtual Terminal连接到 USART1 的 TX 引脚。代码里配置 USART1 为 115200、8N1然后在主循环里每隔一秒发送一句 “Hello STM32”。运行仿真如果虚拟终端里能看到这句话说明 USART 配置正确。整体逻辑验证就是把所有外设组合起来跑完整的业务逻辑。比如读取 DHT11 的温湿度数据显示在 OLED 屏幕上同时通过串口上报给上位机。这一步最容易出问题因为多个外设同时工作的时候可能会出现资源冲突、时序冲突、中断优先级冲突。我在仿真里就遇到过 TIM2 和 USART1 共用中断向量的问题后来查参考手册才发现 TIM2 的中断向量和 USART1 不冲突是我在代码里把中断优先级配错了。// 中断优先级配置示例 void bsp_nvic_init(void) { NVIC_InitTypeDef NVIC_InitStructure; // 抢占优先级分组2 位抢占优先级2 位响应优先级 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // USART1 中断抢占优先级 1响应优先级 1 NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // TIM2 中断抢占优先级 2响应优先级 0 NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }中断优先级这块值得多说两句。STM32F103 的中断优先级分为抢占优先级和响应优先级。抢占优先级高的中断可以打断抢占优先级低的中断响应优先级只在抢占优先级相同的时候起作用高的先执行但不能打断。NVIC_PriorityGroup_2表示 2 位抢占优先级、2 位响应优先级所以抢占优先级可以取 0 到 3响应优先级也可以取 0 到 3。数字越小优先级越高。我一般把通信接口的中断优先级设高一点定时器的设低一点因为通信数据丢失比定时器抖动更严重。4. 常见问题与排查技巧实录4.1 代码编译与下载阶段的典型问题问题一编译报错 “cannot open source input file ‘stm32f10x.h’”这个问题的原因通常是头文件路径没配好。检查Options for Target-C/C-Include Paths里有没有把StdPeriph_Driver/inc和CMSIS的路径加进去。另外stm32f10x.h这个文件在标准库的Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x目录下确认这个文件存在。问题二下载报错 “No target connected”先检查硬件连接SWDIO、SWCLK、GND、3.3V 四根线有没有接对。然后检查 ST-Link 驱动有没有装好设备管理器里能不能看到 ST-Link 设备。如果都没问题试试把 BOOT0 接高电平再下载下载完再接回低电平。还有一种可能是芯片被读保护了需要用 ST-Link Utility 解除读保护。问题三程序下载成功但不运行检查复位电路。STM32 的 NRST 引脚需要接一个 10K 上拉电阻到 3.3V再接一个 100nF 电容到地。如果 NRST 一直低电平芯片就一直复位。另外检查 BOOT0 和 BOOT1 的电平BOOT0 必须为低才能从主 Flash 启动。问题四串口输出乱码首先确认波特率是否一致。代码里配置的是 115200串口助手也要设成 115200。然后检查晶振频率如果外部晶振是 8MHz但代码里配置的是 12MHz那实际波特率就会偏差很大。可以用示波器测一下 USART 的 TX 引脚看看一个位的时间是不是 8.68us115200 波特率下。如果不是说明时钟配置有问题。4.2 硬件调试阶段的常见故障故障一芯片发热严重芯片发热通常是因为电源接反或者短路。先断电用万用表测一下 VCC 和 GND 之间的电阻正常应该在几百欧姆以上。如果电阻很小说明有短路。检查一下有没有焊接连锡尤其是 LQFP 封装的引脚间距很小很容易连锡。另外检查一下 3.3V 和 5V 有没有接反STM32 的供电范围是 2.0V 到 3.6V接 5V 会烧芯片。故障二晶振不起振用示波器测晶振引脚看有没有正弦波。如果没有先检查晶振有没有焊好负载电容有没有焊错。然后检查代码里的时钟配置是不是把 HSE 使能了。如果还是不起振试试换一个晶振有些晶振的质量不好起振困难。另外STM32 的 HSE 起振时间比较长一般需要几毫秒到几十毫秒如果代码里刚使能 HSE 就立刻切换时钟源可能会失败。正确的做法是使能 HSE 之后等待 HSERDY 标志位置位再切换。故障三ADC 采样值跳动大ADC 采样值跳动是正常现象但如果跳动范围超过几十个 LSB就不正常了。首先检查参考电压是否稳定VREF 引脚要接 3.3V 并且加去耦电容。然后检查模拟输入引脚有没有加滤波电容一般加一个 100nF 到地。另外ADC 的采样时间要设置合理采样时间太短会导致采样不充分太长会影响采样率。对于高阻抗信号源采样时间要设长一点。故障四I2C 通信失败I2C 通信失败最常见的原因是上拉电阻没接。I2C 的 SDA 和 SCL 都是开漏输出必须接上拉电阻才能输出高电平。上拉电阻一般取 4.7K 到 10K总线速度越快上拉电阻越小。另外检查从机地址对不对很多 I2C 器件的地址有 7 位和 8 位两种表示方式容易搞混。还有I2C 的时序要求比较严格如果主频太高标准库的 I2C 驱动可能会出问题可以试试降低主频或者用软件模拟 I2C。4.3 仿真与实物差异的排查思路仿真和实物最大的差异在于时序和电气特性。仿真里没有传播延迟、没有上升沿下降沿、没有电源纹波所以仿真通过不代表实物通过。我遇到过一个典型案例在 Proteus 里仿真 DS18B20 温度传感器读写时序完全正确温度值也能读出来。但实物焊接好之后死活读不出温度。用示波器一看发现 DS18B20 的 DQ 引脚在释放总线之后电平上升很慢因为上拉电阻太大10K而总线电容又比较大。把上拉电阻换成 4.7K 之后问题解决。还有一个案例是 PWM 驱动电机。仿真里电机模型响应很快PWM 占空比一变转速立刻跟着变。实物上电机有惯性而且 PWM 频率太低的话电机会有啸叫。后来把 PWM 频率从 1KHz 提高到 20KHz啸叫消失了但 MOS 管的开关损耗增加了需要加散热片。提示仿真验证的是逻辑实物验证的是电气。两者不能互相替代只能互相补充。4.4 常见问题速查表问题现象可能原因排查方法解决方案编译报错找不到头文件头文件路径未配置检查 Include Paths添加 CMSIS 和 StdPeriph_Driver/inc 路径下载报错 No target connectedSWD 接线错误或驱动未装检查接线和设备管理器重新接线安装 ST-Link 驱动程序下载后不运行复位电路或 BOOT 引脚问题测 NRST 电平和 BOOT0 电平NRST 接 10K 上拉BOOT0 接低串口输出乱码波特率或时钟配置错误示波器测 TX 引脚位时间统一波特率检查晶振频率芯片发热严重电源短路或接反万用表测 VCC-GND 电阻检查焊接确认供电电压晶振不起振负载电容不匹配或晶振损坏示波器测晶振引脚调整负载电容更换晶振ADC 采样跳动大参考电压不稳或采样时间短测 VREF 电压加去耦电容增加采样时间I2C 通信失败上拉电阻未接或地址错误测 SDA/SCL 空闲电平接 4.7K 上拉电阻确认从机地址仿真通过实物失败时序或电气特性差异示波器对比仿真和实物波形调整上拉电阻、PWM 频率等参数5. 开源项目的维护与扩展建议5.1 代码版本管理与协作规范既然是开源项目版本管理就很重要。我用的是 Git托管在 Gitee 上国内访问速度快。分支策略采用简化版的 Git Flowmaster分支放稳定版本develop分支放开发中的版本功能开发在feature/xxx分支上进行完成后合并到develop测试通过后合并到master并打 tag。提交信息我要求写清楚三件事改了什么、为什么改、怎么验证的。比如fix: 修复 USART1 中断优先级配置错误导致接收数据丢失。验证方式连续发送 1000 字节数据接收完整无丢失。这样的提交信息别人看 git log 的时候能快速了解每次变更的内容。代码风格方面我遵循 Linux 内核的代码风格缩进用 4 个空格函数名用小写加下划线宏定义用大写加下划线左大括号不换行。变量命名尽量有意义避免a、b、temp这种无意义的名字。注释要写清楚“为什么这么做”而不是“做了什么”因为代码本身已经说明了“做了什么”。5.2 项目扩展方向与二次开发建议这个项目的基础框架搭好之后可以往很多方向扩展。方向一加入 RTOS。目前是裸机跑主循环如果业务逻辑复杂了可以移植 FreeRTOS 或者 RT-Thread。RT-Thread 有现成的 STM32F103 移植包用起来很方便。加入 RTOS 之后可以把不同的任务分配到不同的线程里比如传感器采集一个线程、数据显示一个线程、通信上报一个线程通过信号量和消息队列来同步。方向二加入 OTA 升级功能。STM32F103C8T6 有 64KB Flash可以划分成 Bootloader 区和 App 区。Bootloader 负责接收新固件并写入 App 区App 区跑业务逻辑。OTA 升级可以通过串口、CAN、或者无线模块比如 ESP8266来实现。这个功能对于部署在远处的设备非常有用不用拆机就能升级固件。方向三加入 Modbus 协议。工业控制领域大量使用 Modbus RTU 协议可以在 USART 的基础上实现 Modbus 从机支持功能码 03读保持寄存器、06写单个寄存器、16写多个寄存器。这样这个项目就可以直接对接组态软件或者 PLC 了。方向四加入数据存储。外挂一颗 SPI Flash比如 W25Q64或者 EEPROM比如 AT24C02用来存储配置参数和历史数据。配置参数比如传感器阈值、通信地址、波特率等历史数据比如温度记录、报警记录等。5.3 开源协议选择与社区运营心得开源协议我选的是MIT 协议。原因很简单MIT 协议最宽松别人可以自由使用、修改、分发甚至用于商业项目只需要保留版权声明。对于这种基础性的嵌入式项目来说宽松的协议更有利于传播和使用。如果你希望别人用了你的代码之后也必须开源那可以选 GPL 协议。但 GPL 协议对商业应用不太友好很多公司会避开 GPL 代码。社区运营这块我的经验是文档比代码更重要。很多人开源项目的时候只丢一堆代码上去README 就写一句“STM32 项目自己看代码”。这样的项目很难有人愿意用。我的做法是 README 里写清楚项目简介、功能列表、硬件需求、软件需求、编译步骤、烧录步骤、常见问题。另外单独写一个docs/目录放引脚定义表、外设配置说明、原理图 PDF、仿真操作指南。Issue 管理也很重要。有人提 Issue 的时候尽量在 24 小时内回复哪怕只是说“收到我看一下”。如果 Issue 是重复的引导对方去看已有的 Issue 或者文档。如果 Issue 是有效的 bug 报告修复之后在 Issue 里说明修复内容和验证方式然后关闭 Issue。这样别人搜索 Issue 的时候能找到解决方案。提示开源项目最怕的是“提了 Issue 没人理”。哪怕你暂时没时间修回复一句“已知悉计划在下个版本修复”也比不回复强。6. 个人实操体会与避坑清单6.1 那些年我踩过的坑第一个坑是电源引脚的去耦电容。早期画板子的时候我觉得去耦电容可有可无结果板子跑起来之后偶尔复位查了很久才发现是电源纹波太大。后来每个电源引脚都加了 100nF 电容问题再也没出现过。这件事让我明白数据手册上的“推荐电路”不是随便写的每一条都有它的道理。第二个坑是晶振的负载电容。有一次用了一个 12MHz 的晶振负载电容标称是 20pF我随手放了两个 20pF 的电容结果起振时间特别长有时候甚至起不来。后来查了晶振的规格书发现它的负载电容是 20pF但计算的时候要把 PCB 寄生电容算进去实际应该用 36pF 左右。换成 36pF 之后起振时间缩短到几毫秒。第三个坑是中断优先级。有一次做串口接收数据量大的时候偶尔丢包。查了半天发现是 USART1 的中断优先级设得太低被 TIM2 的中断打断了。把 USART1 的抢占优先级调到比 TIM2 高之后丢包问题解决。这件事让我养成了一个习惯每个中断的优先级都要仔细考虑不能随便填。第四个坑是Proteus 仿真的局限性。有一次在 Proteus 里仿真一个 SPI 驱动 OLED 的项目显示完全正常。但实物焊好之后屏幕一直白屏。用逻辑分析仪抓 SPI 波形发现时钟频率太高18MHz而 OLED 的 SPI 接口最高只支持 10MHz。把 SPI 时钟降到 9MHz 之后显示正常。这件事让我明白仿真里没有时序约束检查实物上器件的时序参数是硬限制。6.2 给新手的避坑清单画原理图之前先看数据手册的“典型应用电路”那是芯片厂商验证过的电路照着画不会有大问题。每个电源引脚都要加去耦电容100nF 是标配电源入口再加一个 10uF。晶振的负载电容要算不要抄不同晶振的负载电容不一样抄错了可能不起振。BOOT0 和 BOOT1 不要悬空一定要接下拉电阻否则芯片可能进入未知的启动模式。SWD 接口至少引出 SWDIO、SWCLK、GND、3.3V 四根线调试的时候会方便很多。代码里每个外设初始化之后都要检查返回值虽然标准库的初始化函数一般不返回错误但养成检查的习惯没坏处。中断服务函数里不要做耗时操作比如浮点运算、串口打印、延时这些应该放到主循环里做。仿真通过之后一定要在实物上验证仿真和实物的差异可能比你想象的大。开源项目要写文档代码写得再好没有文档别人也用不起来。遇到问题先查数据手册和参考手册网上搜到的答案不一定适用于你的芯片型号。6.3 这个项目后续还可以这样扩展如果你已经跑通了这个项目的基础功能想继续深入我建议从这几个方向入手。第一个方向是低功耗。STM32F103 支持睡眠、停止、待机三种低功耗模式。睡眠模式下 CPU 停止外设继续运行功耗大概几毫安。停止模式下所有时钟停止功耗大概几十微安。待机模式下功耗最低大概几微安但唤醒后相当于复位。如果你的项目是电池供电的低功耗设计是必修课。第二个方向是通信协议。除了 USART、SPI、I2C 这些基础通信接口还可以试试 CAN 总线。STM32F103C8T6 没有内置 CAN 控制器但可以通过 SPI 外挂 MCP2515 来实现。CAN 总线在汽车电子和工业控制领域应用很广抗干扰能力强传输距离远。第三个方向是图形界面。如果你用的是 SPI 或者 FSMC 接口的 TFT 屏幕可以移植 LVGL 或者 u8g2 图形库做出漂亮的用户界面。LVGL 功能强大但占用资源多u8g2 轻量但功能少根据你的 Flash 和 RAM 大小来选择。第四个方向是无线通信。通过 USART 或者 SPI 接口外挂无线模块比如 NRF24L012.4GHz、LoRa 模块远距离低功耗、蓝牙模块手机互联。无线通信可以让你的设备摆脱线缆的束缚应用场景更广。我个人在实际操作中的体会是嵌入式开发这件事基础比技巧重要耐心比聪明重要。很多问题不是靠什么高级技巧解决的而是靠老老实实查手册、认认真真测波形、一遍一遍试出来的。这个开源项目里的每一行代码、每一个元件、每一个配置都是我踩过坑之后总结出来的。你拿去用的时候可能会遇到我没遇到过的问题但只要你理解了背后的原理排查起来就不会太困难。
返回列表