ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STC8开发板PWM实战:从呼吸灯到舵机与电机控制

STC8开发板PWM实战:从呼吸灯到舵机与电机控制 最近在玩STC8A8K64S4A12开发板核心的PWM脉冲宽度调制算是被我彻底用明白了。这块板子用的是STC8系列增强型8051内核片上集成了64KB Flash和8KB SRAM外设给得也齐PWM模块、ADC、串口、定时器都有所以拿来学PWM非常合适。写这篇东西主要想把这段时间从看手册、配寄存器、跑呼吸灯到后来接舵机、调直流电机转速的整个流程整理一下。不管你是刚买开发板的新手还是已经接触过51单片机想进一步搞电机控制和智能硬件的朋友这篇都值得对照着操作一遍。硬要说门槛会一点C语言和基本电路常识就行其余我会尽量把每一步的为什么讲清楚。1. 为什么先选STC8A8K64S4A12来学PWM1.1 这块板子做PWM学习到底强在哪很多朋友学单片机点灯、跑马灯都玩过一到PWM就开始犯怵因为PWM牵扯到频率、占空比、分辨率、死区这些概念还要跟寄存器打交道。STC8A8K64S4A12这个板子的优势是它把高级PWM能力下放到了51单片机上既有传统51容易上手的特性又能体验到类似ARM芯片上才有中心对齐、死区、紧急停止这类高级功能。芯片本身的主频可以在内部高精度IRC下跑到比较高的频率指令周期比老8051快不少所以做PWM输出时不会因为CPU太慢导致实时性拉胯。再加上板载的PWM模块支持多路独立输出配合定时器和ADC完全可以在这个平台上实现一个简易的电机驱动、舵机控制或者调光系统。我选它还有一层考虑资料和例程足够多。STC官方有完整的数据手册和示例代码网上社区也有大量讨论遇到问题基本都能查到解法。对一个正在学PWM的人来说芯片便宜、开发资料全、板子不容易烧坏这三点组合起来就是最友好的学习平台。1.2 硬件PWM和软件PWM怎么选刚开始学PWM时很多人会纠结我是不是直接用定时器中断翻转IO口就可以了这就是经典的“软件PWM”思路。软件PWM的好处是原理直观随便用一个定时器在中断服务函数里翻转引脚就能得到粗略的波形非常适合理解PWM的时序关系。但软件PWM的短板也很明显它会一直占着CPUCPU还要处理按键、显示、通信实时性很容易被破坏。而且在输出频率比较高、占空比分辨率要求细的时候中断响应延迟会让波形抖动电机转起来就会听到明显的噪音。STC8A8K64S4A12内部集成了硬件PWM模块这种方案让硬件电路自己翻转IO口CPU只需要在初始化时设置好周期、占空比然后写一次比较寄存器后面就不用管了占空比会一直稳定输出。硬件PWM还支持死区时间和故障保护这些在做大功率电机驱动时几乎是必备功能软件PWM很难做得这么可靠。所以我的建议是理论学习用软件PWM演示一下真正做产品原型和稳定控制一定用硬件PWM。1.3 学完PWM能上手哪些项目PWM这个外设属于“学一次用到老”的类型应用场景覆盖面很广。LED呼吸灯和调光是最简单的入门项目核心就是不断修改占空比。接着是直流电机调速用PWM控制MOS管或电机驱动模块的输入能实现从零到满速的无级调速。舵机控制也是典型应用比如SG90这类航模舵机给一个固定频率的PWM信号通过调节脉宽就能控制舵机转动角度。再往上走可以用在开关电源、逆变器、功率变换器这类电力电子设备里。像热词里提到的PWM死区、PWM故障保护、电机飞车防护都是工业场景下必须考虑的问题。所以别看刚开始只是在板上跑了一个呼吸灯整个知识体系顺着PWM往外扩展是非常庞大的。学透PWM之后你会发现很多电机控制项目其实就是“换一个被控对象改一下频率和占空比”而已。2. PWM核心概念先把参数吃透再动手2.1 PWM是什么频率和占空比怎么算PWM脉冲宽度调制本质上就是用一个固定频率的方波信号通过改变高电平持续的时间来传递控制信息。它输出的不是模拟电压而是高低电平切换的波形但通过外部电路的滤波作用就能等效出不同大小的平均电压。用一个生活化的例子来理解你房间里的灯如果以极快的速度不停开关开的时间占比高房间就亮开的时间占比低房间就暗。人眼因为有视觉暂留根本察觉不到灯在闪只会感觉光线亮度变了。PWM就是干这个事的那个“开的时间占比”就是占空比用百分比表示公式很简单占空比 高电平时间 / 整个周期时间 × 100%频率则是周期时间的倒数表示每秒钟重复多少个完整周期单位是Hz。例如周期是20ms那频率就是1/0.0250Hz。LED调光常用1kHz以上电机驱动常用10kHz到20kHz舵机则固定50Hz这些都是由负载特性决定的。另一个重要参数是分辨率。对16位PWM来说周期寄存器ARR可以设到0到65535占空比可以通过比较寄存器CCR在0到65535之间调节占空比分辨率就是1/65536大概0.0015%。这个精度对于大多数电机和灯光控制已经绰绰有余。如果追求更细腻的控制就要选更高位的PWM或者用软件扩展分辨率。2.2 分辨率、死区和中心对齐模式分辨率这个概念很多新手会忽略但它直接决定了你的PWM控制能做到多细腻。拿8位PWM举例占空比只能分成256档电机转速在低速区间会感觉一档一档跳不够平滑。换成16位PWM同样的占空比范围可以分成65536档控制曲线就平顺得多。STC8A8K64S4A12的高级PWM模块支持16位精度做精细调光或伺服控制是够用的。死区是另一个非常关键的概念。在半桥或全桥驱动电路里上桥臂和下桥臂的开关管不能同时导通否则电源直接短路瞬间大电流会烧管子。为了避免这个问题需要在上下桥臂切换时插入一小段“同时关闭”的时间这段保护时间就是死区。硬件PWM模块有专门的死区生成电路你只需要在寄存器里配置时间长度比如1us硬件就会自动在信号翻转的边沿插入死区软件不用任何干预。中心对齐模式和边沿对齐模式的区别主要体现在计数器的工作方式上。边沿对齐模式里计数器从0加到ARR然后直接回0PWM波形的高电平集中在周期开始或结束一侧中心对齐模式里计数器先加后减PWM波形相对于周期中心对称谐波成分更小适合电机控制。很多做电机驱动的人会特意选中心对齐模式并且配合ADC在PWM波形的固定时刻采样电流这样能避开开关噪声拿到更干净的电流反馈。2.3 不同负载下怎么选频率和占空比频率不是越高越好也不是越低越好要针对负载来选。LED调光通常选择1kHz以上避免出现人眼可见的频闪。直流电机调速一般选10kHz到20kHz这个频段已经超出了人耳听觉范围电机不会发出刺耳的啸叫如果频率太低比如100Hz电机会发出明显的嗡嗡声同时电流纹波变大发热也会增加。但频率太高也不行MOS管的开关损耗会随着频率上升而增大驱动电路发热严重所以要在噪音和损耗之间取平衡。舵机的情况比较特殊标准舵机的控制信号频率固定在50Hz也就是20ms周期。舵机不是靠占空比百分比判断角度的而是靠高电平脉宽50Hz信号下0.5ms脉宽对应0度1.5ms脉宽对应90度2.5ms脉宽对应180度。所以你会发现占空比其实只有2.5%到12.5%范围很窄但这不影响舵机工作因为舵机内部电路看的是这个绝对脉宽。还有一个很容易被忽略的点是占空比突变的问题。电机启动时如果直接给100%占空比启动电流会非常大可能把电源电压拉低甚至触发驱动电路的过流保护。正确做法是从很小的占空比开始按一定斜率逐渐升高也就是软启动。这个思想在做电机驱动、大功率LED调光时都很实用我在后面的实操部分会再讲。3. 开发板PWM输出配置和呼吸灯实测3.1 引脚复用和最小硬件连接拿到开发板先别急着写代码第一件事是查数据手册里的引脚复用表。STC8A8K64S4A12的PWM输出通道并不一定固定在哪一个引脚上很多引脚要通过配置复用寄存器来选择第二功能。比如某个PWM通道可以切换到P几的某几个引脚具体用哪个引脚由控制寄存器决定。配置步骤一般是先把目标引脚设为推挽输出模式然后设置PWM模块的复用选择把PWM输出信号引到目标引脚上。这一步漏了的话后面代码写得再好引脚上也出不来波形。硬件连接方面呼吸灯最简单一个LED串一个限流电阻接到PWM输出引脚然后共地就能跑。LED限流电阻不要省很多新手直接把LED接到引脚上结果电流过大把IO口烧了。按经验5V供电时330欧到1k欧都行根据LED颜色和亮度要求调整。需要特别说明的是开发板上的PWM输出IO口只能提供很小的驱动电流千万别直接拿去驱动电机、舵机这类大功率负载一定要通过外部驱动电路或者专用的驱动模块来中转。3.2 PWM初始化流程和寄存器操作步骤初始化PWM的完整流程可以拆成几步配置引脚功能、选择时钟源和预分频、设置自动重载值、设置比较值、选择PWM模式和输出极性、使能输出通道、最后启动计数器。时钟源的选择很关键因为PWM频率的计算基准就是它。STC8A8K64S4A12可以用内部IRC时钟也可以用外部晶振我默认用内部高精度IRC频率稳定性足够日常学习和调试。预分频的作用是把高频系统时钟降到一个合适的计数频率这样周期寄存器ARR和比较寄存器CCR的值不会太大也更容易算。以24MHz主频为例我要输出1kHz的PWM给LED调光先设置预分频系数为24那么计数时钟就是24MHz/241MHz也就是每个计数周期1us。1kHz的PWM周期是1000us所以自动重载值ARR1000-1999占空比50%对应的比较值CCR500。这套换算逻辑非常重要几乎所有的PWM配置都需要按这个思路去算。每个寄存器在官方头文件里都有对应宏名使用前先看一遍stc8a8k64s4a12.h不同版本IDE的宏名可能会有差异照着替换就行。3.3 呼吸灯完整代码和周期换算下面是我实际在开发板上跑通的呼吸灯代码功能是从最暗平滑变到最亮再从最亮平滑变到最暗循环往复。代码里关于周期和占空比的换算我都写了注释方便你对照自己的主频和引脚重新计算。#include STC8A8K64S4A12.h #define FOSC 24000000UL // 主频 #define PSC 24 // 预分频系数计数时钟 1MHz #define ARR_VALUE 999 // PWM频率 1MHz / (9991) 1kHz void PWM_Init(void) { // 1. 将PWM输出引脚设为推挽模式具体引脚看你的板子 P1M0 | 0x01; // P1.0推挽 P1M1 ~0x01; // 2. 设置PWM引脚复用具体寄存器看数据手册复用表 // 此处省略按你的板和输出通道配置即可 // 3. 配置PWM时钟分频使计数时钟为1MHz PWMA_PSCR PSC - 1; // 4. 配置周期 PWMA_ARR ARR_VALUE; // 5. 初始占空比50% PWMA_CCR1 500; // 6. PWM模式1边沿对齐 PWMA_CCMR1 0x68; // 7. 使能主输出、通道输出并启动计数器 PWMA_BKR | 0x80; PWMA_CCER1 0x01; PWMA_CR1 | 0x01; } void main(void) { unsigned int duty 0; unsigned char dir 1; PWM_Init(); while (1) { // 更新占空比 PWMA_CCR1 duty; // 每3ms改变一次占空比实现平滑过渡 Delay3ms(); if (dir) { if (duty 900) dir 0; // 上限 } else { if (--duty 10) dir 1; // 下限 } } }这里占空比我没有直接拉到0和满值因为LED在接近0或接近满占空比附近时亮度变化很不明显留一点余量可以避免灯突然灭掉或满亮造成的视觉突兀。周期选择的1kHz对LED足够平滑同时也不会因为频率太高导致芯片负担加重。3.4 用示波器检查PWM波形的正确姿势代码跑起来后最靠谱的验证方式是用示波器看引脚上的实际波形不要只用肉眼看LED亮暗。示波器是学习PWM最重要的工具哪怕是最便宜的便携式示波器都能帮你快速确认频率、占空比、上升沿和下降沿是否正常。测量时把示波器探头接到PWM输出引脚探头的地线夹子尽量夹在最近的GND上不要拉很长的线去夹远端地否则容易拾取噪声波形上会有很多毛刺。观察波形时先用示波器的自动测量功能读频率和占空比看和你的计算值是否一致。比如设置1kHz、50%占空比示波器应该显示频率约1kHz、占空比约50%偏差很小。如果发现波形上沿或下沿有严重的振铃多半是测量方式问题比如探头接地线太长如果波形本身抖动不停就要检查电源是否稳定、引脚复用配置是否正确、有没有别的外设干扰。调试PWM的过程本质就是个“设置参数-看波形-调整参数”的循环多看几次波形很多奇怪的问题都能一眼定位。4. PWM实战舵机、直流电机和灯带扩展4.1 舵机PWM控制固定20ms周期和脉宽换算呼吸灯跑通之后接舵机是下一个很自然的进阶项目。标准舵机SG90的控制频率固定是50Hz也就是20ms周期但真正决定角度的是高电平脉宽。0.5ms的高电平对应0度1.5ms对应90度2.5ms对应180度中间线性对应。在STC8A8K64S4A12上配置舵机PWM我用的参数是预分频24得到1MHz计数时钟自动重载值ARR19999这样PWM周期就是20000us也就是20ms。这时比较值CCR每增加1对应脉宽增加1us。舵机0度对应CCR50090度对应CCR1500180度对应CCR2500换算关系非常直观。接线的时候一定要注意舵机的供电问题。舵机转动时需要几百毫安甚至更高的电流直接从开发板的5V引脚取电很可能让板载稳压器过载或者电压被拉低导致单片机复位这就是很多人遇到的“舵机一动作屏幕就重启”的坑。正确做法是舵机电源接外部5V电源外部电源和开发板GND共地PWM信号线再从开发板引脚接到舵机信号线。共地是底线不共地的话PWM信号的电平没有参考基准舵机根本不会正常转动。4.2 直流电机PWM调速频率、死区和故障保护直流电机调速和舵机控制思路完全不同。舵机只需要很小的信号电流而电机工作电流大驱动电路结构也复杂。通常的做法是用驱动模块比如L298N、DRV8871这类成品模块单片机输出PWM信号给模块的IN口模块内部通过H桥电路把PWM功率放大后驱动电机。频率选择上直流电机我习惯用15kHz左右。这个频率段比人耳听觉上限高电机不会啸叫同时开关损耗还在可控范围。如果你自己搭了MOS管H桥就不得不重视死区和故障保护。死区插入时间根据开关管参数来定常见配置在几百纳秒到几微秒之间设置太短起不到保护作用设置太长会增加开关损耗。故障保护方面很多专用电机驱动模块都有一个FAULT引脚或EN引脚当检测到过流、过温时主动把PWM输出切断防止电机飞车和驱动板烧毁。STC8A8K64S4A12的高级PWM模块也支持紧急停止功能把外部故障信号接入刹车输入引脚当故障信号到来时硬件自动封锁PWM输出对应热词里说的PWM故障保护和PWM电机飞车场景。这个功能在很多低端单片机上是没有的遇到工业场景时才感受到它的价值。电机调速还有个控制细节不要直接给目标占空比然后等电机响应这样会导致转速突变产生冲击。我一般会做斜率控制也就是让占空比从一个较低的值逐步向目标值爬升同时配合ADC采集电流或编码器测速度做简单的闭环控制。这个进阶玩法在STM32和RT-Thread驱动的项目里也有类似实现理解了核心原理后换平台就是换个寄存器名的事。4.3 软件模拟16位PWM与WS2812灯带驱动如果你的项目里需要的PWM路数超过了硬件PWM模块的通道数或者需要极高分辨率可以考虑用软件模拟16位PWM。这个方案其实并不神秘核心思路是写一个定时器中断服务函数在中断里判断当前计数和比较值的关系决定引脚输出高电平还是低电平。只要定时器中断频率足够稳定就能得到16位分辨率的PWM输出代价是CPU占用率很高。软件模拟PWM时特别要注意中断服务函数里不能做太多事所有耗时操作都要挪到主循环里。中断函数越短PWM波形越稳定。如果中断里加了一个长时间延时整个PWM周期都会被拉长频率就飘了电机转速也会跟着抖动。WS2812灯带驱动的场景则更像是严格的时序控制而不是传统PWM。WS2812的数据协议对每个bit都有固定的高电平和低电平时间要求一般在微秒量级这就没法用普通的硬件PWM一个通道去控制一整串灯了。更实用的方案是用定时器模拟精确延时或者在更高级的平台上配合DMA和SPI发送数据。很多热词里提到51单片机PWM驱动WS2811我自己试下来离线模式下一颗灯珠确实可以用PWM输出模拟但多颗灯珠串接时最好还是老老实实用软件时序稳定性远高于盲目扩展PWM通道。5. 常见PWM开发问题与排查心得5.1 占空比写了没反应怎么办这是PWM调试中遇到最多的问题代码看着没问题引脚上却始终是高电平或者没有波形。我通常按三条线去查引脚复用是否配置、PWM通道输出是否使能、主输出使能是否打开。引脚复用是最容易漏的一步很多朋友以为只要操作PWM寄存器引脚就会自动变成PWM功能其实不是。你必须明确告诉单片机这个IO口要切换到PWM第二功能否则IO初始化时还是普通GPIO模式。PWM通道输出使能是另一个独立步骤通道使能之后还要看是否开启了主输出刹车使能尤其在使用高级定时器风格的PWM模块时主输出使能没打开即使通道配置正确也输出不了波形。还有一个常见原因是寄存器写入时没有按照正确字节顺序操作特别是16位寄存器的上下字节写入顺序搞反了会导致周期和占空比变成乱值。排查的时候不要只盯着代码逻辑建议用示波器先看引脚有没有任何波形再看波形频率对不对最后看占空比是否符合预期一步一步缩小问题范围。5.2 PWM输出干扰、电机飞车和发热PWM信号带载后出现干扰或者电机乱转飞车这种问题我在调试中也遇到过好几次。最常见的原因是共地问题。单片机控制电机时如果没有和电机驱动模块共地PWM信号根本没有完整的回流路径电机会出现无规则抖动甚至完全失控。电机飞车的另一个高发原因是PWM输出引脚悬空或状态未定义。单片机复位期间IO口可能处于高阻态此时外部干扰信号会影响电机驱动器让电机朝随机方向转。解决办法是给PWM引脚加下拉电阻或者在电机驱动模块的ENABLE端接上拉/下拉确保单片机还没初始化时电机处于放行或禁止的确定状态。发热问题则主要集中在驱动电路上。如果你发现MOS管发热严重先检查PWM频率是否过高再检查死区时间是否设置得过短。死区太短上下桥臂直通电流会急剧增大发热异常频率过高开关损耗上升同样会导致发热。这类问题通过示波器观察切换时的波形毛刺和直通瞬间基本能判断出来。5.3 换引脚和移植到其他开发板时的避坑很多例程默认使用P0口或P1口输出PWM但你的板子可能把这两个引脚接了别的外设这时候就需要换引脚。换引脚绝不是改一行代码的事要重新查数据手册确认新引脚是否支持PWM复用还要同时改动引脚模式配置和复用选择寄存器两处都改对才会生效。移植到其他开发板时的核心逻辑是一样的差异主要在寄存器名和引脚编号。比如把代码从STC8A8K64S4A12移到其它STM32开发板PWM模块的寄存器结构完全不同但是配置流程高度相似引脚复用、时基设置、比较值设置、输出使能、主输出使能。只要理解了每个步骤解决什么问题换个平台就只是把寄存器名和数值换一下。热词里还有提到RT-Thread驱动PWM思路也是先看驱动框架里需要配置设备树或board级初始化本质还是在配同一套时基和占空比参数。移植时最容易出错的是头文件宏定义不同厂商或不同版本的库寄存器命名风格差异很大。我习惯先把官方例程编译通过再一点点往自己的项目里搬逻辑每次只改一个地方改完就用示波器看波形变化这样能最大限度避免“一次改了太多代码出错也找不到哪行导致”的尴尬。调试PWM这段时间我个人最深的体会是不要只盯着代码要用示波器说话。很多看起来像是配置问题、寄存器问题的情况最后用示波器一看其实都是硬件连接或者电源的问题。还有一个小技巧调试初期把占空比设置在一个中间值比如30%到50%这样示波器上既能看清脉宽也能看清边沿的毛刺比从极端值开始调效率高得多。这套方法从51单片机到STC8系列再到STM32和嵌入式Linux开发板我一直沿用可以说百试不爽。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进