2017-12-04 14:46:01 u011153192 阅读数 5275
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3419 人正在学习 去看看 朱有鹏
因为51单片机I/O口内部结构的限制,在读取外部引脚状态的时候,需要向端口写1.51单片机复位后,不需要进行此操作也可以进行读取外部引脚的操作。因此,在按键的端口没有复用的情况下,可以省略此步骤。而对于其它一些真正双向I/O口的单片机来说,将引脚设置成输入状态,是必不可少的一个步骤。
下面的程序代码初始化引脚为输入。
void KeyInit(void)
{
    io_key_1 = 1 ;
    io_key_2 = 1 ;
    io_key_3 = 1 ;
    io_key_4 = 1 ;            
}
根据按键硬件连接定义按键键值
#define KEY_VALUE_1              0x0e
#define KEY_VALUE_2              0x0d
#define KEY_VALUE_3                0x0b
#define KEY_VALUE_4                0x07
#define KEY_NULL                    0x0f
下面我们来编写按键的硬件驱动程序。
根据第一章所描述的按键检测原理,我们可以很容易的得出如下的代码:
static uint8 KeyScan(void)
{
    if(io_key_1 == 0)return KEY_VALUE_1 ;
    if(io_key_2 == 0)return KEY_VALUE_2 ;
    if(io_key_3 == 0)return KEY_VALUE_3 ;
    if(io_key_4 == 0)return KEY_VALUE_4 ;
    return KEY_NULL ;
}
其中io_key_1等是我们按键端口的定义,如下所示:
sbit io_key_1 = P3^0 ;
sbit io_key_2 = P3^1 ;
sbit io_key_3 = P3^2 ;
sbit io_key_4 = P3^3 ;

KeyScan()
作为底层按键的驱动程序,为上层按键扫描提供一个接口,这样我们编写的上层按键扫描函数可以几乎不用修改就可以拿到我们的其它程序中去使用,使得程序复用性大大提高。同时,通过有意识的将与底层硬件连接紧密的程序和与硬件无关的代码分开写,使得程序结构层次清晰,可移植性也更好。对于单片机类的程序而言,能够做到函数级别的代码重用已经足够了。
在编写我们的上层按键扫描函数之前,需要先完成一些宏定义。
//
定义长按键的TICK,以及连_发间隔的TICK
#define KEY_LONG_PERIOD        100
#define KEY_CONTINUE_PERIOD    25

//
定义按键返回值状态(按下,长按,_,释放)
#define KEY_DOWN                0x80
#define KEY_LONG                    0x40
#define KEY_CONTINUE            0x20
#define KEY_UP                  0x10

//
定义按键状态
#define KEY_STATE_INIT            0
#define KEY_STATE_WOBBLE            1
#define KEY_STATE_PRESS            2
#define KEY_STATE_LONG            3
#define KEY_STATE_CONTINUE      4
#define KEY_STATE_RELEASE        5

接着我们开始编写完整的上层按键扫描函数,按键的短按,长按,连按,释放等等状态的判断均是在此函数中完成。对照状态流程转移图,然后再看下面的函数代码,可以更容易的去理解函数的执行流程。完整的函数代码如下:

void GetKey(uint8 *pKeyValue)
{
    static uint8 s_u8KeyState =KEY_STATE_INIT ;
    static uint8 s_u8KeyTimeCount = 0 ;
    static uint8 s_u8LastKey = KEY_NULL;  //
保存按键释放时候的键值
    uint8 KeyTemp = KEY_NULL ;

    KeyTemp = KeyScan() ;        //
获取键值

    switch(s_u8KeyState)
    {
        case KEY_STATE_INIT :
                {
                    if(KEY_NULL !=(KeyTemp))
                    {
                        s_u8KeyState =KEY_STATE_WOBBLE ;
                    }
                }
        break ;

        case KEY_STATE_WOBBLE :      //
消抖
                {
                    s_u8KeyState =KEY_STATE_PRESS ;    
                }
        break ;

        case KEY_STATE_PRESS :
                {
                    if(KEY_NULL !=(KeyTemp))
                    {
                        s_u8LastKey =KeyTemp ; //
保存键值,以便在释放按键状态返回键值
                        KeyTemp |=KEY_DOWN ;  //
按键按下
                        s_u8KeyState =KEY_STATE_LONG ;
                    }
                    else
                    {
                        s_u8KeyState =KEY_STATE_INIT ;
                    }
                }
        break ;

        case KEY_STATE_LONG :
                {
                    if(KEY_NULL !=(KeyTemp))
                    {
                       if(++s_u8KeyTimeCount > KEY_LONG_PERIOD)
                        {
                           s_u8KeyTimeCount = 0 ;
                            KeyTemp |= KEY_LONG ;  //
长按键事件发生
                            s_u8KeyState= KEY_STATE_CONTINUE ;
                        }
                    }
                    else
                    {
                        s_u8KeyState =KEY_STATE_RELEASE ;
                    }
                }
        break ;

        case KEY_STATE_CONTINUE :
                {
                    if(KEY_NULL !=(KeyTemp))
                    {
                       if(++s_u8KeyTimeCount > KEY_CONTINUE_PERIOD)
                        {
                           s_u8KeyTimeCount = 0 ;
                            KeyTemp |=KEY_CONTINUE ;
                        }
                    }
                    else
                    {
                        s_u8KeyState = KEY_STATE_RELEASE;
                    }
                }
        break ;

        case KEY_STATE_RELEASE :
                {
                    s_u8LastKey |= KEY_UP;
                    KeyTemp = s_u8LastKey;
                    s_u8KeyState = KEY_STATE_INIT;
                }
        break ;

        default : break ;
    }
    *pKeyValue = KeyTemp ; //
返回键值    
}
关于这个函数内部的细节我并不打算花过多笔墨去讲解。对照着按键状态流程转移图,然后去看程序代码,你会发现其实思路非常清晰。最能让人理解透彻的,莫非就是将整个程序自己看懂,然后想象为什么这个地方要这样写,抱着思考的态度去阅读程序,你会发现自己的程序水平会慢慢的提高。所以我更希望的是你能够认认真真的看完,然后思考。也许你会收获更多。
不管怎么样,这样的一个程序已经完成了本章开始时候要求的功能:按下,长按,连按,释放。事实上,如果掌握了这种基于状态转移的思想,你会发现要求实现其它按键功能,譬如,多键按下,功能键等等,亦相当简单,在下一章,我们就去实现它。
在主程序中我编写了这样的一段代码,来演示我实现的按键功能。
void main(void)
{    
    uint8 KeyValue = KEY_NULL;
    uint8 temp = 0 ;
      LED_CS11 = 1 ; //流水灯输出允许
    LED_SEG = 0 ;
    LED_DIG = 0 ;
    Timer0Init() ;
    KeyInit() ;
    EA = 1 ;
    while(1)
    {
        Timer0MainLoop() ;
        KeyMainLoop(&KeyValue) ;
        
        if(KeyValue == (KEY_VALUE_1 |KEY_DOWN)) P0 = ~1 ;
        if(KeyValue == (KEY_VALUE_1 |KEY_LONG)) P0 = ~2 ;
        if(KeyValue == (KEY_VALUE_1 |KEY_CONTINUE)) { P0 ^= 0xf0;}
        if(KeyValue == (KEY_VALUE_1 |KEY_UP)) P0 = 0xa5 ;
    }

}
    按住第一个键,可以清晰的看到P0口所接的LED的状态的变化。当按键按下时候,第一个LED灯亮,等待2 S后第二个LED亮,第一个熄灭,表示长按事件发生。再过500 ms 第5~8个LED闪烁,表示连按事件发生。当释放按键时候,P0口所接的LED的状态为:
灭亮灭亮亮灭亮灭,这也正是P0 = 0xa5这条语句的功能。
2019-01-22 20:40:49 qq_41151162 阅读数 1034
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3419 人正在学习 去看看 朱有鹏

51的P0 - P3都是准双向I/O端口,作为输入端口时需要先将其置1,然后再读取引脚状态。

这里抛开汇编,单纯讲下C51编程时的哪种情况是读引脚、哪种情况是读端口,以及读引脚前为什么要置1,以防止大家出错。


1.读引脚:(下面是测试代码)

#include <reg51.h>

sbit LED =  P0^0;    //外加上拉电阻
sbit f = P0^1;

void main()
{
	while(1)
        {
	        if(f == 0)    //如果P0.1是低电平的话就让LED亮
	        {
		    LED = 0;
	        }
        }
}

单片机通电复位后,P1口的锁存器Q端都为1,对应的MOS管不导通,P1口在上拉电阻作用下全是高电平,所以LED不亮,用导线将P0.1口拉低,LED瞬间点亮;

说明 if(f == 0)判断的P0.1引脚的状态即读引脚,LED是否点亮受P0.1引脚状态影响。

51单片机通电复位后直接读引脚是没问题的。

但大家可能发现:先将P0.0置0,然后用导线将它拉高,单片机也可以读出此引脚是高电平,这种做法很危险!

将P0.0置0后,对应的MOS管导通,VCC和GND间通过这个MOS管连接,由于电流较大以及MOS管的电阻作用使得P0.0引脚被拉高(通过万用表可以测出这个高电平较正常情况下低一些,单片机的其他端口的电平状态可能也不正常了),但这会缩短单片机寿命,因此读引脚前一定要向该引脚写一!然后读取引脚电平。(可以说,先写1 就是为了避免这种情况)

2.读端口:(下面是测试代码)

#include <reg51.h>

sbit LED =  P0^0;

void delay()
{
	unsigned char i = 255;
	while(--i);
}

void main()
{
	//先将P0.0引脚用导线拉低
	delay();
	LED = ~LED;    //LED低电平亮
	//拔出导线,将P0.0接到LED负极上
}

P0.0开始被拉低了,那么对其取反之后,P0.0应该为1,LED应该不亮,但实际是LED亮了,因为这里

不是读引脚而是读端口(锁存器):单片机上电后P0口全是1,其内部的锁存器输出端Q也为1,虽然P0.0引脚被

拉低了,但是其锁存器Q端仍为1不变,对1取反后再赋值给P0.0口,点亮了LED。

可以总结下:

1. 51单片机里对某一个I/O口进行读--改--写(例如上面的取反后写回)操作时,读取的是与之对应的锁存器的输出,而不

是实际物理引脚的电平;其他非读--改--写(例如上面的f == 0)指令读取的是引脚电平。

2. 读引脚电平时一定要先向该端口写1,然后再读取引脚电平!

 

 

 

 

2019-01-05 20:03:30 whynat 阅读数 567
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3419 人正在学习 去看看 朱有鹏

要解答这个问题,我们先考虑一种情况,假如我们要对P0.1引脚输出(赋值)为1,那么单片机内部是如何工作的?我的分析如下:
我们知道,引脚属于IO设备,单片机内部CPU是通过地址总线,数据总线,控制总线对其进行赋值的,也就是说CPU不可能只对P0.1赋值,应该是对整个P0端口整体赋值,但是我们确实在编程过程中是可以通过指令对P0.1进行赋值。假如要给P0.1置位1,这个时候CPU的工作应该是这样的,CPU先读取整个P0寄存器的数值,然后将其跟1000 0000 进行或操作,再送回至P0寄存器。这样除了将P0.1引脚置1,其他引脚状态不变。
那么如果我们读取不是寄存器而是引脚的话就会出现问题,因为在某些情况下(应该说这种情况很多,比如我们将P0口接上拉电阻,然后检测其引脚状态,这个时候引脚状态是随外电路变化而变化的)引脚的状态与寄存器输出不相同,就会使得我们在对P0.1置位的同时其他引脚也被错误的置位。
以上就是为什么会有读锁存器与读引脚两个选择的原因。

2017-05-07 21:03:00 weixin_30376083 阅读数 33
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3419 人正在学习 去看看 朱有鹏

利用外围电路控制单片机的引脚电平

  前几天帮一个朋友处理一些电路,正好解决了自己以前经常遇到的一个问题:传感器检测信号传送给单片机,如何改变单片机引脚电平信号,使之可在程序中直接读取,进而实现对外部环境的检测。

以前的思路:

(1)最初,我将检测信号直接传入控制器,由于外围电路的驱动能力不够,不足以拉高单片机的电平,一直都是失败的。
(2)经过查找资料后,单片机的大部分引脚均可做I/O口,默认高电平,并且在做输入口时,必须先将该引脚设置为高电平。我用三极管做了个开关电路,由于当时只焊接了电路,没有画电路图,所以现在也忘记了是如何实现的,但是三极管的开关电路方面的资料有很多,有需要可自行查找。

当下实现方案:使用继电器,实现开关

(1)思路分析:
  法很简单,既然单片机的引脚默认高电平,接地后就能满足低电平了。那么,就用继电器开关控制引脚与地的连接,传感器的检测信号控制继电器的开关状态,间接控制单片机的引脚电平。
  其实,这也是继电器的小电流控制大电流的思想,与三极管的开关电路的思想基本一致,只是三极管的电阻电容需要选择合适大小,而继电器就没有什么需要太多考虑的了,可能需要考虑成本。

(2)电路图及仿真分析:
1115880-20170507205038898-1935943104.jpg
红色标记第一处为传感器检测信号输出口;
第二处是NPN三极管,信号驱动三极管的基极,当第一处信号为高是,三极管导通,使第三处的继电器开关换向;
当第三处开关换向后,第四处与单片机连接的引脚电平被拉低

仿真效果图

无检测信号时,单片机引脚P3.0为高电平

1115880-20170507210023336-1121516380.jpg

当有检测信号时,单片机引脚P3.0为地点低电平

1115880-20170507210126226-46216276.jpg

转载于:https://www.cnblogs.com/HZL2017/p/6822279.html

2019-09-15 13:17:20 qq_44051174 阅读数 80
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3419 人正在学习 去看看 朱有鹏

在这里插入图片描述如图,是一个三极管驱动继电器的电路,当P2.6=1时,继电器会吸和,此时如果写if(P2.61) LED=0;
LED永远都不会动作,虽然在软件中写了P2.6=1,但是P2.6的实际电位是低电位,if函数不会成立,这种情况能加一个标志位,P2.6=1,flag=1,然后再if(flag
1) LED=0;

注:if(P2.6==1) LED=0;只是为了省时间,实际中需要先定义P2.6

单片机产生随机数

阅读数 591

没有更多推荐了,返回首页