嵌入式基础学习笔记(51)

一、GPIO(通用输入输出)

1. GPIO基本概念

  • 定义:General Purpose Input Output,单片机与外界交互最基本的形式

  • 特点:可以独立将引脚配置为输入模式或输出模式

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;
    }
}
相关推荐
小鱼23332 小时前
STM32中的中断机制与应用
c语言·stm32·单片机·嵌入式硬件·mcu
musenh2 小时前
spring学习1
java·学习·spring
Engineer邓祥浩2 小时前
设计模式学习(12) 23-10 外观模式
学习·设计模式·外观模式
爱潜水的小L2 小时前
自学嵌入式day44,51单片机
单片机·嵌入式硬件
专注于大数据技术栈2 小时前
java学习--Vector
java·学习
v先v关v住v获v取2 小时前
ZY8600-25-50型掩护式液压支架设计支撑6张cad+设计说明书
科技·单片机·51单片机
宵时待雨2 小时前
STM32笔记归纳1:STM32的基本信息与引脚分布
笔记·stm32·嵌入式硬件
CS Beginner2 小时前
STM32F103ZET6中I2C、SPI和USART
单片机
_叶小格_2 小时前
ansible自动化入门基础
运维·笔记·学习·自动化·ansible