STM32纯软件入门:不买开发板,用Keil仿真跑通GPIO、定时器、串口、ADC与PWM

📅 发布时间:2026/10/21 20:01:23
STM32纯软件入门:不买开发板,用Keil仿真跑通GPIO、定时器、串口、ADC与PWM
1. 为什么我要把 STM32 入门做成纯软件版本刚接触 STM32 的人十有八九会被同一个问题卡住到底要不要先买一块开发板。淘宝上一搜从十几块的核心板到几百块的综合实验板价格跨度大得离谱评论区还经常有人说“买回来吃灰”。我当年也是这么过来的板子买了两三块焊台、下载器、杜邦线配了一堆结果真正跑通的第一个工程还是靠仿真软件完成的。后来带新人我发现一个很现实的问题很多人连 GPIO 输出高低电平都没搞明白就先被硬件连接、供电跳线、下载器驱动折腾得没了脾气。所以这次我干脆把 STM32 入门做成了一个“纯软件”版本。核心思路很简单不买板子不接任何外部电路只用一台电脑把 STM32 的常用外设一个一个在仿真环境里跑通。你可能会问仿真能跑外设吗能跑而且跑得比你想的完整。GPIO 电平翻转、定时器中断、串口收发、ADC 采样、PWM 输出、I2C 读写这些在仿真环境里都有对应的模型代码写对了就能看到结果写错了也能立刻定位问题。这个方案适合谁第一类是完全零基础、还在犹豫要不要投入硬件成本的人第二类是有板子但外设一直调不通、想先排除软件问题的人第三类是带学生的老师或者带新人的老手想找一个统一的环境让大家先跑通逻辑再上真实硬件。说白了仿真不是替代硬件而是让你在花钱之前先把“代码怎么写、寄存器怎么配、中断怎么进”这些事搞明白。我实测下来用纯软件方式把 STM32 的 GPIO、定时器、串口、ADC、PWM 这几个核心外设跑一遍大概需要两到三个晚上每天两小时左右。这个时间投入比等快递、焊排针、装驱动要划算得多。下面我就把这套流程拆开讲包括环境怎么搭、每个外设怎么配、仿真里能看到什么现象、以及我踩过的那些坑。2. 仿真环境选型与搭建思路2.1 为什么选这套组合而不是别的市面上能跑 STM32 仿真的方案不止一种我试过几种之后最终固定在一套组合上Keil MDK 对应芯片的仿真模型。原因有三个。第一Keil 的调试界面里自带外设寄存器视图你写进去的值和寄存器实际值能对上这对理解底层很有帮助。第二它的仿真模型覆盖了 STM32 常用外设GPIO、定时器、串口、ADC 这些都有不需要额外装插件。第三网上关于 Keil 的教程最多遇到问题搜一下基本都能找到答案。有人会问为什么不直接用 Proteus 加 Keil 联合仿真Proteus 确实能画电路图、接虚拟示波器看起来更“真实”。但它的坑在于元件库版本和 Keil 工程配置经常对不上新手光解决“为什么仿真里没反应”就要花掉大量时间。而且 Proteus 的 STM32 模型对中断和时钟树的模拟并不总是准确有时候代码没问题但仿真就是不动。所以我建议第一阶段先用 Keil 自带的仿真把代码逻辑跑通等逻辑没问题了再考虑要不要上 Proteus 看波形。还有一个选择是纯软件模拟器比如某些开源项目。这类工具轻量但外设支持参差不齐文档也少出了问题很难排查。对于入门来说稳定比轻量重要所以我最终选了 Keil 这条路线。2.2 安装与工程创建的关键步骤安装 Keil MDK 本身没什么好说的一路下一步就行。但有几个地方要注意。第一安装路径不要有中文和空格否则后面编译和仿真都可能出问题。第二安装完成后要装对应芯片系列的 Device Family Pack比如你用的是 STM32F1 系列就装 F1 的包。这个包在 Keil 的 Pack Installer 里可以直接下载不用去别的地方找。创建工程的时候芯片型号选一个最基础的比如 STM32F103C8 或者 STM32F103RB。为什么选 F1 系列因为它的仿真模型最成熟资料也最多。选太新的型号仿真支持可能不完整反而给自己找麻烦。工程创建好之后有一个关键设置必须改在 Options for Target 的 Debug 选项卡里把调试器从默认的硬件调试器改成Use Simulator。这一步不做你点调试就会提示找不到硬件。改完之后再在同一个选项卡里点 Settings确认仿真器能识别到芯片。如果这里显示正常说明仿真环境已经就绪。注意每次新建工程都要检查 Debug 选项卡是不是 SimulatorKeil 不会记住你上一次的选择默认永远是硬件调试器。我因为这个设置漏改白白折腾了半小时。2.3 仿真里能看到什么、不能看到什么仿真环境能看到的寄存器值的变化、变量值的变化、GPIO 引脚电平在逻辑分析仪窗口里、串口输出的字符、定时器计数器的增减、中断触发记录。这些足够你验证代码逻辑对不对。仿真环境看不到的真实电路的电气特性比如上拉电阻够不够、信号有没有毛刺、电源纹波大不大。这些必须上真实硬件才能测。所以我的建议是仿真阶段只关心“代码逻辑对不对”不要纠结“信号质量好不好”。等逻辑跑通了再上板子调硬件效率会高很多。还有一个限制仿真里的时钟频率是模拟的不是真实的。你设 72MHz仿真里跑起来的时间感受和真实芯片不一样。所以做延时的时候仿真里看到的延时长度只能作为参考不能当作精确值。这一点在调串口波特率的时候尤其要注意仿真里能收到正确字符不代表真实硬件上波特率就准。3. GPIO 与定时器从点灯到精确延时3.1 GPIO 输出仿真里的“点灯”点灯是嵌入式的 Hello World但在仿真里没有真实的 LED怎么看结果答案是看寄存器。以 STM32F103 的 PA5 引脚为例你要做的是使能 GPIOA 时钟、配置 PA5 为推挽输出、然后循环翻转 PA5 的输出电平。代码写完之后进入调试模式打开 Peripherals 菜单里的 GPIO 窗口找到 GPIOA。你会看到 ODR 寄存器的第 5 位在 0 和 1 之间来回跳。这个跳变就相当于真实硬件上 LED 的亮灭。如果你在代码里加了延时还能看到跳变的间隔和你的延时设置对应。这里有一个新手常犯的错误忘了使能 GPIO 时钟。在 STM32 里任何外设在使用前都必须先开时钟GPIO 也不例外。代码里就是一句RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);。忘了这句寄存器窗口里 GPIOA 的配置全是 0你怎么改都没反应。我当年第一次写的时候就是漏了这句对着屏幕找了半天最后发现是时钟没开。实操心得在仿真里调 GPIO 的时候先把 ODR 窗口打开然后单步执行。每执行一步就看一眼 ODR 的值这样能非常直观地理解“写寄存器”和“引脚电平”之间的关系。比看教程上的文字描述管用十倍。3.2 定时器中断仿真里的“心跳”GPIO 翻转用延时循环实现简单但占 CPU。真正做项目的时候定时任务都是用定时器中断。在仿真里调定时器最大的好处是你能清楚地看到中断服务函数到底有没有进去、进去几次、间隔对不对。以 TIM2 为例配置步骤是使能 TIM2 时钟、设置预分频器和自动重装载值、开启更新中断、配置 NVIC、使能定时器。预分频器和重装载值的计算是重点。假设系统时钟 72MHz你想要 1ms 中断一次计算过程是72MHz 除以预分频器加一再除以重装载值加一结果等于 1000Hz。比如预分频器设 7199那么 72MHz / 7200 10kHz再除以 10 得到 1kHz所以重装载值设 9。在仿真里跑起来之后你可以在中断服务函数里翻转一个 GPIO然后用逻辑分析仪窗口看这个 GPIO 的波形。如果波形周期是 2ms因为中断一次翻转一次所以周期是中断间隔的两倍说明配置正确。如果波形周期不对就回去检查预分频器和重装载值的计算。这里有个坑仿真里的定时器计数速度和你电脑的性能有关电脑卡的时候波形会抖。所以看波形只看周期对不对不要纠结边沿是否绝对整齐。另外NVIC 的优先级配置在仿真里也能看到效果你可以设两个不同优先级的定时器中断观察哪个先执行。3.3 仿真与真实的差异延时和时钟前面提到仿真里的时钟是模拟的这里展开说一下。在真实芯片上你写delay_ms(1000)用示波器测就是精确的 1 秒前提是延时函数写对了。但在仿真里这个 1 秒可能跑成 1.5 秒甚至 2 秒取决于你电脑的 CPU 性能和 Keil 的仿真精度。所以我在仿真阶段调延时的时候从来不纠结绝对时间只看相对关系。比如我要确认“先点灯再延时再灭灯”这个顺序对不对仿真里完全能看出来。但我要确认“延时是不是精确的 500ms”仿真给不了答案必须上真实硬件用示波器测。这个差异还影响串口通信。仿真里调串口的时候波特率设 9600你能看到字符正确收发但这不代表真实硬件上 9600 的波特率就准。真实硬件上波特率不准收到的就是乱码。所以串口调试的最终验证一定要在真实硬件上做仿真只能验证“发送逻辑”和“接收逻辑”对不对。4. 串口、ADC 与 PWM仿真里的进阶外设4.1 串口收发仿真里的“打印调试”串口是嵌入式调试最常用的工具在仿真里也能用。配置步骤是使能 USART 和对应 GPIO 的时钟、配置 TX 为复用推挽输出、RX 为浮空输入或上拉输入、设置波特率、数据位、停止位、使能 USART。仿真里怎么看串口输出Keil 的仿真环境有一个 UART 窗口在 View 菜单里可以打开。你调用发送函数之后UART 窗口里会显示你发出来的字符。这个功能非常实用相当于给你一个不用接硬件的“串口助手”。接收怎么测在 UART 窗口里有一个输入框你可以手动输入字符然后触发接收中断。仿真里能看到中断服务函数被调用接收到的字符存在你指定的变量里。这样你就能完整地测试“发送”和“接收”两条路径。注意事项仿真里的串口没有真实的电平转换和波特率误差所以不要用仿真来验证“波特率设 115200 能不能稳定通信”。仿真只能验证你的发送逻辑和接收逻辑写对了没有。真实硬件上波特率误差、线缆质量、干扰都会影响通信这些必须实测。我踩过的一个坑是忘了配置 GPIO 的复用功能。STM32 的 USART 引脚默认不是串口功能必须把 GPIO 配置成复用推挽输出TX和浮空输入RX。忘了这一步UART 窗口里什么都不会显示。这个错误在真实硬件上也是一样的所以仿真里养成的检查习惯上板子之后直接能用。4.2 ADC 采样仿真里的“虚拟电压”ADC 在仿真里也能跑。配置步骤是使能 ADC 和 GPIO 时钟、配置 ADC 引脚为模拟输入、设置 ADC 的通道、采样时间、转换模式、使能 ADC、校准、启动转换。仿真里的 ADC 输入值是可以手动设置的。在 Keil 的仿真环境里打开 ADC 的外设窗口你可以直接修改输入电压的值。比如你设 1.65V参考电压 3.3V12 位 ADC 的转换结果应该是 2048 左右。你可以在代码里读转换结果然后通过串口打印出来或者存在变量里在 Watch 窗口看。这个功能对理解 ADC 的工作原理非常有帮助。你可以手动改输入电压观察转换结果的变化验证“输入电压和转换结果是不是线性关系”。真实硬件上你要用可调电阻或者信号发生器才能做这个实验仿真里动动鼠标就行了。这里要注意仿真里的 ADC 没有噪声转换结果非常干净。真实硬件上 ADC 会有噪声低位会跳。所以仿真里调通了 ADC 读取不代表真实硬件上读出来的值就稳定。真实硬件上通常要做多次采样取平均或者加滤波电路。这些在仿真阶段可以先不做等上板子之后再补。4.3 PWM 输出仿真里的“呼吸灯”PWM 是定时器的一个高级功能在仿真里也能看到效果。配置步骤是使能定时器和 GPIO 时钟、配置 GPIO 为复用推挽输出、设置定时器的预分频器和重装载值决定 PWM 频率、设置比较值决定占空比、配置 PWM 模式、使能定时器和 PWM 输出。仿真里怎么看 PWM打开逻辑分析仪窗口把对应的 GPIO 引脚加进去就能看到方波。改比较值方波的占空比会跟着变。如果你在代码里让比较值循环增减就能看到占空比来回变化的“呼吸灯”效果。PWM 频率的计算和定时器中断的计算类似。假设系统时钟 72MHz预分频器设 71重装载值设 999那么 PWM 频率是 72MHz / 72 / 1000 1kHz。比较值设 500占空比就是 50%。这些计算在仿真里可以立刻验证改一个参数就能看到波形变化比在真实硬件上改代码、重新编译、下载、用示波器测要快得多。实操心得调 PWM 的时候先把频率算对再调占空比。频率不对后面占空比怎么调都不对。我见过有人把预分频器和重装载值搞反了结果频率差了上千倍波形根本看不出来。仿真里逻辑分析仪的时间轴可以缩放放大之后能看清周期这个功能要善用。5. 常见问题与排查技巧实录5.1 仿真里代码跑不起来怎么办这是最常见的问题。代码编译通过了但一进调试就卡在启动文件里或者直接跑飞。排查顺序是这样的第一检查 Debug 选项卡是不是选了 Use Simulator。这个前面说过但还是要再强调一遍因为太容易漏。第二检查芯片型号和工程里选的型号是不是一致。不一致的话仿真模型对不上代码可能跑飞。第三检查启动文件里的堆栈大小。仿真环境对堆栈的模拟和真实硬件有差异堆栈设太小可能在仿真里就溢出了。把堆栈改大一点试试。第四检查有没有用到仿真不支持的指令。比如某些芯片特有的协处理器指令仿真模型可能不支持。这种情况只能换芯片型号或者换仿真方案。5.2 外设寄存器写了没反应代码里明明写了配置寄存器的语句但外设窗口里看寄存器还是 0。原因通常是时钟没开。STM32 的外设时钟默认是关闭的用之前必须使能。GPIO 在 APB2 上定时器在 APB1 或 APB2 上串口在 APB2 上ADC 在 APB2 上。每个外设的时钟使能函数不一样写代码的时候要查参考手册确认。还有一个可能是寄存器写保护。有些寄存器需要先解锁才能写比如某些配置寄存器。这个在 F1 系列里不常见但在其他系列里要注意。5.3 中断进不去或者进去就出不来中断进不去先检查 NVIC 配置。NVIC 的优先级分组、使能位、优先级设置都要对。仿真里可以在 NVIC 窗口看到中断的挂起状态和使能状态对照着查。中断进去就出不来通常是忘了清中断标志位。在中断服务函数里处理完业务逻辑之后必须清除对应的中断标志否则中断会一直触发程序就卡在中断里了。仿真里能看到中断触发次数疯狂增加这就是标志位没清的典型现象。5.4 串口仿真收不到字符先检查 GPIO 配置。TX 要配成复用推挽输出RX 要配成浮空输入或上拉输入。配成普通推挽输出的话串口功能出不来。再检查波特率。仿真里的波特率和真实硬件不一样但如果你设了一个特别离谱的值比如 1Mbps仿真可能也收不到。先用 9600 试确认逻辑通了再改。最后检查 UART 窗口有没有打开。这个窗口默认不显示要在 View 菜单里手动打开。我见过有人代码没问题就是没开窗口以为仿真坏了。5.5 常见问题速查表现象可能原因排查方法调试卡在启动文件Debug 没选 Simulator检查 Options for Target 的 Debug 选项卡寄存器写了没反应外设时钟没使能检查 RCC 使能语句中断进不去NVIC 没配置检查 NVIC 窗口的使能位中断出不来中断标志没清检查中断服务函数里的清标志语句串口收不到GPIO 模式配错检查 TX 和 RX 的 GPIO 配置PWM 没波形定时器没使能检查定时器使能语句和 PWM 输出使能ADC 读数不变转换没启动检查 ADC 启动转换的语句6. 从仿真到真实硬件的过渡建议仿真跑通之后下一步就是上真实硬件。这个过渡不是简单的“把代码下载进去就行”有几个地方需要调整。第一时钟配置。仿真里的时钟是模拟的真实硬件上要配置外部晶振和 PLL。如果你在仿真里用的是默认的内部时钟上板子之后要改成外部晶振否则串口波特率会不准。第二延时函数。仿真里的延时和真实硬件不一样上板子之后要用 SysTick 或者定时器重新校准延时。我通常的做法是在仿真里验证逻辑上板子之后用示波器测延时然后调整延时函数的参数。第三GPIO 驱动能力。仿真里 GPIO 输出高低电平没有电流限制真实硬件上 GPIO 的驱动能力有限直接驱动 LED 要加限流电阻驱动继电器要加三极管。这些在仿真阶段不用管但上板子之前要补上。第四电源和复位电路。仿真里没有电源和复位真实硬件上电源不稳或者复位电路设计不当程序可能跑不起来。这个属于硬件设计范畴仿真帮不上忙但至少仿真能帮你排除软件问题让你在调硬件的时候心里有底。我个人的习惯是仿真里跑通一个外设就上板子验证一个外设。不要等所有外设都在仿真里跑通了再上板子那样一旦出问题排查范围太大。一个一个来仿真和真实硬件交替验证效率最高。最后分享一个小技巧在仿真里调代码的时候把关键变量加到 Watch 窗口把关键寄存器加到 Peripherals 窗口把关键引脚加到 Logic Analyzer 窗口。这三个窗口同时开着代码执行到哪一步、变量变成什么值、引脚电平怎么变一目了然。这个习惯我从仿真阶段一直保持到真实硬件调试只是真实硬件上把 Logic Analyzer 换成了示波器。