ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STC15F104W软件PWM实现:定时器中断模拟多路可调脉冲

STC15F104W软件PWM实现:定时器中断模拟多路可调脉冲 前几天翻库存找到一颗STC15F104W。这颗芯片属于STC15系列里的小体格SOP8封装、管脚少、价格便宜PCB上占指甲盖大小的地方就能放进去。我想让它输出几路PWM脉冲做LED呼吸灯和直流电机调速用。刚开始想当然以为这芯片跟普通51差不多随便配个定时器就能出硬件PWM。等翻数据手册发现STC15F104W不带硬件PWM模块连ADC也没有唯一能用的就是T0/T1两个16位定时器和几个普通IO。也就是说所有PWM波形都得靠软件在中断里翻转IO来模拟。这也是很多用这颗芯片的人绕不开的场景没有硬件PWM片上资源又少怎么才能稳定、准确地输出PWM脉冲这篇文章就围绕这个问题展开先分析芯片资源与方案选型再讲软件PWM的参数计算与代码实现最后把实测波形和踩过的坑一起放出来。适合手里有STC15F104W、想做小体积低成本PWM输出的朋友也适合想理解“软PWM”原理的初学者。整个过程只需要一个芯片、几个电阻电容和一台逻辑分析仪没有的话示波器也行甚至LED亮度变化也能看出点门道。1. 项目需求拆解与芯片选型分析1.1 STC15F104W的资源底子要在这颗芯片上做PWM先得把家底盘清楚。STC15F104W的内核是8051但指令周期做了大幅优化大部分指令在一个系统时钟内完成所以虽然叫51跑起来比传统51快不少。内部自带高精度RC振荡器烧录时通过STC-ISP工具选择运行频率我习惯用11.0592MHz因为和串口波特率计算对上方便调试。Flash容量4KBRAM 256字节这个容量对控制类小项目完全够用但你要是想跑复杂滤波算法或者大数组就有点局促了。外设方面芯片提供了T0/T1两个定时器、一个UART串口、若干外部中断和P3口的一组IO。重点来了手册里没有PWM外设也没有PCA/CCP捕获比较单元更没有ADC。这意味着任何需要定时翻转电平的波形都得占用CPU来产生。STC15F104W之所以少这么多东西主要是为了压低成本和管脚数SOP8封装下能省则省。选这颗芯片的人通常不是冲着“功能全”来的而是冲着“体积小、能干活、还便宜”来的。开始写之前要明确一点软件PWM并不是万能的。它本质上是用CPU时间去模拟一个硬件模块能做但精度、分辨率、路数都有限制。对LED调光、低速电机调速、蜂鸣器发声这类场景完全够用但如果你要做高精度伺服控制或者WS2811那种ns级时序协议还是换带硬件PWM的型号更稳。我这次的目标就是在STC15F104W上输出1kHz到20kHz可调的PWM脉冲占空比能从0到100%调节并且至少跑通三路输出。1.2 为什么坚持用软件模拟而不是外挂PWM芯片如果说要用PWM那外接一颗555或者专用PWM控制器也能做甚至有些场景下555电路更简单。但MCU软件PWM的优势在于“可调节、可联动”占空比可以通过按键、串口、传感器数据动态改变还能根据应用场景随时换频率。555电路的RC参数决定了频率和占空比想在线调就得换电阻电容或者加数字电位器协调性差漂移还不好控制。做过一次555 PWM的朋友应该深有体会理论计算是一回事实际波形又是另一回事电容漏电、电阻温漂都会让占空比飘。MCU方案就不存在这个问题频率由晶振和定时器决定占空比由代码决定稳定性和可控性都强很多。另外对STC15F104W这种1元级芯片来说软件方案不增加任何BOM成本只需要一个退耦电容就能跑起来把“低成本小体积”的优势发挥到极致。从硬件设计角度软件PWM还省了外部RC网络和驱动电路。拿电机调速举例如果MCU直接输出不够驱动电机那就用一颗MOS管或小功率三极管做开关PWM信号从IO口直接进栅极中间不需要模拟比较器整体电路简单很多。后面5.1节我会专门讲PWM接H桥和电机这种感性负载时要注意的细节。1.3 功能拆解与验收目标把项目拆开落到功能上就是这么几个点PWM通道至少3路同时输出分别接LED和电机驱动。频率范围50Hz到20kHz软件可配置。占空比0到100%调节步进做到千分级16位定时器下能达到。调节方式串口下发指令修改占空比和频率或者按键切换。输出稳定性频率不许有长时间漂移占空比抖动尽量小不影响视觉和电机运行。验收时我用逻辑分析仪抓波形看频率误差在1%以内、占空比误差小于0.5%就判定合格。对一颗软件PWM来说这个指标不算苛刻但需要把参数算清楚、代码写对才能稳定达标。下面两节就详细讲参数和代码。2. 软件PWM原理与关键参数设计2.1 三种实现思路选哪种软件PWM在51系单片机上有很多种玩法我总结下来核心就是三种思路。第一种是“中断翻转变装载值”。PWM波形由一连串高低电平交替组成我们只需要让定时器在高电平结束时产生中断翻转IO并装载低电平所需的初值低电平结束时再翻转重新装载高电平初值。这样每个PWM周期只进两次中断中断频率低CPU压力小。而且因为初值可以精确计算到16位占空比分辨率很高。第二种是“固定时基计数器比较”。定时器以固定间隔产生中断比如每10us一次中断服务函数里让计数值加一然后拿计数值和占空比寄存器比较小于占空比就输出高否则输出低。这个方案的多路扩展性很好所有通道共享同一个时基每个通道只多一次比较和置电平操作缺点是分辨率被中断周期限制想要高分辨率就必须提高中断频率CPU占用会飙升。第三种是“主循环查询定时器标志”。不用中断在while循环里不断读取TF0标志判断时间是否到达然后翻转IO。这个方案代码最简单也不用保存现场但主循环里一旦有延时、等待等耗时代码PWM波形就会抖动得厉害只能用于对时序要求极低的场合。我这颗STC15F104W跑的是11.0592MHz、1T模式CPU算力相对老51强了很多但毕竟还是8位内核中断切换和现场保护是有开销的。所以我的选择是单路高分辨率用方案一多路输出用方案二。两种方案代码我都写了下面会分别给出核心实现思路和需要注意的参数。2.2 占空比计算公式与定时器初值不管哪种实现最核心的计算就两个公式。第一个是占空比公式占空比D T_on / T_period × 100%其中T_on是高电平持续时间T_period是PWM周期。第二个是定时器初值公式。定时器从初值开始向上计数计数溢出产生中断所以初值等于最大计数值减去我们想要计数的次数。对16位定时器初值 65536 - (定时器时钟频率 / PWM频率)。定时器时钟频率可以是系统时钟也可以是系统时钟分频后的值。拿我用的11.0592MHz举例。如果PWM频率是1kHz那么每个周期需要计数11059次定时器初值65536减11059等于54477十六进制是0xD4CD。如果PWM频率提到20kHz每个周期只需要计数553次初值64983十六进制0xFDD7。这里有个前提计数次数必须小于65536否则初值装不下。所以理论上最低PWM频率不能低于定时器时钟除以6553611.0592MHz下就是168Hz左右。想输出更低频率要么把定时器切成12T模式让时钟变慢要么在中断里做软件分频即每N次中断才翻转一次IO。占空比部分变装载方案的高电平时间和低电平时间分别对应两个独立初值。比如1kHz周期总计数11059次占空比30%那么高电平计数11059×30%约等于3318低电平计数11059减3318等于7741。初始化时先装载高电平初值65536减3318高电平结束中断后装载低电平初值65536减7741这样就能得到精确的30%占空比。这里需要注意的是所有计算最好在代码里用整数完成避免浮点运算拖慢速度。2.3 频率选择LED、电机、舵机怎么定PWM频率并不是越高越好也不是越低越好不同负载有不同讲究。对LED调光来说1kHz以上就行常用4kHz到5kHz已经足够平滑人眼完全分辨不出闪烁。频率再高也没问题但软件中断开销白白增加。对直流电机调速来说频率太低会听到明显的“滋滋”声这是因为电枢电流出现明显纹波频率太高则开关损耗增大MOS管或三极管发热变严重电机电感的滤波作用也会被弱化。业界一般推荐10kHz到20kHz既超出人耳听觉范围又不会让开关管太烫。我实测20kHz下STC15F104W依然能稳定输出但中断耗时变长主循环里再处理其他任务时需要留足余量。舵机是另一个典型场景。普通航模舵机的控制信号周期固定20ms也就是50Hz高电平脉宽1ms到2ms对应角度变化换算成占空比就是5%到10%。这种应用其实对“PWM周期”不敏感对“脉宽精确度”要求高。用软件PWM时不用纠结在50Hz分辨率低的问题上只要把高电平时间控制在微秒级精度就行1ms到2ms对应的计数范围是11059到2211816位定时器完全能覆盖。2.4 关于“16位PWM”的说法网上经常有人讨论“用8051模拟出16位PWM”其实指的就是变装载方案下高电平和低电平时长分别用16位寄存器装载理论上粒度可以达到一个时钟周期。虽然最终输出占空比精度受中断延迟影响不可能做到真正65536级稳定但在低频应用里把定时器初值算准占空比分辨率做到千分级是没问题的。用固定时基方案则只能做到“时基频率除以系统时钟”的分辨率比如10us时基就是1000级。两种方案按需取舍这也是软件PWM的灵活之处。3. 核心代码实现可调占空比与周期3.1 寄存器初始化先把T0跑起来STC15F104W的定时器控制寄存器和标准8051兼容TMOD设置工作模式TCON里的TR0启动定时器ET0和EA开中断。区别在于AUXR寄存器里的T0x12位决定定时器是1T还是12T计数。我是想要高分辨率所以选择1T模式也就是定时器计数频率等于系统时钟11.0592MHz。初始化代码如下#include reg51.h #define FOSC 11059200UL #define PWM_PIN P30 // 假设P3.0输出PWM sfr AUXR 0x8E; void timer0_init(void) { TMOD 0xF0; // 清T0模式位 TMOD | 0x01; // T0工作在16位模式 AUXR | 0x80; // T0时钟1T模式计数频率系统时钟 TR0 0; // 先关定时器 ET0 1; // 开T0中断 EA 1; // 开总中断 }这段代码要注意两点。第一TMOD寄存器高4位是T1的配置低4位是T0的配置用“先清后置”的方式避免误改另一路定时器。第二AUXR里的T0x12位是STC15系列特有寄存器如果在别的51上编译会报错需要根据芯片手册确认位地址。我习惯把AUXR的地址用sfr显式声明这样即使头文件没跟上也能编过。3.2 变装载式PWM单路高分辨率输出初始化做完PWM的核心在中断服务函数里。我这里用全局变量维护两个参数pwm_duty表示占空比范围0到100pwm_cycle_cnt表示当前周期的计数总量由PWM频率实时换算得到。每次中断根据当前输出电平决定后续动作bit pwm_level 0; unsigned int pwm_duty 50; // 占空比 0~100 unsigned int pwm_cycle_cnt; // 周期对应的计数次数 void timer0_isr(void) interrupt 1 { if (pwm_level) { PWM_PIN 0; // 高电平结束翻回低 pwm_level 0; // 装载低电平计数 TH0 (65536 - (pwm_cycle_cnt - (pwm_cycle_cnt * pwm_duty / 100))) 8; TL0 (65536 - (pwm_cycle_cnt - (pwm_cycle_cnt * pwm_duty / 100))) 0xFF; } else { PWM_PIN 1; // 低电平结束翻成高 pwm_level 1; // 装载高电平计数 TH0 (65536 - (pwm_cycle_cnt * pwm_duty / 100)) 8; TL0 (65536 - (pwm_cycle_cnt * pwm_duty / 100)) 0xFF; } }写这段代码时我犯过一个低级错误在中断里反复算乘法除法导致高电平时间和低电平时间计算耗时不短。虽然这些时间不属于PWM的高低电平计时范围因为装载完初值后定时器才重新开始计数但在高速PWM输出时中断服务函数的执行时间会叠加到翻转延迟里造成占空比边界偏移。后来我把“高电平计数”和“低电平计数”拆成两个全局变量在主循环里提前算好中断里只做装载和判断波形就干净很多。最终代码如下bit pwm_level 0; unsigned int pwm_on_cnt; // 高电平计数 unsigned int pwm_off_cnt; // 低电平计数 void timer0_isr(void) interrupt 1 { if (pwm_level) { PWM_PIN 0; pwm_level 0; TH0 (65536 - pwm_off_cnt) 8; TL0 (65536 - pwm_off_cnt) 0xFF; } else { PWM_PIN 1; pwm_level 1; TH0 (65536 - pwm_on_cnt) 8; TL0 (65536 - pwm_on_cnt) 0xFF; } }高电平计数和低电平计数的更新要放在主循环里更新前记得关中断一瞬间防止更新到一半时定时器溢出触发中断装载了半个新值半个旧值。这个操作就是常说的临界区保护单片机上一般用EA0和EA1包裹整段更新即可。还要注意启动顺序。main函数里先完成所有变量赋值配置好IO方向最后TR01启动定时器。第一次进中断时pwm_level默认是0所以先走else分支输出高电平并开始装载高电平初值时序不会乱。如果你在启动定时器之前就把PWM引脚拉低就不会有任何多余脉冲。3.3 固定时基多通道输出中断里比较计数如果想同时输出3路PWM用变装载方案就得多个定时器轮流做STC15F104W只有T0/T1两个定时器路数不够。这时固定时基方案的优势体现出来了。我常用的做法是T0产生一个10us时基中断中断里对每个通道做判断代码结构如下#define CH1 P30 #define CH2 P31 #define CH3 P32 unsigned int tick_cnt 0; unsigned char ch1_duty 50; // 0~100 unsigned char ch2_duty 30; unsigned char ch3_duty 20; void timer0_isr(void) interrupt 1 { TH0 (65536 - 111) 8; // 11.0592MHz下10us对应约111个计数 TL0 (65536 - 111) 0xFF; tick_cnt; if (tick_cnt 100) tick_cnt 0; // 100个时基1ms即1kHz if (tick_cnt ch1_duty) CH1 1; else CH1 0; if (tick_cnt ch2_duty) CH2 1; else CH2 0; if (tick_cnt ch3_duty) CH3 1; else CH3 0; }这段代码有几点值得说。第一时基中断里先重装初值再执行判断和输出保证每次中断周期一致。第二tick_cnt从0到99100个时基合成1ms也就是PWM频率1kHz占空比寄存器0到100正好对应0%到100%。第三多路通道共用一个时基三路波形的频率严格一致相位也一致这在某些应用里反而是优点。缺点刚才也提过10us时基下最高只能做到100级分辨率要提升分辨率就得把时基调小比如5us对应200级、2us对应500级但中断频率也随之上去了CPU负担直线上升。对普通LED调光来说100级已经够用对电机调速建议把时基调到5us或更小。3.4 串口调节与按键调节让占空比真正可操作代码里最终调参数我用了两种方式。第一种是串口指令上位机发字符串“PWM75\r\n”单片机在UART中断里逐字符接收收到换行后解析出数值更新占空比。这种方式的优点是调试方便配合串口助手可以快速试各种数值还能顺便打印当前频率、占空比等状态。第二种是按键调节短按一次占空比加5%长按连续加减同时用一个LED显示当前大致亮度。按键扫描要加去抖一般用10ms延时或者状态机去抖防止误触。不过要提醒一点在这颗芯片上串口和PWM共用同一套定时器中断资源时要小心。STC15F104W的串口波特率发生器通常建议用T1或独立波特率发生器如果T1被占用了就得仔细规划。我实际用的方案是串口用定时器1做波特率发生器PWM占用定时器0互不干扰。如果你也用这个组合记得TMOD初始化时同时设置T0和T1的模式避免初始化T0时把T1的模式清掉。3.5 呼吸灯优化占空比查表防突变最后做个呼吸灯效果纯粹是为了直观验证PWM是否稳定。呼吸灯的本质就是让占空比按正弦或指数规律缓慢变化人眼看到的就是亮度忽明忽暗。最简单的做法是在主循环里延时改变占空比比如从0递增到100再递减回来每次变化间隔20ms。但这样看起来像“开关灯”而不像“呼吸”因为人眼对亮度感知是近似对数的线性变化会让亮的部分停留很久、暗的部分一闪而过。更好看的做法是用指数曲线占空比等于百分比对应的指数变换值查表放在Flash里主循环每50ms移动一次表指针。代码不复杂code unsigned char breathe_table[101] { /* 预计算的指数曲线0~100 */ }; unsigned char phase 0; bit dir 0; void breathe_update(void) { if (dir 0) { phase; if (phase 100) dir 1; } else { phase--; if (phase 0) dir 0; } pwm_duty breathe_table[phase]; }查表方式比实时计算省CPU也避免浮点库被链接进工程撑大Flash。关于表的数值网上很多工具能一键生成按0到100%的指数曲线生成101个点就行。这个呼吸灯例程虽然简单但用来验证PWM输出有没有抖动非常有效如果PWM不稳呼吸过程会出现肉眼可见的阶梯抖动或频闪。4. 实测波形与踩坑记录4.1 用逻辑分析仪看波形误差从哪里来代码写好后我把P3.0接到逻辑分析仪上分别设置1kHz和10kHz频率抓波形。结果显示变装载方式的PWM波形非常干净高电平和低电平都接近理想矩形频率误差主要来自内部IRC振荡器的精度STC官标的误差在正负1%以内实测1kHz时波形周期大约1.000kHz到1.010kHz完全够用。10kHz时占空比50%的误差大约在几个微秒级别换算成百分比不到0.5%。如果把系统时钟改成22.1184MHz或者33.1776MHz中断周期更短误差还会更小一些。误差来源主要有三处。第一是IRC振荡器本身的频率误差这个无法用软件完全消除只能换外置晶振或者接受误差。第二是定时器中断响应延迟从中断请求到中断服务函数第一条语句执行8051内核需要几条指令周期不同情况下延迟还不一样这会导致占空比上下沿有小幅抖动。第三是IO翻转本身需要时间P3口在推挽模式下翻转很快但准双向模式下高电平上拉能力弱波形边沿会变缓。所以我在实测时把P3.0配成了推挽模式代码里设置P3M0和P3M1寄存器一行搞定。4.2 常见问题排查速查表大多数问题其实和代码无关出在配置和硬件连接上。我把遇到过的典型问题整理成一张表现象可能原因解决办法完全没有输出芯片没运行、电源不稳、复位引脚被外部拉低先看LED或示波器确认芯片是否运行检查VCC和GND确认RST引脚电平输出一直高或一直低IO模式配置不准、占空比变量初始值异常检查P3M0/P3M1配置确认pwm_duty范围在0到100内频率整体偏大或偏小代码里FOSC宏和实际IRC频率不一致用STC-ISP查看烧录时选择的频率改宏保持一致占空比在50%附近抖动主循环里有长时间关中断或耗时代码缩短关中断区域把重计算移出中断和临界区上电瞬间输出异常高电平P3口上电默认准双向高电平在main开头尽快初始化PWM并将IO拉低必要时加下拉电阻接电机后波形变形感性负载反电动势、电源被拉垮电机两端并续流二极管PCB上加100uF电解电容这张表是实际调试记录不是理论推演。特别是“输出一直高或一直低”这个问题我遇到过两次一次是初始化时TMOD没清T0模式位导致定时器配置错误另一次是占空比变量被不小心写成了大于100的值低电平计数计算成负数波形直接乱掉。排查这类问题建议先加一个LED心跳灯证明定时器还在跑再抓PWM输出能省很多时间。4.3 IO模式、上电状态与复位引脚的处理细节STC15F104W的IO口默认是准双向模式输出高电平时驱动能力有限大概只能提供几百微安级别的上拉电流。要驱动LED直接接没问题LED限流电阻选1k到2k就行要驱动MOS管、三极管或直接控制电机驱动芯片建议把IO配置成推挽模式输出能力更强。配置方法是设置P3M0和P3M1寄存器比如把P3.0配成推挽代码是P3M1 0xFEP3M0 | 0x01。还有两个细节非常容易忽略。第一P3.7/RST引脚可以配置成普通IO也可以保持复位功能。如果不用它做IO保持默认即可如果要用必须在STC-ISP软件里设置“复位脚用作IO”否则这个引脚始终是复位输入程序里写P371也没反应。第二STC15系列支持内部复位不需要外部复位电路但电源上电瞬间IO默认状态是高电平。如果你的PWM输出直接接的是功率管栅极上电瞬间可能在驱动侧产生一个不可控的窄脉冲轻则电机抖动一下重则驱动电路误动作。稳妥做法是在硬件上加下拉电阻同时软件里main函数第一行就配置IO并拉低PWM引脚。4.4 关于“PWM故障保护”的想法热词里看到有人问PWM故障保护我顺便说一下自己的经验。软件PWM能做到的故障保护很有限它本质是一个输出波形生成器不具备硬件比较器、刹车输入这类功能。如果项目要求电机过流时立刻封锁PWM输出建议在硬件上加采样电阻和比较器把故障信号接到外部中断引脚中断里直接关TR0、拉低输出。如果只用软件轮询电流值再关PWM响应速度大概率不够电机的惯性会把危害扩大。一句话总结PWM输出可以软做故障保护必须硬做。5. 扩展应用与进阶思路5.1 H桥PWM驱动电机死区与平均电压有了PWM脉冲最常用的扩展就是驱动直流电机。控制逻辑分两路一路用PWM调速度另一路用方向引脚换极性。如果是H桥驱动同一个桥臂上上下两个MOS管绝对不能同时导通否则电源直接短路。即使你在代码里保证“先关一个再开另一个”由于MOS管开关延迟仍然可能出现短暂直通这个短暂直通就是“死区”要解决的问题。硬件死区一般都由驱动芯片内部处理比如常见电机驱动芯片会有DT引脚设置死区时间。如果驱动电路没有死区功能软件上加死区就比较麻烦在切换方向时插入一段延时让所有管子先关断等几百纳秒到几微秒再打开目标管子。对STC15F104W这种软件方案处理方向切换时的死区更简单不是高频反复切换方向而是一次切换后保持住软件PWM只负责调速脚方向脚只做低频率切换这样死区压力非常小。平均电压是PWM调速的数学本质。假设电源电压是12V占空比50%那么电机两端平均电压约6V转速大概也是一半。公式上就是U_avg等于D乘以U_supply。频率选择对电机的影响前面提过再补充一点低频PWM会让电机电流纹波大绕组发热加重高频PWM纹波小但开关损耗高小电机在20kHz下开关损耗往往可以忽略所以我的推荐直接是10kHz到20kHz。顺手再提一句用软件PWM驱动电机时单片机、逻辑电源和电机功率电源最好共地并且功率地要短粗否则PWM大电流会在地线上产生噪声把MCU复位或干扰串口。5.2 舵机控制别被“PWM周期”绕晕舵机控制很多人会误解成“20ms周期固定占空比按角度变化”其实更本质的是“高电平脉宽对应角度”。20ms是一个总周期约束但你看舵机协议会发现高电平1ms到2ms决定了角度周期只要大于等于约2.5ms就能正常工作。所以用STC15F104W控制舵机时我建议直接输出50Hz周期然后精确控制高电平时长0度对应0.5ms90度对应1.5ms180度对应2.5ms。放在1T定时器11.0592MHz下计算0.5ms对应5530个计数2.5ms对应27648个计数用16位初值完全能装下。这个场景用变装载方案比较合适高电平结束中断后翻转低电平低电平时间固定约17.5ms绝大部分时间CPU是空闲的。舵机对PWM频率不敏感但对脉宽稳定性比较敏感因为1ms和1.02ms的脉宽意味着好几度的角度差。用软件PWM时脉宽精度主要取决于定时器初值和IRC精度实测下来稳定度足够驱动普通金属舵机不需要额外优化。如果你发现舵机抖动优先检查舵机供电是不是被电机电流拉低了而不是怀疑PWM波形。5.3 555 PWM电路、硬件PWM芯片和MCU软件PWM怎么选聊到PWM热词里出现比较多的还有“555 PWM电路”。555做PWM的优点是电路简单、完全模拟、无程序跑飞风险适合手边没有MCU或者不想写代码的场合。缺点是参数离散性大RC电阻电容精度直接影响频率和占空比温度变化也会让占空比飘移想精确调节就得用可调电阻人工校准做成产品很难保证一致性。MCU软件PWM则完全相反参数由代码和时钟决定批次一致性非常好改功能只需要改代码不需要动硬件。那什么时候该换带硬件PWM的芯片我个人的标准是如果PWM频率超过20kHz、占空比精度要求超过0.5%、或者占空比需要在每个PWM周期内实时更新比如电机FOC控制软件方案就开始吃力了直接换STC15W系列或者其他带硬件PWM外设的MCU更省心。像RK3588这类高性能平台的PWM风扇调试逻辑虽然原理一样但人家有独立的PWM控制器配置寄存器就能输出和单片机软件模拟完全是两个量级的复杂度。软件模拟适合学习和低成本控制硬件PWM适合高性能场景懂了这个界限选型就不纠结了。5.4 换一种玩法用T0/T1外部计数功能测量PWM信号最后分享一个反向玩法既然芯片没有硬件PWM输出但它有两个定时器除了输出波形我们还可以把定时器配置成计数器模式用来测量外部输入的PWM信号。P3.4和P3.5分别对应T0和T1的计数输入引脚把外部PWM信号接到这两个脚上设置定时器为16位计数模式再配合一个定时器做时间基准就能测出输入信号的周期和占空比。这个用法相当于把外部PWM频率计和占空比计功能做进去了在很多传感器解码场景里很有用比如遥控器接收机输出PWM信号或者摄像头同步信号测量。虽然STC15F104W没有捕获单元但用“外部计数加软件查询”的方式也能凑合完成测量只是精度受软件轮询频率限制适合10kHz以下的信号。我个人在实际操作中的最大体会是软件PWM不是“没有硬件PWM时的无奈凑合”只要你把参数算清楚、把中断服务函数写精简它能覆盖绝大多数对实时性要求不高的控制场景。STC15F104W这颗小芯片虽然寒碜但它的1T内核和16位定时器给软件留了足够折腾的空间也正是因为硬件资源有限整个过程反而逼着我去把每一个寄存器、每一次中断延迟都抠明白。最后再分享一个小技巧调试软件PWM时一定要先把频率宏和IRC烧录频率核对一致再动代码。我因为改错频率宏花过整整一个晚上这个教训绝对值得记下来。希望这篇文章能让你少踩几个坑。
返回列表