一、GPIO(通用输入输出)
1. GPIO基本概念
2. GPIO工作模式
| 模式类型 |
子模式 |
功能描述 |
应用场景 |
| 输出模式 |
开漏输出 |
只能输出低电平或高阻态 |
I2C总线等 |
|
推挽输出 |
可输出强高/低电平 |
LED驱动、数码管 |
|
复用开漏 |
外设功能开漏输出 |
I2C复用 |
|
复用推挽 |
外设功能推挽输出 |
UART、SPI |
| 输入模式 |
上拉输入 |
内部上拉电阻使能 |
按键检测 |
|
下拉输入 |
内部下拉电阻使能 |
特殊应用 |
|
浮空输入 |
高阻抗输入状态 |
外部中断 |
|
模拟输入 |
模拟信号输入 |
ADC采样 |
3. GPIO配置示例代码
cs
复制代码
/**
* GPIO配置示例
* P1口配置为推挽输出,P3.2配置为上拉输入
*/
#include <reg52.h>
// 宏定义
#define LED_PORT P1 // LED连接到P1口
#define KEY_PIN P3_2 // 独立按键连接到P3.2
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 120; j++);
}
void main() {
// 初始化GPIO
LED_PORT = 0xFF; // 初始化为高电平(LED灭)
while(1) {
// 检测按键状态
if(KEY_PIN == 0) { // 按键按下(低电平)
delay_ms(10); // 消抖延时
if(KEY_PIN == 0) { // 确认按键按下
LED_PORT = ~LED_PORT; // 翻转LED状态
while(!KEY_PIN); // 等待按键释放
}
}
}
}
二、独立按键
1. 独立按键工作原理
| 状态 |
引脚电平 |
电路状态 |
| 按键未按下 |
高电平(VCC通过上拉电阻) |
引脚与GND断开 |
| 按键按下 |
低电平(引脚与GND短路) |
引脚直接接地 |
2. 按键消抖处理
| 消抖方法 |
原理 |
实现方式 |
| 硬件消抖 |
RC电路滤波 |
并联电容 |
| 软件消抖 |
延时检测 |
检测后延时10-20ms再确认 |
3. 独立按键示例代码
c
复制代码
/**
* 独立按键检测示例
* 4个独立按键控制4个LED
*/
#include <reg52.h>
// 引脚定义
sbit KEY1 = P3^0; // 按键1
sbit KEY2 = P3^1; // 按键2
sbit KEY3 = P3^2; // 按键3
sbit KEY4 = P3^3; // 按键4
sbit LED1 = P1^0; // LED1
sbit LED2 = P1^1; // LED2
sbit LED3 = P1^2; // LED3
sbit LED4 = P1^3; // LED4
// 消抖延时函数
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 120; j++);
}
// 按键检测函数
unsigned char key_scan() {
unsigned char key_value = 0;
if(KEY1 == 0) { key_value = 1; }
if(KEY2 == 0) { key_value = 2; }
if(KEY3 == 0) { key_value = 3; }
if(KEY4 == 0) { key_value = 4; }
return key_value;
}
void main() {
unsigned char key_val;
// 初始化LED为熄灭状态
LED1 = LED2 = LED3 = LED4 = 1;
while(1) {
key_val = key_scan(); // 检测按键
if(key_val != 0) {
delay_ms(10); // 消抖延时
if(key_val == key_scan()) { // 确认按键
// 根据按键值控制LED
switch(key_val) {
case 1: LED1 = ~LED1; break;
case 2: LED2 = ~LED2; break;
case 3: LED3 = ~LED3; break;
case 4: LED4 = ~LED4; break;
}
// 等待按键释放
while(key_scan() != 0);
}
}
}
}
三、中断系统
1. 中断基本概念
| 概念 |
描述 |
说明 |
| 中断 |
CPU暂停当前任务处理紧急事件 |
提高响应速度 |
| 中断源 |
引发中断的事件或设备 |
51单片机有5个中断源 |
| 中断优先级 |
决定同时请求时的处理顺序 |
4个优先级 |
| 中断嵌套 |
处理中断时响应更高优先级中断 |
最多嵌套2层 |
2. 51单片机中断源
| 中断源 |
中断向量地址 |
触发条件 |
优先级顺序 |
| 外部中断0 |
0003H |
INT0引脚电平变化 |
最高(0) |
| 定时器0 |
000BH |
T0计数器溢出 |
1 |
| 外部中断1 |
0013H |
INT1引脚电平变化 |
2 |
| 定时器1 |
001BH |
T1计数器溢出 |
3 |
| 串口中断 |
0023H |
串口收发完成 |
4 |
3. 中断优先级寄存器(IP、IPH)
| 寄存器 |
地址 |
功能 |
位定义 |
| IP |
B8H |
中断优先级低字节 |
见下表 |
| IPH |
B7H |
中断优先级高字节 |
见下表 |
中断优先级设置表:
| 优先级设置 |
含义 |
中断优先级 |
| 0.0 |
PX0H=0, PX0=0 |
优先级0(最低) |
| 0.1 |
PX0H=0, PX0=1 |
优先级1 |
| 1.0 |
PX0H=1, PX0=0 |
优先级2 |
| 1.1 |
PX0H=1, PX0=1 |
优先级3(最高) |
4. 中断相关寄存器
中断允许寄存器(IE - 地址A8H):
| 位 |
符号 |
功能 |
说明 |
| B7 |
EA |
总中断允许 |
1:允许所有中断 |
| B6 |
- |
保留位 |
|
| B5 |
ET2 |
定时器2中断允许 |
1:允许T2中断 |
| B4 |
ES |
串口中断允许 |
1:允许串口中断 |
| B3 |
ET1 |
定时器1中断允许 |
1:允许T1中断 |
| B2 |
EX1 |
外部中断1允许 |
1:允许INT1中断 |
| B1 |
ET0 |
定时器0中断允许 |
1:允许T0中断 |
| B0 |
EX0 |
外部中断0允许 |
1:允许INT0中断 |
定时器控制寄存器(TCON - 地址88H):
| 位 |
符号 |
功能 |
说明 |
| B7 |
TF1 |
定时器1溢出标志 |
溢出时硬件置1 |
| B6 |
TR1 |
定时器1运行控制 |
1:启动定时器1 |
| B5 |
TF0 |
定时器0溢出标志 |
溢出时硬件置1 |
| B4 |
TR0 |
定时器0运行控制 |
1:启动定时器0 |
| B3 |
IE1 |
外部中断1标志 |
INT1中断请求标志 |
| B2 |
IT1 |
外部中断1类型 |
0:低电平触发,1:下降沿触发 |
| B1 |
IE0 |
外部中断0标志 |
INT0中断请求标志 |
| B0 |
IT0 |
外部中断0类型 |
0:低电平触发,1:下降沿触发 |
5. 外部中断示例代码
cs
复制代码
/**
* 外部中断示例
* INT0(P3.2)控制LED翻转
*/
#include <reg52.h>
// 引脚定义
sbit LED = P1^0; // LED连接到P1.0
sbit KEY_INT0 = P3^2; // 外部中断0按键
// 外部中断0服务函数
void int0_isr() interrupt 0 {
LED = ~LED; // LED状态翻转
}
void main() {
// 初始化
LED = 0; // LED初始熄灭
// 配置外部中断0
IT0 = 1; // 设置为下降沿触发(1:下降沿, 0:低电平)
EX0 = 1; // 允许外部中断0
EA = 1; // 开启总中断
while(1) {
// 主循环可执行其他任务
// 中断发生时自动跳转到int0_isr函数
}
}
6. 中断嵌套示例
cs
复制代码
/**
* 中断嵌套示例
* 外部中断0(高优先级)和定时器0中断(低优先级)
*/
#include <reg52.h>
// 全局变量
unsigned int timer0_count = 0;
// 定时器0中断服务函数(低优先级)
void timer0_isr() interrupt 1 {
TH0 = (65536 - 50000) / 256; // 重装50ms初值
TL0 = (65536 - 50000) % 256;
timer0_count++;
// 每10次中断(0.5秒)翻转P1.1
if(timer0_count >= 10) {
P1_1 = ~P1_1;
timer0_count = 0;
}
}
// 外部中断0服务函数(高优先级)
void int0_isr() interrupt 0 {
// 高优先级中断可以打断低优先级中断
P1_0 = ~P1_0; // 翻转P1.0
}
void main() {
// 配置中断优先级
PX0 = 1; // 外部中断0设为高优先级
PT0 = 0; // 定时器0设为低优先级
// 配置定时器0
TMOD = 0x01; // 定时器0,模式1(16位定时器)
TH0 = (65536 - 50000) / 256; // 50ms初值
TL0 = (65536 - 50000) % 256;
TR0 = 1; // 启动定时器0
ET0 = 1; // 允许定时器0中断
// 配置外部中断0
IT0 = 1; // 下降沿触发
EX0 = 1; // 允许外部中断0
EA = 1; // 开启总中断
while(1) {
// 主程序空循环
}
}
四、定时器
1. 定时器基本概念
| 特性 |
描述 |
| 数量 |
2个(Timer0和Timer1) |
| 类型 |
16位自增型定时器 |
| 工作模式 |
4种工作模式 |
| 时钟源 |
系统时钟分频 |
2. 定时器工作模式(TMOD寄存器)
| 模式 |
M1 M0 |
描述 |
特点 |
| 模式0 |
0 0 |
13位定时器 |
兼容8048,TL用低5位 |
| 模式1 |
0 1 |
16位定时器 |
常用模式,TH+TL全用 |
| 模式2 |
1 0 |
8位自动重装 |
自动重装载初值 |
| 模式3 |
1 1 |
双8位定时器 |
T0分为两个8位定时器 |
TMOD寄存器结构:
| 位 |
符号 |
功能(高4位-T1) |
功能(低4位-T0) |
| B7 |
GATE |
门控位(T1) |
|
| B6 |
C/T |
计数/定时选择(T1) |
|
| B5 |
M1 |
模式选择高位(T1) |
|
| B4 |
M0 |
模式选择低位(T1) |
|
| B3 |
GATE |
门控位(T0) |
|
| B2 |
C/T |
计数/定时选择(T0) |
|
| B1 |
M1 |
模式选择高位(T0) |
|
| B0 |
M0 |
模式选择低位(T0) |
|
3. 定时器初值计算方法
| 时钟频率 |
定时时间 |
计算公式 |
示例 |
| 12MHz |
1ms |
初值 = 65536 - 1000 |
TH0=0xFC, TL0=0x18 |
| 12MHz |
50ms |
初值 = 65536 - 50000 |
TH0=0x3C, TL0=0xB0 |
| 11.0592MHz |
1ms |
初值 = 65536 - 921.6 |
TH0=0xFC, TL0=0x66 |
4. 定时器示例代码
cs
复制代码
/**
* 定时器0示例
* 使用定时器0实现1秒精确延时
*/
#include <reg52.h>
// 全局变量
unsigned int timer0_count = 0;
sbit LED = P1^0;
// 定时器0中断服务函数
void timer0_isr() interrupt 1 {
// 重装50ms初值(假设晶振12MHz)
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
timer0_count++;
// 20次中断 = 1秒(50ms × 20 = 1000ms)
if(timer0_count >= 20) {
LED = ~LED; // 翻转LED
timer0_count = 0; // 计数器清零
}
}
void main() {
// 配置定时器0
TMOD = 0x01; // 定时器0,模式1(16位定时器)
TH0 = (65536 - 50000) / 256; // 50ms初值
TL0 = (65536 - 50000) % 256;
// 开启中断
ET0 = 1; // 允许定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 开启总中断
LED = 0; // 初始化LED状态
while(1) {
// 主程序可执行其他任务
// 定时器中断会周期性执行
}
}
5. 定时器模式2示例(自动重装载)
cs
复制代码
/**
* 定时器模式2示例
* 8位自动重装载模式,产生精确的100us定时
*/
#include <reg52.h>
sbit LED = P1^0;
unsigned char t_count = 0;
// 定时器0中断服务函数(模式2自动重装)
void timer0_isr() interrupt 1 {
t_count++;
// 100us × 10000 = 1秒
if(t_count >= 100) {
LED = ~LED; // 每0.01秒翻转一次
t_count = 0;
}
}
void main() {
// 配置定时器0为模式2(8位自动重装)
TMOD = 0x02; // 模式2
// 计算100us的初值(12MHz晶振)
// 机器周期 = 1us,100us需要100个计数
TH0 = 256 - 100; // 自动重装载值
TL0 = 256 - 100; // 初始计数值
// 开启中断
ET0 = 1; // 允许定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 开启总中断
LED = 0; // 初始化LED
while(1) {
// 主程序
}
}
五、PWM(脉冲宽度调制)
1. PWM基本概念
| 概念 |
定义 |
公式 |
| PWM周期 |
一个完整方波的时间 |
T = 1/f |
| 占空比 |
高电平时间占周期的比例 |
D = Ton/T |
| 频率 |
单位时间内周期数 |
f = 1/T |
2. PWM波形示意图
text
复制代码
占空比 = 25%: ▁▁▁▁▇▇▇▇▁▁▁▁▇▇▇▇
占空比 = 50%: ▁▁▁▇▇▇▇▁▁▁▇▇▇▇
占空比 = 75%: ▁▇▇▇▇▁▇▇▇▇▁▇▇▇▇
3. PWM控制LED亮度示例
cs
复制代码
/**
* PWM控制LED亮度示例
* 使用定时器产生PWM信号控制LED亮度
*/
#include <reg52.h>
// 全局变量
unsigned char pwm_duty = 50; // 初始占空比50%
unsigned char pwm_counter = 0;
sbit LED = P1^0;
// 定时器0中断服务函数
void timer0_isr() interrupt 1 {
TH0 = (65536 - 100) / 256; // 重装100us初值
TL0 = (65536 - 100) % 256;
pwm_counter++;
if(pwm_counter >= 100) { // PWM周期 = 100×100us = 10ms
pwm_counter = 0;
}
// 根据占空比控制LED
if(pwm_counter < pwm_duty) {
LED = 0; // 低电平点亮(假设共阳极)
} else {
LED = 1; // 高电平熄灭
}
}
void main() {
// 配置定时器0
TMOD = 0x01; // 模式1,16位定时器
TH0 = (65536 - 100) / 256; // 100us初值
TL0 = (65536 - 100) % 256;
// 开启中断
ET0 = 1; // 允许定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 开启总中断
while(1) {
// 可以通过按键改变pwm_duty值来调整亮度
// 例如:pwm_duty从0到100变化,实现呼吸灯效果
}
}
六、蜂鸣器控制
1. 蜂鸣器类型对比
| 特性 |
有源蜂鸣器 |
无源蜂鸣器 |
| 驱动方式 |
直流电压 |
方波信号 |
| 音调 |
固定频率 |
可调频率 |
| 控制 |
简单(电平控制) |
复杂(需要PWM) |
| 成本 |
较低 |
较高 |
| 应用 |
报警、提示音 |
音乐播放 |
2. 蜂鸣器驱动电路
text
复制代码
有源蜂鸣器:VCC → 蜂鸣器 → NPN三极管 → GND
无源蜂鸣器:PWM信号 → 蜂鸣器 → 三极管放大
3. 蜂鸣器驱动示例代码
cs
复制代码
/**
* 作业示例:按键控制蜂鸣器不同频率
* Key1: 200Hz, Key2: 400Hz, Key3: 600Hz, Key4: 800Hz, Key5: 1000Hz
*/
#include <reg52.h>
// 引脚定义
sbit BEEP = P1^5; // 蜂鸣器(无源)
sbit KEY1 = P3^0;
sbit KEY2 = P3^1;
sbit KEY3 = P3^2;
sbit KEY4 = P3^3;
sbit KEY5 = P3^4;
// 全局变量
unsigned int freq_table[5] = {200, 400, 600, 800, 1000}; // 频率表
unsigned int current_freq = 200; // 当前频率
unsigned int timer_reload; // 定时器重装值
// 定时器0中断服务函数
void timer0_isr() interrupt 1 {
// 根据当前频率重装定时器初值
TH0 = timer_reload / 256;
TL0 = timer_reload % 256;
BEEP = ~BEEP; // 翻转蜂鸣器引脚,产生方波
}
// 初始化定时器
void timer0_init(unsigned int freq) {
unsigned long reload;
// 计算定时器初值(12MHz晶振)
// 方波周期T = 1/f,半周期 = 1/(2f)
// 定时时间 = 1/(2f)秒
// 机器周期 = 1us,所以计数值 = 1/(2f) / 0.000001 = 500000/f
reload = 65536 - (500000UL / freq);
timer_reload = (unsigned int)reload;
// 配置定时器0
TMOD &= 0xF0; // 清除T0配置位
TMOD |= 0x01; // T0模式1,16位定时器
TH0 = timer_reload / 256;
TL0 = timer_reload % 256;
ET0 = 1; // 允许定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 开启总中断
}
// 按键扫描函数
unsigned char key_scan() {
unsigned char key_val = 0;
if(KEY1 == 0) key_val = 1;
else if(KEY2 == 0) key_val = 2;
else if(KEY3 == 0) key_val = 3;
else if(KEY4 == 0) key_val = 4;
else if(KEY5 == 0) key_val = 5;
return key_val;
}
// 消抖延时
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 120; j++);
}
void main() {
unsigned char key_val, last_key = 0;
// 初始化蜂鸣器为关闭状态
BEEP = 0;
// 初始化为200Hz
timer0_init(current_freq);
while(1) {
key_val = key_scan();
if(key_val != 0 && key_val != last_key) {
delay_ms(10); // 消抖
if(key_val == key_scan()) { // 确认按键
switch(key_val) {
case 1: current_freq = 200; break;
case 2: current_freq = 400; break;
case 3: current_freq = 600; break;
case 4: current_freq = 800; break;
case 5: current_freq = 1000; break;
}
// 重新配置定时器
TR0 = 0; // 先关闭定时器
timer0_init(current_freq);
// 等待按键释放
while(key_scan() != 0);
}
}
last_key = key_val;
}
}