ReRain
认证:普通会员
所在专题目录 查看专题
【蓝桥杯单片机】五、DS18B20温度传感器
【蓝桥杯单片机】六、关于定时器(+PWM波)
【蓝桥杯单片机】七、长按键(完整代码加注释)
【蓝桥杯单片机】八、第八届省赛之电子钟
【蓝桥杯单片机】九、第九届省赛之彩灯控制器
【蓝桥杯单片机】十、第九届蓝桥杯国赛之“多功能测量仪表”
作者动态 更多
【蓝桥杯单片机】十二、底层练习
2021-04-20 17:46
【蓝桥杯单片机】十一、第十届蓝桥杯省赛失败总结加试题分析
2021-04-11 20:50
【蓝桥杯单片机】十、第九届蓝桥杯国赛之“多功能测量仪表”
2021-04-01 16:36
【蓝桥杯单片机】九、第九届省赛之彩灯控制器
2021-03-30 18:19
【蓝桥杯单片机】八、第八届省赛之电子钟
2021-03-29 18:41

【蓝桥杯单片机】九、第九届省赛之彩灯控制器

代码参考:<我的GitHub>第九届省赛之彩灯模拟器


题目参看:

1、基本功能描述

  • 通过单片机控制 8 个 LED 指示灯按照特定的顺序(工作模式)亮灭;
  • 指示灯的流转间隔可通过按键调整,亮度可由电位器 RB2 进行控制;
  • 各工作模式的流转间隔时间需在 E2PROM 中保存,并可在硬件重新上电后,自动载入。

2、设计说明

  • 关闭蜂鸣器、继电器等与本试题程序设计无关的外设资源;
  • 设备上电后默认数码管、LED 指示灯均为熄灭状态;
  • 流转间隔可调整范围为 400ms-1200ms;
  • 设备固定按照模式 1、模式 2、模式 3、模式 4 的次序循环往复运行。

3、LED 指示灯工作模式

  • 模式 1:按照 L1、L2…L8 的顺序,从左到右单循环点亮。
  • 模式 2:按照 L8、L7…L1 的顺序,从右到左单循环点亮。
  • 模式 3:

  • 模式 4:

4、亮度等级控制 检测电位器RB2的输出电压,控制8个LED指示灯的亮度,要求在0V-5V的可调区间内,实现 4 个均匀分布的 LED 指示灯亮度等级

5、按键功能

  • 按键 S7 定义为“启动/停止”按键,按下后启动或停止 LED 的流转。
  • 按键 S6 定义为“设置”按键,按键按下后数码管进入“流转间隔” 设置界面,如下图所示:

   通过按键 S6 可切换选择“运行模式”和“流转间隔”两个显示单元,当前被选择的显示单元以 0.8 秒为间隔亮灭 。

  • 按键 S5 定义为“加”按键,在设置界面下,按下该键,若当前选择的是运行模式,则运行模式编号加 1,若当前选择的是流转间隔,则流转间隔增加 100ms。
  • 按键 S4 定义为“减”按键,在设置界面下,按下该键,若当前选择的是运行模式,则运行模式编号减 1,若当前选择的是流转间隔,则流转间隔减少 100ms。
  • 按键功能说明: a) 按键 S4、S5 的“加”、“减”功能只在“设置状态”下有效,数值的调整应注意边界属性。 b)在非“设置状态”下,按下 S4 按键可显示指示灯当前的亮度等级,4 个亮度等级从暗到亮,依次用数字 1、2、3、4 表示;松开S4 按键,数码管显示关闭,亮度等级的显示格式如下图所示:


分析题目:

  1. 用到哪几个底层? 数码管按键E2PROM(I2C)AD
  2. 特殊功能:Led四个闪烁模式(流水灯的思想),亮度等级控制(这个应该是比较难的地方),按键功能和数码管闪烁功能和第八届的类似;
  3. 注意明确E2PRPOM的功能,理解原理。

功能实现: 

一. 流水灯的四个工作模式

注意看清题目要求:设备固定按照模式 1、模式 2、模式 3、模式 4 的次序循环往复运行(emm,我刚开始就理解错了题意),模式1和模式2类似,模式3和模式4类似,那这里就只举模式1和模式3的例子吧:

void LedMode1()
{
	static u8 shift1 = 0x01;//L1-L8
	P2 = (P2 & 0x1F) | 0x80;
	P0 = ~shift1;
	PWMSTA = ~shift1;//不要直接操作P0口!!!!
	
	shift1 <<= 1;
	
	if(shift1 == 0x00)
	{
		runmode = 2;
		shift1 = 0x01;
	}
	
	P2 &= 0x1F;
}

void LedMode3()
{
	static u8 index = 1;
	P2 = (P2 & 0x1F) | 0x80;
	if(index == 1)
	{
		P0 = 0x7E;
		PWMSTA = 0x7E;
		index+=1;
	}
	else if(index == 2)
	{
		P0 = 0xBD;
		PWMSTA = 0xBD;
		index += 1;
	}
	else if(index == 3)
	{
		P0 = 0xDB;
		PWMSTA = 0xDB;
		index += 1;

	}
	else if(index == 4)
	{
		P0 = 0xE7;
		PWMSTA = 0xE7;
		index = 1;
		runmode = 4;
	}
	P2 &= 0x1F;
}

做几点说明

  1. 关于shift这个中间变量:为什么要定义一个中间变量呢?我们知道51上的LED是低电平点亮的,要实现流水灯的效果,我们需要让8个LED循环点亮,这就需要通过移位来实现,但是移0肯定是不行的,(以右移为例)移位后高位是补0的,那么我们实现的效果最后就是全亮的状态,所以(移1设置一个中间变量,P0口的状态只要再取反就可以了;
  2. 关于PWMSTA这个变量:是用来存储P0口的状态的,调LED亮度的时候会用到,是一个全局变量,LED模式这部分比较简单,主要还是在后面。 二. 按键功能: 关于“启动/停止”功能,这个功能很好实现,只要设置一个标志量,然后在按键按下的时候改变标志量的值就可以了;
u8 flagstart = 0;//0-停止LED流转,1-开始LED流转

流转间隔设置,以及数码管闪烁,这个功能也比较好实现,数码管闪烁的思想和第八届的电子钟是一样的思想,需要注意的一点是:看清题目要求,题目中说 “各工作模式的流转间隔时间需在 E2PROM 中保存,并可在硬件重新上电后,自动载入”,说明是4个不同的模式分别对应四个不同的流转间隔,那么我们需要定义一个数组,用来存储4个模式下的流转间隔,并实现分别设置时间,看代码:

u16 runtime[4] = {400, 400, 400, 400};//四个模式下的闪烁间隔时间(注意类型错误)

void KeyAction(u8 keycode)
{
	u8 buff[4];//定义一个数组用来存储缩小10倍后的流转间隔(因为E2PROM里最多写入u8类型的数据)
	u8 i;
	if(keycode == '1')
	{
		if(flagstart == 0)
		{
			flagstart = 1;
		}
		else if(flagstart == 1)
		{
			flagstart = 0;
		}
	}
	else if(keycode  == '2')
	{
		if(flagset == 0)
		{
			flagset = 1;
			SetMode = 0;
		}
		else if(flagset == 1)
		{
			SetMode += 1;
			if(SetMode > 1)
			{
				flagset = 0;
				runmode = 1;
			}
		}
	}
	else if(keycode  == '3')
	{
		AddTime();
		for(i = 0; i<4; i++)
		{
			buff[i] = runtime[i] / 10;
		}
		E2Write(buff, 0x00, 4);//将数组中的值写入E2PROM
		//(注意这个写法),写入地址从0x00开始,因为是4个字节的数据,所以字节数写4
	}
	else if(keycode  == '4')
	{
		SubTime();
		for(i = 0; i<4; i++)
		{
			buff[i] = runtime[i] / 10;
		}
		E2Write(buff, 0x00, 4);
	}
}

三. 加减时间功能:(以加为例,减法类似)

u8 runmode = 1;//运行模式编号
u16 runtime[4] = {400, 400, 400, 400};//四个模式下的闪烁间隔时间
u8 SetMode = 0;//0-闪烁LED的模式,1-闪烁流转间隔

void AddTime()
{
	u8 i;
	if(SetMode == 0)
	{
		runmode+=1;
		if(runmode >4)
		{
			runmode = 1;
		}
	}
	else 
	{
		for(i = 0; i<4; i++)
		{
			if(runmode == i+1)
			{
				runtime[i] += 100;
				if(runtime[i] > 1200)
				{
					runtime[i] = 400;
				}
			}
		}
	}
}

模式1对应runtime[0],模式2对应runtime[1],模式3对应runtime[2],模式4对应runtime[3] 

四. 设置模式(这里我写的不是很简便,有很多重复部分,读者可以自己优化一下)

numBlinkSta这个标志量是在中断里每隔0.8秒改变一次状态的,这样就实现了数码管每隔0.8秒亮灭了,第八届的题里我有详细写;

u8 numBlinkSta = 1;//0-数码管灭,1-数码管亮

void SetTime()
{
	LedBuff[4] = 0xFF;//关闭第5个数码管
	if(SetMode == 0)//设置LED的模式
	{
		if(numBlinkSta)
		{
			LedBuff[7] = 0xBF;//第8个数码管显示"-"
			LedBuff[5] = 0xBF;//第6个数码管显示"-"
			LedBuff[6] = LedChar[runmode];//第7个数码管显示运行模式
		}
		else
		{
			LedBuff[7] = 0xFF;
			LedBuff[5] = 0xFF;
			LedBuff[6] = 0xFF;
		}
		LedBuff[0] = LedChar[runtime[runmode-1] % 10];
		LedBuff[1] = LedChar[(runtime[runmode-1]/10) % 10];
		LedBuff[2] = LedChar[(runtime[runmode-1]/100) % 10];
		if(runtime[runmode-1] < 1000)
		{
			LedBuff[3] = 0xFF;
		}
		else
		{
			LedBuff[3] = LedChar[(runtime[runmode-1]/1000) % 10];
		}
	}
	else if(SetMode == 1)//设置流转间隔
	{
		if(numBlinkSta)
		{
			LedBuff[0] = LedChar[runtime[runmode-1] % 10];
			LedBuff[1] = LedChar[(runtime[runmode-1]/10) % 10];
			LedBuff[2] = LedChar[(runtime[runmode-1]/100) % 10];
			if(runtime[runmode-1] < 1000)
			{
				LedBuff[3] = 0xFF;
			}
			else
			{
				LedBuff[3] = LedChar[(runtime[runmode-1]/1000) % 10];
			}
		}
		else
		{
			LedBuff[0] = 0xFF;
			LedBuff[1] = 0xFF;
			LedBuff[2] = 0xFF;
			LedBuff[3] = 0xFF;
		}
		LedBuff[7] = 0xBF;
		LedBuff[5] = 0xBF;
		LedBuff[6] = LedChar[runmode];
	}
}

五. 重头戏!!!关于调整亮度!!!

用到的也是PWM波的思想,先明确一点:我们是如何实现亮度调节的呢?说白了,就是在极短的一段时间里让小灯灭一段时间,然后再亮一段时间,这样在视觉上看起来灯的亮度就是发生变化的,看代码,有详细注释:

u8 brightness = 0;//亮度等级

void ConfigTimer1()//软件生成的10us定时器(每10us进一次中断)
{
	AUXR &= 0xBF;		//定时器时钟12T模式
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	TL1 = 0xF7;		//设置定时初值
	TH1 = 0xFF;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	ET1 = 1;//注意这里软件生成的函数里是没有这条语句的,要自己加上!!!!
}

void InterruptTimer1()interrupt 3
{
	static u16 PWMCnt = 0;//存储进入中断的次数
	
	TL1 = 0xF7;		//设置定时初值
	TH1 = 0xFF;		//设置定时初值
	PWMCnt++;
	PWMCnt %= 101;//666的操作,实现满100清零,设置总时间为1ms,即PWM波的频率为1k(在1ms的时间里让小灯灭hightime的时间,其余时间是亮的状态)
	              //这里就体现了之前的PWM波的思想了,只不过之前的那个是更高级的操作,就是频率可调
	              //这里我们是直接限定了频率	
	P2 = (P2 & 0x1F) | 0x80;
	if(PWMCnt >= hightime)//如果进入中断扫描的时间大于等于高电平持续的时间
	{
		P0 = PWMSTA;//点亮对应状态下的led
	}
	else
	{
		P0 = 0xFF;//否则关闭所有的小灯
	}

	P2 &= 0x1F;
}

u8 AdjustDC()//判断高电平的时间,实现调节rb2,灯的亮度发生改变(高电平时间需要在主函数里不断获取,每隔200ms获取一次即可)
{
	u16 val;

	val = GetADCValue(3);
	if(val<60)
	{
		hightime = 90;
		brightness = 1;
	}
	else if(val <130)//这里必须大于等于130(否则不会显示这个等级的亮度)
	{
		hightime = 70;
		brightness = 2;
	}
	else if(val <195)//这里必须大于190,emm这个数据是后期调的时候发现的,不太懂为啥
	{
		hightime = 30;
		brightness = 3;
	}
	else 
	{
		hightime = 5;
		brightness = 4;
	}
	return hightime;
}

emmm,读者可以好好看看注释,还有一点我觉得特别重要的地方!!!就是什么时候读取E2PROM的问题:读取E2PROM里面的数据必须要放在总中断打开之前,主函数里读取一次就可以了,为什么呢?因为我们是一次读取四个字节的数据的,而我们的定时器1是一个10us定时器,时间太快了,会影响到E2PROM读取数据,比如说我们正在读第一个字节的数据,这时候中断来了,我们需要进入中断,那么就会扰乱E2PROM读取数据;

另外还要注意!!!刚下载程序的时候,要先向E2PROM里面写入数据,不能先读取,也就是说下第一遍程序的时候要先把E2Read这个函数屏蔽,看一下代码:

u8 runtimebuff[4];
	
	E2Read(runtimebuff, 0x00, 4);//一个字节是1个u8,E2Prom的地址范围是0x00到0xFF(数组的名字就是地址,不需要再取地址符)
	for(i = 0;i <4; i++)
	{
	    runtime[i] = runtimebuff[i] * 10;
	}
	 
	EA = 1;
	CloseOther();
	ConfigTimer0(2);
	ConfigTimer1();

六. 按下S4显示亮度等级

 这个功能和第八届省赛试题里,按下按键显示温度差不多,我们在AdjustDC这个函数里面改变的亮度等级,只要在按下S4的时候显示出来就可以了,需要注意的是一直按着S4才会显示,一旦松开就不显示了,所以我们需要一直扫描S4的状态,那么我们就在KeyScan这个按键扫描函数里处理不就可以了吗,看代码:

void KeyScan()
{
	static u8 keybuff[4] = {0xFF, 0xFF, 0xFF, 0xFF};
	u8 i;
	
	keybuff[0] = (keybuff[0] << 1) | KEY_IN_1;
	keybuff[1] = (keybuff[1] << 1) | KEY_IN_2;
	keybuff[2] = (keybuff[2] << 1) | KEY_IN_3;
	keybuff[3] = (keybuff[3] << 1) | KEY_IN_4;
	
	for(i = 0; i<4; i++)
	{
		if(keybuff[i] == 0x00)
		{
			KeySta[i] = 0;
			if(keybuff[3] == 0x00)
			{
				if(flagset == 0)
				{
					ShowNumber(brightness);//注意shownumber函数调用时会自动关闭除显示数字之外的数码管,所以要把他放前面
				     LedBuff[1] = 0xBF;
				}
			}
		}
		else if(keybuff[i] == 0xFF)
		{
			KeySta[i] = 1;
		}
	}
}

具体功能就是这样实现的,注意一下细节,打好底层,基本上就可以写出来了,我会再写一遍程序,遇到问题的话还会再更新~


第一遍遇到的问题: 

1. nice的操作:到8归零,两种方法:index &= 7(是2的整数次方才能用这个方法)或者!!!!!!index %= 8;(通用!!!!!)

2. 每调试一个地方就要下载验证一下,再次强调一下E2PROM的操作:E2Read(runtimebuff, 0x00, 4);//一个字节是1个u8,E2Prom的地址范围是0x00到0xFF(数组的名字就是地址,不需要再取地址符)

3. 要先往E2PROM里面写入你想写入的数据,然后再读,否则你从E2PROM里面读到的是E2PROM里原有的值!!!!!

4. 另外!!!要特别注意数据溢出的问题!!!!!

5. 关于软件生成的10us定时器,有几点需要注意的问题:

6. 进入中断时,不要忘记设置定时初值!!!!!!!


第二遍遇到的问题:

  1. 尽量不要使用ShowNumber这个函数,直接操作LedBuff[]比较好 ,我第二遍做的时候想把SetTime这个函数写得简单一点,然后就用到了ShowNumber这个函数,但是出现了问题,一个是数码管显示不稳定,还有一个就是亮度显示部分也出现了问题;
  2. 对寄存器执行操作时,哪里用到就在哪里使用,用完就关闭:

  3. 还有就是 if  else if 的问题

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 74
收藏 74
关注 302
成为作者 赚取收益
全部留言
0/200
  • dy-SgCRSAWd 2021-04-29 21:22
    感谢分享
    回复
  • dy-lZg1ikQS 2021-04-29 21:03
    不亚于看了一篇高质量论文
    回复
  • dy-6jaMNnKj 2021-04-29 11:04
    大开眼界,真是好文
    回复
  • dy-uP6R9bIG 2021-04-28 22:21
    讲的真好!
    回复
  • dy-faUxdNVf 2021-04-28 22:16
    佩服楼主
    回复
  • dy-ARsdJtu2 2021-04-28 16:41
    什么时候更新
    回复
  • dy-PfBg9fHc 2021-04-28 15:16
    精彩,很多东西还没接触到
    回复
  • dy-BntE74dS 2021-04-27 11:36
    思路清晰,受益匪浅
    回复
  • dy-AstN3YsZ 2021-04-27 11:24
    请教一下
    回复
  • dy-kWQSvfcY 2021-04-26 11:27
    什么时候更新
    回复
  • dy-IRhxrrTG 2021-04-26 11:05
    围观学习
    回复
  • dy-EWZRbIzj 2021-04-22 11:26
    请教一下
    回复
  • dy-HlVFyepq 2021-04-21 20:49
    精彩,很多东西还没接触到
    回复
  • dy-fEyVNAbF 2021-04-21 15:38
    加油
    回复
  • dy-cfgdwamL 2021-04-21 12:58
    不亚于看了一篇高质量论文
    回复
  • dy-ayNTwT3L 2021-04-21 12:24
    大开眼界,真是好文
    回复
  • dy-LiDZV1Qr 2021-04-20 18:09
    感谢分享
    回复
  • dy-rfrUF2fp 2021-04-20 17:02
    期待继续
    回复
  • dy-bHww98js 2021-04-19 23:01
    思路清晰,受益匪浅
    回复
  • dy-FPtpScGU 2021-04-19 09:06
    请教一下
    回复