一、pwm是什么?有什么用?
pwm指的是脉冲宽度调制技术,通过对脉冲宽度的调节可以达到通信(如控制舵机)、模拟“模拟输出”(如调节灯的亮度),前者在以后再结合舵机来讲,本文侧重讲后者。
首先,我们来了解几个概念:
1、pwm频率、pwm周期
这是一个约为50hz的pwm输出波形
这个pwm的周期约为20ms
pwm频率指1秒的时间里pwm运行的次数;
pwm周期指一次完整的pwm输出所使用的时间。
2、占空比
从上往下,占空比分别为25%、50%、75%
占空比指在一个周期内接通的时间占这一周期的比例。
明白这些后,恭喜你已经基本掌握pwm的原理了!
我们知道单片机的io口只有0和1两种输出状态,只能控制led的亮与灭,如果我们想要得到下面这样的输出效果,思考一下,结合pwm我们可以怎么做?
你可能已经想到了,io口保持高电平(1)时led最亮,此时电压为5v(以5v电压工作的单片机为例),如果在里面插入低电平,输出10101010...不就相当于输出2.5v了吗?
不严谨地说,这样使用pwm确实能达到“模拟输出”的效果,但如果真的需要模拟输出,单单这样是不够的(所以前面标了引号),在此不进行细说。
二、怎样设计pwm程序?
我们先来构造这么一个框架:
1、确定一个单位时间t,每个t内固定地输出0或1;
2、过了n个t完成一个pwm周期;
3、使用程序控制一个周期内输出1的数量为m,输出0的数量为(n-m)。
有了上面的框架,设计程序就不难了:
我们可以使用定时器,每隔一定的时间进入一次中断,并记录进入中断的次数x,直到完成一次pwm周期,将x归零;
设我们所需要的pwm输出占空比为y,当xy时输出低电平。
这样,我们的程序基本就设计出来了,是不是很简单?(〃'▽'〃)
在正式编写程序前,我们还需要考虑一些小问题:
因为51单片机的运行频率不高,pwm的频率也不能设计得太高,过于频繁地进入中断也会影响程序的正常运行。
在下面的例程中,我所设置的定时器中断的间隔为0.1ms,每20ms完成一次pwm周期。
在这一小节的最后,我们整理一下思路,可以得到下面的流程图:
三、写个程序试试看!
按上面的流程图,我们就可以写一个控制led亮度的程序了:
#include #define pwm_t 200 //产生中断的时间,因为是24mhz,200即100微秒(0.1毫秒)#define led p1int pwm_count0 = 0; //进入中断的次数int pwm0 = 100; //控制pwm的占空比,下同int pwm1 = 170;int pwm2 = 188;int pwm3 = 198;sbit led0 = p1^0; //led引脚定义,下同sbit led1 = p1^1;sbit led2 = p1^2;sbit led3 = p1^3;void pwm_start() //pwm初始化函数,打开了定时器0{ ea = 1; et0 = 1; tmod = 0x09; tr0 = 1; th0 = (65536-pwm_t)/256; tl0 = (65536-pwm_t)%256;}void main(){ pwm_start(); //pwm开始运行 while(1) { if(pwm_count0 <= pwm0) //调节led0的亮度 { led0 = 1; } else { led0 = 0; } if(pwm_count0 <= pwm1) //调节led1的亮度 { led1 = 1; } else { led1 = 0; } if(pwm_count0 <= pwm2) //调节led2的亮度 { led2 = 1; } else { led2 = 0; } if(pwm_count0 <= pwm3) //调节led3的亮度 { led3 = 1; } else { led3 = 0; } }}void timer0() interrupt 1{ th0 = (65536-pwm_t)/256; tl0 = (65536-pwm_t)%256; pwm_count0++; if(pwm_count0 == 200) //完成了一个pwm周期,计数变量清零 { pwm_count0 = 0; }}把上面的程序编译后下载到开发板上:
小提示:人眼对亮度的感觉不是线性变化的,因此led0与led1虽然占空比相差较大,但肉眼感觉亮度不相上下,感兴趣的可以去研究一下。
用逻辑分析仪收集一下io口的输出信息:
黄框里的为一个pwm周期
上面的程序还有一些需要注意的地方:
1、记得加while循环,因为pwm输出是持续的,没有循环就只会进行一个周期;
2、晶振频率建议设置为24mhz,12mhz也可以,相应地定时器中断时间也要更改。
我们可以将上面的程序进一步优化,如果我们把if语句写成子函数,通过参数控制占空比,返回值控制0和1的输出,程序会简化很多:
#include #define pwm_t 200 //产生中断的时间,因为是24mhz,200即100微秒(0.1毫秒)#define led p1int pwm_count0 = 0; //进入中断的次数int pwm0 = 100; //去掉7~10行 //控制pwm的占空比,下同int pwm1 = 170;int pwm2 = 188;int pwm3 = 198;sbit led0 = p1^0; //led引脚定义,下同sbit led1 = p1^1;sbit led2 = p1^2;sbit led3 = p1^3;void pwm_start() //pwm初始化函数,打开了定时器0{ ea = 1; et0 = 1; tmod = 0x09; tr0 = 1; th0 = (65536-pwm_t)/256; tl0 = (65536-pwm_t)%256;}int pwm(int pwm_value) //控制pwm输出的子函数{ if(pwm_count0 <= pwm_value) { return 1; } else { return 0; }}void main(){ pwm_start(); //pwm开始运行 while(1) { led0 = pwm(100); //调节led0的亮度 led1 = pwm(170); //调节led1的亮度 led2 = pwm(188); //调节led2的亮度 led3 = pwm(198); //调节led3的亮度 }}void timer0() interrupt 1{ th0 = (65536-pwm_t)/256; tl0 = (65536-pwm_t)%256; pwm_count0++; if(pwm_count0 == 200) //完成了一个pwm周期,计数变量清零 { pwm_count0 = 0; }}可以看到,写成子函数后调用pwm输出方便了不少。
中国科学院院士褚君浩:ChatGPT等数字经济技术下,传感器更加重要!
最新GPS研究和使用报告(下)
医学人工智能应用研究指南将出台
具体电值大小受管内足
Linux操作系统知识讲解:走进Linux 内存使用场景
PWM是什么?有什么用?PWM能玩出什么花样?
Surface Laptop评测:非常正统的Surface!续航达8小时
揭秘华灿光电LED外延片制备方法专利
8G顶配1999!小米平板3性价赛手机
Redmi旗舰新品系统界面截图曝光 可手动对GPU进行超频
Zoom视频会议服务遭遇市场强烈质疑和批评声
鸿蒙OS背后的灵魂人物你知道是谁吗?
一款简单的感应式电子迎宾器
日本或将2023年发射全球首颗木制人造卫星
LTE终端的应用设计和测试的技术挑战
DO-178C 有助于使飞行更安全
为什么Gerber数据很重要?
MCU应用大势研读:8位MCU何以将继续“横行”?
如何使用GPU编程优化模型/代码
悲剧的连续发生,四川九寨沟地震和新疆精河县地震能让我们重视地震监测的技术嘛?