找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3878|回复: 1
打印 上一主题 下一主题
收起左侧

基于AT89S51的单片机无线双显示抢答器

[复制链接]
跳转到指定楼层
楼主
ID:108852 发表于 2016-4-2 16:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
基于AT89S51的单片机无线双显示抢答器

1关于抢答器
1.1目前常见的抢答器有以下几种类别:
(1) 数字电路:只要使用555定时电路作为时序触发,配合相应的数字电路实现。
(2) 单片机式:以单片机为作为总控制单元利用单片机的扫描读取外部输入, 并进行相应的判断以及数据处理。
(3) 电脑程序实现的:以电脑作为上位机,利用USB端口或者串口编程技术实现,逻辑上面的判断以及处理均由程序来完成。这种抢答器可以在电脑上面显示并且可以与相应的由高级语言完成答题系统一起使用,配合单片机控制能力强的优势,将会是完美的结合,这也是我们追求的目标。
1.2抢答器的常见功能:
(1) 判断抢答端的序号。
(2) 判断是否犯规并且在主机显示。
(3) 在抢答端显示犯规或是抢答成功。
(4) 设定倒计时时间并显示倒计时时间。
(5) 显示各组分数。
(6) 主持人的控制。既主持人可以开始和取消倒计时。
(7) 无线抢答端的实现
(8) 电脑显示抢答情况
(9) 选择抢答方式。一种是有倒计时有犯规的倒计时结束开始答题的,另外一种是无倒计时无犯规可以直接答题的,比如说像Lucky52那样的,两种只是在程序逻辑以及显示上面稍有区别。
1.3功能实现的选择
我们的抢答器选择了上面的(1),(2)(3)(4)(6)(7)(8)进行实现。
抢答的逻辑具体流程如下图:
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps1847.tmp.png 0009、八路扫描式抢答器设计论文.rar (159.69 KB, 下载次数: 14)
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps1857.tmp.png

2主要芯片及设备的选择:
2.1 AT89S51芯片
很熟悉的ATMEL公司的51单片机,主要具有一下功能:
40个引脚
4k Bytes Flash片内程序存储器
128 bytes的随机存取数据存储器(RAM)
32个外部双向输入/输出(I/O)口
5个中断优先级2层中断嵌套中断
2个16位可编程定时计数器
2个全双工串行通信口
看门狗(WDT)电路
片内时钟振荡器
2.2 Max7219芯片
MAX7219是一种串行接口的8位数码管显示驱动器。它与通用微处理器只有3根串行线相连,最多可驱动8个共阴数码管或64个发光二极管。它内部有可存储显示信息的8×8静态RAM,动态扫描电路,以及段、位驱动器。
它的特点有:串行接口的传输速率可达10MHz;独立的发光二极管段控制;译码与非译码两种显示方式可选;数字、模拟两种亮度控制方式;可以级联使用。
2.3 Max232芯片
Max232用于TTL电平向RS-232串口电平转换。该产品是由德州仪器公司(TI)推出的一款兼容RS232标准的芯片。由于电脑串口rs232电平是-10v +10v,而一般的单片机应用系统的信号电压是ttl电平0 +5v,max232就是用来进行电平转换的,该器件包含2驱动器、2接收器和一个电压发生器电路提供TIA/EIA-232-F电平。
2.4 PT2262/2272芯片
PT2262和PT2272是CMOS三态编码集成芯片,这组器件广泛用于各种遥控器件上,只需较低的+3V电压就能工作(本机选用12V作为发射电压)。PT2262 是发射编码芯片,PT2272是接收解码芯片,两者的地址必须配对,而且振荡电阻必须符合要求。PT2262的TE端是发射允许端,接受低电平时,17脚DOUT端输出一串编码。该串编码在载波上发送出去,被接收端接受和解调,输入PT2272的14脚,当地址配对时,VT解码有效端输出高电平,数据端口就会输出与PT2262发射端口一致的数据,从而实现遥控功能。
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps1858.tmp.pngfile:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps1878.tmp.png
2.5 超再生无线发射模块(F04P)与接收模块(J04V)
2.5.1 超再生低功耗射频发射模块(315Hz)
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps1879.tmp.png

主要特点:
低功耗发射,声表稳频,无数据时发射电流为零,较宽的工作电压范围
发射电路:
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps187A.tmp.png

2.5.2超再生低功耗射频接受模块(315Hz)
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps188B.tmp.png

主要特点:
(0.15mA)特低功耗超再生接收模块,输出无噪声干扰,接收灵敏度高,具有接受锁存功能
接受电路于发射电路基本相似。
2.5.3 发射接收简要原理
通过引脚的接地,悬空或者接高电平设置好发射模块的发射地址,当发射使能端从高电位到低点位变化时,奖发射地址和发射的数据组成16为编码发射。

2.6 ULN2003达林顿管阵列
ULN2003 是高耐压、大电流达林顿陈列,由七个硅NPN 达林顿管组成。
达林顿管又称复合管。它将二只三极管适当的连接在一起,以组成一只等效的新的三极管。这等于效三极管的放大倍数是二者之积。在电子学电路设计中,达林顿接法常用于功率放大器和稳压电源中。
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps188C.tmp.png

最大的作用与特点是每路可以介绍500mA的灌流,这也是我们使用的目的。
2.7 USB转串口模块(USB/RS232)
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps189D.tmp.png

采用USB转串口的标准芯片PL2303和RS232电平接口芯片MAX211的组合。
2.8高亮度Super Red四寸共阴极数码管
正常发光电压约5--6V, 正常导通电流约40--80mA

3硬件电路的实现以及各个部分的作用
3.1 硬件电路图(Proteus仿真图)
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps18EC.tmp.png

3.2各个模块的说明与简介
3.2.1 单片机最小系统:
这个不用多说,可惜是买别人的。上面得是提供的是12MHz的晶振,与程序中的保持一样.因为在计算定时的时候,要用到晶振的大小。
3.2.2 Max7219模块
这个,还是买的别人做的PCB,我们自己用面包版连了,可以不行,怀疑是没有选择正确的电容的问题,或者说,我们不会选择电容。
3.2.3 无线抢答端发射模块
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps18ED.tmp.png

无线抢答端采用PT2262发射编码芯片+F05P发射模块的组合,另外使用一根拉直的长24CM的天线。
关于按钮,买的不知道用来控制什么的按钮,反正正和我们意,买这个的时候要注意手感,要让使用者用的舒适。从院科协L同学那里淘来的半圆形按钮外壳。
3.2.4无线接收模块
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps18FD.tmp.png

无线抢答端采用PT2272发射编码芯片+F04V接收模块的组合。Pt2272与PT2262要设置为地址相同,电阻匹配。接受到的信号传至单片机中。
3.2.5 主持人控制按键
这里有两个按钮,分别为OK/START,CANCEL/CLEAR. 前者是用来设置完毕抢答倒计时时间和开始倒计时,后者是用来取消抢答和清理数码管并显示倒计时开始时间,用来等待再次开始抢答。
3.2.6 设置倒计时时间的拨码开关
仿“8421”码设置的“2321”码,全部低位的时候为“1”,四个开关可以设置1-9任何数字。缺点就是买的拨码开关小了点。
3.2.7 译码器以及抢答端的发光二极管
译码器从单片机得到输入,然后低位选通发光二极管。
3.2.8 位选反向驱动
说不清了,这样的了。
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps191E.tmp.png

3.2.9 段选反向驱动
两个反向驱动的本意在于,虽然Max7219驱动的是共阴极数码管,但是从datasheet上面可以看出来,段选的拉流不可以满足这种型号的数码管,位选的灌流也不能接受,所以这里配合ULN2003来使用。因为ULN2003的特点,高位接受灌流,低位为高阻,所以要使用反向驱动。这里,段选的反向驱动在程序里面的反向了,而对于位选,用于是该芯片自己进行的,所以程序控制不了,所以在Max7219位选的下游使用了一个与非门来当作反向器来使用。
3.3.10 上位机接口
采用MAX232芯片进行RS232-TTL电平转换,输出至串口,然后再通过USB转串口模块连接至上位机。
3.2.11 大尺寸数码管
这个前面说过了。

4硬件实现的步骤
4.1硬件实现的各个步骤
(1) 通过实验板来验证各个主要功能模块的正确实现。
这个就是在实验板接线,来分开的检测各个部分的正确性,当然有的部分还是没有办法验证的。
(2) 仿真器+面包板上面的插线实验
恩,仿真器很好使。就是上面的限流电阻给不小心被烧了,郁闷。
(3) 最小系统+面包板上面插线实验
使用仿真器的时候用的是电脑提供的+5V电压,可能和用自己的供电的不太一样,尤其是同时也在使用+12V电源的时候,电脑给出的+5V可能的比较低,这样一来会有些不必要的问题,而用最小系统则没有这些问题。
(4) 焊接万能板
这个很有趣也很关键。
(5) 仿真器+电路板的实现
用来验证电路板上的各个部分是否焊接成功。
(6) 最小系统+电路板的实现
恩这个就是最后的验证了。
4.2一个原则
由上可见,在实验过程中,总体上也采取单变量原则,这样方便有效地对于每一部分进行检验。另外,如果在检验焊接板上面的错误的话,要配合着面包板使用才行,也是保证单变量,这样对比出来面包上成功的时候和现在失败的时候只有哪个地方不同,那百分之九十就是这个地方了。

5软件实现(单片机上的软件)
5.1程序编写
模块化,使整个程序的每个部分具有较好的可移植性。
5.2程序测试
在Keil Uvision + Proteus联调环境下进行逻辑的测试,不过这个只是逻辑上面的测试,距离能成功还有很远。
5.3最终代码
最后的代码将在另见。

6电脑端(上位机)显示程序
电脑端使用C#编写程序,利用了.Net的SerialPort类对电脑端口检测和接受数据。这个将会另有介绍。


7 后续工作和几个问题
(1) 电源还需优化, 可以采用三稳压管电源。
(2) 电阻发热问题: 我们使用的3W的大功率电阻竟然不到1.5W的情况下就发热了,还没有搞懂。
(3) 我们的74HC00与非门在未加高电平的情况下就已经正常工作了,不清楚这是什么情况。
(4) 可以考虑增加抢答模式与小组积分,实现按着使用者的要求对于程序进行选择。
(5) 无线端抢答端电池的问题。因为目前的设计是电池如果没有取出的话,将一直对发射系统供电,这样会消耗电池,但采用一次按键先后接通电路,发射编码的方法,总是不稳定
#include <REG51.h>
#include <STRING.H>

/******************************************************************
*    自定义Macro
*******************************************************************/

//编码的均为反向编码

#define CLEAR 0x7f                  //定义清空的反码
#define LED_BEGIN 0x01            // 定义开始时数码管的显示
#define LED_FOUL 0x38             // 犯规后显示字母"F",数码管编码
#define LED_C 0x31                // 字母"C"的编码
#define LED_L 0x71                // 字母"L"的编码,两个用来在主持人取消之后显示"CL"--cancel
#define GET 1                     // 这个是作为一个函数的参数来混的,就是成功抢答的意思
#define FOUL 0                    // 和上面的参数一起混的,犯规---这两个的用法在后面体现
#define READY 0x7e

//下面是给上位机发送的指令,对应的是cmdID
#define _STRING_READY_ 9//调整好抢答倒计时,准备开始抢答
#define _STRING_START_ 8//读秒结束,抢答开始
#define _STRING_CANCEL_ 7//取消抢答
#define _CHANGE_TIME_ 6//每次读秒
//发送1--4的指令代表抢答端的序号

//因为在板子上面采用的是12M的晶振,仿真时候采用的是11.0529M的晶振,为了方便不同时候编译方便,这里与后面的条件编译一起使程序修改参数比较方便
//#define CLOCK_FREQUENCY_12M 1

/******************************************************************
*    自定义数据类型
*******************************************************************/
typedef unsigned char Byte;       // 一个字节
typedef unsigned int Word;        // 一个字,两个字节
typedef bit Bool;                 // 模仿布尔型变量
//typedef sbit Port;              // 本想用自定义一个端口类型的变量,比较方便,但是这句话步知道为何通不过编译

/******************************************************************
*    定义MAX7219寄存器
*******************************************************************/
#define REG_NO_OP 0x00        // 定义空操作 register
#define DIG_1 0x01            // 定义数码管1 register
#define DIG_2 0x02            // 定义数码管2 register
#define DIG_3 0x03            // 定义数码管3 register
#define DIG_4 0x04            // 定义数码管4 register
#define DIG_5 0x05            // 定义数码管5 register
#define DIG_6 0x06            // 定义数码管6 register
#define DIG_7 0x07            // 定义数码管7 register
#define DIG_8 0x08            // 定义数码管8 register
#define REG_DECODE 0x09       // 定义解码控制 register
#define REG_INTENSITY 0x0a    // 定义显示亮度 register
#define REG_SCAN_LIMIT 0x0b   // 定义扫描限制 register
#define REG_SHUTDOWN 0x0c     // 定义"shutdown"模式 register
#define REG_DISPLAY_TEST 0x0f // 定义"display test"模式 register
#define INTENSITY_MIN 0x00    // 定义最低显示亮度
#define INTENSITY_MAX 0x0f    // 定义最高显示亮度

/*********************************************************************
*    定义硬件引脚连接
**********************************************************************/
sbit DATA=P2^0;               // MAX7219的数据口
sbit LOAD=P2^1;               // MAX7219的锁存端口
sbit CLK=P2^2;                // MAX7219的时钟端口

sbit HOST_START=P0^0;             //主持人按键,用来重新开始的按键    start
sbit HOST_CANCEL=P0^1;            //主持人用来取消抢答的按键        clear

sbit SWITCH1_3=P1^4;    // 调节倒计时时间的拨码开关,下划线前面的号代表开关的序号,下划线后面的号代表该开关的数值
sbit SWITCH2_2=P1^5;          // 同上
sbit SWITCH3_2=P1^6;          // 同上
sbit SWITCH4_1=P1^7;          // 同上

sbit BEEP=P0^7;            //定义蜂鸣器端口

#ifdef USE_SOUND       //可以通过define来选择要不要使用仿真时候的声音
sbit LS138_C=P2^4;       //定义译码器输入端
sbit LS138_B=P2^5;       //同上                  
sbit LS138_A=P2^6;       //同上
sbit LS138_E1=P2^7;       //定义译码器使能端
#endif

/*********************************************************************
*    定义全局变量
**********************************************************************/
Byte data intrCounter; // 计时器中断次数
Byte data beginNum;    // 开始倒计时的时间
Byte data counterBack;  // 将中断次数放在里面以备后用
Byte data showNum;     // 数码管正在显示的时间
Bool data isStart;     // 是否开始抢答  
Bool data isFoul;      // 是否犯规
Bool data isPressed;   // 是否有抢答的键按下
Byte data number_temp; // 用来记录P1口上次状态的一个变量
Bool data needResetTimes;//记录是否需要重设Timer0的溢出次数

code unsigned char C51BOX2[3] _at_ 0x43; //使用C51Box时候防止程序跑丢

/***********************************************************************
*   共阴极七段数码管显示对应段查询表(数字0-9分别对应code_table[0]-[9]
***********************************************************************/
Byte code code_table_zheng[10]=
{0x7e,0x30,0x6d,0x79,0x33,0x5b,0x5f,0x70,0x7f,0x7b};

Byte code code_table[10]=
{0x01,0x4f,0x12,0x06,0x4c,0x24,0x20,0x0f,0x00,0x04};

/***********************************************************************
*   函数声明
***********************************************************************/
void MAX7219_SendByte (Byte dataout);
void MAX7219_Write (Byte reg_number, Byte dataout);
void MAX7219_DisplayChar(Byte digit, Byte character);
void MAX7219_Clear (void);
void MAX7219_SetBrightness (Byte brightness);
void MAX7219_DisplayTestStart (void);
void MAX7219_DisplayTestStop (void);
void MAX7219_ShutdownStart (void);
void MAX7219_ShutdownStop (void);
void MAX7219_Init (void);
void Delay10ms(void);
Bool GetHostStartKey (void);
Bool GetHostCancelKey (void);
void GetCounter(void);
Byte GetPressed(Byte KeyState);
Byte GetPressedWireless(Byte KeyState);
void IT0_Init(void);
void Timer0_Overflow();
void PressedHandle(Byte keyPressed);
void GetOrFoulHandle(Bool state);
void CancelHandle();
void SPEAKER_count (void);    //声明倒计时声音函数
void SPEAKER_start(void);        //声明开始抢答声音函数
void SPEAKER_get(void);    //声明抢到声音函数
void SPEAKER_foul(void);   //  声明犯规声音函数
void initialSerial();
void sendNumber(int number);//串口发送数字,这里发送的是CommandID
void sendString(unsigned char *string);//串口发送字符串

/***********************************************************************
* MAX7219_SendByte()
*
* 描述: MAX7219传送一个字节的数据
* Arguments : dataout = data to send
* Returns : none
*************************************************************************/
void MAX7219_SendByte (Byte dataout)
{
    Byte i;
    for (i=8;i>0;i--)
    {
        Byte mask=1<<(i-1);//mask是个掩码,取位使用
        CLK=0;//MAX7219的位传入是在时钟的上升沿之前,所以在每发一位之前都要变为低电平
        if (dataout&mask)
            DATA=1;
        else
            DATA=0;
        CLK=1;//八个bit都传递完成后变为高电平,锁存
    }
}

/***********************************************************************
* MAX7219_Write()
*
* 描述: MAX7219 写命令
* Arguments : reg_number = register to write to
* dataout = data to write to MAX7219
* Returns : none
未完~
***************************************************************************/
void MAX7219_Write (Byte reg_number, Byte dataout)
{
        LOAD=0;//也是锁存上升沿之前的,发这两个字节之前要变为低电平
        MAX7219_SendByte(reg_number);//发送寄存器地址
        MAX7219_SendByte(dataout);//发送数据
        LOAD=1;//变为高电平,锁存
}

/**************************************************************************
* MAX7219_DisplayChar()
*
* 描述: 使某一位显示一个数字
* Arguments : digit = digit number (0-7)
* character = character to display (0-9, A-Z)
* Returns : none
**************************************************************************/
void MAX7219_DisplayChar(Byte digit, Byte character)
{
         MAX7219_Write(digit, character);
}

/**************************************************************************
* MAX7219_Clear()
*
* 描述: 清除所有位的显示
* Arguments : none
* Returns : none
***************************************************************************/
void MAX7219_Clear (void)
{
         Byte i;
         for (i=1; i<=2; i++)
         MAX7219_Write(i, CLEAR);//把八个数码管全都清零了,已经写反了^_^
}

/**************************************************************************
* MAX7219_SetBrightness()
*
* 描述: 设置数码管显示亮度
* Arguments : brightness (0-15)
* Returns : none
***************************************************************************/
void MAX7219_SetBrightness (Byte brightness)
{
         brightness &= 0x0f;
         MAX7219_Write(REG_INTENSITY, brightness);
}

/**************************************************************************
* MAX7219_DisplayTestStart()
*
* 描述: 进入 test 模式
* Arguments : none
* Returns : none
***************************************************************************/
void MAX7219_DisplayTestStart (void)
{
    MAX7219_Write(REG_DISPLAY_TEST, 1);
}

/**************************************************************************
* MAX7219_DisplayTestStop()
*
* 描述: 退出 test 模式
* Arguments : none
* Returns : none
***************************************************************************/
void MAX7219_DisplayTestStop (void)
{
    MAX7219_Write(REG_DISPLAY_TEST, 0);
}

/**************************************************************************
* MAX7219_ShutdownStart()
*
* 描述: 进入 shutdown 模式
* Arguments : none
* Returns : none
***************************************************************************/
void MAX7219_ShutdownStart (void)
{
    MAX7219_Write(REG_SHUTDOWN, 0);
}

/**************************************************************************
* MAX7219_ShutdownStop()
*
* 描述: 退出 shutdown 模式
* Arguments : none
* Returns : none
***************************************************************************/
void MAX7219_ShutdownStop (void)
{
        MAX7219_Write(REG_SHUTDOWN, 1);
}

/**************************************************************************
* MAX7219_Init()
*
* Description: MAX7219初始化模块; 应该先于其他MAX7219函数而被调用
* Arguments : none
* Returns : none
***************************************************************************/
void MAX7219_Init (void)
{
    DATA=1;
    CLK=1;
    LOAD=1;
    MAX7219_Write(REG_SCAN_LIMIT,1);//这里设置的是扫描两个数码管
    MAX7219_Write(REG_DECODE, 0x00);
    MAX7219_SetBrightness(INTENSITY_MAX);//设置最大亮度显示
    MAX7219_DisplayTestStart();
    MAX7219_DisplayTestStop();
    MAX7219_ShutdownStop();
    MAX7219_Clear();
}

/**************************************************************************
* Delay_100us()
*
* 描述: 延时100us,主要用在消除开关抖动时
* Arguments : none
* Returns : none
***************************************************************************/
void Delay10ms(void)
{
unsigned char i,j;
for(i=20;i>0;i--)
for(j=248;j>0;j--);
}
/**************************************************************************
* GetHostStartKey()
*
* Description: 取得主持人开始按键的键值
* Arguments : none
* Returns : 1-->主持人按键; 0-->主持人未按键
***************************************************************************/
Bool GetHostStartKey (void)
{
    if (HOST_START ==1)
        return 0;
    else
        Delay10ms ();//如果发现主持人按键接通,要先延时100us,防止抖动
    if (HOST_START==1)
        return 0;
    else
        return 1;//延时时候还是接通,则判断为该键确实按下   
}

/**************************************************************************
* GetHostCancelKey()
*
* Description: 取得主持人取消按键的键值
* Arguments : none
* Returns : 1-->主持人按键; 0-->主持人未按键
***************************************************************************/
Bool GetHostCancelKey (void)
{
    if (HOST_CANCEL ==1)
        return 0;
    else
        Delay10ms ();//如果发现主持人按键接通,要先延时100us,防止抖动
    if (HOST_CANCEL ==1)
        return 0;
    else
        return 1;//延时时候还是接通,则判断为该键确实按下   
}

/**************************************************************************
* GetCounter
*
* Description: 取得预先设置的倒计时时间
* Arguments : none
* Returns : none
***************************************************************************/
void GetCounter(void)
{
    beginNum=1;//在所有开关都没有拨动的时候倒计时为1,比设置为0秒要好
    intrCounter=20;//每一秒对应的中断次数为20
    if (SWITCH1_3==1)
    {
        beginNum+=3;
    }
    if (SWITCH2_2==1)
    {
        beginNum+=2;
    }
    if (SWITCH3_2==1)
    {
        beginNum+=2;
    }
    if (SWITCH4_1==1)
    {
        beginNum+=1;
    }//以上判断语句为判断拨码开关状态
    intrCounter=20*beginNum;//计算总扫描次数
}

/**************************************************************************
* GetPressed
*
* Description: P2口连接抢答端的四位来判断抢答情况
* Arguments : Byte KeyState-->P2 state
* Returns : 抢答端的号码 ; 0-->没人抢答
***************************************************************************/
Byte GetPressed(Byte KeyState)
{
    Byte key;//记录抢答端的号码
    KeyState&=0xf0;//P1口的低四位
   
    switch (KeyState)
    {
        case 0xf0: key=0;break;//全高,无人抢答
        case 0xe0: key=1;break;//只有P1.1,key1抢答
        case 0xd0: key=2;break;//只有P1.2,key2抢答
        case 0xb0: key=3;break;//只有P1.3,key3抢答
        case 0x70: key=4;break;//只有P1.4,key4抢答
    }
    /*
    switch (KeyState)
    {
        case 0x00: key=0;break;//全高,无人抢答
        case 0x01: key=1;break;//只有P1.0,key1抢答
        case 0x02: key=2;break;//只有P1.1,key2抢答
        case 0x04: key=3;break;//只有P1.2,key3抢答
        case 0x08: key=4;break;//只有P1.3,key4抢答
    }
    */
    //上面是在用高电平来判断抢答状态时的程序,经证明不知道为何无效

    return key;   
}

/**************************************************************************
* GetPressedWireless
*
* Description: P2口的高四位于Pt2272接受模块相连,该方法用来判断无线抢答序号
* Arguments : Byte KeyState-->P2 state
* Returns : 抢答端的号码 ; 0-->没人抢答
***************************************************************************/
Byte GetPressedWireless(Byte KeyState)
{
    Byte key;//记录抢答端的号码
    KeyState&=0xf0;//P2口的高四位
   
    /*
    switch (KeyState)
    {
        case 0x0f: key=0;break;//全高,无人抢答
        case 0x0e: key=1;break;//只有P1.1,key1抢答
        case 0x0d: key=2;break;//只有P1.2,key2抢答
        case 0x0b: key=3;break;//只有P1.3,key3抢答
        case 0x07: key=4;break;//只有P1.4,key4抢答
    }
    */

    switch (KeyState)
    {
        case 0x00: key=0;break;//全低,无人抢答
        case 0x10: key=1;break;//只有P1.0,key1抢答
        case 0x20: key=2;break;//只有P1.1,key2抢答
        case 0x40: key=3;break;//只有P1.2,key3抢答
        case 0x80: key=4;break;//只有P1.3,key4抢答
    }
    return key;   
}

/**************************************************************************
* IT0_Init
*
* Description: 初始化计时器T0的状态
* Arguments : none
* Returns : none
***************************************************************************/
void IT0_Init(void)
{
    TMOD=0x21;//设置T0在方式1下工作,同时还要保证T1,也就是波特率所学的定时器的正常工作

    #ifdef CLOCK_FREQUENCY_12M//对使用哪种晶振进行条件编译
    TH0=0x3C;//12M晶振时的装入值
    TL0=0xAF;
    #else
    TH0=0x4C;//11.0529M晶振时装入值
    TL0=0x00;
    #endif

    ET0=1;//使T0中断可以溢出
    EA=1;//开启总中断
    TF0=0;//溢出位清零
    TR0=1;//开启T0
}

/**************************************************************************
* Timer0_Overflow() interrupt 1
*
* Description: 中断溢出服务程序, 采用的是中断方式1, 后面最好不加using选择寄存器组以免与系统用在主程序的寄存器冲突
* Arguments : none
* Returns : none
***************************************************************************/
void Timer0_Overflow() interrupt 1
{
    static Byte times=20;//溢出次数,20次中断来判断1
    //这里存在重大bug,到由于有相应而停止Timer0,再次启用时,这里的second没有回归原值

    if (needResetTimes==1)
    {
        times=20;
        needResetTimes=0;//已重设,不需要再次重新设置溢出次数
    }

    #ifdef CLOCK_FREQUENCY_12M//对使用哪种晶振进行条件编译
    TH0=0x3C;//12M晶振时的装入值
    TL0=0xAF;//这两个寄存器存的是计数器的计数开始的值,计算发现这两个值累加至溢出后正好是50ms
    #else
    TH0=0x4C;//11.0529M晶振时装入值
    TL0=0x00;//同理,这两个寄存器存的是计数器的计数开始的值,计算发现这两个值累加至溢出后正好是50ms
    #endif

    times--;
    intrCounter--;

    /* 原来使用的方法
    if (times==0)//每隔一秒的操作
    {
        MAX7219_DisplayChar(DIG_2,code_table[--showNum]); //要避免用上面的会造成显示"0"后面一秒钟,才进入开始
        times=20;//重新赋值每秒计数器
        if (intrCounter==0)
        {
            TR0=0;//关闭T0计数器
            isStart=1;//计时结束,进入正常抢答
            //SPEAKER_start();//开始抢答的声音
        }
        //待显示"0"以后就开始抢答
        else
        {
            //SPEAKER_count();//倒计时声音        
        }
    }
    */

    if (times==0)//每隔一秒的操作
    {
        //if (showNum!=1) SPEAKER_count();//倒计时声音
        //else SPEAKER_start();//开始抢答的声音
        MAX7219_DisplayChar(DIG_2,code_table[--showNum]);//显示数字
        sendNumber(_CHANGE_TIME_);//给上位机发送要显示数字的命令
        times=20;//重新赋值每秒计数器
     }//待显示"0"以后就开始抢答

     if (intrCounter==0)
     {
        TR0=0;//关闭T0计数器
        isStart=1;//计时结束,进入正常抢答
     }

}

/**************************************************************************
* PressedHandle
*
* Description: 按键处理
* Arguments : Byte keyPressed-->按下的按键
* Returns : none
***************************************************************************/
void PressedHandle(Byte keyPressed)
{
    MAX7219_Clear();//LED clear
    MAX7219_DisplayChar(DIG_2,code_table[keyPressed]);//在右侧数码管显示抢答选手号码,此时没有去判断是否犯规
   
    //给上位机发抢答选手的号码
    sendNumber(keyPressed);
}

/**************************************************************************
* GetOrFoulHandle(Bool state)
*
* Description: 正常抢答或是犯规处理
* Arguments : Bool state-->GETFOUL两个宏的取之之一
* Returns : none
***************************************************************************/
void GetOrFoulHandle(Bool state)
{
    if (!state)
    {
        MAX7219_DisplayChar(DIG_1,LED_FOUL);//如果是犯规的话左边的LED要显示"F",foul
    }
}

/**************************************************************************
* CancelHandle()
*
* Description: 处理主持人取消倒计时
* Arguments : none
* Returns : none
***************************************************************************/
void CancelHandle()
{   
    MAX7219_DisplayChar(DIG_1,LED_C);
    MAX7219_DisplayChar(DIG_2,LED_L);//主持人取消倒计时之后,两个数码管显示"CL"-->cancel
}

/**************************************************************************
* delayus()
*
* Description: 延时程序
* Arguments : t-->us
* Returns : time delayed
***************************************************************************/
void delayus(unsigned char t )
{
     unsigned char j;
    for(;t>0;t--)
        for(j=19;j>0;j--);
}

/**************************************************************************
* SPEAKER_count/start/foul/get()
*
* Description: speaker发声程序->计数/开始/犯规/抢答 四种声音
* Arguments : none
* Returns : none
***************************************************************************/
void SPEAKER_count(void)
{
    unsigned char i;
    for    (i=0;i<10;i++)
    {
        BEEP =1; //点亮
        delayus(20);
        BEEP =0; //熄灭   
        delayus(20);
     }
}
                                                              
void  SPEAKER_start(void)
{
    unsigned char i;
    for(i=0;i<200;i++)
    {
          BEEP =1; //点亮
        delayus(10);
        BEEP =0; //熄灭   
        delayus(10);
    }
}

void  SPEAKER_foul(void)
{
    unsigned char i;
    for(i=0;i<250;i++)
    {
          BEEP =1; //点亮
        delayus(15);
        BEEP =0; //熄灭   
        delayus(17);
    }

}

void  SPEAKER_get(void)
{
    unsigned char i;
    for(i=0;i<250;i++)
    {
          BEEP =1; //点亮
        delayus(10);
        BEEP =0; //熄灭   
        delayus(10);
    }
            
    for(i=0;i<250;i++)
    {
          BEEP =1; //点亮
        delayus(20);
        BEEP =0; //熄灭   
        delayus(20);
    }
}

/**************************************************************************
* initialSerial()
*
* Description: 初始化串口
* Arguments : none
* Returns : none
***************************************************************************/
void initialSerial()
{
    EA=0;//关闭所有中断
    TMOD=0x21;//设置T1定时器的工作模式:方式2;同时保证T0的工作模式正常.这儿太重要了
    TH1=0xFA;
    TL1=0xFA;
    PCON=0x80;//设置SMOD=1
    SCON=0x50;//选择工作方式1,即为UART
    TR1=1;//设置好定时器并打开定时器,定好波特率9600bit/s
}

/**************************************************************************
* sendNumber(int number)
*
* Description: 通过串口发送一位数字
* Arguments : number->要发送的数字
* Returns : none
***************************************************************************/
void sendNumber(int number)
{
    TI=0;
    SBUF=number+48;
    while(!TI);
    TI=0;
}

/**************************************************************************
* sendString(unsigned char *string)
*
* Description: 通过串口发送字符串
* Arguments : 要发送的字符串的头指针
* Returns : none
***************************************************************************/
void sendString(unsigned char *string)
{
    do
    {
        SBUF=*string;
        while(!TI);
        TI=0;
        string++;
    }while(*(string-1)!='\0');

/**************************************************************************
* 主程序
***************************************************************************/
void main()
{   
    //P1=0xff;
    Byte keyPressed,i;//选手按键号码,没有的话为0   
    Bool hostPressed;//用来记录主持人按键取消,0为没有动作,1为取消
    Byte buf[10];//设置发送缓冲区
    number_temp=P1&0xf0;//P1口上次的状态,在调整倒计时时间的时候用到的   
   
    #ifdef USE_SOUND
    LS138_E1=1;         //译码器初始化
    #endif

    needResetTimes=0;//不需要重新设置溢出次数
   
    MAX7219_Init();//数码管初始化
    initialSerial();//初始化串口发送
   
    GetCounter();//获取开始时候设置的倒计时时间   
    MAX7219_DisplayChar(DIG_1,code_table[beginNum]);
//显示开始时设置的倒计时时间,在左位设置     
    MAX7219_DisplayChar(DIG_2,READY);//调时间的时候右位的显示
   
    while(GetHostStartKey()==0)//当主持人没有按键的时候进入循环
    {
        if (number_temp!=(P1&0xf0))
//若调整了倒计时时间,P1口状态变了,就要重新设置和显示
        {
            GetCounter();//获取调整以后的倒计时时间
            MAX7219_DisplayChar(DIG_1,code_table[beginNum]);
//显示调整以后的倒计时时间
            number_temp=P1&0xf0;//记录下来现在P1口的状态,以备后面的比较
        }            
    }    //当主持人按键以后就结束调整进入抢答倒计时
    sendNumber(beginNum);

    MAX7219_DisplayChar(DIG_1,READY);
    while(GetHostCancelKey()==0);
   
    sendNumber(_STRING_READY_);//发送调节完毕准备抢答的命令;

    //调整好倒计时时间后,按下start显示"--",再按下cancel则显示倒计时时间,此时可以开始倒计时了.
    MAX7219_DisplayChar(DIG_1,READY);//清空右边一位数码管
    MAX7219_DisplayChar(DIG_2,code_table[beginNum]);
    for (i=100;i--;i>0)
        Delay10ms();//防止后面出现连读的情况..

    counterBack=intrCounter; //备份要中断的总次数
    while(1)//这里要用自己加的循环来把程序束缚在这里运行
    {
        showNum=beginNum;//设置要显示的时间,当然时从倒计时时间开始
        intrCounter=counterBack;//设置总中断的次数
        TR0=0;//禁用计时器0
        isPressed=0;//记录是否有人按键
        isStart=0;//没有开始抢答
        while(GetHostStartKey()==0);

        //给上位机发送开始倒计时的指令
        sendNumber(_STRING_START_);

        needResetTimes=1;//Timer0已经是第二轮开始,需要重新设置溢出次数
        IT0_Init();//初始化计时器0, 启用.

        MAX7219_DisplayChar(DIG_1,CLEAR);//清空左边一位数码管
        while(!isPressed)//如果没有记录到有人按键就进入
        {
            keyPressed=GetPressed(P2);
//查询一下P2口的状态,即按键情况,P2口的后四位作为抢答端输入,有线时候使用
            //keyPressed=GetPressedWireless(P2);//查询一下P2口的状态,即按键情况,无线使用
            hostPressed=GetHostCancelKey();
            if (!keyPressed&&!hostPressed)//如果没有人按键,就进入下次循环
                continue;
            else
            {
                TR0=0;//关闭定时器
                isPressed=1;//记录到有人按键,提供条件跳出循环
            }
        }

        if (keyPressed!=0)
        {
            if (isStart)//如果已经开始抢答
            {
                PressedHandle(keyPressed);//处理按键,即显示抢答选手号码
                GetOrFoulHandle(GET);//处理抢答

                #ifdef USE_SOUND//对是否使用扬声器进行条件编译
                LS138_E1=0;       //译码器准备工作
                switch (keyPressed)
                {
                    case 1:      LS138_A=0;LS138_B=0;LS138_C=0;break;       //1号成功灯亮
                    case 2:      LS138_A=0;LS138_B=1;LS138_C=0;break;       //2号成功灯亮
                    case 3:  LS138_A=1;LS138_B=0;LS138_C=0;break;       //3号成功灯亮
                    case 4:  LS138_A=1;LS138_B=1;LS138_C=0;break;       //4号成功灯亮
                    default : break;
                }
                #endif
            }   
                                                  
            else//否则,没有开始抢答
            {   
                PressedHandle(keyPressed);//处理按键,即显示抢答选手号码
                GetOrFoulHandle(FOUL);//处理犯规,必须要放在后面,因为显示数字的里面有一个clear

                #ifdef USE_SOUND//对是否使用扬声器进行条件编译
                LS138_E1=0;       //译码器准备工作
                switch (keyPressed)
                {
                    case 1: LS138_A=0;LS138_B=0;LS138_C=1;break;                                       //1号犯规灯亮
                    case 2:  LS138_A=0;LS138_B=1;LS138_C=1;break;       //2号犯规灯亮
                    case 3:  LS138_A=1;LS138_B=0;LS138_C=1;break;       //3号犯规灯亮
                    case 4:  LS138_A=1;LS138_B=1;LS138_C=1;break;       //4号犯规灯亮
                    default : break;
                }
                #endif
            }
        }

        if (hostPressed==1)
        {
            CancelHandle();
            //向上位机发送主持人取消的指令
            sendNumber(_STRING_CANCEL_);
            for (i=100;i--;i>0)
                Delay10ms();//防止后面出现连读的情况..        
        }
        while(GetHostCancelKey()==0);//如果主持人没有按键再次开始,则停在次死循环处
         
        #ifdef USE_SOUND//对是否使用扬声器进行条件编译
        LS138_E1=1;        //关闭译码器
        #endif

        MAX7219_DisplayChar(DIG_1,READY);//清空右边一位数码管
        MAX7219_DisplayChar(DIG_2,code_table[beginNum]);//左位显示设置的倒计时时间

        //给上位机发送重新开始倒计时的指令
        sendNumber(_STRING_READY_);

         //到这里一次循环结束
    }
}   

51智能8路抢答器.zip

109.62 KB, 下载次数: 22, 下载积分: 黑币 -5

评分

参与人数 1黑币 +60 收起 理由
admin + 60 分享一个好帖#单片机论坛#《基于AT89S51的.

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:126485 发表于 2016-6-14 00:39 | 只看该作者
学习下感谢楼主
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表