蓝桥杯单片机基础之PWM(Pulse width modulation)

概念

对于PWM的概念这里就不过多赘述,大家可以直接查看一些文章对于pwm的介绍,下面给一段我认为比较好的说法:

脉冲宽度调制(PWM)是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM进行编码。–参考百度百科

这是一个什么意思呢?

为了后续理解的方便,我先给出PWM的两个重要指标:

  1. 周期(频率)
  2. 占空比.

先来看一幅图:
下图是一个周期信号,他的周期是3.70ms,其高电平所占时间为1.85ms
我们就说这个信号的周期为3.70ms,占空比( 高电平时间/总周期时间)为50%

在这里插入图片描述
在这里插入图片描述
如果高电平是5V,低电平为0V,那么这个信号,在一个周期有一半时间是5V,一半时间是0V,它的电压可以这样计算

v

=

(

5

+

0

)

50

%

=

2.5

v = (5 + 0)*50\% = 2.5伏

v=(5+0)50%=2.5

那么如果我们要产生4V的电压如何算呢?

4

=

(

5

+

0

)

m

4 = (5+0)*m

4=(5+0)m
可以解得

m

=

80

%

m= 80\%

m=80%,也就是说高电平需要占整个周期得4/5。这样说来我们可以通过占空比来调节输出电压的大小,但是周期的大小又对pwm来说意味着什么呢?

我们先看如下实验,周期为20us,占空比为10%

在这里插入图片描述

然后周期为20us,占空比为90%

在这里插入图片描述

然后是周期为4ms,占空比为10%
请添加图片描述

可以看到周期太长,会导致闪烁,这个很好理解,如何你把周期设置为1s,那么不久是在1s开灯,1s关灯嘛,所以说周期必须设置要够小才能有效果,根据我的测试,周期为1ms以下效果就还是不错的

代码思路

了解原理后,我们如何实现pwm呢?因为pwm涉及到定时,因此我们可以利用定时器来实现!
如果我们设置一个1us的定时器中断,那么每过1us,就会进入中断,我把这个定时器1us叫做最低步进
设pwm参数为一个结构体,其定义如下
其中cnt为计数值,max表示计数的最大值,highCnt表示高电平所占计数值

typedef struct pwm
{
	uint cnt;
	uint max;
	uint highCnt;
}pwm_t;
pwm_t pwm1 = {0, 20, 2};

在定时器中断这样写:

void Timer0Handle() interrupt 1
{
	//如果cnt和highCnt相等,表示高电平的时间已经到了,因此这里要灭灯(也即高电平灭)
	if(pwm1.cnt == pwm1.highCnt)
	{
		SL(LED, 0xff);
	}
	//让cnt变量在0-max之间不断运行
	if(pwm1.cnt++ < pwm1.max);
	else
	{
		//如果cnt为0表示周期开始,那么周期开始的时候是亮灯(也即低电平亮)
		SL(LED, 0);
		pwm1.cnt = 0;
	}
}

在这个代码中,我只需要修改highCnt的值就是修改占空比了,因此要想实现呼吸灯的效果,我只需要在一段时间修改占空比的值就行了,比如
在这里插入图片描述
效果如下

请添加图片描述

pwm的思考

话说其实每次考试的时候,它都会说让你pwm设定几个等级,其实这个也就是说占空比在整个周期中设定为几个值,比如你是4个等级,如果周期为100份,那么这四个等级的占空比就是[1,25,50,100].
因此呀,pwm占空比的值其实是整个周期的每一份都是可以用的。所以在保证周期足够小的基础上面,我们尽量选择更细一点,让我们有更多的周期份数可以用。
比如上面说的周期有100份,如果1份的时间是1s显然是不行的!为1us的话,100份就是100us,而这个一份的时间就是我们定时器来提供的。

比如我们可以直接实现以下效果:
从左到右亮度逐渐增加
在这里插入图片描述
其中核心代码如下

uint pwm_cnt = 0;
uint pwm_max = 105;
uint pwm_value[] = {5, 10, 15, 20, 30, 40, 50, 105};
uchar ledValue = 0x00;

void Timer0Handle() interrupt 1
{
	uchar i = 0;
	//让cnt变量在0-max之间不断运行
	if(pwm_cnt++ < pwm_max);
	else
	{
		//结束周期,ledValue全部亮起
		ledValue = 0x00;
		pwm_cnt = 0;
		//如果cnt为0表示周期开始,那么周期开始的时候是亮灯(也即低电平亮)
		//因为都是占空比的不同,但是起始的时候都是高电平,不同在于低电平来到的时间
		SL(LED, 0);
	}
	//遍历8个灯的情况
	for(i=0; i<8; i++)
	{
		if(pwm_cnt == pwm_value[i])
		{
			//如果cnt等于对应灯的占空比了,那么这个灯就应该被关闭了
			ledValue |= (1 << i);
			SL(LED, ledValue);
		}
	}
}

同时上述代码也可以改为流水灯,效果如下
(实现亮度不同的状态下,实现流水灯)
请添加图片描述
核心代码:
就是要在每次pwm初始亮灯的时候考虑到led状态的变量,而这个变量决定了到底led应该如何亮!
在这里插入图片描述

uchar maskValue = 0xff;

void Timer0Handle() interrupt 1
{

	uchar i = 0;
	//让cnt变量在0-max之间不断运行
	if(pwm_cnt++ < pwm_max);
	else
	{
		//结束周期,注意:(如果要外部控制每个LED,那么这里就不是全部亮起了!)
		ledValue = maskValue;
		//如果cnt为0表示周期开始,那么周期开始的时候是亮灯(也即低电平亮)
		//因为都是占空比的不同,但是起始的时候都是高电平,不同在于低电平来到的时间
		SL(LED, ledValue);
		pwm_cnt = 0;
	}
	//遍历8个灯的情况
	for(i=0; i<8; i++)
	{
		if(pwm_cnt == pwm_value[i])
		{
			//如果cnt等于对应灯的占空比了,那么这个灯就应该被关闭了
			ledValue |= (1 << i);
			SL(LED, ledValue);
		}
	}
	TimeRun(&delay1s);
}

void liushui()
{
	if(delay1s.ok)
	{
		static uchar i;
		delay1s.ok = 0;
		if(i++ < 8);
		else
		{
			i = 0;
		}
		maskValue = ~(1 << i);
	}
}

总结

相比经过上述的介绍,对pwm的使用,各位都应该有了一个比较清晰的认识了,我想说的是pwm是一个非常常规的知识,但是想要用好它,还是需要一定的思考的,对于蓝桥杯历年的题来说,pwm也是一个比较难的考点,大家平时还是要按照题目要求,多总结一下,多练练!

测试代码

周期为20us,占空比为10%

#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f 	

code enum{LED = 4, EXT, SEL, CODE};

typedef struct pwm
{
	uint cnt;
	uint max;
	uint highCnt;
	uchar ok;
}pwm_t;

pwm_t pwm1 = {0, 20, 2, 0};

void SL(uchar _dev, uchar _data)
{
	P0 = _data;
	SELE(_dev);
}

void Timer0Handle() interrupt 1
{
	if(pwm1.cnt == 0)
	{
		SL(LED, 0);
	}
	else if(pwm1.cnt == pwm1.highCnt)
	{
		SL(LED, 0xff);
	}
	if(pwm1.cnt++ < pwm1.max);
	else
	{
		pwm1.cnt = 0;
	}
}


void Timer0Init(void)		//1微秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
}


void main()
{
	SL(LED, 0x0);
	Timer0Init();
	EA = 1;
	while(1)
	{
		
	}
}                                                 

呼吸灯

#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f 	

code enum{LED = 4, EXT, SEL, CODE};

typedef struct pwm
{
	uint cnt;
	uint max;
	uint highCnt;
}pwm_t;

typedef struct Delay
{
	uint max;
	uint cnt;
	uchar ok;
}t_delay;

pwm_t pwm1 = {0, 20, 2};
t_delay delay5000 = {5000, 0, 0};
void SL(uchar _dev, uchar _data)
{
	P0 = _data;
	SELE(_dev);
}

void TimeRun(t_delay* _time)
{
		if(_time->cnt++ < _time->max);
		else
		{
			_time->cnt = 0;
			_time->ok = 1;
		}
}

void Timer0Handle() interrupt 1
{
	//如果cnt为0表示周期开始,那么周期开始的时候是亮灯(也即低电平亮)
	if(pwm1.cnt == 0)
	{
		SL(LED, 0);
	}
	//如果cnt和highCnt相等,表示高电平的时间已经到了,因此这里要灭灯(也即高电平灭)
	else if(pwm1.cnt == pwm1.highCnt)
	{
		SL(LED, 0xff);
	}
	//让cnt变量在0-max之间不断运行
	if(pwm1.cnt++ < pwm1.max);
	else
	{
		pwm1.cnt = 0;
	}
	TimeRun(&delay5000);
}

void Timer0Init(void)		//1微秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
}

void huxi()
{
	//每5ms让占空比加
	if(delay5000.ok)
	{
		delay5000.ok = 0;
		pwm1.highCnt = (pwm1.highCnt + 1)%pwm1.max;
	}
}

void main()
{
	SL(LED, 0x0);
	Timer0Init();
	EA = 1;
	while(1)
	{
			huxi();
	}
}                                                 

8个亮度等级

#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f 	

code enum{LED = 4, EXT, SEL, CODE};


uint pwm_cnt = 0;
uint pwm_max = 105;
uint pwm_value[] = {5, 10, 15, 20, 30, 40, 50, 105};
uchar ledValue = 0x00;

void SL(uchar _dev, uchar _data)
{
	P0 = _data;
	SELE(_dev);
}


void Timer0Handle() interrupt 1
{
	//如果cnt为0表示周期开始,那么周期开始的时候是亮灯(也即低电平亮)
	//因为都是占空比的不同,但是起始的时候都是高电平,不同在于低电平来到的时间
	uchar i = 0;
	if(pwm_cnt == 0)
	{
		SL(LED, 0);
	}
	//让cnt变量在0-max之间不断运行
	if(pwm_cnt++ < pwm_max);
	else
	{
		//结束周期,ledValue全部亮起
		ledValue = 0x00;
		pwm_cnt = 0;
	}
	//遍历8个灯的情况
	for(i=0; i<8; i++)
	{
		if(pwm_cnt == pwm_value[i])
		{
			//如果cnt等于对应灯的占空比了,那么这个灯就应该被关闭了
			ledValue |= (1 << i);
			SL(LED, ledValue);
		}
	}
	
}

void Timer0Init(void)		//1微秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
}



void main()
{
	SL(LED, 0x0);
	Timer0Init();
	EA = 1;
	while(1)
	{

	}
}                                                 

8个亮度等级流水灯

#include "STC15F2K60S2.h"
#define uchar unsigned char
#define uint unsigned int
#define SELE(x) P2 = P2 & 0x1f | x << 5; P2 = P2 & 0x1f 	

code enum{LED = 4, EXT, SEL, CODE};


uint pwm_cnt = 0;
uint pwm_max = 105;
uint pwm_value[] = {3, 20, 30, 50, 60, 90, 100, 105};
uchar ledValue = 0x00;
uchar maskValue = 0xff;

typedef struct Delay
{
	unsigned long max;
	unsigned long cnt;
	uchar ok;
}t_delay;

t_delay delay1s = {10000, 0, 0};

void SL(uchar _dev, uchar _data)
{
	P0 = _data;
	SELE(_dev);
}

void TimeRun(t_delay* _time)
{
		if(_time->cnt++ < _time->max);
		else
		{
			_time->cnt = 0;
			_time->ok = 1;
		}
}

void Timer0Handle() interrupt 1
{
	//如果cnt为0表示周期开始,那么周期开始的时候是亮灯(也即低电平亮)
	//因为都是占空比的不同,但是起始的时候都是高电平,不同在于低电平来到的时间
	uchar i = 0;
	if(pwm_cnt == 0)
	{
		SL(LED, ledValue);
	}
	//让cnt变量在0-max之间不断运行
	if(pwm_cnt++ < pwm_max);
	else
	{
		//结束周期,注意:(如果要外部控制每个LED,那么这里就不是全部亮起了!)
		ledValue = maskValue;
		pwm_cnt = 0;
	}
	//遍历8个灯的情况
	for(i=0; i<8; i++)
	{
		if(pwm_cnt == pwm_value[i])
		{
			//如果cnt等于对应灯的占空比了,那么这个灯就应该被关闭了
			ledValue |= (1 << i);
			SL(LED, ledValue);
		}
	}
	TimeRun(&delay1s);
}

void liushui()
{
	if(delay1s.ok)
	{
		static uchar i;
		delay1s.ok = 0;
		if(i++ < 8);
		else
		{
			i = 0;
		}
		maskValue = ~(1 << i);
	}
}


void Timer0Init(void)		//1微秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
}



void main()
{
	SL(LED, 0x0);
	Timer0Init();
	EA = 1;
	while(1)
	{
			liushui();
	}
}                                                 

加群

在这里插入图片描述

版权声明:本文为CSDN博主「_WILLPOWER_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37429313/article/details/122393472

生成海报
点赞 0

_WILLPOWER_

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

蓝桥杯单片机总结

蓝桥杯单片机总结 参赛历程 就在大二上学期,刚好实验室要报名蓝桥杯,实验室里面的大部分人都选择了蓝桥杯单片机,只有几个同学报了蓝桥杯嵌入式,当然我对自己是没有那么自信的,