目录
1.GPIO
概念:GPIO(general purpose intput output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出,是单片机与 LED、按键、传感器等外设通信的桥梁。。
功能: GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
输出模式:控制外设
输出模式下,单片机可主动输出高电平(VCC)或低电平(GND),驱动外设工作,主要分为 4 种类型:
- 推挽输出:引脚可直接输出高 / 低电平,驱动能力强(如直接点亮 LED),是最常用的输出模式;
- 开漏输出:仅能输出低电平,高电平需依赖外部上拉电阻实现,适合多设备总线通信(如 I2C 总线);
- 复用开漏:引脚复用为外设功能(如串口 TX),同时保持开漏特性;
- 复用推挽:引脚复用为外设功能(如定时器 PWM 输出),同时保持推挽驱动能力。
输入模式:检测引脚电平变化
输入模式下,单片机被动检测引脚的电平状态,获取外部设备的信号,主要分为 4 种类型:
- 上拉输入:引脚通过内部 / 外部上拉电阻接 VCC,未检测到信号时默认输出高电平(如按键未按下时);
- 下拉输入:引脚通过内部 / 外部下拉电阻接 GND,未检测到信号时默认输出低电平;
- 浮空输入:引脚不接上下拉电阻,电平状态完全由外部信号决定(易受干扰,适合高精度信号检测);
- 模拟输入:引脚作为模拟信号通道,用于连接 ADC(模数转换器),检测连续变化的模拟量(如温度传感器信号)。
LED
即发光二极管。它具有单向导电性 ,通过 5mA 左右电流即可发光 ,电流越大,其亮度越强,但若电流过大,会烧毁二极管,一般我们控制在3 mA-20mA 之间,通常我们会在 LED 管脚上串联一个限流电阻。。当发光二极管发光时,测量它两端电压约为 1.7V ,这个电压又叫做发光二极管的"导通压降"。
实验一:点亮LED
cpp
/****************************************************************
实验名称:点亮第一个LED
接线说明:
实验现象:下载程序后"LED模块"的D1指示灯点亮
注意事项:
*****************************************************************/
#include"reg52.h"
sbit LED1=P2^0;//将P2.0管脚定义为LED1
/****************************************************************
*函 数 名:main
*函数功能:主函数
*输入:无
*输出:无
*****************************************************************/
void main()
{
LED1=0;//LED1端口设置为低电平
while(1)
{
}
}
编译结果:
可以看到没有错误,也没有警告。
从编译信息可以看出,我们的代码占用FLASH 大小为:19 字节,所用的 SRAM 大小为:9 个字节(9+0)。
这里我们解释一下,编译结果里面的几个数据的意义:
code:表示程序所占用FLASH的大小。
data:数据储存器内部RAM占用大小。
xdata:数据储存器外部RAM占用大小。
一定要注意的是程序的大小不是.hex 文件的大小,而是编译后的 code 和 data 之和。
cpp
/****************************************************************
常用重定义数据类型:
关键字 typedef 对系统默认数据类型重新命名,可以方便代码的书写和变量类型的查看。有了这个
就知道参数的传送范围,不能超过形参定义的范围。
*****************************************************************/
typedef unsigned int u16;//对系统默认数据类型进行重命名
typedef unsigned char u8;
实验二:LED流水灯
cpp
/****************************************************************
实验名称:使用移位和循环实现LED流水灯
接线说明:
实验现象:下载程序后实现LED流水灯
注意事项:
*****************************************************************/
#include"reg52.h"
#define LED_PORT P2//使用宏定义P2端口
typedef unsigned char u8;
/****************************************************************
*函 数 名:delay_10us
*函数功能:延时函数,ten_us=1时,大约延时10us
*输入:ten_us
*输出:无
**********************************************************************/
void delay_10us(u16ten_us)
{
while(ten_us--);
}
/****************************************************************
*函 数 名:main
*函数功能:主函数
*输入:无
*输出:无
*****************************************************************/
void main()
{
u8 i=0;
while(1)
{
for(i=0;i<8;i++)
{
LED_PORT=~(0x01<<i);//将1右移i位,然后取反将结果赋值到LED_PORT
delay_10us(50000);
}
}
}
cpp
/****************************************************************
实验名称:使用KEIL C51 软件内的移位库函数--左移_crol_、右移_cror_函数实现LED流水灯
接线说明:
实验现象:下载程序后实现LED流水灯
注意事项:
*****************************************************************/
#include"reg52.h"
#include"intrins.h"
/****************************************************************
*头文件:intrins.h
*函数功能:intrins.h 头文件中定义了KEIL C51 软件内的移位库函数,左移函数
是_crol_(),右移函数是_cror_()。这两个移位函数内部实现过程是看不到的,该
移位函数实现的移位功能就相当于一个队列内循环移动
****************************************************************/
#define LED_PORT P2//使用宏定义P2端口
typedef unsigned char u8;
/****************************************************************
*函 数 名:delay_10us
*函数功能:延时函数,ten_us=1时,大约延时10us
*输入:ten_us
*输出:无
**********************************************************************/
void delay_10us(u16ten_us)
{
while(ten_us--);
}
/****************************************************************
*函 数 名:main
*函数功能:主函数
*输入:无
*输出:无
*****************************************************************/
void main()
{
u8 i=0;
LED_PORT=~0x01;
delay_10us(50000);
while(1)
{
for(i=0;i<7;i++)//将led左移一位
{
LED_PORT=_crol_(LED_PORT,1);
delay_10us(50000);
}
for(i=0;i<7;i++)//将led右移一位
{
LED_PORT=_cror_(LED_PORT,1);
delay_10us(50000);
}
}
}
cpp
//LED功能函数
// led灯全亮
void led_all_on(void)
{
P2 = 0;
}
// led灯全灭
void led_all_off(void)
{
P2 = 0xFF;
}
// led翻转
void led_nor(void)
{
P2 = P2 ^ 0xFF;
}
// 指定某个led亮
void led_on(unsigned int n)
{
P2 = ~n;
}
LED的高级玩法:
- LED点阵:可显示数字和图形。
数码管
51 开发板通常搭载4 位共阴极数码管,特性如下:
- 显示规则:同一时刻仅能点亮 1 位数码管,通过 "位选→段选" 的顺序控制
位选:通过 NPN 三极管基极(P10~P13 引脚)输出高电平,选中目标数码管
段选:给对应段引脚输出高电平,点亮数码管的特定段(如 a 段、b 段)
- 动态显示原理:利用人眼视觉暂留效应(时间差不得大于 24ms),快速刷新 4 位数码管的显示状态,实现 "同时亮" 的视觉效果
- NPN 三极管导通条件:基极输入高电平
数码管实物图:

开发板原理图:

使用注意:
- 如果使用共阴数码管 : 需要注意增加单片机 IO 口驱动电流,因为共阴数码管是要靠单片机 IO 口输出电流来点亮的,但单片机 I/O 口难以输出稳定的、如此大的电流,所以数码管与单片机连接时需要加驱动电路,可以用上拉电阻的方法、三极管或使用专门的数码管驱动芯片,比如 74HC573、74HC245 等,其输出电流较大,电路接口简单。
- 如果使用共阳数码管 : 数码管内部发光二极管点亮时,也需要 5mA 以上的电流,而且电流不可过大,否则会烧坏发光二极管。因此不仅要防止数码管电流过大,同时要防止流经数码管的电流集中到单片机时电流不能过大,否则会损坏主芯片。
- 常用数码管:一般共阳极数码管更为常用------因为IC 芯片的驱动能力往往是比较小的,如果采用共阴极数码管,它的驱动端有可能受限于 IC 芯片输出电流不够而显示
昏暗,要外加上拉电阻或者是增加三极管加大驱动能力。但是 IC 芯片的灌电流,
即输入电流范围比较大。所以使用共阳极数码管的好处是:将驱动数码管的工作
交到公共端(一般接驱动电源),加大驱动电源的功率自然要比加大 IC 芯片 I/O
口的驱动电流简单许多。另一方面,这样也能减轻主芯片的负担。- 若多个数码管,可以使用 IO 扩展芯片,比如 74HC138、74HC164、74HC595(串转并芯片,仅需单片机 3 个 IO 口即可扩展 8 个) 、74HC138 译码器芯片(如下图)等
74HC138 :是一种三通道输入、八通道输出译码器,主要应用于消费类电子产品。总结:A0、A1、A2输入就相当于3位2进制数,A0是低位,A1是次高位,A2是高位。而Y0-Y7具体哪一个输出有效电平,就看输入二进制对应的十进制数值。比如输入是101(A2,A1,A0),其对应的十进制数是5,所以Y5输出有效电平(低电平)。
更多详细介绍,可以在芯片数据手册查看
74HC245 :是一种三态输出、八路信号双向收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动。
数码管码表
共阴数码管码表:
cpp//共阴极数码管显示0~F的段码数据,最后的0X00表示无显示 u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0X00};共阳数码管码表:
cpp//共阳极数码管显示0~F的段码数据,最后的0XFF表示无显示 u8 gsmg_code[17]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92,0x82, 0xF8, 0x80, 0x90, 0x88, 0x83,0xC6, 0xA1, 0x86, 0x8E, 0xFF};注意: a 段作为最低位,b 段作为次低位,其他按顺序类推,dp 段为最高位,共 8 位。
cpp
//数码管功能函数
#include <reg51.h>
#include "delay.h"
// 段码表
//共阴极数码管显示0~F的段码数据,最后的0X00表示无显示
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0X00};
// 位选
void Bit_Select(unsigned int n)
{
P1 &= ~(0x0F << 0);
P1 |= (1 << n);
}
// 段选
void Seg_Select(unsigned int n)
{
P0 = seg_table[n];
}
// 动态显示函数:将一个无符号整数n逐位分解,通过动态扫描方式在多位数码管上显示
// 参数n:需要显示的无符号整数(非负整数)
void digiter_show(unsigned int n)
{
int m = 0;
int t = 0;
while (n != 0)
{
m = n % 10;
P0 = 0;
Bit_Select(t++);
Seg_Select(m);
delay(100);
n = n / 10;
}
}
独立按键
独立按键介绍:
- 实物图


- 注意:按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态, 如
果按键按下,初始导通状态变为断开,初始断开状态变为导通。
**消抖:**抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时 10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。

注意:函数内定义的static 变量 key,相当于全局变量。
cpp
//按键功能函数
// 按键初始化
void key_init(void)
{
P1 |= (0x0F << 4);
P3 |= (1 << 5);
}
// 判断按键是否被按下
int key_press(void)
{
int ret = 0;
if ((P1 & (1 << 4)) == 0) // KEY1
{
ret = 1;
}
else if ((P1 &(1 << 5)) == 0) // KEY2
{
ret = 2;
}
else if ((P1 & (1 << 6)) == 0) // KEY3
{
ret = 3;
}
else if ((P1 & (1 << 7)) == 0) // KEY4
{
ret = 4;
}
else if ((P3 & (1 << 5)) == 0) // KEY5
{
ret = 5;
}
return ret;
}
按键的高级玩法:
- 矩阵按键:可以节约IO口
- 按键的长按,短按,双击,三击等等

