目录
任务要求:
1、按键 1、2、3、4 按下,使 8 个 LED 实现下面对应的模式 1、 2、 3、4,上电默认每种模式流水灯的流转时间间隔为 500ms。
1)模式1:按照L1、L2……L8的顺序,从左到右循环点亮。
2)模式2:按照L8、L7……L1的顺序,从右刀座循环点亮。
3)模式3:从两边向中间点亮( (L1,L8)->(L2,L7)->(L3,L6)->(L4,L5) )
4)模式4:从中间向两边点亮( (L4,L5)->(L3,L6)->(L2,L7)->(L1,L8) )
2、按键5按下流水灯的流转时间间隔增加100ms,超过1200ms从400ms开始,用定时器控制时间
3、代码简洁,注释简单易懂。
实现思路:
按键部分
定时器部分
LED处理部分
函数部分
程序源码
程序大体框架如下图:
还没学过Visio画图,第一次尝试,轻喷~
我们先把按键放在定时器里刷新,识别到几号按键按下,就对应LED灯按照第几个模式点亮,按键1按下就是模式1,按键2按下就是模式2……模式1234转换可以用一个全局变量实现,我代码中本变量名称为Light_Mode
主函数要循环判断Light_Mode变量的数值,所以写一个无参数无返回值的LED处理的函数,先根据按键返回值,给Light_Mode变量赋相应的状态值,再根据这个状态值,实现四种不同模式的点灯。
定时时间10ms,因为51单片机定时时间上限大约是70ms,按键消抖的部分大概是10ms,所以按键在定时器中以10ms时间扫描一次它的状态,每10ms扫描一次它的状态,就会滤除硬件抖动的部分,LED要求的500ms定时,可以在这个10ms基础上累加计数,就是每10ms计数变量自加1,计数变量==50的时候,就是500ms了
要求2 要改变LED闪烁时间间隔 ,那就再定义一个全局变量 unsigned int SpaceT = 500;
名称SpaceT,初值500ms
按键5 按一下SpaceT变量自加100,超过1200,给SpaceT赋值400
定时器0初始化代码
void Timer0_Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
定时器0的中断
void Timer0_Routine() interrupt 1 //定时器0的中断函数
{
static unsigned char Button; //按键扫描时间变量 10ms
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
Button++;
T0Count++;
if(T0Count >= SpaceT){
i++;
T0Count = 0; //软件复位
}
if(Button >= 10){
MatrixKey_Loop();
Button = 0;
}
}
上图是普中科技的51开发板 原理图上说明P2端口对应了8个发光二极管,从P20~P27
如果想点亮LED1(图示D1),就写P2_0 = 0;
或者对P2整个端口赋值,P2 = 0xFE; 十六进制的0xFE转化二进制后为1111 1110,只有第一个LED点亮。
如果想先点亮第二个LED,那就P2 = 0xFD; 0xFD代表1111 1101,只有第二个灯点亮
如果想实现流水灯的效果,那就给P2端口依次赋值如下八个数
1111 1110
1111 1101
1111 1011
1111 0111
1110 1111
1101 1111
1011 1111
0111 1111
不难发现,只有0向前移位,一次移动移位,由此可以想到位运算中的移位运算符
参考书 C primer plus
书上指出,左移运算符,高位舍弃,低位补0(补0的那些位LED就会亮起),所以不符合我们的要求
由于高位舍弃,低位补0,如果我们找到和上面八个数相反的数,再给它取反,就得到了想要的效果
留点空余时间思考一下
……
我们可以对0x01 (0000 0001)移位,左移
左移1次 0000 0010 (高位舍弃,低位补0), 取反 1111 1101
左移2次 0000 0100 取反 1111 1011
……
变量 i 也要定义为全局变量,因为 i 要在中断函数里改变
for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔
{
P2 = ~(0x01<
模式2流水
for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔
{
P2 = ~(0x80>>i);
}
模式3
for(i=0;i<4;)
{
P2 = ~((0x01<>i));//两边向中间 亮灯
}
模式4
for(i=0;i<4;)
{
P2 = ~((0x10<>i));
}
本题要求简单,所以我就建了两个模块化的代码
Timer0.c
#include
#include "Timer0.h"
//定时器0初始化模板 1毫秒@11.0592MHz
void Timer0_Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void); //1毫秒@11.0592MHz
#endif
MatrixKeyT.c
矩阵按键模块化的代码在之前的文章有发过,需要的童鞋们可以去翻阅一下
#include
#include "MatrixKeyT.h"
unsigned char Key_KeyNumber;
/**
*@brief 名称:矩阵键盘扫描,获取按键键码
*@param 参数:无 (放在主函数while里 赋值给一个 代表键码的变量)
*@retval返回值:KeyNumber 0代表无按键按下,1~16代表键码
*/
unsigned char Key(void)
{
unsigned char Temp = 0;
Temp = Key_KeyNumber; //赋值给暂存变量
Key_KeyNumber = 0; //清零
return Temp; //返回暂存变量
}
/**
*@brief 名称:矩阵键盘读取按键键码, 内部函数(不需要外部调用的)
*@param 参数:无
*@retval返回值:KeyNumber 0代表无按键按下,1~16代表键码
*/
unsigned char MatrixKey_GetState()
{
unsigned char KeyNumber=0;
P1=0xFF;
P1_3=0;
if(P1_7==0 && P1_3==0){KeyNumber=1;}
if(P1_6==0 && P1_3==0){KeyNumber=5;}
if(P1_5==0 && P1_3==0){KeyNumber=9;}
if(P1_4==0 && P1_3==0){KeyNumber=13;}
P1=0xFF;
P1_2=0;
if(P1_7==0 && P1_2==0){KeyNumber=2;}
if(P1_6==0 && P1_2==0){KeyNumber=6;}
if(P1_5==0 && P1_2==0){KeyNumber=10;}
if(P1_4==0 && P1_2==0){KeyNumber=14;}
P1=0xFF;
P1_1=0;
if(P1_7==0 && P1_1==0){KeyNumber=3;}
if(P1_6==0 && P1_1==0){KeyNumber=7;}
if(P1_5==0 && P1_1==0){KeyNumber=11;}
if(P1_4==0 && P1_1==0){KeyNumber=15;}
P1=0xFF;
P1_0=0;
if(P1_7==0 && P1_0==0){KeyNumber=4;}
if(P1_6==0 && P1_0==0){KeyNumber=8;}
if(P1_5==0 && P1_0==0){KeyNumber=12;}
if(P1_4==0 && P1_0==0){KeyNumber=16;}
return KeyNumber;
}
void MatrixKey_Loop(void)
{
static unsigned char NowState,LastState;
LastState = NowState; //按键状态更新
NowState = MatrixKey_GetState(); //获取当前按键状态
if(LastState == 1 && NowState == 0) {Key_KeyNumber=1;}
if(LastState == 2 && NowState == 0) {Key_KeyNumber=2;}
if(LastState == 3 && NowState == 0) {Key_KeyNumber=3;}
if(LastState == 4 && NowState == 0) {Key_KeyNumber=4;}
if(LastState == 5 && NowState == 0) {Key_KeyNumber=5;}
if(LastState == 6 && NowState == 0) {Key_KeyNumber=6;}
if(LastState == 7 && NowState == 0) {Key_KeyNumber=7;}
if(LastState == 8 && NowState == 0) {Key_KeyNumber=8;}
if(LastState == 9 && NowState == 0) {Key_KeyNumber=9;}
if(LastState ==10 && NowState == 0) {Key_KeyNumber=10;}
if(LastState ==11 && NowState == 0) {Key_KeyNumber=11;}
if(LastState ==12 && NowState == 0) {Key_KeyNumber=12;}
if(LastState ==13 && NowState == 0) {Key_KeyNumber=13;}
if(LastState ==14 && NowState == 0) {Key_KeyNumber=14;}
if(LastState ==15 && NowState == 0) {Key_KeyNumber=15;}
if(LastState ==16 && NowState == 0) {Key_KeyNumber=16;}
}
MatrixKeyT.h
#ifndef __MATRIXKEYT_H__
#define __MATRIXKEYT_H__
unsigned char Key(void);
void MatrixKey_Loop(void);
#endif
main.c
#include
#include "Timer0.h"
#include "MatrixKeyT.h"
unsigned char i;//循环变量
unsigned char KeyNumer;
unsigned int SpaceT = 500;//初值400ms
unsigned char Light_Mode;
void LED_Func();
void main()
{
Timer0_Init();
while(1)
{
KeyNumer = Key();
LED_Func();
}
}
void LED_Func()
{
if (KeyNumer == 1) Light_Mode=1;
else if(KeyNumer == 2) Light_Mode=2;
else if(KeyNumer == 3) Light_Mode=3;
else if(KeyNumer == 4) Light_Mode=4;
else if(KeyNumer == 5)
{
SpaceT += 100;
}
if(SpaceT>1200)
SpaceT = 400;
if(Light_Mode == 1)
{
for(i=0;i<8;) //i变量在定时器里加加,控制闪烁时间间隔
{
P2 = ~(0x01<>i);
// if(Light_Mode != 2)
// break;
}
}
else if(Light_Mode == 3)
{
for(i=0;i<4;)
{
P2 = ~((0x01<>i));//两边向中间 亮灯
}
}
else if(Light_Mode == 4)
{
for(i=0;i<4;)
{
P2 = ~((0x10<>i));
}
}
}
void Timer0_Routine() interrupt 1 //定时器0的中断函数
{
static unsigned char Button; //按键扫描时间变量 10ms
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
Button++;
T0Count++;
if(T0Count >= SpaceT){
i++;
T0Count = 0; //软件复位
}
if(Button >= 10){
MatrixKey_Loop();
Button = 0;
}
}
LED流水实现部分有个BUG:因为我写的是for循环,虽然这时候按下按键,变量Light_Mode已经被重新赋值了,所以不能立刻跳出循环,执行下一个流水灯效果。之前有测试过跳出那个for循环,但是没有实现过,有兴趣的小伙伴可以尝试修改一下bug。
不过有一个解决方案,把每一个流水灯状态否存放在数组里,依次实现,这样就避免使用for循环~
补充一个模式1流水灯的方法
/* void forward(void)函数说明
第一次给P1端口赋值 0xfe = 1111 1110
延时200ms
1111 1110<<1 1111 1100 最后一个跟着亮了,给最后一位清零,即灭,“或”0x01,变成1111 1101 ,下次赋值本数
第二次赋值后,延时200ms
1111 1101<<1 1111 1010 也要把最后一位清零,|0x01,变成1111 1011……
*/
void forward(void)
{
unsigned char TempOut = 0xfe,j;
for (j=0;j<8;j++)
{
P1 = TempOut;
Delay_ms(200);
TempOut = (TempOut << 1) | 0x01;
}
}
小生文采拙劣,多多包涵!
欢迎大家讨论!