6

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

 2 years ago
source link: https://blog.csdn.net/qq_37429313/article/details/122393472
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

专栏收录该内容
29 篇文章 5 订阅

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

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

这是一个什么意思呢?

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

  1. 周期(频率)

先来看一幅图:
下图是一个周期信号,他的周期是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();
	}
}                                                 

在这里插入图片描述


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK