51单片机入门——16路抢答器

设计要求

  1. 同时为16支参赛队提供抢答功能,抢答成功后应能通过数码管显示出参赛队号数,同时点亮发光二极管示意抢答成功。
  2. 加入独立开关,可启动10秒倒计时功能,通过数码管显示出倒计时时间(倒计时状态下抢答功能不起作用,反之亦然)。

电路原理图

在这里插入图片描述

硬件原理

时钟信号(晶振)

在这里插入图片描述
单片机晶振部位电路,详情请参考《51单片机入门——单片机最小系统》,在此项目中我们选择 11.0592 MHz的晶振。

矩阵按键与独立按键

在这里插入图片描述

在这里插入图片描述
在该项目中矩阵按键用于选手的抢答器,独立按键用于主持人复位重置抢答。

代码解析

矩阵按键部分代码:
keyboard.c

#include "KEYBOARD.H"

uchar keySta[4][4] = {//矩阵按键的当前状态 1为高电平 ,0为低电平
	{1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} 	
};

uchar ledChar[16] = { //共阳极数码管显示字符转换表
 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

/* 按键驱动函数,检测按键动作,调度相应动作函数*/
void KeyDriver()
{
	uchar i , j;
	static char backup[4][4] = {
		{1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1}
	};

	for (i = 0 ; i < 4 ; i ++)
	{
		for (j = 0 ; j < 4 ; j ++)
		{
			if (keySta[i][j] != backup[i][j]) //检测按键动作
			{
				if (backup[i][j] != 0)	   //按键按下时执行动作
				{
					P0 = ~ledChar[i*4+j];
					LED1 = 1;
				}
				backup[i][j] = keySta[i][j];  //备份按键状态
			}
		}
	}
}

/* 按键扫描函数 , 在定时中断中调用,推荐1ms*/
void KeyScan()
{
	uchar i;

	static uchar keyout = 0;   // 矩阵按键扫描输出索引
	static uchar keybuf[4][4] = {  // 矩阵按键扫描缓冲区
		{0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} , 
		{0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} 
	};

	// 将一行的4个按键值移入缓冲区
	keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN1;
	keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN2;
	keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN3;
	keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN4; 

	// 消抖后更新按键状态
	for (i = 0 ; i < 4 ; i ++) //每行4个按键,所以循环4次
	{
		if ((keybuf[keyout][i] & 0x0f) == 0x00)
		{	//连续 4 次扫描值为 0,即 4*4ms 内都是按下状态时,可认为按键已稳定的按下
			keySta[keyout][i] = 0;
		}
		else if ((keybuf[keyout][i] & 0x0f) == 0x0f)
		{	//连续 4 次扫描值为 1,即 4*4ms 内都是弹起状态时,可认为按键已稳定的弹起
			keySta[keyout][i] = 1;
		}
	}
	//执行下一次的扫描输出

	keyout ++;	 //输出索引递增
	keyout = keyout & 0x03; //索引值加到 4 即归零

	switch(keyout)	//根据索引,释放当前输出引脚,拉低下次的输出引脚
	{
		case 0: KEY_OUT4 = 1; KEY_OUT1 = 0; break;
		case 1: KEY_OUT1 = 1; KEY_OUT2 = 0; break;
		case 2: KEY_OUT2 = 1; KEY_OUT3 = 0; break;
		case 3: KEY_OUT3 = 1; KEY_OUT4 = 0; break;
		default: break;
	}
}

keyborad.h

#ifndef _KEY_BOARD_H_
#define _KEY_BOARD_H_

#include<reg52.h>

typedef unsigned char uchar;
typedef unsigned int uint;
typedef	unsigned long ulong;

sbit KEY_OUT1 = P2^7;
sbit KEY_OUT2 = P2^6;
sbit KEY_OUT3 = P2^5;
sbit KEY_OUT4 = P2^4;
sbit KEY_IN1 = P2^3; 
sbit KEY_IN2 = P2^2; 
sbit KEY_IN3 = P2^1; 
sbit KEY_IN4 = P2^0;

sbit KEY1 = P3^0;
sbit LED1 = P3^1;


extern uchar ledChar[16];

void KeyDriver();
void KeyScan();

#endif

主函数代码:

#include <reg52.h>
#include "KEYBOARD.H"

uchar T0RH = 0; // T0 重载值的高字节
uchar T0RL = 0; // T0 重载值的低字节

bit a = 0; // 独立按键索引位
bit countdownRunning = 1; // 倒计时运行标志

uchar integerPart = 10; //计数

void ConFigTimer0(uchar ms);
void KeyControl();
void CountdownDisplay();

void main()
{	
	P0 = ~ledChar[0]; // 初始化数码管
	ConFigTimer0(2); // 定时2ms
	EA = 1; // 开启总中断
	while(1)
	{
		if ((LED1 != 1) && (countdownRunning == 0))
			KeyDriver();
		KeyControl();
		CountdownDisplay();	
	}	
}

/* 倒计时复位函数 */
void CountdownReset()
{
	countdownRunning = 1; //重启
 	integerPart = 10;//初始计数值
	LED1 = 0;
}

/* 倒计数函数 */
void CountdownCount()
{
	if (countdownRunning)
	{
		if (integerPart != 0)
			integerPart --;
		else
			countdownRunning = 0;		
	}
}

/* 倒计时显示函数 */
void CountdownDisplay()
{
	if (countdownRunning == 1)
	{
		LED1 = 0;
		P0 = ~ledChar[integerPart];
	}
}

/* 复位函数 */
void KeyControl()
{
	if (KEY1 == 0 && a == 0)
	{
		a = 1 ;	
	} 
	if (KEY1 == 1 && a == 1)
	{
		CountdownReset();
		a = 0;	
	}
}

/* 配置并启动T0 ,11.0592MHz */
void ConFigTimer0(uchar ms)
{
	ulong tmp ; //临时变量

	tmp = 11059200 / 12; //定时器计数频率
	tmp = (tmp * ms) / 1000; //计算所需的计数值
	tmp = 65536 - tmp; //计算定时器重载值
	tmp += 2;  //补偿中断响应延时造成的误差	
	T0RH = (uchar)(tmp >> 8); //定时器重载值拆分为高低字节
	T0RL = (uchar)tmp;
	TMOD &= 0xF0; //清零 T0 的控制位 	
	TMOD |= 0x01; //配置 T0 为模式 1
	TH0 = T0RH; //加载 T0 重载值
	TL0 = T0RL;
	ET0 = 1; //使能 T0 中断
	TR0 = 1; //启动 T0
}

/* T0 中断服务函数,用于按键状态的扫描并消抖,倒计时的时间计算 */
void InterruptTimer0() interrupt 1
{
	uint t;
	TH0 = T0RH;
	TL0 = T0RL;
	KeyScan();
	t ++;
	if (t > 500)
	{
		CountdownCount(); //调用倒计数函数
		t = 0;
	}
}

关于这个程序有1点值得提一下:定时器配置函数ConFigTimer0(uchar ms),虽然这样在程序里通过计算得出初值(重载值)增加了些许代码,但它换来的是便利性和编程效率,因为只要你完成这个函数,之后所有需要用定时器定时 x 毫秒的场合,你都可以直接把函数拿过去,用所需要的毫秒数作为实参调用它即可,不需要在用计算器埋头算一通了,是不是很值呢。

  • 19
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾晨灬雨曦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值