STM32开源项目:代码、原理图、仿真三件套完整闭环

发布时间:2026/9/23 16:21:45
STM32开源项目:代码、原理图、仿真三件套完整闭环 1. 为什么一个STM32项目值得把代码、原理图和仿真三件套一起开源做过嵌入式的人大概都有这种体会从网上找到一个STM32项目代码能跑但想看硬件怎么接的没有原理图或者拿到一份原理图想验证逻辑对不对又没有仿真文件再或者代码、原理图都有但版本对不上编译报错、引脚冲突折腾半天跑不起来。这种“三缺一”甚至“三缺三”的情况在嵌入式开源圈子里太常见了。我这次开源的STM32项目核心思路就一条把代码、原理图、仿真三样东西打包在一起保证任何人拿到之后不用猜、不用补、不用到处找资料直接就能复现。关键词里的STM32、开源、代码、原理图、仿真这五个词看起来简单但真正能把它们串成一个完整闭环的项目并不多。大部分开源项目要么只丢一个Keil工程上去要么只给一张PDF原理图仿真文件更是很少有人愿意整理——因为仿真往往是在开发过程中随手搭的格式乱、注释少作者自己看得懂别人拿到就是天书。这篇文章适合谁看如果你是刚接触STM32的初学者想找一个结构完整、资料齐全的项目来练手那这套东西能帮你省掉大量“找资料、对引脚、调环境”的时间。如果你是有一定经验的嵌入式开发者想参考别人的工程组织方式、原理图设计习惯和仿真验证思路那这篇文章会拆解我在整理这套开源资料时的具体做法和踩过的坑。如果你正在准备基于STM32的毕业设计或者课程项目那这套“代码原理图仿真”的组织方式可以直接套用到你自己的项目里。先说清楚这个项目大概是什么。它是一个基于STM32F103C8T6的最小系统级项目外设不算复杂但覆盖了嵌入式开发中最常见的几个模块GPIO控制、定时器、串口通信、ADC采样以及一个简单的状态机逻辑。代码用标准外设库和HAL库两套都做了适配原理图用立创EDA绘制仿真部分则是在Proteus里搭建了对应的虚拟验证环境。整个项目的组织逻辑是原理图定义硬件连接代码实现功能逻辑仿真验证关键时序和信号。三者互相印证任何一个环节出问题都能快速定位。提示开源项目最怕的不是代码写得烂而是资料不全导致别人根本跑不起来。代码、原理图、仿真三件套齐全才算是真正意义上的“可复现开源”。2. 代码仓库的组织方式让拿到工程的人三分钟内能编译通过2.1 目录结构设计的取舍很多STM32开源项目的代码仓库打开之后根目录下散落着十几个文件夹和文件Keil工程文件、启动文件、库文件、用户代码混在一起找一个main.c要翻半天。我在整理这个项目的时候第一件事就是把目录结构重新规划了一遍。最终的目录结构是这样的STM32_OpenSource_Project/ ├── Doc/ # 文档目录 │ ├── 原理图说明.md │ ├── 仿真说明.md │ └── 引脚分配表.md ├── Hardware/ # 硬件相关 │ ├── Schematic/ # 原理图源文件 │ │ └── STM32_MinSys.epro │ ├── PCB/ # PCB文件预留 │ └── BOM/ # 物料清单 │ └── BOM_List.csv ├── Firmware/ # 固件代码 │ ├── HAL_Version/ # HAL库版本 │ │ ├── Core/ │ │ ├── Drivers/ │ │ └── MDK-ARM/ │ ├── SPL_Version/ # 标准外设库版本 │ │ ├── USER/ │ │ ├── HARDWARE/ │ │ └── PROJECT/ │ └── Common/ # 公共头文件 ├── Simulation/ # 仿真文件 │ ├── Proteus/ │ │ └── STM32_Sim.pdsprj │ └── 仿真说明.md └── README.md这个结构看起来简单但背后有几个考虑。第一硬件和软件分离。很多初学者会把原理图PDF直接丢在代码根目录时间一长就找不到了。单独建一个Hardware目录里面再分Schematic、PCB、BOM逻辑清晰后续如果要打板或者采购物料直接进对应目录就行。第二HAL库和标准库分开。现在网上关于STM32用HAL还是标准库的争论一直没停过我的做法是不争论两套都提供。HAL版本适合新手快速上手标准库版本适合需要精细控制寄存器的场景。两套代码功能完全一致只是底层驱动方式不同。第三仿真文件独立成目录。Proteus工程文件有时候会引用外部HEX文件如果和代码混在一起路径容易乱。单独放一个Simulation目录里面再放一个仿真说明告诉别人怎么加载HEX、怎么设置时钟频率。2.2 编译环境的兼容性处理STM32开发最让人头疼的问题之一就是环境配置。Keil MDK、STM32CubeIDE、IAR不同的人用不同的工具一个工程文件不可能同时兼容所有IDE。我的处理方式是以Keil MDK为主同时提供STM32CubeIDE的导入说明。Keil工程文件里我做了几件事来保证兼容性。首先把芯片包版本写清楚。STM32F103C8T6对应的器件支持包是Keil.STM32F1xx_DFP版本号我固定在2.3.0这个版本在Keil5和Keil4上都能正常识别。其次把编译器的优化等级统一设为-O0虽然这样生成的代码体积会大一些但调试信息最全单步跟踪的时候不会出现变量被优化掉的情况。第三在工程里把所有的头文件路径都用相对路径不用绝对路径。这一点看起来是常识但我见过太多开源项目因为用了绝对路径别人拿到之后一堆“找不到头文件”的报错。对于STM32CubeIDE的用户我在Doc目录下放了一份导入说明。核心步骤是新建一个STM32工程选择对应的芯片型号然后把Firmware/HAL_Version/Core目录下的用户代码复制到新工程的Core目录把Drivers目录下的HAL库文件替换掉自动生成的版本。这里有个细节要注意CubeIDE自动生成的HAL库版本可能和我的代码不匹配所以我在说明里明确写了“建议使用CubeMX 6.5.0版本生成底层初始化代码”因为这个版本的HAL库和我的代码兼容性最好。注意如果你用的是Keil5并且同时安装了C51和STM32的器件包有时候会出现编译器冲突的问题。解决办法是在Keil的安装目录下把C51和ARM两个文件夹的TOOLS.INI文件分开配置或者在工程设置里手动指定编译器路径。这个坑我在整理项目的时候踩过一次后来在README里专门加了一段说明。2.3 代码注释和文档的配合代码注释这件事我的原则是注释解释“为什么”代码本身解释“是什么”。比如在定时器初始化函数里我不会写“设置预分频值为7199”而是写“系统时钟72MHz预分频719917200定时器时钟为10kHz自动重装载值99911000定时周期为100ms”。这样别人看到注释就知道这个参数是怎么算出来的而不是只看到一个数字。另外我在Doc目录下放了一份引脚分配表用Markdown表格的形式列出每个引脚的功能、外设、备注。这份表格和原理图、代码里的引脚定义是完全对应的。如果你要修改引脚只需要改三个地方原理图、引脚分配表、代码里的宏定义。我在代码里把所有的引脚定义都集中在一个board_config.h文件里用宏定义的方式管理改起来很方便。// board_config.h 引脚定义示例 #define LED1_PIN GPIO_PIN_13 #define LED1_PORT GPIOC #define KEY1_PIN GPIO_PIN_0 #define KEY1_PORT GPIOA #define UART1_TX_PIN GPIO_PIN_9 #define UART1_RX_PIN GPIO_PIN_10 #define UART1_PORT GPIOA这种集中管理的方式好处是显而易见的。当你要把代码移植到另一块板子上时只需要改这一个文件不用在成千上万行代码里搜索“GPIO_PIN_13”然后一个个替换。3. 原理图设计从“能画出来”到“别人能看懂”之间的距离3.1 原理图工具的选择与文件格式画STM32原理图的工具很多Altium Designer、Cadence、立创EDA、KiCad各有各的优缺点。我这次用的是立创EDA原因有三个第一免费且跨平台。不管你是Windows、Mac还是Linux打开浏览器就能用不需要安装几个G的软件。第二元件库丰富。立创EDA自带立创商城的元件库STM32F103C8T6、AMS1117、CH340这些常用器件的符号和封装都是现成的不用自己画。第三导出格式友好。可以导出PDF、PNG、SVG也可以导出Altium Designer格式的文件方便用AD的人继续编辑。原理图源文件我提供了两种格式立创EDA的.epro文件和导出的PDF。PDF是给只想看不想改的人准备的.epro是给想自己修改的人准备的。这里有个细节立创EDA的工程文件如果直接分享链接有时候会因为权限问题打不开所以我建议把.epro文件下载到本地然后用立创EDA的“导入工程”功能打开。3.2 最小系统电路的几个关键设计点STM32F103C8T6的最小系统电路看起来简单但有几个地方如果设计不当会出现“代码能下载但跑不起来”或者“运行一段时间后死机”的问题。我在原理图里重点处理了以下几个部分。电源部分。STM32F103的供电范围是2.0V到3.6V典型值是3.3V。我用的是AMS1117-3.3输入5V输出3.3V。这里要注意的是AMS1117的输入输出压差不能太小如果输入是3.7V的锂电池输出3.3V压差只有0.4VAMS1117可能工作不稳定。所以我在原理图上标注了“输入电压建议5V”。另外3.3V输出端我放了两个电容一个10uF的钽电容和一个0.1uF的陶瓷电容。10uF负责滤低频纹波0.1uF负责滤高频噪声两个配合使用效果最好。晶振电路。STM32F103C8T6有两个晶振一个8MHz的高速晶振HSE一个32.768kHz的低速晶振LSE。高速晶振负责给系统提供主时钟经过PLL倍频后可以得到72MHz的系统时钟。低速晶振主要用于RTC实时时钟。我在原理图上把两个晶振都画了出来但LSE标注为“可选”因为如果你的项目不需要RTC功能LSE可以不焊。高速晶振的负载电容我选的是20pF这个值是根据晶振的负载电容参数和PCB的寄生电容估算出来的。如果你用的晶振负载电容是12.5pF那匹配电容应该选18pF左右。这个计算不复杂但很多初学者会忽略导致晶振起振困难或者频率偏差大。复位电路。复位电路我用了经典的RC复位加手动复位按钮。电阻选10k电容选100nF复位时间常数是1ms左右足够STM32完成上电复位。手动复位按钮并联在电容两端按下时把复位引脚拉到地松开后电容重新充电产生一个复位脉冲。这里有个小技巧复位引脚NRST内部有上拉电阻但为了可靠性外部还是建议加一个10k的上拉电阻。启动模式配置。STM32的BOOT0和BOOT1引脚决定了芯片从哪启动。BOOT0接10k下拉电阻到地BOOT1也接10k下拉电阻到地这样默认从主闪存启动。如果你需要用串口下载程序可以把BOOT0通过跳线帽接到3.3V下载完再跳回来。我在原理图上把BOOT0做成了跳线选择的方式方便切换。调试接口。SWD接口我留了4个引脚3.3V、GND、SWDIO、SWCLK。这4个引脚足够用ST-Link下载和调试程序了。如果你用的是J-Link可能需要额外的复位引脚但SWD模式下一般不需要。我在原理图上标注了“SWD调试接口兼容ST-Link和J-Link”。3.3 外设电路的连接逻辑这个项目的外设不算多但每个外设的连接都有明确的逻辑。LED接在PC13这是STM32F103C8T6最小系统板上最常见的LED引脚。按键接在PA0配置为上拉输入按下时读到低电平。串口用的是USART1TX接PA9RX接PA10通过CH340芯片转成USB接口方便和电脑通信。ADC采样用的是PA1接了一个电位器用来模拟模拟量输入。这里重点说一下CH340电路。CH340是一个USB转串口芯片价格便宜驱动成熟。但它的外围电路有几个容易出错的地方。第一CH340的V3引脚需要接一个0.1uF的电容到地这个电容是内部3.3V稳压器的滤波电容不接的话CH340可能工作不稳定。第二CH340的TXD和RXD要和STM32的PA10和PA9交叉连接即CH340的TXD接STM32的PA10RXCH340的RXD接STM32的PA9TX。这个交叉连接是初学者最容易搞错的地方我在原理图上用不同颜色的网络标签做了标注。第三CH340的D和D-引脚需要串联22欧姆的电阻这两个电阻的作用是阻抗匹配提高USB通信的稳定性。提示如果你在画原理图的时候用的是网络标签Net Label而不是直接连线一定要确保标签名称完全一致包括大小写。我见过有人把“USART1_TX”写成“USART1_Tx”结果编译没问题但硬件上信号就是不通。4. 仿真验证在打板之前把问题找出来4.1 为什么还要做仿真有人可能会问代码能编译原理图画好了直接打板不就行了为什么还要做仿真我的回答是仿真不是为了替代实物测试而是为了在打板之前把逻辑错误和时序问题找出来。打一次板从下单到收货至少三四天如果板子回来发现引脚接错了或者某个时序不对那这几天的等待就白费了。而仿真可以在几分钟内验证你的逻辑是否正确。这个项目的仿真部分是在Proteus里做的。Proteus支持STM32F103系列芯片的仿真可以加载Keil编译生成的HEX文件然后观察GPIO输出、串口数据、ADC采样值等。虽然Proteus的仿真速度和精度不如实物但对于验证基本的逻辑功能来说已经足够了。4.2 Proteus仿真的搭建步骤搭建Proteus仿真的过程我总结为“选芯片、连电路、加载HEX、设时钟、跑仿真”五步。选芯片。在Proteus的元件库中搜索“STM32F103C8”找到对应的型号。注意要选STM32F103C8T6不要选成STM32F103C8T6TR或者其他变体虽然它们功能基本一样但仿真模型可能有细微差别。连电路。按照原理图的连接方式在Proteus里把LED、按键、串口、ADC这些外设连好。Proteus里的元件和实际元件可能不完全一样比如LED的符号、按键的符号但电气连接逻辑是一样的。这里有个技巧Proteus支持网络标签你可以用网络标签来代替长距离的连线让原理图看起来更简洁。加载HEX。双击STM32芯片在弹出的属性对话框里找到“Program File”一栏选择Keil编译生成的HEX文件。HEX文件通常在工程的Objects目录下文件名和工程名一致。加载之后还要设置“Crystal Frequency”为8MHz这是外部晶振的频率Proteus会根据这个频率来计算芯片的运行速度。设时钟。在Proteus的STM32模型里还需要设置PLL倍频系数。STM32F103的PLL可以把8MHz的晶振倍频到72MHz。在属性对话框里找到“PLL Mul”或者类似的选项设置为9倍频。如果找不到这个选项可以在代码里把系统时钟配置成默认的8MHz虽然运行速度慢一些但仿真更稳定。跑仿真。点击Proteus左下角的运行按钮仿真就开始了。你可以观察LED的闪烁频率、串口终端输出的数据、ADC采样值的变化。如果发现LED不亮或者串口没输出先检查HEX文件是否加载正确再检查时钟设置是否正确。4.3 仿真中常见的“假故障”与排查仿真和实物最大的区别在于仿真里出现的“故障”有时候是仿真软件本身的问题而不是你的代码或电路有问题。我在做这个项目仿真的时候遇到过几个典型的“假故障”这里分享出来帮你少走弯路。第一个假故障串口没有输出。在Proteus里串口通信需要配合“Virtual Terminal”或者“COMPIM”元件。如果你用的是Virtual Terminal要确保它的波特率和代码里设置的波特率一致。我一开始设的是115200但Virtual Terminal默认是9600结果终端上显示的都是乱码。后来把两边都改成9600就正常了。另外Proteus的Virtual Terminal有时候不会自动滚动你需要手动把滚动条拉到底才能看到最新的输出。第二个假故障ADC采样值一直是0。在Proteus里ADC输入引脚如果悬空采样值可能是0或者随机值。你需要给ADC输入引脚接一个电压源或者电位器。我一开始忘了接电位器结果ADC采样值一直是0还以为是代码写错了。后来在PA1引脚上接了一个电位器采样值就正常变化了。第三个假故障定时器中断不触发。这个问题比较隐蔽。Proteus的STM32仿真模型对定时器的支持有时候不太完善特别是当定时器的时钟源选择比较复杂的时候。我的解决办法是先用最简单的定时器配置比如内部时钟、不分频、直接计数确认中断能触发之后再逐步增加复杂度。如果实在不行可以在代码里加一个GPIO翻转用示波器或者逻辑分析仪看波形比在Proteus里看中断标志位更直观。注意Proteus仿真不能完全替代实物测试。仿真通过不代表实物一定没问题仿真失败也不代表实物一定有问题。仿真的价值在于验证逻辑而不是验证电气特性。5. 三件套的版本对齐最容易被忽视的“隐形坑”5.1 代码、原理图、仿真之间的版本对应关系代码、原理图、仿真这三样东西单独看都没问题但放在一起就容易出现版本不对齐的问题。比如代码里LED接的是PC13原理图上画的是PA5仿真里又接的是PB0那这个项目就是一团乱麻。我在整理这个开源项目的时候专门花了一天时间做版本对齐确保三个文件里的引脚定义、外设配置、时钟设置完全一致。具体做法是以原理图为基准代码和仿真都向原理图对齐。因为原理图是硬件设计的最终体现代码和仿真都是为硬件服务的。我先确定原理图上的引脚分配然后把引脚分配表写出来再根据引脚分配表去改代码和仿真。改完之后做一次交叉验证代码编译生成的HEX文件加载到仿真里仿真里的外设行为要和代码逻辑一致同时代码里的引脚定义要和原理图上的网络标签一致。这里有一个细节代码里的引脚定义和原理图上的网络标签命名规则要统一。比如原理图上LED的网络标签是“LED1”代码里的宏定义就是“LED1_PIN”和“LED1_PORT”仿真里的元件标签也是“LED1”。这样在排查问题的时候不管看哪个文件都能快速对应上。5.2 用表格管理引脚分配引脚分配表是版本对齐的核心工具。我用Markdown表格的形式把每个引脚的功能、外设、电气特性、备注都列出来。这个表格放在Doc目录下代码和原理图都以此为准。引脚功能外设电气特性备注PA0KEY1GPIO输入上拉输入按下为低电平PA1ADC_IN1ADC1通道1模拟输入接电位器PA9USART1_TXUSART1推挽输出接CH340 RXDPA10USART1_RXUSART1浮空输入接CH340 TXDPA13SWDIOSWD上拉调试接口PA14SWCLKSWD下拉调试接口PC13LED1GPIO输出推挽输出低电平点亮这个表格看起来简单但作用很大。当你需要修改引脚的时候先改这个表格然后同步改代码和原理图最后跑一遍仿真验证。这样就不会出现“改了代码忘了改原理图”的情况。5.3 版本号管理和更新日志开源项目还有一个容易被忽视的问题版本管理。如果没有版本号别人下载了你的项目过了一段时间你更新了代码别人就不知道他手里的是哪个版本。我在项目的README里加了一个简单的版本号规则主版本号.次版本号.修订号。比如v1.0.0表示第一个正式版本v1.1.0表示增加了新功能v1.1.1表示修复了bug。每次更新我都会在README的“更新日志”部分记录改了什么。比如“v1.1.0增加了标准外设库版本的代码”“v1.1.1修复了串口波特率计算错误的问题”。这样别人一看就知道每个版本的区别也方便他们选择适合自己的版本。另外我在代码里也加了一个版本宏定义#define PROJECT_VERSION_MAJOR 1 #define PROJECT_VERSION_MINOR 1 #define PROJECT_VERSION_PATCH 1 #define PROJECT_VERSION_STRING v1.1.1这个版本宏可以在串口初始化的时候打印出来这样你一看串口输出就知道板子上跑的是哪个版本的代码。6. 从开源项目中真正能学到什么一些个人体会整理这个STM32开源项目的过程中我最大的体会是开源的价值不在于代码有多高级而在于资料有多完整。一个功能简单但资料齐全的项目比一个功能复杂但缺东少西的项目对别人的帮助要大得多。我见过很多开源项目代码写得非常漂亮但README只有一句话“基于STM32的项目自行编译”这种项目基本上没人能跑起来。另一个体会是仿真不是万能的但没有仿真也是万万不能的。仿真能帮你验证逻辑但不能帮你验证电气特性。比如电源纹波、信号完整性、EMC这些问题仿真里看不出来必须打板实测。但如果你连逻辑都没验证就直接打板那风险就太大了。我的建议是先用仿真验证逻辑再用实物验证电气。两步走稳扎稳打。还有一点关于代码风格的体会。STM32的代码HAL库和标准库各有各的写法但不管用哪种可读性永远是第一位的。我见过有人用HAL库写代码把所有初始化都塞在main函数里几百行代码没有一个注释这种代码别人根本没法维护。我的做法是每个外设的初始化单独写一个函数函数名用“外设名_Init”的格式比如“LED_Init”“UART1_Init”“ADC1_Init”。main函数里只调用这些初始化函数然后进入主循环。这样代码结构清晰别人一看就知道每个函数是干什么的。最后分享一个我在整理原理图时的小技巧用颜色区分不同的功能模块。在立创EDA里你可以给不同的网络标签设置不同的颜色。比如电源网络用红色地网络用黑色信号网络用蓝色调试网络用绿色。这样一张原理图打开一眼就能看出各个模块的分布排查问题的时候也更容易定位。这个技巧看起来很小但实际用起来非常方便尤其是当原理图比较复杂的时候。这个项目后续还可以这样扩展增加OTA升级功能通过串口或者无线模块实现固件远程更新增加FreeRTOS把裸机代码改成多任务架构增加CAN通信适配工业控制场景。这些扩展方向我在README里都列了出来如果你有兴趣可以基于现有的代码和原理图继续往下做。