遇到一个面试题
题目:设计一个按键+跑马灯的DEMO
假设现在有A、B、C三个按键(均按下低电平),五个LED灯LED1~LED5高电平点亮
核心功能位跑马灯,每次只点亮相邻的三盏灯,以一定时间频率向左或向右移动一盏灯。
例如:
11100 -> 01110 -> 00111 -> 10011
程序刚上电或复位状态:灯显11100,移动方向向右,移动间隔时间1S,跑马灯静止状态
按键A单击为修改移动方向向左,双击为移动间隔时间-100ms
按键B单击为修改移动方向向右,双击为移动间隔时间+100ms
按键C单击为反转跑马灯运行或静止状态,长按(2S)为复位程序到初始状态
备注:
默认在32bitMCU裸机下实现,按键在硬件上无消抖处理。
只考察编程思维和代码风格,不检查语法,不需要完整的工程通过编译。
表述有不清晰不完整的地方,按照自己理解完成就行,无标准答案,无需在意真实硬件环境。
实现方法不一,我的程序框架如下:
//按键:KEY_A、KEY_B、KEY_C
//假设开一个100ms定时器 Time1_Callback()
//假设延时函数 delay_ms()
//LED灯:11100(0x1C)->01110(0x0E)->00111(0x07)->10011(0x13)->11001(0x19)->11100(0x1C)
#define RIGHT = 0;//跑马灯移动方向向右
#define LEFT = 1;//跑马灯移动方向向左
#define ON = 0;//按键按下
#define RUN = 1;//跑马灯运行
#define STOP = 0;//跑马灯停止
uint8 LED = 0x1C;//低五位表示跑马灯的亮灭
uint8 DIR = RIGHT;//跑马灯移动方向
uint8 time_count = 10;//定时器回调函数中计时值
uint8 time_inital_value = 10;//设定跑马灯移动频率值
uint8 run_stop = STOP;//跑马灯运行或停止标志
struct KEY_STATUS//定义按键的三个状态
{
uint8 one_click;//按键单击标志
uint8 double_click;//按键双击标志
uint8 long_click;//按键长按标志
};
/* 100ms调用1次回调函数 */
void Time1_Callback()
{
if(time_count > 0 )
{
time_count--;
}
}
/* LED跑马灯运行接口 */
void LED_Run()
{
if(run_stop == RUN)
{
if((time_count == 0) && (DIR = RIGHT))
{
time_count = time_inital_value;
switch(LED)
case 0x1C:
//点亮对应位LED 11100
LED = 0x0E;
break;
case 0x0E:
//点亮对应位LED 01110
LED = 0x07;
break;
case 0x07:
//点亮对应位LED 00111
LED = 0x13;
break;
case 0x13:
//点亮对应位LED 10011
LED = 0x19;
break;
case 0x19:
//点亮对应位LED 11001
LED = 0x1C;
break;
}
else if((time_count == 0) && (DIR = LEFT))
{
switch(LED)
case 0x1C:
//点亮对应位LED 11100
LED = 0x19;
break;
case 0x19:
//点亮对应位LED 11001
LED = 0x13;
break;
case 0x13:
//点亮对应位LED 10011
LED = 0x07;
break;
case 0x07:
//点亮对应位LED 00111
LED = 0x0E;
break;
case 0x0E:
//点亮对应位LED 01110
LED = 0x1C;
break;
}
}
}
void main ()
{
struct KEY_STATUS KEYA = {0,0,0};//按键A
struct KEY_STATUS KEYB = {0,0,0};//按键B
struct KEY_STATUS KEYC = {0,0,0};//按键C
uint8 dec = 0;//用于循环计时判断按键长按
uint8 i =0;//用于循环计时判断按键双击
while(1)
{
/* KEY_A */
if(KEY_A == ON)
{
delay_ms(20);//消抖
if(KEY_A == ON)
{
while(KEY_A == ON && dec<20)
{
dec++;delay_ms(100); //长按判断的计时2S
}
if(dec >= 20)//长按
{
dec = 0;
KEYA.one_click = 0;
KEYA.double_click = 0;
KEYA.long_click = 1;
/* 按键A长按处理 */
}
else
{
for(i=0;i<10;i++)
{
delay_ms(20);
if(KEY_A == ON)//双击
{
KEYA.one_click = 0;
KEYA.double_click = 1;
KEYA.long_click = 0;
/* 按键A双击处理 */
if(time_inital_value > 0)
{
time_inital_value--;//周期减小100ms
}
}
}
if(KEYA.double_click == 0)//单击
{
KEYA.one_click = 1;
KEYA.double_click = 0;
KEYA.long_click = 0;
/* 按键A单击处理 */
DIR = LEFT;//切换方向
}
}
}
/* KEY_B */
if(KEY_B == ON)
{
delay_ms(20);//消抖
if(KEY_B == ON)
{
while(KEY_B == ON && dec<20)
{
dec++;delay_ms(100); //长按判断的计时2S
}
if(dec >= 20)//长按
{
dec = 0;
KEYB.one_click = 0;
KEYB.double_click = 0;
KEYB.long_click = 1;
/* 按键B长按处理 */
}
else
{
for(i=0;i<10;i++)
{
delay_ms(20);
if(KEY_B == ON)//双击
{
KEYB.one_click = 0;
KEYB.double_click = 1;
KEYB.long_click = 0;
/* 按键B双击处理 */
time_inital_value++;//周期增加100ms
}
}
if(KEYB.double_click == 0)//单击
{
KEYB.one_click = 1;
KEYB.double_click = 0;
KEYB.long_click = 0;
/* 按键B单击处理 */
DIR = RIGHT;//切换方向
}
}
}
}
/* KEY_C */
if(KEY_C == ON)
{
delay_ms(20);//消抖
if(KEY_C == ON)
{
while(KEY_C == ON && dec<20)
{
dec++;delay_ms(100); //长按判断的计时2S
}
if(dec >= 20)//长按
{
dec = 0;
KEYC.one_click = 0;
KEYC.double_click = 0;
KEYC.long_click = 1;
/* 按键C长按处理 */
/* 复位 */
run_stop = RUN;
DIR = RIGHT;
LED = 0x16;
time_inital_value = 10;
}
else
{
for(i=0;i<10;i++)
{
delay_ms(20);
if(KEY_C == ON)//双击
{
KEYC.one_click = 0;
KEYC.double_click = 1;
KEYC.long_click = 0;
/* 按键C双击处理 */
}
}
if(KEYC.double_click == 0)//单击
{
KEYC.one_click = 1;
KEYC.double_click = 0;
KEYC.long_click = 0;
/*按键C单击处理*/
if(run_stop == RUN)//停止、运行
{
run_stop = STOP;
}
else
{
run_stop = RUN;
}
}
}
}
}
}
/* 循环执行 */
LED_Run();
}
}
——作为以后需要用到类似功能时的参考程序