蓝桥杯单片机组备赛指南请查看 :本专栏第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调制整合实现呼吸灯
- 定时器初始化:通过
init_timer0
函数,定时器0被设置为50微秒溢出一次,实现基础的时间计数功能。- 定时器中断服务:在
timer0_service
中断服务函数中,每50微秒pwm_50us
变量递增。当累计到100(即每5ms),pwm_5ms
变量递增,用于控制呼吸灯的亮度变化周期。- PWM调制:在同一个中断服务函数中,通过比较
pwm_50us
与pwm_duty
的值来控制LED的亮与灭。如果pwm_50us
小于pwm_duty
,则LED亮;否则,LED灭。这实现了PWM调制,pwm_duty
的值决定了LED的亮度。- 亮度和方向控制:在
ledrunning
函数中,pwm_5ms
用于控制每0.5秒改变LED的亮度方向,同时也根据pwm_direction
更新LED的位置,实现了呼吸灯的循环亮度变化和位置变换。- 按键输入:
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();
}
}