蓝桥杯-单片机组基础15——会呼吸的流水灯制作详解

时间:2024-04-09 19:33:48

蓝桥杯单片机组备赛指南请查看 :本专栏第1篇文章

本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。

型号:国信天长4T开发板(绿板),芯片:IAP15F2K61S2

(使用国信天长蓝板也可以完美兼容,与绿板几乎无差别)


        关于pwm调制基本原理本文不细讲,请查看本专栏基础7文章。但本文难度不影响您直接阅读

        对于呼吸灯,我本采用IO编程,2天的调试效果仍然不如意。采用MM编程瞬间BUG全无。因此比赛时本人铁了心采用MM编程。

        本文末尾将放出IO编程的程序,希望有大佬可以为我解答应如何修改!

1. 代码目的

        程序目的,实现小蜜蜂老师的呼吸灯讲解题目。感兴趣的朋友可以去小蜜蜂老师的CSDN观看。本文程序与小蜜蜂老师略有不同,希望有助于理解,写出自己的代码。

        题目要求如下:

【1】初始时:板子启动,led3,led4常亮,其他灯全部熄灭。此时按下S7并松开,启动呼吸灯。按下S6不松开数码管显示当初始信息,松开后数码管熄灭。蜂鸣器等无用外设全部关闭。

【2】呼吸灯要求:当前led由灭变亮500ms,再由亮变灭500ms。完成后向左或向右,下一个led灯开始呼吸

【3】第一次开机:按下并松开S7后,从led0开始呼吸,并默认从左向右流水移动;

【4】呼吸运行时:按下S7不松开,则保持当前led当前亮度不变;松开S7后从当前亮度继续呼吸,并向相反反向进行流水变化

【5】呼吸运行时:按下S6不松开,则保持当前led亮度不变,且数码管显示当前为几号灯(1~8),与灯光亮度数值(0~99);松开S6后,呼吸灯继续运行

【6】数码管显示格式:“        X _ _ _ _ _ 0 0        ”,X为第几号灯,最后两位为数值

2. 实现思路

        需要在500ms内由灭变亮,又经过500ms由暗变灭

        因此我考虑将500ms分成100份,每份50ms。用于表示100个周期内的不同占空比

        将50ms分成100份,每份50us。用于中断产生变量自增,当自增的变量小于占空比就亮。

更详细的说明如下:(来源于chatgpt4.0)

关键变量

  • pwm_50us:用于计数每50微秒增加1,以实现定时器的基本计时功能。
  • pwm_5ms:基于pwm_50us实现的更长时间计数,每累计到100(即每5ms),pwm_5ms会增加1,用于控制呼吸灯亮度变化的时间间隔。
  • pwm_duty:PWM占空比的值,这个值决定了LED亮和灭的时间比例,从而控制LED的亮度。
  • pwm_location:当前亮起的LED位置,用于在多个LED中循环显示呼吸效果。
  • pwm_direction:控制呼吸灯方向的标志位,用于决定LED是顺序点亮还是逆序点亮。
  • pwm_bright:LED亮度变化的方向标志位,0表示变亮,1表示变暗。
  • pwm_stop:控制呼吸灯停止的标志位,用于暂停或继续呼吸灯效果。
  • sys_stop:系统启动停止标志,用于控制整个呼吸灯系统是否运行。

PWM调制整合实现呼吸灯

  1. 定时器初始化:通过init_timer0函数,定时器0被设置为50微秒溢出一次,实现基础的时间计数功能。
  2. 定时器中断服务:在timer0_service中断服务函数中,每50微秒pwm_50us变量递增。当累计到100(即每5ms),pwm_5ms变量递增,用于控制呼吸灯的亮度变化周期。
  3. PWM调制:在同一个中断服务函数中,通过比较pwm_50uspwm_duty的值来控制LED的亮与灭。如果pwm_50us小于pwm_duty,则LED亮;否则,LED灭。这实现了PWM调制,pwm_duty的值决定了LED的亮度。
  4. 亮度和方向控制:在ledrunning函数中,pwm_5ms用于控制每0.5秒改变LED的亮度方向,同时也根据pwm_direction更新LED的位置,实现了呼吸灯的循环亮度变化和位置变换。
  5. 按键输入keyrunning函数处理外部按键输入,允许用户改变呼吸方向和系统的启停,从而增加了呼吸灯的交互性。

3. 代码参考(MM编程)

        详细注释版本,如果不喜欢太多注释,可以用python先将代码中的中文清洗掉

#include <reg52.h> // 包含8051系列单片机的寄存器定义
#include <intrins.h> // 包含内置函数定义
#include <absacc.h> // 包含绝对访问函数定义

// 定义辅助寄存器AUXR位地址和P3端口的位地址
sbit AUXR = 0x8e;
sbit S7 = P3^0; // 定义S7为P3.0引脚,用于按键输入
sbit S6 = P3^1; // 定义S6为P3.1引脚,用于按键输入

// 定义全局变量
unsigned char pwm_50us = 0;    // 定时计数器50微秒的计数值
unsigned char pwm_5ms = 0;     // 定时计数器5毫秒的计数值
unsigned char pwm_duty = 0;    // PWM占空比的计数值
unsigned char pwm_location = 0;    // LED亮灯位置的计数值
bit pwm_direction = 1;   	// 控制呼吸灯方向的标志位,0向右,1向左.因为按键函数有一个取反操作,因此初始向左,按下启动后则向右
bit pwm_bright = 0;   	    // 控制LED亮度变化的标志位,0变亮,1变暗
bit pwm_stop = 0;            // 控制呼吸灯停止的标志位,0继续,1停止
bit sys_stop = 0;			//初始时系统不启动,0不启动,1启动呼吸

// 函数声明
void SMGrunning (); // 数码管运行函数声明
void ledrunning (); // LED运行函数声明
void state_SMG_all ( unsigned char value_SMG_all );

// 数码管显示的编码,代表不同的数字和符号
unsigned char code duanma[18] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
                                 0x88,0x83,0xc6,0xc0,0x86,0x8e,0xbf,0x7f};

// 系统初始化函数
void init_sys ()
{
    XBYTE[0x8000] = 0xe7; // 初始化led的3,4亮,其他熄灭
    XBYTE[0xa000] = 0x00; // 初始化外部设备
	state_SMG_all ( 0xff );
}

// 数码管状态设置函数
void state_SMG ( unsigned char pos_SMG , unsigned char value_SMG )
{
    XBYTE[0xc000] = 0xff; // 关闭所有数码管显示,用于消影
    XBYTE[0xc000] = 0x01 << pos_SMG ; // 根据pos_SMG选择一个数码管显示
    XBYTE[0xe000] = value_SMG; // 设置选中数码管的显示值
}

// 设置所有数码管显示同一个值的函数
void state_SMG_all ( unsigned char value_SMG_all )
{
    XBYTE[0xc000] = 0xff; // 关闭所有数码管显示
    XBYTE[0xc000] = 0xff; // 选择所有数码管
    XBYTE[0xe000] = value_SMG_all; // 设置所有数码管的显示值
}

// 定时器0初始化函数,设置定时器为50微秒溢出一次
void init_timer0 (void)
{
    AUXR |= 0x80; // 定时器时钟1T模式,提高定时器的计数速度
    TMOD &= 0xF0; // 清除定时器0的模式位
    TMOD |= 0x02; // 设置定时器0为模式2(自动重装载)
    TL0 = 0xC9;   // 设置定时器初始值,与TH0一起决定定时周期
    TH0 = 0xC9;   // 设置定时器重装载值
    TF0 = 0;      // 清除定时器0的溢出标志
    TR0 = 1;      // 启动定时器0

    EA = 1;  // 全局中断使能
    ET0 = 1; // 定时器0中断使能
}

// 定时器0中断服务函数
void timer0_service () interrupt 1
{
	if ( sys_stop == 0 )//初始时系统不启动
	{
		return;    //到此程序终止,下面语句不执行
	}
	
	
    pwm_50us ++; // 50微秒计数器递增
    
    // 每5ms完成一次PWM周期的处理
    if(pwm_50us == 100)
    {   
        pwm_50us = 0; // 重置50微秒计数器
        
        if (pwm_stop == 0)
        {
            pwm_5ms++; // 5ms计数器递增
        }
    }
    
    // 根据PWM占空比控制LED亮暗
    if (pwm_50us < pwm_duty)
    {
        XBYTE[0x8000] = ~(0x01 << pwm_location); // 根据pwm_location点亮对应LED
    }
    else
    {
        XBYTE[0x8000] = 0xff; // 关闭所有LED
    }
}

// LED运行函数,控制LED的亮度和位置
void ledrunning ()
{
    // 每0.5秒改变亮度方向
    if (pwm_5ms == 100)
    {
        pwm_5ms = 0; // 重置5ms计数器
        if (pwm_bright == 1) // 如果当前是变暗阶段
        {
            // 根据呼吸方向更新LED位置
            if (pwm_direction == 0) // 向右呼吸
            {
                if (++pwm_location == 8) // 到达边界,重置位置
                {
                    pwm_location = 0;
                }
            }
            else // 向左呼吸
            {
                if (--pwm_location == 255) // 到达边界,重置位置
                {
                    pwm_location = 7;
                }   
            }               
        }

        pwm_bright = ~pwm_bright; // 改变亮度方向
    }
    
    // 根据亮度方向调整PWM占空比
    if (pwm_bright == 1)
    {
        pwm_duty = 99 - pwm_5ms;
    }
    else
    {
        pwm_duty = pwm_5ms;
    }
    
}

void Delay2ms()		//@11.0592MHz
{
	unsigned char i, j;

	_nop_();
	_nop_();
	i = 22;
	j = 128;
	do
	{
		while (--j);
	} while (--i);
}


// 按键处理函数,实现对呼吸灯的控制
void keyrunning ()
{
    // S7按键控制呼吸灯的方向
    if (S7 == 0)
    {
        Delay2ms(); // 去抖动
        if (S7 == 0)
        {
            while (S7 == 0)
			{
				pwm_stop = 1; // 暂停LED操作
			}
			sys_stop = 1;  //启动系统
            pwm_direction = ~pwm_direction; // 改变呼吸方向
            pwm_stop = 0; // 恢复LED操作
        }
    }
    
    // S6按键控制数码管的显示
    if (S6 == 0)
    {
        Delay2ms(); // 去抖动
        if (S6 == 0)
        {
            while (S6 == 0) // 等待按键释放
            {
                pwm_stop = 1; // 暂停LED操作
                SMGrunning (); // 更新数码管显示
            }
            pwm_stop = 0; // 恢复LED操作
        }
    }
}

// 数码管运行函数,显示当前LED状态
void SMGrunning ()
{
    // 分别显示LED位置和占空比
    state_SMG (0, duanma[pwm_location]); // 显示LED位置
    Delay2ms(); // 短暂延时
    state_SMG (6, duanma[pwm_duty/10]); // 显示PWM占空比的十位
    Delay2ms(); // 短暂延时
    state_SMG (7, duanma[pwm_duty%10]); // 显示PWM占空比的个位
    Delay2ms(); // 短暂延时

    state_SMG_all(0xff); // 关闭所有数码管显示
    Delay2ms(); // 短暂延时
}

// 主函数
void main ()
{
    init_sys (); // 系统初始化
    init_timer0 (); // 定时器0初始化
    while (1)
    {
        ledrunning (); // LED运行控制
        keyrunning(); // 按键处理
    }
}

4. IO代码(有bug,欢迎指出如何修改)

        我调试了两天,基本确定问题出在led显示的消隐问题没有处理好。尝试了各种歪门邪道依旧不起效果。欢迎大佬为此代码debug!

#include <reg52.h>
#include <intrins.h>

sbit AUXR = 0x8e;
sbit S7 = P3^0;
sbit S6 = P3^1;

unsigned char pwm_50us = 0;    //定时计数器50us次数值
unsigned char pwm_5ms = 0;    //定时计数器5ms次数值
unsigned char pwm_duty = 0;    //根据pwm_5ms值得出的占空比数值
unsigned char pwm_location = 0;    //亮灯的位置数值
bit pwm_direction = 0;   	//0表示向右呼吸,1表示向左呼吸
bit pwm_bright = 0;   	 //0表示逐渐变亮,1表示逐渐变暗
bit pwm_stop = 0;    //0表示继续呼吸,1表示停止呼吸
bit open_SMG = 0;    //0表示不启动数码管,1表示启动数码管,需要进行中断刷新led灯光

void SMGrunning ();
void ledrunning ();

unsigned char code duanma[18]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
											0x88,0x83,0xc6,0xc0,0x86,0x8e,0xbf,0x7f};

											
void select_HC573 ( unsigned char channal )
{
	switch ( channal )
	{
		case 4:
			P2 = ( P2 & 0x1f ) | 0x80;
		break;
		case 5:
			P2 = ( P2 & 0x1f ) | 0xa0;
		break;
		case 6:
			P2 = ( P2 & 0x1f ) | 0xc0;
		break;
		case 7:
			P2 = ( P2 & 0x1f ) | 0xe0;
		break;
		case 0:
			P2 = ( P2 & 0x1f ) | 0x00;
		break;
	}
}

void init_sys ()
{
	select_HC573 ( 5 );
	P0 = 0x00;
	select_HC573 ( 4 );
	P0 = 0xe7;
}

void state_SMG ( unsigned char pos_SMG , unsigned char value_SMG )
{
	select_HC573 ( 7 );
	P0 = 0xff;
	
	select_HC573 ( 6 );
	P0 = 0x00;
	select_HC573 ( 6 );
	P0 = 0x01 << pos_SMG;
	
	
	select_HC573 ( 7 );
	P0 = 0xff;
	select_HC573 ( 7 );
	P0 = value_SMG;
	
	select_HC573 ( 0 );
	
}

void state_led ( unsigned char pos_led , unsigned char pwm_duty )
{
	
	if ( pwm_50us < pwm_duty )
	{
		select_HC573 ( 4 );
		P0 = 0xff;
		select_HC573 ( 4 );
		P0 = ~( 0x01 << pos_led );
	}
	else
	{
		select_HC573 ( 4 );
		P0 = 0xff;
	}
	select_HC573 ( 0 );
	P0 = 0xff;

}

void init_timer0 (void)		//5微秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x02;		//设置定时器模式
	TL0 = 0xC9;		//设置定时初始值
	TH0 = 0xC9;		//设置定时重载值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0计时
	
	EA = 1;
	ET0 = 1;
}

void timer0_service () interrupt 1
{
	pwm_50us ++;
	
	//一个周期5ms内的操作
	if( pwm_50us == 100 )
	{	
		pwm_50us = 0;
		
		if ( pwm_stop == 0 )
		{
			pwm_5ms++;    
		}
	}
	
	state_led ( pwm_location , pwm_duty );
	
	
}

void ledrunning ()
{
	//定时时间达到0.5s,此时需要转变为由亮变暗
	if ( pwm_5ms == 100 )
	{
		pwm_5ms = 0;
		if ( pwm_bright == 1 )    //判定此时是否是在由亮变暗环节,计时达到0.5s且为1时,则为真
		{
			if ( pwm_direction == 0 )    //判定此时是否为向右呼吸,则location自增
			{
				if ( ++pwm_location == 8 )    //向右呼吸到达第7位,返回第0位
				{
					pwm_location = 0;
				}
			}
			else    //不是向右呼吸,则向左呼吸,则location自减
			{
				if ( --pwm_location == 255 )    //向左呼吸到达第0位,返回第7位。在unsigned char数中,0-1=255
				{
					pwm_location = 7;
				}	
			}				
		}

		pwm_bright = ~pwm_bright;
	}
	
	if ( pwm_bright == 1 )
	{
		pwm_duty = 99 - pwm_5ms;
	}
	else
	{
		pwm_duty = pwm_5ms;
	}
	
}

void Delay1ms()		//@11.0592MHz
{
	unsigned char i, j;

	_nop_();
	_nop_();
	_nop_();
	i = 11;
	j = 190;
	do
	{
		while (--j);
	} while (--i);
}

void keyrunning ()
{
	if ( S7 == 0 )
	{
		Delay1ms();
		if ( S7 == 0 )
		{
			while ( S7 == 0 )
			{
				pwm_stop = 1;
			}
			pwm_stop = 0;
			pwm_direction = ~pwm_direction;
		}
	}
	
	if ( S6 == 0 )
	{
		Delay1ms();
		if ( S6 == 0 )
		{
			while ( S6 == 0 )
			{
				pwm_stop = 1;
				SMGrunning ();
			}
			pwm_stop = 0;
		}
	}
}

void SMGrunning ()
{

	state_SMG ( 0 , duanma[pwm_location] );
	Delay1ms();
	state_SMG ( 6 , duanma[pwm_duty/10] );
	Delay1ms();
	state_SMG ( 7 , duanma[pwm_duty%10] );
	Delay1ms();

	state_SMG ( 0 , 0xff );
	state_SMG ( 6 , 0xff );
	state_SMG ( 7 , 0xff );
}

void main ()
{
	init_sys ();
	init_timer0 ();
	while ( 1 )
	{
		ledrunning ();
		keyrunning();
	}
	

}

#include <reg52.h>
#include <intrins.h>

sbit AUXR = 0x8e;
sbit S7 = P3^0;
sbit S6 = P3^1;

unsigned char pwm_50us = 0;    //定时计数器50us次数值
unsigned char pwm_5ms = 0;    //定时计数器5ms次数值
unsigned char pwm_location = 0;    //亮灯的位置数值
bit pwm_direction = 0;   	//0表示向右呼吸,1表示向左呼吸
bit pwm_bright = 0;   	 //0表示逐渐变亮,1表示逐渐变暗
bit pwm_stop = 0;    //0表示继续呼吸,1表示停止呼吸

void SMGrunning ();
void ledrunning ();

unsigned char code duanma[18]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
											0x88,0x83,0xc6,0xc0,0x86,0x8e,0xbf,0x7f};

											
void select_HC573 ( unsigned char channal )
{
	switch ( channal )
	{
		case 4:
			P2 = ( P2 & 0x1f ) | 0x80;
		break;
		case 5:
			P2 = ( P2 & 0x1f ) | 0xa0;
		break;
		case 6:
			P2 = ( P2 & 0x1f ) | 0xc0;
		break;
		case 7:
			P2 = ( P2 & 0x1f ) | 0xe0;
		break;
	}
}

void init_sys ()
{
	select_HC573 ( 5 );
	P0 = 0x00;
	select_HC573 ( 4 );
	P0 = 0xe7;
}

void state_SMG ( unsigned char pos_SMG , unsigned char value_SMG )
{
	select_HC573 ( 7 );
	P0 = 0xff;
	
	select_HC573 ( 6 );
	P0 = 0x01 << pos_SMG;
	select_HC573 ( 7 );
	P0 = value_SMG;
}

void state_SMG_all ( unsigned char value_SMG )
{
	select_HC573 ( 6 );
	P0 = 0xff;
	select_HC573 ( 7 );
	P0 = value_SMG;
}

void state_led ( unsigned char pos_led , unsigned char pwm_duty )
{
	select_HC573 ( 4 );
	if ( pwm_50us < pwm_duty )
	{
		P0 = ~( 0x01 << pos_led );
	}
	else
	{
		P0 = 0xff;
	}
}

void init_timer0 (void)		//5微秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x02;		//设置定时器模式
	TL0 = 0xC9;		//设置定时初始值
	TH0 = 0xC9;		//设置定时重载值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0计时
	
	EA = 1;
	ET0 = 1;
}

void timer0_service () interrupt 1
{
	pwm_50us ++;
}

unsigned char pwm_duty = 0;
void ledrunning ()
{
		//一个周期5ms内的操作
	if( pwm_50us == 100 )
	{	
		pwm_50us = 0;
		
		if ( pwm_stop == 0 )
		{
			pwm_5ms++;    
		}
	}
	
	//定时时间达到0.5s,此时需要转变为由亮变暗
	if ( pwm_5ms == 100 )
	{
		pwm_5ms = 0;
		if ( pwm_bright == 1 )    //判定此时是否是在由亮变暗环节,计时达到0.5s且为1时,则为真
		{
			if ( pwm_direction == 0 )    //判定此时是否为向右呼吸,则location自增
			{
				if ( ++pwm_location == 8 )    //向右呼吸到达第7位,返回第0位
				{
					pwm_location = 0;
				}
			}
			else    //不是向右呼吸,则向左呼吸,则location自减
			{
				if ( --pwm_location == 255 )    //向左呼吸到达第0位,返回第7位。在unsigned char数中,0-1=255
				{
					pwm_location = 7;
				}	
			}				
		}

		pwm_bright = ~pwm_bright;
	}
	
	if ( pwm_bright == 1 )
	{
		pwm_duty = 99 - pwm_5ms;
	}
	else
	{
		pwm_duty = pwm_5ms;
	}

	state_led ( pwm_location , pwm_duty );
	
}

void Delay100us()		//@11.0592MHz
{
	unsigned char i, j;

	_nop_();
	_nop_();
	i = 2;
	j = 15;
	do
	{
		while (--j);
	} while (--i);
}

bit open_SMG = 0;    //为0时关闭数码管,为1时打开数码管
void keyrunning ()
{
	if ( S7 == 0 )
	{
		Delay100us();
		if ( S7 == 0 )
		{
			while ( S7 == 0 )
			{
				pwm_stop = 1;
				ledrunning ();
			}
			pwm_stop = 0;
			pwm_direction = ~pwm_direction;
		}
	}
	
	if ( S6 == 0 )
	{
		Delay100us();
		if ( S6 == 0 )
		{
			while ( S6 == 0 )
			{
				pwm_stop = 1;
//				SMGrunning ();
				ledrunning ();
			}
			pwm_stop = 0;
		}
	}	
}

void SMGrunning ()
{

	state_SMG ( 0 , duanma[pwm_location] );
	Delay100us();
	state_SMG ( 6 , duanma[pwm_duty/10] );
	Delay100us();
	state_SMG ( 7 , duanma[pwm_duty%10] );
	Delay100us();

	state_SMG ( 0 , 0xff );
	state_SMG ( 6 , 0xff );
	state_SMG ( 7 , 0xff );

}

void main ()
{
	init_sys ();
	init_timer0 ();
	while ( 1 )
	{
		ledrunning ();
		keyrunning();
	}
	

}