ARTICLE DETAIL

资讯详情

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

彻底告别MDK5:用PlatformIO搭建STM32F103C8T6标准库开发环境

彻底告别MDK5:用PlatformIO搭建STM32F103C8T6标准库开发环境 1. 为什么我彻底放弃了MDK5转投PlatformIO1.1 一个用了五年MDK的老用户的真实心路先说结论我不是因为MDK5不能用才换的而是因为用久了之后发现它拖慢我效率的地方实在太多了。STM32F103C8T6这颗芯片圈子里叫它蓝板或者最小系统板几乎每个搞嵌入式的人都拿它入门过。我手上这块板子买了三年前两年一直用MDK5写标准库工程直到去年开始接一些需要频繁切换芯片型号和工程配置的活才痛下决心把整个工具链迁到PlatformIOVSCode上。MDK5的问题不在于它不能编译而在于它的工程管理方式太重了。每次新建一个STM32F103C8T6的标准库工程你得手动建目录、复制启动文件、复制标准外设库的src和inc、配置Include Paths、勾选宏定义、设置下载算法……一套流程走下来顺利的话十五分钟不顺利的话半小时就没了。而且MDK5的代码补全和跳转体验说实话停留在十年前的水平遇到标准库那种层层嵌套的结构体想看一个寄存器的定义要跳好几次。PlatformIO本质上是一个跨平台的嵌入式构建系统它跑在VSCode里面用Python写的构建核心支持上千种开发板和框架。你只需要在配置文件里写几行参数它自动帮你下载对应的编译工具链、框架代码和上传工具。对于STM32F103C8T6来说你可以选择Arduino框架、CMSIS框架也可以选择我们这篇文章要重点讲的标准外设库框架。1.2 这套方案到底适合谁如果你符合下面任意一条这套环境值得你花一个下午搭起来手上有STM32F103C8T6最小系统板想用标准库开发但不想装MDK5已经会用MDK5写标准库但受够了它的编辑体验和工程管理想在一个编辑器里同时写STM32、ESP32、Python脚本和前端代码团队协作时需要把工程配置纳入版本管理而不是靠我的MDK配置截图不适合的情况也说清楚如果你做的是需要特定编译器优化或者依赖MDK独有中间件的项目比如某些需要ARM Compiler 5特定行为的遗留代码那暂时别折腾。另外如果你完全没接触过C语言和单片机建议先用Arduino框架跑通点灯再回来搞标准库。1.3 迁移前你需要知道的几个关键差异从MDK5迁到PlatformIO最大的思维转变在于工程配置从图形界面变成了文本文件。MDK5里你点鼠标勾选的那些选项在PlatformIO里全部写在platformio.ini这个文件里。刚开始会不习惯但一旦习惯了之后你会发现这个文件可以提交到Git可以复制给同事可以在不同电脑上一键还原环境。第二个差异是编译工具链。MDK5用的是ARM CompilerPlatformIO默认用GCC ARM工具链。两者在语法支持上基本一致但GCC对某些非标准写法更严格比如MDK里能过的隐式类型转换GCC可能会给warning。这不是坏事反而帮你写出更规范的代码。第三个差异是下载和调试。MDK5配合ST-Link用起来很顺PlatformIO同样支持ST-Link通过OpenOCD或者stlink工具上传。调试方面PlatformIO支持GDB调试配合Cortex-Debug插件可以做到断点、单步、看寄存器体验不比MDK差。2. 环境搭建的完整实操流程2.1 VSCode和PlatformIO插件的安装细节VSCode去官网下载对应系统的安装包Windows用户注意勾选添加到PATH这样后面命令行操作方便。安装完成后第一件事是装中文语言包在扩展面板搜索Chinese就能找到官方简体中文包。然后搜索PlatformIO IDE安装这个插件比较大会连带下载Python环境和一堆工具安装过程视网络情况可能要五到十分钟。安装完成后左侧会出现一个蚂蚁头图标那就是PlatformIO的主页。第一次打开它会自动初始化下载核心组件。这里有个坑如果你的网络环境访问国外源比较慢初始化可能会卡住。解决办法是配置国内镜像源在用户目录下创建.platformio文件夹里面放一个platformio.ini全局配置加上镜像地址。具体地址我就不贴了搜PlatformIO 国内镜像能找到最新的。注意PlatformIO插件安装过程中不要关闭VSCode也不要中途断网否则可能导致Python环境损坏修复起来很麻烦。2.2 创建STM32F103C8T6标准库工程的正确姿势打开PlatformIO主页点New Project填写工程名称Board那一栏搜索STM32F103C8选择ST STM32F103C8 (BluePill)或者通用的Generic STM32F103C8。Framework选择CMSIS因为标准库本质上就是基于CMSIS的。Location选一个没有中文和空格的路径这点很重要中文路径会导致编译工具链报错。创建完成后你会看到工程目录结构src放源文件include放头文件lib放自定义库platformio.ini是配置文件。默认生成的main.c是一个空的CMSIS工程我们要把它改造成标准库工程。2.3 标准外设库文件的获取与目录规划标准外设库也就是ST官方出的Standard Peripheral Library最新版本是V3.5.0。这个库ST已经不再维护了但网上到处都能下载到。下载下来解压后你会看到Libraries文件夹里面有STM32F10x_StdPeriph_Driver这就是核心。我的做法是在工程根目录下建一个lib文件夹里面再建STM32F10x_StdPeriph_Driver把src和inc两个文件夹复制进去。然后把CMSIS相关的文件也整理进来core_cm3.h、stm32f10x.h、system_stm32f10x.h、system_stm32f10x.c、启动文件startup_stm32f10x_md.s。启动文件要根据芯片容量选STM32F103C8T6是64KB Flash属于中等容量选md后缀的。目录结构建议这样组织project/ ├── lib/ │ ├── STM32F10x_StdPeriph_Driver/ │ │ ├── inc/ │ │ └── src/ │ └── CMSIS/ │ ├── core_cm3.h │ ├── stm32f10x.h │ ├── system_stm32f10x.h │ └── system_stm32f10x.c ├── src/ │ ├── main.c │ ├── stm32f10x_it.c │ └── startup_stm32f10x_md.s ├── include/ │ └── stm32f10x_conf.h └── platformio.ini2.4 platformio.ini的关键配置逐行解读这是整个工程最核心的文件我直接贴出我实际在用的配置然后逐行解释[env:bluepill_f103c8] platform ststm32 board bluepill_f103c8 framework cmsis upload_protocol stlink debug_tool stlink build_flags -I lib/CMSIS -I lib/STM32F10x_StdPeriph_Driver/inc -I include -D STM32F10X_MD -D USE_STDPERIPH_DRIVER -D HSE_VALUE8000000 build_src_filter ../lib/STM32F10x_StdPeriph_Driver/src/*.c ../lib/CMSIS/system_stm32f10x.c ../src/*.c ../src/*.splatform ststm32指定平台是ST的STM32系列。board bluepill_f103c8指定板子型号这个决定了默认的编译参数和上传配置。framework cmsis表示用CMSIS框架标准库就是建立在这个基础上的。upload_protocol stlink和debug_tool stlink指定用ST-Link下载和调试。如果你用的是串口下载改成upload_protocol serial然后指定端口。build_flags里的-I是头文件搜索路径-D是宏定义。STM32F10X_MD告诉库我们用的是中等容量芯片USE_STDPERIPH_DRIVER启用标准外设库HSE_VALUE8000000指定外部晶振是8MHz这个值影响系统时钟配置。build_src_filter告诉PlatformIO哪些源文件要参与编译。默认它只编译src目录我们加了lib下的标准库源文件和启动文件。提示build_src_filter里的路径是相对于platformio.ini所在目录的表示包含-表示排除。如果你发现某个文件没被编译先检查这里。3. 标准库工程的核心文件配置3.1 stm32f10x_conf.h的裁剪与配置这个文件是标准库的总开关里面通过#include的方式决定哪些外设驱动参与编译。默认的模板把所有外设都打开了但实际项目里你可能只用GPIO、USART和TIM其他的可以注释掉能省不少编译时间。#ifndef __STM32F10x_CONF_H #define __STM32F10x_CONF_H #include stm32f10x_gpio.h #include stm32f10x_rcc.h #include stm32f10x_usart.h #include stm32f10x_tim.h #include misc.h #endifmisc.h是NVIC和SysTick的配置函数中断相关的项目必须包含。如果你要用DMA加上stm32f10x_dma.h要用ADC加上stm32f10x_adc.h。这个文件放在include目录下因为stm32f10x.h里会引用它。3.2 系统时钟配置的常见坑STM32F103C8T6最小系统板上的晶振通常是8MHz但有些廉价板子焊的是12MHz甚至没有晶振。如果你发现串口输出的波特率不对十有八九是时钟配置和实际晶振不匹配。标准库的system_stm32f10x.c里默认把系统时钟配到72MHz前提是HSE是8MHz。配置流程是使能HSE等待稳定配置PLL倍频到9倍选择PLL作为系统时钟源。如果你板子上是12MHz晶振需要把PLL倍频改成6倍同时HSE_VALUE改成12000000。我实测过几块不同批次的蓝板有的晶振负载电容不匹配起振很慢甚至不起振。判断方法是写个点灯程序如果灯闪得比预期慢很多可能就是HSE没起来系统自动切到了内部8MHz RC振荡器。解决办法是在SystemInit里加超时判断起振失败就切HSI。3.3 启动文件的选择与中断向量表启动文件startup_stm32f10x_md.s里定义了中断向量表每个中断服务函数的弱符号都指向一个死循环。你要在stm32f10x_it.c里重写对应的函数比如USART1_IRQHandler否则中断触发后就会卡在死循环里。这里有个容易忽略的点启动文件里的栈大小和堆大小。默认栈是0x400也就是1KB如果你用了比较大的局部数组或者递归可能会栈溢出。我一般把栈改成0x800堆保持0x200。改的位置在启动文件开头Stack_Size EQU 0x00000800 Heap_Size EQU 0x00000200栈溢出是嵌入式里最难查的bug之一表现是程序跑飞或者变量莫名其妙被改。如果你遇到这类问题先把栈加大试试。3.4 链接脚本与内存布局PlatformIO默认的链接脚本会把Flash起始地址设为0x08000000RAM起始地址0x20000000大小20KB。STM32F103C8T6的Flash是64KBRAM是20KB这些默认值是对的。但如果你用了Bootloader需要把起始地址偏移就要自己写链接脚本。在platformio.ini里加一行board_build.ldscript my_custom.ld然后把你改过的链接脚本放在工程根目录。链接脚本里主要改FLASH和RAM的ORIGIN和LENGTH。这个操作不常用但做OTA升级或者双区备份的时候必须掌握。4. 编译、下载与调试的实操记录4.1 第一次编译常见报错与解决第一次点编译按钮大概率会遇到几个报错。最常见的是undefined reference to SystemInit这是因为system_stm32f10x.c没被编译进去。检查build_src_filter里有没有包含这个文件。第二个常见报错是cannot open source input file stm32f10x.h这是头文件路径没配对。确认build_flags里的-I路径是否正确注意大小写Linux下路径大小写敏感。第三个是region RAM overflowed说明你的变量太多20KB RAM不够用了。STM32F103C8T6的RAM确实小用标准库的时候要注意少用大数组能用const放Flash的就放Flash。4.2 ST-Link下载配置与接线ST-Link和蓝板的接线是SWDIO接PA13SWCLK接PA14GND接GND3.3V接3.3V。注意有些ST-Link的3.3V是输出有些是输入接反了可能烧。保险起见先只接SWDIO、SWCLK和GND三根线用板子自己的USB供电。在platformio.ini里配置好upload_protocol stlink后点上传按钮就会自动编译并下载。如果报st-link not found检查驱动装没装Windows下需要装ST-Link的USB驱动。Linux下需要配置udev规则否则普通用户没权限访问USB设备。4.3 用GDB做断点调试的配置PlatformIO的调试功能需要装Cortex-Debug插件。装好后在platformio.ini里加上debug_tool stlink然后点左侧的调试按钮会自动启动OpenOCD和GDB停在main函数入口。调试界面里可以看变量、看寄存器、看内存还可以在platformio.ini里配置debug_init_break指定初始断点位置。我一般设成main这样一启动就停在主函数方便单步跟初始化流程。有个小技巧调试的时候如果发现断点打不上检查优化等级。PlatformIO默认Debug构建是-Og如果platformio.ini里手动设了-O2断点可能会被优化掉。调试阶段建议用默认优化等级。4.4 串口打印的配置与printf重定向标准库工程里用printf需要重定向fputc函数。在main.c里加上#include stdio.h int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }然后在初始化里配好USART1波特率1152008位数据1位停止无校验。注意fputc里的USART_FLAG_TC是发送完成标志不是发送数据寄存器空标志。用TC能保证数据真正发出去了不会因为发送太快丢数据。注意用了printf之后链接时会引入标准库的printf实现代码体积会增加几KB。如果Flash紧张可以用iprintf或者自己写个轻量级的串口打印函数。5. 从MDK5迁移的避坑指南5.1 编译器差异导致的代码修改MDK的ARM Compiler和GCC在几个地方行为不同。第一是__weak关键字MDK里写__weakGCC里要写__attribute__((weak))。标准库的启动文件已经处理好了但你自己写回调的时候要注意。第二是内联汇编的语法不同。MDK用__asmGCC用__asm__ volatile。如果你用了__NOP()这类CMSIS内联函数一般没问题但手写汇编就要改。第三是位域的对齐方式。MDK默认按4字节对齐GCC默认按实际类型对齐。标准库的寄存器定义都是uint32_t不受影响但你自己定义的结构体如果用了位域跨编译器可能会有差异。5.2 中断服务函数的重定义问题MDK工程里中断服务函数写在stm32f10x_it.c里PlatformIO工程里也一样。但要注意如果你在多个文件里定义了同一个中断函数GCC会报重复定义错误而MDK可能只是给个warning。所以确保每个中断函数只在一个地方定义。另外标准库的启动文件里所有中断都是弱定义你重写的时候函数名必须完全一致包括大小写。USART1_IRQHandler不能写成Usart1_IRQHandler否则链接不会报错但中断触发时会跳到默认的死循环。5.3 下载算法与Flash编程的差异MDK5用ST-Link下载时用的是ST官方的Flash算法PlatformIO用OpenOCD的Flash驱动。两者在大多数情况下都能正常烧写但如果你遇到烧写失败可以试试在platformio.ini里加upload_flags -c set CPUTAPID 0x1ba01477强制指定TAP ID。还有一个情况是芯片被读保护了。STM32F103C8T6支持读保护如果之前被人设过保护位OpenOCD会报错。解决办法是用ST-Link Utility先解除保护再回到PlatformIO下载。5.4 工程文件版本管理的最佳实践MDK5的工程文件是二进制格式的.uvprojxGit diff基本看不出改了什么。PlatformIO的platformio.ini是纯文本每次改动在Git里一目了然。我的做法是把.pio文件夹加入.gitignore只提交platformio.ini、src、lib和include。这样别人克隆下来装好PlatformIO一编译就能跑不需要任何额外配置。如果标准库文件太大不想提交可以在README里写清楚下载地址和放置路径或者用Git Submodule的方式引入。我一般直接把标准库提交进去因为V3.5.0版本不会再变了提交一次一劳永逸。6. 常见问题速查与独家经验6.1 编译下载问题排查表现象可能原因解决办法编译报找不到头文件Include路径配置错误检查build_flags里的-I路径链接报undefined reference源文件未参与编译检查build_src_filter下载报st-link not found驱动未安装或USB权限装驱动或配置udev规则程序跑飞栈溢出或中断未处理加大栈、检查中断函数串口乱码时钟配置与晶振不匹配核对HSE_VALUE和实际晶振断点打不上优化等级过高调试时用-Og6.2 我踩过的三个印象最深的坑第一个坑是启动文件选错。我一开始用了startup_stm32f10x_hd.s编译能过下载也能跑但中断向量表偏移不对串口中断死活进不去。后来查数据手册才发现C8T6是中等容量应该用md后缀。这个坑隐蔽在于编译不报错只是功能不正常。第二个坑是HSE_VALUE没改。我有一块板子焊的是12MHz晶振但HSE_VALUE还是默认的8000000结果系统时钟跑到了108MHz串口波特率全乱。后来用示波器量了晶振频率才找到原因。所以拿到新板子第一件事就是确认晶振频率。第三个坑是build_src_filter的路径写法。我一开始写的是lib/STM32F10x_StdPeriph_Driver/src/*.c编译一直报找不到文件。后来发现PlatformIO的源文件过滤是相对于src目录的要写成../lib/...。这个在官方文档里写得很隐晦试了好几次才搞对。6.3 提升开发效率的几个小技巧第一个技巧是善用VSCode的代码片段。标准库的GPIO初始化代码很长我把它做成snippet输入gpioinit就能展开。具体做法是在VSCode里按CtrlShiftP输入snippet选配置用户代码片段然后编辑对应的JSON文件。第二个技巧是用PlatformIO的串口监视器。点左下角的插头图标就能打开串口终端不用再开额外的串口助手。波特率在platformio.ini里用monitor_speed 115200配置。第三个技巧是把常用的编译下载命令绑定到快捷键。VSCode的快捷键设置里搜索PlatformIO可以给Build、Upload、Monitor分别绑定顺手的组合键。我绑的是F5编译、F6下载、F7开串口效率提升明显。6.4 关于国产替代芯片的兼容性说明现在市面上有不少STM32F103C8T6的国产替代品比如GD32、APM32、CH32等。这些芯片在引脚和寄存器层面做了兼容但细节上有差异。用标准库开发时大部分代码可以直接跑但要注意几点一是Flash编程算法可能不同下载时如果报错需要换对应的OpenOCD配置二是某些外设的时序参数有差异比如ADC采样时间三是系统时钟配置国产芯片的PLL锁定时间可能更长需要加长超时等待。我实测过GD32F103C8T6用同样的标准库工程改一下upload_protocol和Flash算法就能正常下载运行GPIO和USART完全兼容但ADC的精度和STM32有细微差别。如果你做的是对精度要求不高的项目国产替代完全可用。6.5 后续可以扩展的方向这套环境搭好之后你可以继续往上加东西。比如接入FreeRTOSPlatformIO的库管理器里直接搜就能装比MDK下手动移植方便得多。再比如用PlatformIO的单元测试功能在PC上跑逻辑代码的测试不用每次都下载到板子上。还可以配置CI/CD每次提交代码自动编译确保没有破坏构建。我个人下一步打算把调试配置再优化一下加上RTT打印这样就不用占用串口了。RTT是SEGGER出的实时传输技术通过SWD接口输出日志速度比串口快得多而且不占外设资源。J-Link支持得最好ST-Link配合OpenOCD也能用配置稍微麻烦一点但值得折腾。
返回列表