简介基于STM32与Proteus的智能小车仿真项目以STM32F103C6为主控面向嵌入式电机控制初学者提供无需实物即可验证小车运动逻辑的完整工程方案。资源共155个文件压缩包6.08MB主要文件涵盖C语言源码与头文件、编译中间文件、Keil开发环境工程配置、Proteus仿真工程以及启动与链接脚本适合对照源码、电路和仿真状态学习。小车利用按键分别控制双电机正反转并借助脉宽调制调节转速覆盖通用输入输出、定时器、中断等STM32核心外设应用在虚拟环境中先行调试验证可显著减少硬件试错成本。包内工程备份与工作区文件还能辅助理解从底层初始化到运动控制逻辑的完整流程。通过仿真可在无硬件条件下反复验证代码正确性减少实物实验中的错误与时间成本也为后续更复杂的机器人项目打下基础。已有10226人学习下载适合作为STM32入门、电机驱动及Proteus仿真开发的实战参考。1. 用Proteus跑STM32智能小车先仿真后动手到底省了什么“智能小车”这个题目在嵌入式课设里快被写烂了但很多人第一版实物翻车的细节其实都能在仿真里提前测出来电机一侧转一侧不转先查共地循迹阈值不对先查传感器供电PWM频率不对电机吼叫但就是不动。如果你能在Proteus里先把STM32F103C8的引脚映射、PWM参数、循迹和避障逻辑跑通再去画板子、焊板子至少能省下一个通宵的飞线排查时间。这篇笔记就是讲这套仿真怎么做从Keil工程到Proteus接线从驱动层到感知层再到5个最典型的翻车点。适合正在做课设或毕业设计的同学也适合想先在仿真里验证完逻辑再动手的开发者——仿真不是玩具它是给实物调试铺路的。2. Proteus Keil 工程搭建让 STM32F103C8 在仿真图里先跑起来2.1 版本选型与器件库先确认你的 Proteus 能搜到 STM32F103C8Proteus 对 STM32 的支持是最近几个大版本才完善的如果你打开元件库搜不到 STM32F103C8多半是库版本太老或者没有安装对应的芯片库。常见的做法是直接用 8.x 之后的版本元件库搜索框里输入STM32F103C8然后从分类Microprocessor ICs - ST ARM Cortex里拖出来。注意有些下载的精简版 Proteus 会把 ARM 库裁掉装完以后先做一个测试在库管理器里搜LM324这种通用运放如果能搜到但搜不到 STM32那就是芯片库缺失需要补装。Keil 这边建议直接用 MDK 5 的社区版或标准版新建工程时选择芯片型号为 STM32F103C8。这里有个细节Keil 工程里的芯片型号不会影响最终生成的 HEX 文件内容但会影响编译器对寄存器地址的判断所以型号选错可能导致链接器报错。工程创建完成后在 Options for Target 里做三件事第一Output 选项卡勾选 Create HEX File第二Debug 选项卡选择 Use Simulator别选 ULINK 或 ST-Link第三C/C 选项卡里把优化等级设为 -O0 或 -O1方便调试。Proteus 里加载固件的路径是双击仿真图里的 STM32 芯片在 Program File 一栏选择 Keil 输出目录下的 HEX 文件。加载完成后点左下角的运行按钮如果芯片有点反应说明整体链路已经通了。这里我建议第一步别直接写小车逻辑先做一个闪灯程序确认最小系统没问题否则后面遇到问题你分不清是硬件仿真图的问题还是代码的问题。2.2 最小系统接线与时钟设置仿真图里的晶振是个黑匣子很多第一次用 Proteus 仿真的同学会照着实物最小系统的画法给 STM32 接 8M 晶振、两个 20pF 电容、复位电路结果发现仿真跑不起来或者芯片完全不工作。这里有个关键的认知差别Proteus 的 STM32 模型在大多数版本里并不真正依赖外接晶振起振芯片的工作主频是在属性对话框里设置的外接晶振只影响 HSE 引脚的逻辑电平不参与内部 PLL 的计算。所以我的习惯是仿真图里不画外部晶振电路直接双击芯片在 Component Properties 里找到 Clock Frequency 这一项设成 72MHz。然后代码里保持 Keil 默认的 SystemInit 逻辑不动这样 SystemCoreClock 就是 72MHz。如果你在代码里手动配置了 HSE 或 PLL 倍频系数反而容易和仿真模型的时钟对不上出现定时器时间全部偏慢或偏快的诡异现象。电源和地线倒是不能马虎。VDD 接 3.3VVSSA 和 VDDA 也接 3.3VGND 统一接地。仿真模型对复位脚 NRST 一般不太敏感但建议接一个 10k 上拉到 3.3V防止某些版本的模型因为复位脚悬空而出现随机复位。BOOT0 和 BOOT1 直接接地不要悬空。这些引脚在仿真模型里不接也能跑但养成和实物一致的习惯后面移植代码时少改一个是一个。2.3 Keil 工程的三个关键配置输出 HEX、仿真模式与 Flash 下载设置工程建好后最容易翻车的三个配置点如下。第一Output 选项卡的 Create HEX File 必须勾选否则你编译一万次也生成不了 HEX 文件Proteus 那边自然无程序可加载。第二Debug 选项卡要选 Use Simulator并且在旁边设置 Dialog DLL 参数时留空默认值不要填成 ST-Link 的 DLL否则编译和调试会进入一个找硬件的死循环。第三Utilities 选项卡里的 Flash Download 设置对仿真无用但建议把 Reset and Run 勾上这样以后接实物调试器时不用再改配置。代码文件组织方面我建议三个文件起步main.c只放主循环和状态路由driver.c放电机、PWM、串口这类硬件初始化sensor.c放循迹和超声波的数据读取与处理。不要把所有初始化堆在 main 函数里因为仿真调试时你想注释掉某一段逻辑文件之间耦合太紧会牵一发动全身。仿真工程还有一个好处修改代码后重新编译生成 HEX在 Proteus 里点暂停再点运行就能热加载不需要重启仿真。但注意如果芯片正在运行状态直接加载新 HEX有些版本会出现程序区更新不同步的问题保险起见先暂停仿真再加载。这一步的血泪经验是每次修改代码后先确认 Keil 编译输出框里出现了 Build Target Completed 且没有 Error再去加载 HEX不要编译报错还硬加载旧文件那样你排查半天还以为代码没写对。3. 驱动层设计L298N 接线、PWM 调速与差速转向参数3.1 L298N 在仿真里的接法与 IO 映射电机驱动方案里L298N 是课设和仿真中最常见的模块不是因为性能最好而是它的逻辑电平容忍度高、接法直观。Proteus 元件库搜索 L298N 或 L298能找到一个独立的电机驱动芯片模型。这个模型有 IN1~IN4 四个逻辑输入、ENA 和 ENB 两个使能端、OUT1~OUT4 四个输出端以及 VSS 和 VS 两个电源引脚。接法上我的习惯是VSS 接 5V逻辑电源VS 接 12V电机电源GND 和 STM32 的 GND 必须共地。输出 OUT1、OUT2 接左侧电机两端OUT3、OUT4 接右侧电机两端。IN1~IN4 接到 STM32 的 GPIOENA 和 ENB 接 PWM 输出通道用来调速。下面给一个我常用的引脚映射直接在代码里用宏定义写死后面移植到实物时只改这个文件即可。// motor_io.h #define MOTOR_LEFT_IN1 GPIO_Pin_0 // GPIOB.0 - L298N IN1 #define MOTOR_LEFT_IN2 GPIO_Pin_1 // GPIOB.1 - L298N IN2 #define MOTOR_RIGHT_IN3 GPIO_Pin_2 // GPIOB.2 - L298N IN3 #define MOTOR_RIGHT_IN4 GPIO_Pin_3 // GPIOB.3 - L298N IN4 #define MOTOR_LEFT_PWM TIM2_CH1 // PA0 - L298N ENA #define MOTOR_RIGHT_PWM TIM2_CH2 // PA1 - L298N ENB这里有一个很多人踩过的坑IN1 和 IN2 同时为高电平或同时为低电平时电机处于刹车或自由停止状态只有一高一低时电机才转动。很多同学初始化时把 IN1~IN4 全部设成低电平然后只调 PWM发现电机完全不动还以为是 PWM 通道配置错了其实根本没有形成差模电压。L298N 的使能端 ENA/ENB 如果直接接 5V 不做 PWM电机就是以全速转只有把使能端接到 PWM 输出上才能实现调速。仿真里一样有这个行为接错了现象和实物完全一致。3.2 PWM 频率与占空比这三个参数决定电机听不听话STM32 定时器输出 PWM 时核心参数就三个预分频器 PSC、自动重装载值 ARR、捕获比较值 CCR。输出频率的计算公式是PWM频率 定时器时钟 / (PSC 1) / (ARR 1)占空比是CCR / (ARR 1)。以 STM32F103C8 的 APB1 定时器时钟 72MHz 为例想要 1kHz 的 PWM可以设 PSC 71ARR 999那么频率就是 72MHz / 72 / 1000 1kHz。占空比 50% 对应的 CCR 就是 500。// driver.c - TIM2 输出 PWM 通道1和通道2 void TIM2_PWM_Init(uint16_t freq_hz, uint16_t duty_percent) { GPIO_InitTypeDef gpio_init; TIM_TimeBaseInitTypeDef tim_base; TIM_OCInitTypeDef tim_oc; uint16_t arr, psc, ccr; // 计算 ARR 和 PSC保证频率落在合理区间 arr 1000 - 1; // 自动重装载值决定 PWM 周期 psc (72000000UL / (arr 1)) / freq_hz - 1; // 预分频72MHz / 分频后 目标频率 ccr (arr 1) * duty_percent / 100; // 占空比CCR / (ARR1) // 时钟使能TIM2 挂在 APB1GPIOA 挂在 APB2 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置 PA0、PA1 为复用推挽输出50MHz 速度 gpio_init.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; gpio_init.GPIO_Mode GPIO_Mode_AF_PP; gpio_init.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio_init); // 定时器时基向上计数PSC 和 ARR 生效 tim_base.TIM_Prescaler psc; tim_base.TIM_CounterMode TIM_CounterMode_Up; tim_base.TIM_Period arr; tim_base.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, tim_base); // PWM1 模式CNT CCR 时输出高电平 tim_oc.TIM_OCMode TIM_OCMode_PWM1; tim_oc.TIM_OutputState TIM_OutputState_Enable; tim_oc.TIM_Pulse ccr; tim_oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, tim_oc); TIM_OC2Init(TIM2, tim_oc); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, TIM_ARRPreload_Enable); TIM_Cmd(TIM2, ENABLE); }占空比设置方面我建议初始给 50% 而不是 100%。因为仿真里电机模型对电压突变很敏感100% 占空比下从静止到全速的冲击会让小车起步时明显抬头而且转向时冲击电流会让虚拟示波器上的波形出现毛刺。先把占空比设在 40%~60% 之间确认电机转动方向和转速变化符合预期再逐步调高。有一点需要特别说明仿真里 1kHz 的 PWM 能看到明显的轮速脉动这是模型对低频 PWM 的自然响应不影响逻辑验证而实物直流电机一般推荐 10kHz~20kHz 以避免啸叫。所以仿真调逻辑、实物调频率这个顺序不要反过来。3.3 左右轮差速转向加减速的幅度才是转向质量的关键差速转向的思想很简单左转时左轮减速、右轮加速小车就能向左偏转。但具体减多少、加多少直接决定小车是平稳过弯还是原地打转。一个容易犯的错误是把转向差速设得过大——左轮 10%、右轮 90%结果小车不是转向而是绕着左轮中心点旋转循迹时轨道完全丢失。我常用的策略是直行时左右轮都是 50% 占空比轻度转向时一侧 40%、另一侧 60%急转时一侧 20%、另一侧 80%。这个比例要配合转向持续时间来调仿真里每次转向控制周期建议 20ms不要让转向行为一次性持续几百毫秒否则必然过冲。// driver.c - 电机速度控制接口 #define PWM_STRAIGHT 50 // 直行占空比 #define PWM_TURN_LOW 30 // 转向内侧轮 #define PWM_TURN_HIGH 70 // 转向外侧轮 void Set_Motor_Left(uint16_t duty) { TIM_SetCompare1(TIM2, (1000 * duty) / 100); // CCR1 占空比 * ARR } void Set_Motor_Right(uint16_t duty) { TIM_SetCompare2(TIM2, (1000 * duty) / 100); // CCR2 占空比 * ARR } void Go_Straight(void) { Set_Motor_Left(PWM_STRAIGHT); Set_Motor_Right(PWM_STRAIGHT); } void Turn_Left(void) { Set_Motor_Left(PWM_TURN_LOW); Set_Motor_Right(PWM_TURN_HIGH); } void Turn_Right(void) { Set_Motor_Left(PWM_TURN_HIGH); Set_Motor_Right(PWM_TURN_LOW); }这里的TIM_SetCompare1和TIM_SetCompare2是标准库的寄存器写接口直接修改 CCR 值下次计数器溢出时自动生效。注意修改的时机不要在计数器计数到一半时频繁修改 CCR否则 PWM 波形会出现一次畸变仿真里不明显实物上表现为电机抖动。建议所有转向控制放在同一个函数里顺序执行保证左右两个通道的 CCR 在同一时间点更新。还有一个容易被忽略的细节差速转向时内侧轮不要减到 0一旦 PWM 占空比降到 0电机相当于断电自由转动轮胎会失去抓地力在循迹场景里表现为车身横摆。保持 20% 以上的最低占空比能避免这个问题。4. 感知与决策循迹阈值标定与超声波避障的协同逻辑4.1 红外循迹模块的仿真建模用光敏电阻近似红外反射检测Proteus 元件库里没有直接叫“红外循迹传感器”的标准型号常见的替代方案有两种一种是直接用 GPIO 引脚的高低电平来模拟传感器输出调试时手动切换代码和实物一致另一种是用光敏电阻加比较器搭一个光电检测电路电路行为更接近真实情况但调试复杂度高不少。我一般先用手动电平模拟的方式跑通逻辑再决定要不要换成光敏电阻方案验证阈值。因为循迹算法的核心是传感器返回的到底是黑线状态还是白底状态你的代码对这个状态的判断是否稳定。如果逻辑本身就不对传感器建模再真实也只是在错误的基础上修修补补。// sensor.h - 循迹传感器引脚定义 #define TRACK_LEFT_GPIO GPIOB #define TRACK_LEFT_PIN GPIO_Pin_4 // 左传感器 - PB4 #define TRACK_RIGHT_GPIO GPIOB #define TRACK_RIGHT_PIN GPIO_Pin_5 // 右传感器 - PB5 // 返回 1 表示检测到黑线0 表示白底 uint8_t Read_Track_Left(void) { return GPIO_ReadInputDataBit(TRACK_LEFT_GPIO, TRACK_LEFT_PIN); } uint8_t Read_Track_Right(void) { return GPIO_ReadInputDataBit(TRACK_RIGHT_GPIO, TRACK_RIGHT_PIN); }注意 GPIO 初始化的模式要设成GPIO_Mode_IPU上拉输入或者GPIO_Mode_IN_FLOATING浮空输入。传感器模块输出端如果是开漏结构必须用上拉输入否则读到的电平不稳定如果是推挽输出浮空也没问题。仿真里手动模拟高低电平时直接用电源或地接到引脚即可但在引脚上串一个 10k 电阻再接入更贴近实物传感器模块的输出阻抗。4.2 循迹阈值与死区双路传感器的状态判断别写得太脆双路循迹是课设里最常见的形式两个传感器并排安装在车头底部车走歪时一边压到黑线、另一边在白底上控制器根据两路信号修正方向。最容易翻车的写法是直接比较两个引脚电平然后立即转向没有去抖、没有死区、没有周期控制结果小车在阈值边缘疯狂震荡表现为走 S 形甚至原地摆动。正确的做法是加一个状态合并函数把两路传感器的读数映射成四个状态左中右和直行。同时给传感器读数一个持续性的判断窗口——连续读到同一个状态 5 次才认为状态有效这能滤掉仿真步长带来的瞬间抖动。// sensor.c - 循迹状态合并与去抖 #define TRACK_STATE_STRAIGHT 0 #define TRACK_STATE_LEFT 1 #define TRACK_STATE_RIGHT 2 #define TRACK_STATE_BOTH 3 uint8_t track_state TRACK_STATE_STRAIGHT; uint8_t track_debounce_count 0; void Track_Update(void) { uint8_t left Read_Track_Left(); uint8_t right Read_Track_Right(); uint8_t new_state; // 状态映射左黑右白 - 左偏右黑左白 - 右偏 if (left 1 right 0) { new_state TRACK_STATE_LEFT; } else if (left 0 right 1) { new_state TRACK_STATE_RIGHT; } else if (left 1 right 1) { new_state TRACK_STATE_BOTH; // 交叉线或出线 } else { new_state TRACK_STATE_STRAIGHT; } // 连续 5 次一致才切换状态滤除抖动 if (new_state track_state) { track_debounce_count 0; } else { track_debounce_count; if (track_debounce_count 5) { track_state new_state; track_debounce_count 0; } } } void Track_Control(void) { Track_Update(); switch (track_state) { case TRACK_STATE_LEFT: Turn_Left(); // 车头偏左向右修正 break; case TRACK_STATE_RIGHT: Turn_Right(); break; case TRACK_STATE_BOTH: Go_Straight(); // 交叉线保持直行 break; default: Go_Straight(); break; } }这里有个容易混淆的地方左传感器检测到黑线说明车头已经偏向线左侧此时应该让小车向右修正而不是继续向左。很多同学在这里写反导致小车越偏越多。去抖计数的数值 5 对应的时间取决于主循环周期如果主循环是 10ms 跑一次那么 5 次就是 50ms这个响应速度在小车速度不高时是合适的如果车子跑得快可以缩短到 3 次。循迹状态TRACK_STATE_BOTH表示两个传感器同时压线这在正常的直线赛道里几乎不会出现但十字路口或障碍物区域会出现。我建议在这个状态下保持直行而不是原地停止因为很多场地赛的十字交叉口就靠这个逻辑直接冲过去。4.3 超声波避障Echo 高电平测距的实现与仿真注入超声波避障的原理是Trig 脚拉高 10us 以上触发测距模块发出声波后 Echo 脚输出高电平高电平持续时间就是声波往返时间。距离 时间 × 声速 / 2常温下声速取 340m/s。STM32 测高电平时长最常见的做法是用定时器输入捕获或者简单一点用定时器计时加口循环读取。// sensor.c - 超声波测距毫秒级超时保护 float Ultrasonic_Read_Distance(void) { uint32_t counter 0; float distance 0.0f; GPIO_SetBits(GPIOB, GPIO_Pin_6); // TRIG 拉高 Delay_us(15); // 至少 10us留点余量 GPIO_ResetBits(GPIOB, GPIO_Pin_6); // TRIG 拉低 // 等待 ECHO 变高带 10ms 超时保护 while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) 0) { counter; if (counter 10000) return -1.0f; // 超时返回 -1 Delay_us(1); } // 测量 ECHO 高电平持续时间计数器单位 us counter 0; while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) 1) { counter; if (counter 30000) return -1.0f; // 超过 30ms约 5m 距离 Delay_us(1); } distance (float)counter * 340.0f / 2.0f / 1000.0f; // 单位 cm return distance; }仿真里没有真实的超声波模块时我建议自己写一个测试函数代替Ultrasonic_Read_Distance()直接返回固定距离来验证避障状态机因为状态机的逻辑判断才是要验证的目标Echo 引脚的真实电平时序在实物调试时自然会验证到。// debug.c - 固定距离注入测试 float Test_Distance_Inject(void) { static uint8_t step 0; // 模拟一个从 50cm 到 10cm 再到 50cm 的障碍物过程 step (step 1) % 40; if (step 20) return 50.0f - step; else return step - 30.0f 10.0f; }避障逻辑一般是三态状态机搜索前进、左转、右转。我的实现是车头在前方障碍物距离小于 25cm 时停车向左转 500ms如果左转后前方距离仍小于 25cm 就向右转否则继续前进。转向持续时间这个参数最容易翻车转太久会把车头转向 180 度转太短又避不开障碍物。仿真里先用固定距离注入测试状态切换逻辑确认每个状态都能正确跳出然后再去调转向时间。5. 仿真调试避坑5 个从黑屏到飞车的高频问题5.1 现象加载 HEX 后点运行芯片完全不响应仿真图点运行后STM32 芯片既没有 IO 变化也没有电流指示就像死机一样。最常见的原因是 HEX 文件加载路径有问题或者芯片属性里的 Clock Frequency 和代码里的系统时钟不一致。Keil 生成的默认 SystemInit 会把时钟倍频到 72MHz而 Proteus 芯片属性默认的 Clock Frequency 往往不是 72MHz两边对不上定时器外设的时钟源就乱了。解决方法是双击仿真图里的 STM32 芯片把 Clock Frequency 属性显式改成 72MHz。同时确认 HEX 文件路径不要包含中文字符Proteus 对路径里的非 ASCII 字符支持有问题这个看起来不起眼却非常致命。改完以后先暂停再重新运行如果还不响应在代码入口放一个 GPIO 翻转的最小程序排除加载问题。5.2 现象程序在跑、LED 亮但电机不转这个现象出现时通常 GPIO 逻辑输出是对的但电机驱动链路断了。先查 L298N 的 VSS 和 GND 是否接对VSS 必须和 STM32 共地否则逻辑输入信号根本没有参考电平。再查 ENA/ENB 是否接在 PWM 输出通道上很多同学把使能端接到了普通 GPIO然后只控制 IN1~IN4电机当然不会调速。仿真里有一个隐蔽的坑L298N 的 VS 端如果没有接 12V 电机电源OUT 端不会有任何输出但芯片表面看不出异常。解决步骤是先用万用表探针Proteus 的 Voltage Probe量 OUT1 和 OUT2 之间的电压如果始终为 0往前查 VS如果有电压但不转查电机模型的额定电压Proteus 默认电机模型可能需要更高的驱动电压才能转动。把电源电压设到 12V再把 ENA/ENB 直接接 5V 排除 PWM 配置问题确认电机能转了再接回定时器通道。5.3 现象循迹小车走 S 形左冲右撞走 S 形的直接原因是转向修正过猛或者控制周期太短。很多同学写的循迹循环是 1ms 一次每次转向都执行满幅差速结果小车像蛇一样扭来扭去。另一个隐蔽原因是传感器去抖没做好在临界状态引脚电平抖动状态机频繁切换。我的解决习惯是把控制周期拉长到 10ms 一次转向幅度从 ±30% 起步而不是直接 ±80%。去抖窗口设 5 次连续采样。如果还走 S 形把直行速度占空比从 50% 降到 30%让小车速度慢下来这样同样幅度的差速产生的角速度更大、更可控。5.4 现象仿真速度慢到没法调Proteus 仿真跑起来像幻灯片尤其是加了超声波测距里那个 10us 级别的 Delay 循环后传感器越多、动画更新率越高仿真越慢。原因在于仿真模型是逐条指令模拟的大量的Delay_us空循环会消耗巨量的仿真计算资源。解决方法是把动画帧率降低菜单 Debug - Simulator Options把 Animation Update Rate 调低或者选择 Run at Real Time Confirmed 之外的 Maximum 模式。同时把主循环里的延时改成定时器分时调度不要用阻塞式 Delay。超声波测距里的轮询循环也可以改成条件计数用定时器中断记录时间戳避免模拟器去执行百万次空循环。仿真速度一快整个调试循环的体验完全不同。5.5 现象ADC 采集的值乱跳循迹误判如果你用 ADC 模式读循迹传感器而不是数字量仿真里的读数会显得比实物噪声大得多原因是 Proteus 的 ADC 模型受仿真步长和电源纹波影响明显采到的数值在十几到几十之间随机抖动。直接拿这个原始值做阈值判断误判率会非常高。解决方法是软件滤波连续采样 5 次取中值或者平均再和阈值比较。更稳一点的做法是在比较器端加迟滞阈值设成两个值从高变低和从低变高之间隔一个不敏感区间。比如识别黑线的阈值设 2.0V识别白底的阈值设 1.5V中间 0.5V 的区间不做判断这样能有效避免临界抖动。6. 验证手段与进阶虚拟示波器看 PWM、串口打印状态机信息前面把逻辑跑通了但“跑通”和“跑对”是两码事。我的习惯是每次调参都用 Proteus 自带的虚拟示波器看一下 PWM 波形而不是只看小车动不动。把虚拟示波器的通道 A 接在 PA0 上通道 B 接在 PA1 上就能同时看到左右两路 PWM 的波形和各自的占空比。常见的做法是在 Proteus 左侧工具栏选择 Virtual Instruments拖出 OSCILLOSCOPE然后接两根探针线到 PWM 输出引脚。如果波形显示高电平时间明显大于低电平时间说明占空比偏大如果频率和设置值差很多回查 PSC 和 ARR 的配置是否和时钟频率匹配。这个检查 30 秒就能完成比实物上拿电表量快得多。另一个很好用的验证工具是 Virtual Terminal。把 USART1 的 TX 引脚接到虚拟串口终端上代码里初始化串口后用 printf 输出当前状态机、传感器读数和 PWM 占空比。注意虚拟终端的波特率要和代码设置一致默认 115200、8 位数据、1 位停止位如果出现乱码先检查配置再检查引脚接线。// debug.c - printf 重定向到 USART1 int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }把调试信息打印出来以后把状态机的所有状态转换打上时间戳比对着虚拟示波器的波形看基本能定位大多数逻辑问题。我第一次做智能车仿真时因为一个 PWM 频率设置不对花了整整一个晚上在实物上调电机后来回头用虚拟示波器看波形才发现占空比根本不对那一步如果早点做至少省三个小时。从那以后我养成了个习惯仿真里验证过的参数必须在笔记里记录频率、占空比、去抖次数、转向时间全写清楚。因为实物调试时你会发现所有莫名其妙的问题最后都能在仿真参数里找到先兆。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?