☰
STM32纯软件仿真入门:不买开发板也能跑通全部外设
2026/10/14 16:56:19 网站建设 项目流程

1. 为什么我把 STM32 入门做成了“纯软件版”

不买开发板,能不能把 STM32 的外设一个个跑通?我的答案是能,而且路线并不复杂。STM32仿真不是新鲜词,但很多人一听到“仿真”就觉得只能点个灯、跑跑流水线,离真正学外设差得远。我花了几个晚上把整套流程整理成了一条“纯软件版”链路:用图形化配置工具生成初始化代码,在集成开发环境里编译,再把程序加载到电路仿真软件里运行。不花钱买板子,也能把 GPIO、串口、定时器、PWM、ADC 这些外设从配置到运行完整跑一遍。这篇文章不聊空话,直接给出可复现的工程方法,适合刚接触 STM32 的新手、手边暂时没有硬件的工程师,以及想在教学演示里把“原理展示”做得更直观的人。

很多新手一开始卡住的并不是 C 语言,而是环境链太长:选开发板要查引脚图,买下载器要驱动,连线时不小心接反,程序下载到一半没反应,整个过程挫败感非常强。仿真模式把这些干扰项全部去掉了:不需要担心接线短路,不需要烧录器,也不需要额外买配件。程序编译成 HEX 文件,加载进仿真软件后,芯片模型会按真实时序去跑,引脚电平、串口数据、PWM 波形都能看得清清楚楚。你可以把虚拟电路当成一个永远不会被焊坏的学习板。

1.1 谁最适合先用这套方案

如果你是零基础,连寄存器、时钟树都还没概念,纯软件版能帮你用最低成本建立“外设运行”的直观印象。每个外设无非就是“配置模块—写逻辑—观察现象”三步,你在仿真里试错,成本几乎为零。

如果你是已经写过代码、但不想陷入硬件调试的软件工程师,这套方案能让你先验证外设驱动逻辑,等到真正联调硬件时,只需要把引脚和时钟参数同步过来即可。仿真环境里能复现的寄存器行为,和真实芯片在绝大多数基础场景下是一致。

对于做技术培训的人,仿真还有一个隐藏优势:你不需要给每个学生配一套板子,也不用担心学生在操作中烧毁 IO 口。大屏幕投屏显示虚拟终端和示波器,讲定时器中断、PWM 原理时,比拿一块实体板讲要直观得多。

1.2 纯软件仿真与实物开发板的差异

实物开发板的最大优势是真实,引脚输出带负载能力、通信时序和芯片本身完全一致。但代价是成本与试错门槛。仿真则是在“理解外设工作过程”这件事上胜出:你可以随时暂停程序,看某个引脚此刻是高还是低,可以反复修改电路,不用焊一根线。

我整理过一张对比表,初学阶段两者差距很明显:

对比项实物开发板纯软件仿真
初始成本需要购买板子和下载器基本为零,用官方工具和评估版
接线风险接错可能损坏器件随便接,改电路只需拖拽
观察信号波形需要示波器虚拟示波器、虚拟终端直接显示
调试中断需要调试器支持可以在仿真软件里暂停运行
真实还原度完整标准外设足够,高级功能有限

我不建议把两者对立起来。纯软件版解决的是“入门门槛”问题,实物板解决的是“真实落地”问题。先把仿真跑顺,再上手板子,学习曲线会平滑很多。

2. 纯软件方案怎么选:别一上来就找“特殊版本”的仿真器

先说最容易被忽略的一点:不要一上来就花大量时间找各种特殊版本。仿真学习工具链完全可以用官方免费工具和评估版撑起来。我长期用的三件套是:STM32CubeMX 负责图形化配置,MDK 负责编译,电路仿真软件负责跑虚拟硬件。

三个环节缺一不可。STM32CubeMX 是 ST 官方提供的初始化代码生成器,你可以在图形界面里点引脚、选外设、配时钟,它自动帮你生成一个包含 HAL 库函数调用的 C 工程。MDK 是我们的编译环境,把生成好的代码编译成能加载的 HEX 文件。电路仿真软件则是那个显示虚拟硬件环境的地方,芯片、LED、按键、串口终端都放在这里。

2.1 三件套的搭配逻辑

很多人以为“仿真”是一项功能,其实它是多个工具之间的协作链条。

CubeMX 解决的是“外设怎么配置”的问题。你选中一个引脚,把它设置为 GPIO 输出,再设置初始电平,它就会在生成的代码里初始化这个引脚的时钟、模式和速率。这就省去了大量手写底层寄存器配置的时间,也减少了把启动文件、系统时钟配置写错的概率。

MDK 解决的是“代码怎么变成可执行文件”的问题。编译链接后会生成 HEX,这是一个可以被仿真器识别的二进制格式。MDK 评估版对学习完全够用,虽然代码量有限制,但入门外设涉及的代码体积很小,远不会触碰上限。

电路仿真软件解决的是“电路怎么响应”的问题。你在虚拟原理图上放一颗 STM32,把 PA5 通过电阻接到 LED,再加载 HEX。芯片模型会按照程序逐条执行指令,并且在仿真界面上实时更新引脚电平,于是你能看到 LED 闪烁,虚拟终端显示串口数据。

2.2 其他纯软件方案的对比

我身边也有人用其他方案来做 STM32 仿真,我整理成一张对比:

方案上手难度外设丰富度接近真实工程程度部署成本
CubeMX + MDK + 电路仿真软件中高较高免费工具+评估版
网页在线仿真平台低一般偏低免费
QEMU 等 CPU 模拟器高低内核层面更真实免费
仅用 STM32CubeIDE中低低免费

网页在线仿真平台最大的优点是免安装,浏览器打开就能拖点电路。但外设模型较少,遇到想试的型号可能没有,写复杂一点的中断逻辑也不容易模拟。QEMU 适合研究 CPU 指令与内核机制,但板级外设支持比较分散,对入门的人来说配置成本偏高。STM32CubeIDE 本身是完整 IDE,但如果没有调试器和实物芯片,“运行”这个环节会比较尴尬。

所以我的结论很明确:想要用软件方式快速跑通多个外设,CubeMX + MDK + 电路仿真软件的链路最直接,它把配时钟、写代码、看现象这三个核心动作都覆盖到了。

3. 搭一套最小工具链,先跑通“空转”工程

不管是点灯还是串口打印,都得先确保“编辑—编译—加载—运行”这条链路是通的。很多人失败在还没开始学外设,就卡在软件环境不齐整上。这一章我把每一步容易踩的坑标出来。

3.1 安装 STM32CubeMX 并准备固件包

STM32CubeMX 安装完成后,不要急着建工程。先进入软件包管理,检查本地是否已经有对应芯片系列的固件包。比如做 STM32F1 系列的话,需要下载 STM32F1 的固件包。这个包是工程代码里 HAL 库的来源,缺了它,CubeMX 即使能建工程,生成代码时也会报错。

建工程时我建议选 STM32F103C6 这一类封装小、外设数量适中的型号。仿真软件里引脚太密的芯片不仅看着费劲,选引脚时也容易乱。C6 型号具备基础的 GPIO、USART、TIM、ADC,足够覆盖入门实验。

3.2 安装 MDK 并勾选生成 HEX 文件

MDK 安装后可以用评估模式继续学习。装好后先找到一个空工程,打开 Option for Target 对话框,在 Output 页面把 Create HEX File 勾上。这个勾选极其重要,否则编译结果只是普通调试文件,仿真软件根本认不到。

我见过太多人跑来问“为什么没有 HEX?”十有八九是这一步漏了。另外建议把 Browse Information 也勾上,方便后期跳转查看函数定义。MDK 的编译路径尽量不要带中文,否则偶尔会出些烦人的路径问题。

3.3 安装电路仿真软件并确认芯片模型

电路仿真软件装好之后,新建一张原理图,在元件库里搜“STM32F103C6”。如果搜不到,说明元件库不完整,需要更新官方库或者再到网络上找对应芯片模型。注意,型号必须和你 CubeMX 里选的完全一致。

有人喜欢换一个类似型号,结果引脚定义对不上,仿真和代码就永远对不上。在开跑前确认模型存在,比做了到一半才发现没有要有价值得多。

3.4 用空工程验证整条链路

CubeMX 新建工程,选好 STM32F103C6,时钟配置暂时全部保持默认,生成工程到 MDK。编译刚才的空工程,如果提示没有错误、生成了 HEX,仿真软件里放芯片,双击芯片加载 HEX,点击运行。

空工程里没有外设代码,程序会停在 while(1) 死循环,仿真界面可能看不出任何现象。你可以把断点放在 main 函数里,或者随便定义一个全局变量填充程序计数器。只要芯片没有一直停在复位状态,就说明链路已经通了。这一步成功之后,第一个外设实验就可以开始。

4. 核心实操:跟着这套流程把外设逐个跑通

从这一章开始,我要按顺序讲 GPIO、输入按键、串口、定时器、PWM、ADC 的完整跑通流程。每个实验都基于同一个套路:配置、写代码、连线、观察现象。建议按章节顺序做,因为串口和定时器在后面会被反复用到。

4.1 GPIO 输出:先点亮一颗 LED

进入 CubeMX 的 Pinout 视图,鼠标点击 PA5,把它设置为 GPIO_Output。接着在 GPIO 设置标签里把默认输出电平改成 High,意思是上电后引脚默认输出高电平。这里也可以设成 Low,等代码里再翻转,只是初始现象不同。

生成代码后,在 main 函数的 while 循环里写:

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); HAL_Delay(500);

HAL_GPIO_TogglePin 会把当前电平翻转一次,HAL_Delay(500) 让程序停 500ms,所以最终效果是 LED 亮 500ms、灭 500ms。

仿真电路里,把 LED 阳极通过一个 220Ω 电阻接到 PA5,阴极接 GND。这里的电阻非常关键,它限制流过 LED 的电流。虽然仿真模型对电流不会像真实芯片那样敏感,但你如果以后移植到真实硬件,没有限流电阻是会很危险。

运行后就能看到 LED 按 1 秒周期闪烁。这个实验虽然简单,但它把 GPIO 输出配置、代码修改、仿真加载这三个动作完整串联到了一起。等你看到 LED 亮起来,整个工具链已经正式跑通。

4.2 GPIO 输入:用按键控制 LED 状态

点灯实验只用到输出,还不能让芯片“感知外部事件”。按键输入实验就是让芯片通过 GPIO 引脚读取外部电平状态。

CubeMX 里把 PA6 配置为 GPIO_Input,然后在 GPIO 设置里开启 Pull-up。内部上拉的意思是:默认状态下,PA6 引脚会被内部电阻拉高到 3.3V,所以读到的电平是 1。按键一端接 PA6,另一端接 GND,按下时引脚被拉低,读到的电平变成 0。

代码核心很简单:

if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_RESET) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); HAL_Delay(50); }

这里的 50ms 看起来像是多余延时,实际起到了类似机械按键消抖的作用。仿真环境里虽然没有真实机械抖动,但保留这样一小段延迟,可以让程序在移植到真实硬件时逻辑更稳。

仿真布线时,Button 元件一端接地,一端接 PA6。运行后用鼠标点击按钮,就能看到 LED 状态翻转。这个实验帮助你理解输入模式、内部上下拉和电平判断这三个核心概念。

4.3 USART:用虚拟终端打印调试信息

调试嵌入式程序,最常用的手段就是串口打印。没有串口输出,你很难知道程序内部的状态。CubeMX 里打开 USART1,模式选择异步,波特率设置成 115200,其余保持默认。生成代码后,在程序运行前发送一行字符串:

HAL_UART_Transmit(&huart1, (uint8_t*)"Hello STM32 Simulation\r\n", sizeof("Hello STM32 Simulation\r\n") - 1, 100);

HAL_UART_Transmit 的参数分别是收发句柄、待发送数据、数据长度和超时时间。这里用 sizeof 减 1,是为了把字符串末尾的“\0”去掉。如果你直接写数字,记错长度就容易只发半个字符串。

仿真电路里放置一个虚拟终端。把 STM32 的 PA9 接到终端 RXD,PA10 接到终端 TXD,GND 接 GND。记住这是交叉连接,对应真实场景里的 TX 接对方 RX、RX 接对方 TX。终端属性中要确认波特率已经设置为 115200。

运行之后,虚拟终端里出现一行 Hello STM32 Simulation,就说明 USART 发送链路成功。串口是所有后续实验中最重要的调试通道,比如定时器和 ADC 的值,都可以用它打印出来观察。

4.4 定时器中断:让时间控制更精确

HAL_Delay 虽然简单,它占住 CPU 死等。真实项目里往往需要一边做任务,一边等待定时器触发。定时器外设就是为了解决“精准计时”和“后台驱动”而生的。

CubeMX 里打开 TIM1,时钟源选 Internal Clock。PSC 和 ARR 的值不能乱填,必须先知道定时器时钟频率。假设定时器时钟是 64MHz,我把 PSC 设成 999,ARR 设成 63999,那么中断频率就是 64MHz / (999+1) / (63999+1) = 1Hz。如果 CubeMX 时钟树显示的不是 64MHz,请先按实际频率重新计算。

在 NVIC Settings 里勾选 TIM1 update interrupt,否则即使代码启动定时器,中断也进不来。生成代码后,在 main 里开启:

HAL_TIM_Base_Start_IT(&htim1);

然后在工程的 stm32f1xx_hal.c 文件里实现弱回调函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim == &htim1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); } }

注意回调函数里要判断是哪个定时器触发的。如果工程里用了多个定时器,不判断实例就会互相干扰。仿真运行后,LED 仍然按 1 秒翻转,但这次的时序由定时器硬件中断完成,CPU 可以在空余时间里做别的事。

4.5 PWM 输出:在虚拟示波器上看方波

PWM 是嵌入式里非常常见的输出方式,它的本质不是真的输出可调电压,而是让引脚在极短时间内快速切换高低电平,通过调整“高电平时间占周期比例”来模拟不同效果。这个比例就是占空比。

CubeMX 里把 TIM2 的 Channel1 设置为 PWM Generation CH1。假设定时器时钟也是 64MHz,PSC 设成 63,ARR 设成 999,那么 PWM 频率就是 64MHz / 64 / 1000 = 1kHz。在 main 里启动并修改比较值:

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 500);

这里的 500 就是比较值,它和 ARR 的 999 共同决定占空比,也就是 500 / 999,大约 50%。

仿真电路里,把 TIM2_CH1 对应的引脚引到虚拟示波器,运行后就能看到 1kHz 方波。把比较值改成 200,波形高电平宽度明显变窄;改成 800,宽度变宽。这就是呼吸灯和调速控制的底层原理。仿真示波器的好处是不用担心探头接触问题,直接能看到波形细节。

4.6 ADC 采集:用电位器读电压

ADC 是 STM32 与模拟世界打交道的方式,它把引脚上的模拟电压转换成数字值。CubeMX 里打开 ADC1,选择通道 IN0,对应 PA0。生成的工程会自动配置 ADC 时钟,不熟悉时不用手动改分频太高。

代码里做一次转换和读取:

HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) { uint32_t adc_value = HAL_ADC_GetValue(&hadc1); }

仿真电路放一个电位器模型,两端分别接 3.3V 和 GND,中间抽头接 PA0。运行后用鼠标拖动电位器滑片,采集到的 ADC 值会随之变化。STM32F1 的 ADC 是 12 位,所以理论范围是 0 到 4095。如果电位器滑到最靠近 3.3V 的一侧,读到的值接近 4095,实际电压接近 3.3V。

ADC 实验的后续玩法很多,比如采集温度传感器、电池电压、光敏电阻信号。关键在于把通道和引脚对应清楚,否则配置半天还是读到固定值。

4.7 综合演练:按键调节 PWM,串口上报状态

单独跑通外设后,可以把几个外设组合成一个小系统:两个按键分别减少和增加 PWM 比较值,LED 亮度随之变化,串口把当前占空比打印出来。这个实验需要的引脚包括 PWM 输出引脚、两个按键输入引脚、串口 TX/RX。

代码核心逻辑是:

if (按键1按下) { compare -= 50; } if (按键2按下) { compare += 50; } if (compare < 0) compare = 0; if (compare > 999) compare = 999; __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, compare);

按键消抖、比较值上下限保护都是必须处理的地方。仿真里 LED 亮度变化可能不如真实硬件明显,你可以观察示波器和串口数据,确认整个系统在按预期工作。做完这个综合实验,你对多个外设如何协同配合的感受会明显深入一层。

5. 常见问题与排查:我在仿真中反复踩过的坑

外设实验数量一多,容易遇到的问题也集中起来。我把它们整理成速查表,方便你直接对照。

5.1 问题速查表

现象可能原因解决方式
编译成功但找不到 HEX 文件MDK 没有勾选 Create HEX File在 Option for Target 里勾选后重新编译
仿真软件里芯片一直不工作没有加载 HEX,或芯片没有默认电源双击芯片加载 HEX,检查电源连接
LED 闪烁频率明显不对时钟配置和仿真模型不一致优先使用内部时钟,核对时钟树频率
虚拟终端没有输出TX/RX 接反、波特率不一致、没有共地检查 PA9/PA10 交叉连接和终端波特率
定时器中断不触发NVIC 没勾中断、没调用 Start_IT、回调没实现补勾 NVIC,确认 Start_IT 被调用
PWM 示波器看不到波形引脚映射错误,示波器探头没接地核对 CubeMX 映射,连接示波器地线
ADC 读取值固定不变电位器没操作、通道和引脚不符确认 IN0 对应 PA0,操作电位器
仿真运行卡顿明显虚拟终端、示波器、多个 LED 同时开启只保留当前实验需要的仪表

5.2 通用排查思路

遇到问题时,先不要怀疑仿真软件有问题。大多数情况是配置没对齐。

第一步,查引脚对应关系。CubeMX 里选的引脚和仿真电路里接的引脚必须一致,差一个位置结果就完全不对。第二步,确认程序真的在运行。可以在 while 循环里翻转一个空闲引脚,接上 LED 或者示波器,看到闪烁就说明代码在跑。第三步,善用串口打印中间变量。写入定时器前的计数值、ADC 读到的原始值,都可以通过串口发出来判断。第四步,对照数据手册或 CubeMX 的时钟树重新计算预分频和重装载值。

这套排查顺序不管是仿真环境还是真实硬件都适用。先确认大环节通没通,再深挖寄存器细节,是最有效的路径。

6. 把仿真玩得更顺的三个小技巧

6.1 把每个外设工程模板化

当 LED、串口、定时器这些工程都能独立跑通后,把整个工程文件夹复制一份,改成模板名称。比如 LED 工程改成 Template_GPIO,串口工程改成 Template_UART。后面做新项目时直接复制模板,在 CubeMX 里重新选引脚、改参数,几分钟就能进入写代码阶段,不用每次重新配环境和时钟。

我自己的模板库里,基础外设都是一式一份,遇到新传感器模组时直接从模板开始改,效率比从空白工程搭高很多。

6.2 虚拟仪表别全开

仿真软件里开着虚拟终端、示波器、好几个 LED 面板,会让运行速度明显下降。动一个电位器都要卡半秒,体验很差。我一般只保留当前实验必要的仪表。如果 LED 闪烁频率变得很慢,先把 HAL_Delay 改成 100ms 试试,如果反应正常,说明原来延时较大,不是卡顿。

6.3 从仿真走向真实的过渡建议

要明确一点:仿真不是万能的,它对高低速外设的支持会有限制,也模拟不了真实线路中的噪声和驱动电流问题。所以这套纯软件版适合作为入门通道,当你已经把外设原理都跑顺之后,再买一块最小系统板去做真实点灯、串口通信,会顺利很多。

我实际使用时,通常先在仿真里把某个功能验证一遍,再烧到真实板子上调试。仿真负责找逻辑错误,真实硬件负责暴露电气时序问题。两边配合,调试时间能省下一大半。这套 STM32 仿真的纯软件版方案,最终目标是让你没板子时也能动手、敢动手,等真正面对硬件时,手里已经攥着好几份跑通的外设模板了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询