硬知识
概念

GPIO(general purpose intput output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出。

I/O口配置
STC89C52系列单片机所有I/O口均(新增P4口)有3种工作类型:准双向口/弱上拉(标准8051输出模式)、仅为输入(高阻)或开漏输出功能。STC89C52系列单片机的P1/P2/P3/P4上电复位后为准双向口/弱上拉(传统8051的I/O口)模式,P0口上电复位后是开漏输出。P0口作为总线扩展用时,不用加上拉电阻,作为I/O口用时,需加10K-4.7K上拉电阻。
STC89C52的5V单片机的P0口的灌电流最大为12mA,其他1/0口的灌电流最大为6mA STC89LE52系列的3V单片机的PO口的灌电流最大为8mA,其他1/0口的灌电流最大为4mA.

准双向口/弱上拉输出配置
准双向口输出类型可用作输出和输入功能而不需重新配置端口输出状态。这是因为当端口输出为1时驱动能力很弱,允许外部装置将其拉低。当引脚输出为低时,它的驱动能力很强,可吸收相当大的电流。准双向口有3个上拉晶体管适应不同的需要。
在3个上拉晶体管中,有1个上拉晶体管称为“弱上拉”,当端口寄存器为1且引脚本身也为1时打开。此上拉提供基本驱动电流使准双向口输出为1。如果一个引脚输出为1而由外部装置下拉到低时,弱上拉关闭而“极弱上拉”维持开状态,为了把这个引脚强拉为低,外部装置必须有足够的灌电流能力使引脚上的电压降到门槛电压以下。
第2个上拉晶体管,称为“极弱上拉”,当端口锁存为1时打开。当引脚悬空时,这个极弱的上拉源产生很弱的上拉电流将引脚上拉为高电平。
第3个上拉晶体管称为“强上拉”。当端口锁存器由0到1跳变时,这个上拉用来加快准双向口由逻辑0到逻辑1转换。当发生这种情况时,强上拉打开约2个时钟以使引脚能够迅速地上拉到高电平。

开漏输出配置
P0口上电复位后处于开漏模式,当P0管脚作I/O口时,需外加10K-4.7K的上拉电阻,当P0管脚作为地址/数据复用总线使用时,不用外加上拉电阻。
当端口锁存器为0时,开漏输出关闭所有上拉晶体管。当作为一个逻辑输出时,这种配置方式必须有外部上拉,一般通过电阻外接到Vcc。如果外部有上拉电阻,开漏的I/O口还可读外部状态,即此时被配置为开漏模式的I/O口还可作为输入I/O口。这种方式的下拉与准双向口相同。

实战

延时函数的生成
使用软件延时计算器生成延时函数
系统频率选择和开发板上的晶振频率一致


闪烁一个LED灯
源码

#include <REGX52.H>
#include "intrins.h"

sbit D1 = P2^0;            //将P2.0管脚定义为D1

void Delay500ms()        //@11.0592MHz
{
    unsigned char i, j, k;

    _nop_();
    i = 4;
    j = 129;
    k = 119;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

void main(void)
{
    while(1)
    {
        D1 = ~D1;
        Delay500ms();
    }
}

实验现象
烧录后可见D1闪烁

流水灯
由原理图可知,P2口控制的LED灯为低电平点亮。

源码
stdint.h内容见【51单片机快速入门指南】一、基础知识和工程创建

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"

#define LED_PORT    P2    //使用宏定义P2端口

void Delay500ms()        //@11.0592MHz
{
    unsigned char i, j, k;

    _nop_();
    i = 4;
    j = 129;
    k = 119;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

void main(void)
{    
       uint8_t i = 0;

    LED_PORT= ~(uint8_t)1;
    while(1)
    {
        //方法1:使用移位+循环实现流水灯
        for(i=0; i<8; i++)
        {
            LED_PORT=~(0x01<<i);    //将1右移i位,然后取反将结果赋值到LED_PORT
            Delay500ms();
        }
        for(i=0; i<8; i++)
        {
            LED_PORT=~(0x80>>i);    //将1右移i位,然后取反将结果赋值到LED_PORT
            Delay500ms();
        }

        //方法2:使用循环+_crol_或_cror_函数实现流水灯
        for(i=0; i<7; i++)             //将led左移一位
        {                                      
            LED_PORT = _crol_(LED_PORT,1);
            Delay500ms();     
        }
        for(i=0; i<7; i++)            //将led右移一位
        {
            LED_PORT = _cror_(LED_PORT,1);
            Delay500ms();    
        }    
    }        
}

实验现象

按键控制LED灯
由原理图可知,K1按下后P31接地,其值应为0

按键介绍
按键是一种电子开关,使用时轻轻按开关按钮就可使开关接通,当松开手时, 开关断开。开发板上使用的按键及内部简易图如下图所示:


按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态, 如果按键按下,初始导通状态变为断开,初始断开状态变为导通。 通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,电压信号如下图所示:

由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间 的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖。
按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。
软件消抖
一般来说一个简单的按键软件消抖就是先读取按键的状态,如果得到按键按下之后,延时一段时间,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。其中延时就是软件消抖处理。
硬件消抖
示例:

源码

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"

sbit K1 = P3^1;
sbit D1 = P2^0;

void Delay20ms()                //@11.0592MHz
{
    unsigned char i, j;

    i = 36;
    j = 217;
    do
    {
        while (--j);
    } while (--i);
}

void main(void)
{    
    while(1)
    {
        if(K1 == 0)
        {
            Delay20ms();        //消抖
            if(K1 == 0)
            {
                D1 = ~D1;
                while(K1 == 0);    //等待按键释放
            }
        }
    }        
}

实验现象
按下K1后可见D1翻转。