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

发布者:幸福约定123最新更新时间:2022-06-14 来源: eefocus关键字:51单片机  入门  16路抢答器 手机看文章 扫描二维码
随时随地手机看文章

设计要求

同时为16支参赛队提供抢答功能,抢答成功后应能通过数码管显示出参赛队号数,同时点亮发光二极管示意抢答成功。


加入独立开关,可启动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


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

#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 毫秒的场合,你都可以直接把函数拿过去,用所需要的毫秒数作为实参调用它即可,不需要在用计算器埋头算一通了,是不是很值呢。

关键字:51单片机  入门  16路抢答器 引用地址:51单片机入门——16路抢答器

上一篇:51单片机入门——(新)简易数字时钟
下一篇:51单片机入门——LCD1602

推荐阅读最新更新时间:2024-11-19 11:18

【STM32】入门 · 流水灯
前提 keil uVision4 奋斗STM32开发板V5 STM32F103VET JLink 具体 新建工程 新建文件夹并命名,在此文件夹下建立user,fwlib,cmsis,output,listing等子文件夹。 user用于存放工程文件和用户层代码,包括主函数main.c。 fwlib用于存放STM32库里的inc和src文件夹,它们包含了芯片上所有的驱动。 smsis用于存放库自带的启动文件和一些位于cmsis层的文件。 output用于保存编译后的输出文件。 listing用于保存编译后生成的链接文件。 接下来打开Keil,新建工程和组,将对应的文件添加到对应的文件夹中。
[单片机]
【STM32】<font color='red'>入门</font> · 流水灯
51单片机MLX90614非接触红外测温程序
这个模块采用I2C通讯,只需要接两个上拉电阻,就可以了,还是很好用的。 单片机源程序如下: #include at89x52.h #include intrins.h //************************************ #define uint unsigned int #define uchar unsigned char #define Nack_counter 10 //************** 端口定义************** //LCD 控制线接口 uchar flag1; sbit RS=P0^7; sbit RW=P0^6; sbit LCDE=P0^5;
[单片机]
51单片机IO口的输入输出方式
简介:传统51单片机IO接口只可以作为标准双向IO接口,如果用其来驱动LED只能用灌电流的方式或是用三极管外扩驱动电路。 灌电流方式:LED正极接VCC,负极接IO口。IO为高电平是LED两极电平相同,没有电流,LED熄灭;IO为低电平时,电流从VCC流入IO,LED点亮。但是当你吧LED正极接在IO接口,负极接GND时,将IO接口置于高电平,LED会亮,但因为IO接口上拉能力不足而使亮度不理想,可以用下面介绍的方式解决这个问题。 推挽工作方式:LED正负极分别接在两个IO口上,然后设置正极IO接口为推挽输出,负极IO接口为标准双向灌电流输入。推挽方式具有强上拉能力,可以实现高电平驱动LED。 IO口的四种使用方法
[单片机]
<font color='red'>51单片机</font>IO口的输入输出方式
手把手教你学51单片机:函数进阶与按键
一、单片机最小系统 单片机最小系统由电源、晶振、复位电路组成。 (1)常见的电源电压值是5v,工作电压典型值是3.3v。 (2)晶振通常为无源晶振和有源晶振两种。 有源晶振是一个谐振振荡器利用石英晶体的压电效应来起振,所以有源晶振需要供电,当有源晶振电路做好后,不需要外接其他器件,只需要给它供电,就可以主动的产生振荡频率,并且可以提供高精度的频率基准,信号质量也比无源信号稳定。 无源晶振自身无法振荡起来,它需要芯片内部的振荡电路一起工作才能震荡,它允许不同的电压,但是信号质量和精度较有源晶振差一些。无缘晶振两侧通常会有两个电容,一般容值都在10~40pf,常用20pf。 (3)复位电路 KST-51电路板中
[单片机]
手把手教你学<font color='red'>51单片机</font>:函数进阶与按键
51单片机的呼吸灯程序
//晶振11.0592 //灯光在单片机控制之下完成由亮到暗的逐渐变化,感觉像是在呼吸 //本例在51hei-5型开发板上实现了一个数码管和一个LED灯一起实现呼吸效果 //文件下载:http://www.51hei.com/f/fxd.rar #include reg52.h #define uint unsigned int #define uchar unsigned char sbit D1=P0^7; uchar sr; uchar jf; uchar code table ={ 0,1,2,3,4,5,6,7,8,9,10, 11,12,13,14,15,16,17,18, 19,20,21,22,23
[单片机]
51单片机 定时器时钟
1.main.c 注:Sec,Min,Hour可不进行赋值 #include REGX52.H #include Delay.h #include LCD1602.h #include Timer0.h unsigned char Sec=55,Min=59,Hour=23; void main() { LCD_Init(); Timer0Init(); LCD_ShowString(1,1, Clock: ); LCD_ShowString(2,1, : : ); while(1) { LCD_ShowNum(2,1,Hour,2); LCD_Sh
[单片机]
基于51单片机的正弦信号发生器的设计
正弦信号是电子电路设计中非常重要的信号之一。在很多电子设备和系统中,需要正弦信号作为输入源。基于51单片机的正弦信号发生器设计是一种较为简单且常见的方法。本文将详细介绍如何设计一个基于51单片机的正弦信号发生器。 一、51单片机简介 51单片机是以英特尔公司的MCS-51单片机为核心的一族单片机,主要用于嵌入式系统和电子设备上。51单片机内部集成了CPU、RAM、ROM、计时器、串行通信接口等功能模块,具有较强的实时控制能力和通用性。 二、正弦信号的生成原理 正弦信号是一种周期性连续信号,可由谐振电路或数字信号处理的方法生成。在本文中,我们采用数字信号处理的方法来生成正弦信号。 数字信号的表示 在51单片机中,数字信号是
[单片机]
【GD32 MCU 入门教程】GD32 MCU 常见外设介绍 (6) ADC 模块介绍
6.1.ADC 基础知识 12 位逐次逼近式模数转换器模块(ADC),可以采样来自于外部输入通道、内部输入通道的模拟信号,采样转换后,转换结果可以按照最低有效位对齐或最高有效位对齐的方式保存在相应的数据寄存器中。 6.2.GD32 ADC 外设原理 GD32 ADC 主要特性 ◼ 高性能: – ADC采样分辨率: 12位、 10位、 8位、或者6位分辨率; – 前置校准功能; – 可编程采样时间; – 数据存储模式:最高有效位对齐和最低有效位对齐; – 支持规则数据转换的DMA请求。 ◼ 模拟输入通道: – 16个外部模拟输入通道; – 1个内部温度传感器输入通道(VSENSE); – 1个内部参考电压输入通道(VREFINT
[单片机]
【GD32 MCU <font color='red'>入门</font>教程】GD32 MCU 常见外设介绍 (6) ADC 模块介绍
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved