一.GPIO
1.1 GPIO简介
通用目的输入输出**(genral purpose input/output)**
相当于嵌入式中的一个通信协议
1.2 引脚两种模式
输出模式:
给定单片机引脚电平,去控制引脚输出高/低电平,LED控制等等
输入模式:
检测引脚电平变化(用于检测外部电平信号),按键检测,检测ADC信号等等
用途:
温度传感器与单片机的连接:VCC,GND,数据线data,单片机要发送起始信号,
eg:500ms高电平,40us低电平(GPIO输出),接收传感器数据(检测data信号电平)

二. 独立按键
2.1 独立按键模块
普中51单片机的独立按键模块:
按键的一端接到GND,另一端接到了P14 - P17

2.2 按键初始化
可以看到按键按下时,P14~P17引脚输入低电平 ,所以初始化按键时,将P14~P17引脚全初始化为高电平即可
示例代码:
cpp
//按键初始化
void key_init()
{
P1 |= (0x0F << 4);
}
2.3 按键检测
检测某一个按键是否按下时,检测对应的引脚是否为低电平 即可,使用**&操作**即可
示例代码:
cpp
//按键检测
int key_press()
{
int ret = 0;
if((P1 & (1 << 4)) == 0)
{
ret = 1;
}
if((P1 & (1 << 5)) == 0)
{
ret = 2;
}
if((P1 & (1 << 6)) == 0)
{
ret = 3;
}
if((P1 & (1 << 7)) == 0)
{
ret = 4;
}
return ret;
}
三. 中断
3.1 中断基本概念
中断概念:
CPU在处理某个任务时,被外界更为紧急的事件打断,要求CPU去处理这个更为紧急的
事件,处理完紧急事件后,再次回到之前被打断的地方继续向下执行

中断源:
打断CPU当前任务的任务源头/事件
80C51单片机有5个中断源 :
(1)INT0 ,外部中断0。可由IT0 选择其有效方式。当CPU检测到P3.2/INT0 引脚上出现有效的中断信号时,中断标志IE0 置"1",向CPU申请中断。
(2)INT1 ,外部中断1。可由IT1 选择其有效方式。当CPU检测到P3.3/INT1 引脚上出现有效的中断信号时,中断标志IE1 置"1",向CPU申请中断。
(3)TF0 ,片内定时/计数器T0 溢出中断。当定时/计数器T0发生溢出时,置位TF0,并向CPU申请中断
(4)TF1 ,片内定时/计数器T1 溢出中断。当定时/计数器T1发生溢出时,置位TF1,并向CPU申请中断。
(5)RI或TI ,串行口中断。当串行口接收完一帧串行数据时置位RI或当串行口发送完一帧串行数据时置位TI,向CPU申请中断。
中断源的5分类:
外部中断0:int0
定时器0:timer0
外部中断1:int1
定时器1:timer1
串口:urat
中断源优先级:
CPU总是先处理优先级高的中断,处理完优先级高的再处理优先级低的中断
默认优先级: INT0>TIMER0>INT1>TIMER1>UART
中断嵌套:
处理一个中断时,又来了一个比此中断优先级高的中断,CPU就去处理优先级高的中断
然后返回优先级低的中断接着处理
注:SOC一般不允许中断嵌套,51单片机最大允许两层中断嵌套
中断处理流程:
从中断源->CPU处理完中断的过程
1、中断源发出中断请求
2、检查CPU是否允许中断 ,中断总开关(能够响应外部中断)及该中断源是否被屏蔽(子开
关,能够产生对应的中断)-->通过寄存器配置
3、比较中断优先级
4、保护现场
5、执行中断服务函数,是一个回调函数
6、恢复现场
中断回调函数:
eg:外部中断0: void Int0_handler(void) interrupt0
通过中断向量访问中断函数,类似一个数组下标
函数指针数组 就是中断向量表
**中断向量表:**本质是一个数组,数组中存放中断服务函数的入口地址,函数指针数组
**中断向量:**中断向量表中的标号,通过标号可以访问到对应的中断服务函数
中断号:

3.2 有关中断的寄存器
与STC89C51RC/RD+系列单片机中断有关的寄存器:

与定时器有关的寄存器

3.2.1 IE - 中断允许控制寄存器
CPU对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器IE控制的。
IE的状态可通过程序由软件设定。某位设定为1,相应的中断源中断允许;某位设定为0,相应的中断源中断屏蔽。
CPU复位时,IE各位清0,禁止所有中断。
IE寄存器各位定义如下:

|---------|--------------|
| EX0 | INT0中断允许位 |
| ET0 | T0中断允许位 |
| EX1 | INT1中断允许位 |
| ET1 | T1中断允许位 |
| ES | 串口中断允许位 |
| EA | PU中断允许(总允许)位 |
3.2.2 IP - 中断优先级控制寄存器
80CS1单片机有两个中断优先级,即可实现二级中断服务嵌套。每个中断源的优先级都是由中断优先级寄存器IP中的相应位来规定的。
IP由软件设定,某位设定为1,则相应的中断源为高优先级;某位设定为0,则相应的中断源为低优先级。
单片机复位时,IP各位清0,各中断源同为低优先级。IP寄存器各位定义如下:

|---------|--------------|
| PX0 | INT0中断优先级设定位 |
| PT0 | T0中断优先级设定位 |
| PX1 | INT1中断优先级设定位 |
| PT1 | T1中断优先级设定位 |
| PS | 串口中断优先级设定位 |
3.2.3 TCON - 中断控制寄存器

|---------|---------------------------------------------------------------------------------------------------|
| TF1 | T1溢出中断标志。 T1被允许计数以后,从初开始加1计数,当产生溢出时, 由硬件置 "1" TF1, 向CPU请求中断,一直维持CPU响应该中断时, 才由硬件清0(也可由查询 软件清0) |
| TR1 | 定时器1的运行控制位 |
| TF0 | T0溢出中断标志。 T0被允许计数以后,从初开始加1计数,当产生溢出时, 由硬件置 "1" TF0, 向CPU请求中断,一直维持CPU响应该中断时, 才由硬件清0(也可由查询 软件清0) |
| TR0 | 定时器0的运行控制位 |
| IE1 | 外部中断1请求源( INT1/P3.3)标志。 IE1=1外部中断1向CPU请求中断,当CPU响应外 部中断时, 由硬件清"0" IE1(边沿触发方式 IT1 = 1) |
| IT1 | 外部中断1中断源类型选择位。 IT0=0, INT1/P3.3引脚上的低电平可触发外部中断1。 IT0=1,外部中断1为下降沿触发方式 |
| IE0 | 外部中断0请求源( INT0/P3.2)标志。 IE0=1外部中断0向CPU请求中断,当CPU响应外 部中断时, 由硬件清"0" IE0(边沿触发方式 IT0 = 1) |
| IT0 | 外部中断0中断源类型选择位。 IT0=0, INT0/P3.2引脚上的低电平可触发外部中断0。 IT0=1,外部中断0为下降沿触发方式 |
3.2.4 TMOD - 定时器/计数器工作模式寄存器
定时和计数功能由特殊功能寄存器TMOD的控制位C/T进行选择

|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| GATE | TMOD.7控制定时器1,置1时只有在IINT1脚为高及TR1控制位置1时才可打开定时器/计数器1。 |
| C/T | TMOD.6控制定时器1用作定时器或计数器,清零则用作定时器(从内部系统时钟输入),置1用作计数器(从T1/P3.5脚输入) |
| M1 M0 | 00:13位定时器/计数器,TL1只用低5位参与分频,TH1整个8位全用。 01:16位定时器/计数器,TL1、TH1全用 10:8位自动重装载定时器,当溢出时将TH1存放的值自动重装入TL1. 11:定时器/计数器1此时无效(停止计数)。 |
| GATE | TMOD.3控制定时器0,置1时只有在IINT0脚为高及TR0控制位置1时才可打开定时器/计数器0。 |
| C/T | TMOD.2控制定时器0用作定时器或计数器,清零则用作定时器(从内部系统时钟输入),置1用作计数器(从T0/P3.4脚输入) |
| M1 M0 | 00:13位定时器/计数器,TL1只用低5位参与分频,TH1整个8位全用。 01:16位定时器/计数器,TL1、TH1全用 10:8位自动重装载定时器,当溢出时将TH1存放的值自动重装入TL1. 11:定时器0此时作为双8位定时器/计数器。TL0作为一个8位定时器/计数器,通过标准定时器 0的控制位控制。THO仅作为一个8位定时器,由定时器1的控制位控制。 |
3.2.5 两个16位计数寄存器
TH0、TL0、TH1、TL1
每个定时器拆成高 8 位 TH + 低 8 位 TL,组合成 16 位计数器
TL0:Timer0 低 8 位计数寄存器
TH0:Timer0 高 8 位计数寄存器
TL1:Timer1 低 8 位计数寄存器
TH1:Timer1 高 8 位计数寄存器
3.3 外部中断0,INT0
可以看到外部中断0 对应的引脚是P32引脚

3.3.1 INT0 操作流程
1、 将P32,外部中断0引脚拉高:P3 |= (1 << 2);
2、设置外部中断0引脚的中断触发方式:下降沿触发,TCON |= (1 << 0)
3、IE寄存器,将外部中断0中断打开,将CPU中断总开关打开:IE |= (1 << 0); IE |= (1 << 7);
示例代码:
cpp
/初始化外部中断0
void int0_init()
{
/将外部中断0对应的引脚拉高
P3 |= (1 << 2);
/设置外部中断0为边沿触发模式
TCON |= (1 << 0);
/将外部中断0允许位置1,允许中断
IE |= (1 << 0);
/将CPU总中断允许位置1,CPU开放中断
IE |= (1 << 7);
}
3.3.2 中断处理函数
示例代码:
cpp
/外部中断0服务函数
void int0_handler(void) interrupt 0
{
g_i++;
}
3.4 定时/计数器
3.4.1 工作模式
51单片机中有两个16位定时器:timer0与timer1
工作模式:
1、16位计数器模式,必须手动重装
定时最大值:65535
步骤:
unsigned short cnt = 0;
赋初值 cnt = 64535
打开计数器,自增型
按照固定的频率++
计数器加到65535,再加1会溢出->CPU定时器中断请求
从初值+到溢出所经历的时间:与初值大小,频率 都有关
实现1ms精准定时
注意: 单片机的晶振频率:12MHZ/11.0592MHZ,但实际达不到,内部有分频器(分
12):12/12 = 1MHZ,对应的时间1/1MHZ=1us
相关寄存器:
TCON
TF0
TR0
TMOD(89H)
M0=1
M1=0
2、8位自动重装模式
常用于串口通信
只有TL参与计数,计数范围0-255,自动重装TH里面的值到TL中
M1 = 1, M0 = 0;
3.4.2 操作步骤
定时器操作步骤
1、配置定时器0工作模式,16位定时器
2、TL0,TH0设置定时器初值,TMOD中的M0置1,bit0
在设置TH0与TL0时,使用移位操作
3、打开定时器0并计数,TCON中的TR0置1,定时器T0运行控制位,bit4
4、配置IE寄存器ET0与EA置1,bit1与bit7
注意: 在16位定时模式下**,**在定时器中断函数中,一定要重装初值
示例代码:
cpp
/初始化定时器timer0
void timer0_init(void)
{
/配置定时器工作模式
TMOD &= ~(0x0F << 0);/配置的是定时器0,先将TMOD寄存器的低四位清零
TMOD |= (1 << 0);
/设置定时器初值
TH0 = 64535 >> 8;/高8位
TL0 = 64535;/低8位
/打开定时器开始工作
TCON |= (1 << 4);
/定时器T0允许控制位置1
IE |= (1 << 1);
/打开CPU总中断
IE |= (1 << 7);
}
中断函数示例代码:
cpp
/定时器0中断处理函数
void timer0_handler() interrupt 1
{
/重装初值
TH0 = 64535 >> 8;
TL0 = 64535;
g_i++;
if(g_i >= 1000)
{
g_i = 0;
g_time++;
led_nor();
}
}
四. 蜂鸣器与PWM
4.1 蜂鸣器分类
有源蜂鸣器:
源:震荡源
内部存在震荡源,通电后可以发出持续固定频率的声音,频率固定
无源蜂鸣器:
不存在震荡源,需要给定对应引脚一个高低电平产生震荡,可以通过PWM使其输出指定
频率音调
4.2 PWM 基本概念
PWM:
脉冲宽度调制:
能够让引脚产生方波,能够让引脚周期性的产生电平变化
PWM周期:
一个方波所经历的周期(上升沿到下一个上升沿所经历的时间)
PWM占空比:
在一个周期内高电平所占的比例(LCD屏幕亮度的调节。LED灯亮度的调节)
4.3 原理图

4.4 实例
使用蜂鸣器实现声音频率为200HZ、400HZ、600HZ、800HZ,占空比为50%的声音
定时初值计算方式:

计算结果:

示例代码:
根据按下按键的不同来输出不同频率的声音
按键模块:
cpp
//按键初始化
extern void key_init();
//按键检测
extern int key_press();
//按键初始化
void key_init()
{
P1 |= (0x0F << 4);
}
//按键检测
int key_press()
{
int ret = 0;
if((P1 & (1 << 4)) == 0)
{
ret = 1;
}
if((P1 & (1 << 5)) == 0)
{
ret = 2;
}
if((P1 & (1 << 6)) == 0)
{
ret = 3;
}
if((P1 & (1 << 7)) == 0)
{
ret = 4;
}
return ret;
}
定时器模块:
cpp
extern unsigned int g_i;
/初始化定时器timer0
extern void timer0_init(void);
unsigned int g_i = 0;
/初始化定时器timer0
void timer0_init(void)
{
/配置定时器工作模式
TMOD &= ~(0x0F << 0);
TMOD |= (1 << 0);
/设置定时器初值
TH0 = g_i >> 8;
TL0 = g_i;
/打开定时器开始工作
TCON |= (1 << 4);
/定时器T0允许控制位置1
IE |= (1 << 1);
/打开CPU总中断
IE |= (1 << 7);
}
/定时器0中断处理函数
void timer0_handler() interrupt 1
{
TH0 = g_i >> 8;
TL0 = g_i;
P2 ^= (1 << 1);/翻转P21,对应连接蜂鸣器的引脚,使蜂鸣器产生震荡
}
主函数模块:
cpp
/不同频率声音的定时初值
#define HZ_200 63231
#define HZ_400 64383
#define HZ_600 64765
#define HZ_800 64959
#define HZ_1000 65489
/主函数
int main()
{
int ret = 0;
key_init();
timer0_init();
while(1)
{
ret = key_press();
if(ret == 1)
{
g_i = HZ_200;
}
if(ret == 2)
{
g_i = HZ_400;
}
if(ret == 3)
{
g_i = HZ_600;
}
if(ret == 4)
{
g_i = HZ_800;
}
}
return 0;
}