Skip to content
成为赞助商

51单片机

基础知识

  • 开发工具:keil5 编写程序、STC-ISP 下载程序、烧录软件-找厂商要
  • 说明:本文档基于 STC89C52 单片机

51单片机

  • 单片机、简称 MCU
  • 将CPU、存储器【RAM、ROM】、输出输出、定时器、中断系统、通讯接口等集成在一个芯片上的微型计算机。成本低、体积小、结构简单、易于编程。
  • 单片机的任务是:信息采集(依靠传感器)、处理(依靠CPU)、控制(电机、LED等)
  • 51单片机指所有 兼容Inter 8031指令系统的单片机的统称。 实现8051指令集
STC89C52
  • 位数:8位单片机;寄存器、数据总线、计算逻辑单元等也都是8位,一次能处理8位数据。
  • RAM:512字节 - 掉电丢失
  • ROM:8kb(Flash)- 掉电不丢失 1k=1024b 1b=8字节
  • 工作频率/速度:不同板子不一定相同(单片机晶振决定?)
  • 命名规则:

image-20250210201203826

image-20250210201358920

image-20231027085413330

image-20231027085446712

引脚
  • 在单片机中有寄存器(存储器)、且8个为一组、每个寄存器都通过一个驱动器(驱动增大电流)连接一个IO口
  • CPU可以直接访问并控制寄存器中的值,从而控制IO的输出
  • 通过CPU控制寄存器、完成对硬件电路的控制
  • 在51中一般使用16进制进行控制

image-20250213182140314

最小系统

image-20250213184350823

三级管

image-20250219122247867

软件准备

image-20250213185606945

STC89C52支持
  • 下载并激活keil【略】
  • 下载STC-ISP
  • 配置STC-ISP【将我们的芯片database 导入到 keil 】
    • 关闭 keil
    • 坐上角 芯片型号 - 选择 STC89C52/LE52RC
    • 右上 keil仿真设置 - 选择 “添加型号和头文件到keil中...”
    • 弹出框中选择keil安装路径
  • 使用 keil 创建项目
    • Poject - new Poject - 设置存放的目录
    • 选择使用的芯片,下图例:STC89C52RC
    • 确定添加启动文件
      • 在单片机复位时执行,清除数据+初始化操作
    • image-20250217192056847
中文乱码解决
  • Edit - Configuration - Encoding - 选择支持中文的字符集即可 GB2312
  • 如果报错,可能是没有修改的权限,需用管理员身份运行
编译配置(魔法棒)
  • Output
    • 勾选 Create HEX File , 生成HEX文件
编译
  • 左上角编译按钮 3个
    1. 只编译当前文件,不会触及其他文件的链接【用于检查当前文件】
    2. 差量化编译, 编译当前Target目录下修改过的所有文件,产生链接【生成可执行文件】
    3. 完全重新编译,【生成可执行文件】
  • 注意
    • 能被开发板识别的可执行文件为 hex 后缀
    • 编译后的文件可通过,项目文件夹 - Objects 文件夹下查看
demo
  • 使用kile5新建项目
  • 第一步:设置项目名及保存的路径
  • 第二步:配置·单片机型号;stc89c52
  • 第三步:新建main.c,开写
c
#include <REGX52.H>
void main(){
    P2 = 0xFE;	// 设置P2组的引脚值,控制高低电平 1111 1110
}



#include <STC89C5xRC.H> // 包含STC89C52的头文件
void main(){
	P00 = 0; // 让P00引脚输出低电平
	while(1); // 死循环,让程序持续运行,从而使单片机持续工作
}

image-20250219121552251

代码烧录
  • 核心板默认不支持USB协议进行数据传输,支持传统串口协议
  • 在开核心板上,存在 CH340 芯片,实现 USB转串口 的协议
  • 注意
    • windows可能需要安装 CH340驱动

image-20250218174135129

使用VSCode

  • Embedded IDE 官方文档
    • VSCode 插件,用来开发单片机项目,比如:8051, stm8, stm32, other cortex-m mcus ...
  • 使用步骤
    • 安装插件
    • 创建项目- 空项目-根据需要选择
    • 打开项目-并切换为工作区
    • 插件配置 - 设置工具链 - 配置相关工具的路径
    • 插件配置 - 项目属性 - 包含目录 - 引入相关INC目录
    • 编译代码 - 成功后就可以使用【后续使用STC-ISP烧录】
      • VSCODE中需要安装 python,借助 py脚本实现

image-20250221173900213

image-20250224192612460

image-20250224192912547

image-20250224193040831

知识点

头文件

头文件重复处理
  • 同一份头文件可能被不同文件引用,避免重复被执行,应使用#ifndef #define #endif做处理
c
#ifndef 模块名称
#define 模块名称

// .h 头定义内容...

#endif
STC89C5xRC.H

包含STC89C52的头文件,keil 代码中 鼠标右键 open document xxx 查看具体内容

  • sfr(Special Function Register)
    • keil C51编译器提供的扩展数据类型,用于声明单片机中的特殊功能寄存器
    • 特殊功能寄存器:用于控制、配置单片机中的各种硬件设备,如GPIO端口、定时器、串口等
    • 每组GPIO端口均有一个对应的8为寄存器
    • P0 的 0x80表示第0组的寄存器地址,不能修改,可通过单片机手册查看
      • sfr P0 = 0x00含义:向 P0 所指向的寄存器赋值为 0x00
  • sbit
    • sbit 表示 特殊功能寄存器中的一位
    • keil C51编译器提供的扩展数据类型,取值为 0/1

image-20250218181512350

零星知识

c
sfr P0 = 0x80;  // p0从此仅表示对应寄存器上的8位,可通过 p0 = 0x01 对其8位赋值
    sbit P00 = P0^0; // p00 仅表示p0上的最低位,可通过 p0 = 1 对这个位赋值

P00 = ~P00; 	// ~ 取反符号


/* 合理改值【位运算 运用】
- P1 有8位,如何修改其中的某3位; P15 P14 P13
- 思路:先把指定的几位改为0;将要修改的数据移动到对应二进制位;两数相或;
*/
positon <<= 3;
P1 &= 0xC7;
P1 |= postion;

C89变量语法

  • 所有的变量都,必须写在当前作用域的最前方

数据类型

image-20231031084039088

image-20250213190316156

模块化编程

将不同的功能、模块拆分到不同文件,再用头文件相互引用。

变量命名
  • g_前缀标识全局变量,例g_system_state
  • s_前缀标识静态变量,例s_internal_counter
  • st_前缀标识静态变量,例st_data
  • p_前缀标识指针,例p_num
  • p_st前缀标识结构体指针,例p_st_data
  • 常量和宏定义使用全大写字母,并用下划线分割单词,例MAX_VALUE
  • 变量通常用小写字母,单词间使用下划线连接,例如sensor_value
  • 结构体类型声明使用_Struct后缀,例Date_Struct
  • 枚举类型名称使用大写字母开始,例ColorType
  • 枚举值使用全大写字母,并用下划线分割单词,COLOR_RED
  • 内部变量都要加static 关键字,只能当前文件访问的变量
函数命名
  • 采用{分层}_{模块}_{功能}作为函数名称,
  • 返回布尔值的函数,名称应是一个问题或断言,IsButtonPressend()
  • 获取属性/变量值的函数使用Get前缀
  • 设置属性/变量值的函数使用Set前缀
  • 内部函数都要加static关键字
文件命名
  • 使用{分层}_{模块}作为文件名称
  • 如果项目规模较大,建议头文件和源文件分开
代码分层规范
  • 工具函数和常规宏定义
    • 特定算法、工具函数、常规宏定义
    • 目录 COM
    • 前缀 COM_
  • 驱动层
    • 所有与自身芯片硬件直接交互的代码,例如 GPIO开关、硬件UART、ADC驱动、计时器等
    • 目录 Dri/
    • 前缀 Dri_
  • 接口层
    • 位于驱动之上,通过标准接口(GPIO、UART、IIC、SPI等)驱动外部硬件的代码,没有外部硬件可不用这一层
    • 目录 Int/
    • 前缀 int_
  • 中间层
    • 提供更高级的服务,如操作系统、文件系统、通信协议栈等。通常用于复杂单片机项目,简单项目不涉及这一层
    • 目录:Mid/
    • 前缀:Mid_
  • 应用层
    • 包含应用程序的主要逻辑,只与 中间层/接口层 进行交互,尽量不直接访问驱动层
    • 目录:App/
    • 前缀:App_

中断系统

中断系统:单片机CPU用于处理外部紧急事件的机制,使单片机能够实时响应外部事件,提高系统的灵活性和响应能力

  • 概念

    • 中断源:能够引发中断的事件
    • 中断标志位
      • 用于标识某个中断是否发生,每个中断都有一个与之对应的中断标识位
      • 某个中断发生时,相应中断标志位就会置为1
      • 当CPU检测到标志位为 1 时,就处理相应的中断
      • CPU处理完,中断标志位需复位 0【自动复位 / 手动复位 - 参看文档】
    • 中断服务程序:中断后要处理的逻辑
    • 中断优先级
      • 高优先级可以打断低优先级的中断
  • STC89C52RC

    • 中断源共8个

      • 外部中断
        • 引脚: INFT0 INFT1
        • 由单片机外部紧急事件触发的中断,通过特定引脚发送特定的信号触发
        • 特定信号:51单片机支持两种触发方式:低电平触发、下降沿触发
      • 定时器中断
        • 定时器:Timer0 Timer1 Timer2,每个定时器都有对应的中断
        • 由单片机内部的定时器触发的中断
        • 定时器是单片机自身具备的功能模块,用于实现定时任务,设置指定时间后触发回调
      • 串口中断【全双工】
        • 串口引脚: TxD 发送数据 RxD 接收数据
        • 由单片机串口触发的中断,当串口接收到数据或者发送完整数据后都会触发相应的中断
    • 中断服务程序语法

      • interrupt 关键字,表示当前函数为中断服务程序
      • 0 中断号
        • 每个中断都有唯一的中断号,标识当前中断服务程序处理的中断 - 中断号定义查手册
      c
      // 函数名-无特殊要求
      void 函数名 interrupt 0 {
        // 编写中断处理逻辑    
      }
    • 中断优先级

      • STC89C52RC共有四个中断优先级,每个中断都可单独设置优先级
      • 优先级高的先被处理,中断优先级相同时,中断号小先执行
      • 中断嵌套:高优先级中断 可打断 低优先级中断
        • STC89C52 仅支持两级中断嵌套,更多的会被搁置等待

image-20250321110223775

image-20250322224249445image-20250322224348145

外部中断
  • 使用步骤
    • 默认CPU会屏蔽所有中断请求,必须先启用再使用。由单片机内的两个特殊功能寄存器控制 - 中断控制
      • IE 中断允许寄存器 EA=1 EX0=1
      • XICON 辅助中断控制寄存器
    • 配置外部中断触发方式 - 终端控制位
      • STC89C52RC 支持两种触发方式:下降沿触发、低电平触发
      • 每个外部中断各需要一个控制位设置触发方式,分别位于XICONTCON寄存器
    • 中断优先级【可选】
      • 支持两级/四级优先级,每个中断源的优先级都需通过1个/2个控制位进行配置,可能位于 IPH-中断优先级高位 IP-中断优先级地位 XICON-辅助中断控制
c
// K3按键按下后触发中断,k3接的外部中断INT0
#include <STC89C5xRC.H> // 包含STC89C52的头文件

void INT0_Init()
{
    EA  = 1; // 总中断使能
    EX0 = 1; // 外部中断0使能
    IT0 = 1; // 外部中断 - 下降沿触发
}

void main()
{
    INT0_Init();
    while (1) {
    }
}

// 中断处理程序
void INT0_CallBack() interrupt 0
{
    delay_10us(1000); // 消斗
	if(KEY3==0) P20 = ~P20; // 再次判断K3键是否按下,并翻转LED状态
}
定时器中断

定时器相比软件延时,具有更高的精确度

  • 工作原理
    • 使用一个n位的脉冲计数器,对时钟信号的脉冲进行计数,每个脉冲+1,当计数器达到最大值时溢出,触发定时器中断
      • 脉冲:信号在短时间内从低到高,再返回低电平的过程
      • 周期:两个相邻脉冲的时间间隔
      • 频率:单位时间内脉冲重复的次数 1s/周期
      • image-20250324135343351
    • 定时时长的影响因素
      • 脉冲计数器的位数
        • 分为8、16、32位
      • 脉冲计数器的初始值
      • 时钟信号的频率
  • 使用步骤
    • 启动定时器中断 ET0 ET1
      • 定时器的启动可被单片机 内部寄存器控制外部引脚控制
      • image-20250326121504079
    • 选择计数/定时方式【脉冲来源不同】 TMOD
      • 定时方式-用于产生精确的时间延迟 - 系统时钟的脉冲信号
      • 计数方式-用于统计外部脉冲信号的个数 - 单片机外部引脚的脉冲信号
      • 每个定时器都有一个控制位,用于设置工作模式 TMOD 不支持按位寻址,需整体赋值
    • 选择工作模式TMOD
      • 定时器0共有四种工作模式
        • 13位【兼容】:最大8192,TL0和TH0两个8位寄存器用于存储脉冲计数器数值,TL0仅用低5位
        • 16位:最大65536,TL0和TH0两个8位寄存器用于存储脉冲计数器数值
          • image-20250324161444479
        • 8位自动重装载
          • 13位、16位,一次执行完,需开发者重新为脉冲计数器设置初始值
          • 自动重装载能够再脉冲计数器溢出时,自动重新设置初始值,适用于执行周期性任务
          • TL0 用于存储脉冲计算器的初始值,TH0 用于存储脉冲计数器的初始值,每次溢出后自动恢复到TH0的值
          • image-20250324161433452
        • 双8位
          • 该模式下,TL0、TH0分别用作一个脉冲计数器,适用于脉冲计数器不够时拆分
          • image-20250324161414943

image-20250324153621309image-20250324161702254

脉冲计数器初始值
  • 以16位工作模式为例,实现1ms定时
    • 已知:最大65536,12分频,时钟频率11.0592MHz = 11059.2KHz = 11059200hz
    • 12分频后的频率 = 计数脉冲频率 = 11059200/12
    • 12分频后的时间 = 1/计数脉冲频率 = 12/11059200s ≈ 1.08us
    • 1ms所需脉冲数:0.001/(12/11059200) ≈ 922
    • 脉冲应设初始值:65536-922 = 64614
c
// 赋值
TL0 = 64614;   TH0 = 64614 >> 8;  // 直接赋值,简单
TL0 = 0x66;   TH0 = 0xFC; // 使用传统十六进制,高效
定时器中端解耦demo
c
// 16位定时器实现led灯 1s闪烁1次  单文件版
// main.c
#include "Dri_Timer0.h"
void main()
{
    Dri_Timer0_Init();
    while (1) {
    }
}



// Dri_Timer0.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Dri_Timer0.h"
void Dri_Timer0_Init(){
    // 1.启用终端
    ET0 = 1; // 使能定时器0中断
    EA  = 1; // 总中断开启
    
    // 2.工作模式:GATE=0;C/T=0;M1=0;M0=1;
	TMOD = (TMOD & 0xF0) | 0x01; // 设置定时器0:16位定时模式
    
    // 3.设置初始值
    TL0  = 64614;                // 设置定时器0低8位的初值
    TH0  = 64614 >> 8;           // 设置定时器0高8位的初值
    
    // 4.启动定时器 0-开始计数 0-停止
    TR0  = 1;
}

void Dri_Timer0_Handler() interrupt 1
{
    // 局部静态变量,
    //  - 静态变量生命周期与程序一致,整个程序结束前都不会被销毁重建
    //  - 只能被当前函数访问,避免被外部意外修改
    static unsigned int temp = 0; 
    // 重装载脉冲计数器-初始值
    TL0 = 64614;
    TH0 = 64614 >> 8;

    temp++;
    if (temp >= 1000) {
        temp = 0;
        P20  = ~P20; // 翻转P20引脚的电平
    }
}
c
// 解耦版
// main.c
// main.c
// 16位定时器实现led灯 1s闪烁1次
#include <STC89C5xRC.H>
#include "Dri_Timer0.h"

void LED_Blink(void)
{
    // 局部静态变量,
    //  - 静态变量生命周期与程序一致,整个程序结束前都不会被销毁重建
    //  - 只能被当前函数访问,避免被外部意外修改
    static unsigned int temp = 0;
    temp++;

    if (temp >= 1000) {
        temp = 0;
        P20  = ~P20; // 翻转P20引脚的电平
    }
}

void main()
{
    Dri_Timer0_Init();
    Dri_Timer0_RegisterCallBack(LED_Blink); // 注册回调函数
    while (1) {
    }
}



// Dri.Timer0.h
// Dri.Timer0.h
#ifndef __DIR_TIMER0_H__
#define __DIR_TIMER0_H__
#include "Com_Util.h"

void Dri_Timer0_Init();

/**
 * @brief: 注册Timer0的中断回调函数,参数FUN是一个函数指针,指向将要注册的回调函数
 * @return bit 0-失败 1-成功
 */
bit Dri_Timer0_RegisterCallBack(FUN callback);

/**
 * @brief: 取消Timer0的中断回调函数,参数FUN是一个函数指针,指向将要注册的回调函数
 * @return bit 0-失败 1-成功
 */
bit Dri_Timer0_DelCallBack(FUN callback);
#endif



// Dri.Timer0.c
// Dri.Timer0.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Dri_Timer0.h"
#define MAX_CALLBACK_NUM 4
#define NULL             ((void *)0) // 定义NULL

static FUN s_callbacks[MAX_CALLBACK_NUM]; // 定义一个静态数组,用于存放回调函数指针

void Dri_Timer0_Init()
{
    unsigned char i;
    // 1.启用终端
    ET0 = 1; // 使能定时器0中断
    EA  = 1; // 总中断开启

    // 2.工作模式:GATE=0;C/T=0;M1=0;M0=1;
    TMOD = (TMOD & 0xF0) | 0x01; // 设置定时器0:16位定时模式

    // 3.设置初始值
    TL0 = 64614;      // 设置定时器0低8位的初值
    TH0 = 64614 >> 8; // 设置定时器0高8位的初值

    // 4.启动定时器 0-开始计数 0-停止
    TR0 = 1;

    // 初始化回调数组
    for (i = 0; i < MAX_CALLBACK_NUM; i++) {
        s_callbacks[i] = NULL;
    }
}

bit Dri_Timer0_RegisterCallBack(FUN callback)
{
    unsigned char i;

    for (i = 0; i < MAX_CALLBACK_NUM; i++) {
        if (s_callbacks[i] == callback) return 1;
        if (s_callbacks[i] == NULL) {
            s_callbacks[i] = callback;
            return 1;
        }
    }

    return 0;
}

bit Dri_Timer0_DelCallBack(FUN callback)
{
    unsigned char i;
    for (i = 0; i < MAX_CALLBACK_NUM; i++) {
        if (s_callbacks[i] == callback) {
            s_callbacks[i] = NULL;
            return 1;
        };
    }

    return 0;
}

void Dri_Timer0_Handler() interrupt 1
{
    unsigned char i;
    // 重装载脉冲计数器-初始值
    TL0 = 64614;
    TH0 = 64614 >> 8;

    for (i = 0; i < MAX_CALLBACK_NUM; i++) {
        if (s_callbacks[i] != NULL) s_callbacks[i]();
    }
}


// Com_Util.h
// Com_Util.h
#ifndef __COM_UTIL_H__
#define __COM_UTIL_H__

typedef unsigned char u8;
typedef unsigned long u32;
typedef void (*FUN)(void);

void Com_Util_Delay100us(void);
void Com_Util_Delay1000ms(void);
#endif

通讯

概念
  • 串行通讯:设备间的数据 通过一条数据线 逐位传输
  • 并行通讯:设备间数据 通过多条数据线 同时传输
  • 单双工:只允许数据在一个方向上传输,只能从发送端到接收端
  • 半双工:数据方向不限制,但同一时刻,数据只能在一个方向上传输
  • 全双工:允许数据同时在两个方向上传输,效率最高
  • 发送方/接收方 对数据的协调统一
    • 同步通讯:发送方发送数据时,同时发送自己的时钟信号;接收方在时钟周期的下降沿/上升沿时接收数据(使用相同的时钟信号)
    • 异步:统一发送方发送数据和接收方接收数据的频率,不需要额外的时钟信号
UART
  • 全双工通讯
  • 通用异步收/发(Universal Asynchronous Receiver/Transmitter):异步、全双工的串行通信接口,常用于微控制器与计算机、其他微控制器或外部设备之间的数据交换
数据格式
  • 数据 逐帧Frame 发送,每个数据帧通常包括:起始位、数据位、校验位、停止位
  • 空闲状态:没有数据传输时,应为高电平
  • 起始位:起始位用低电平
  • 数据位:传输的主体内容,长度5-9位,一般为8位,低电平0,高电平1
  • 校验位(可选):校验当前帧的数据正确性
    • 奇校验:数据位1的数目位偶数,校验位为1,否则0
    • 偶校验:数据位1的数目位偶数,校验位为0,否则1
  • 停止位:数据帧的结束,通常为1位或2位,应为高电平
  • 其他约定
    • 波特率(Baud Rate)
      • 表示数据传输速率,每秒传输的基本符号symbol数,二进制的基本符号为0/1,因此二进制中 波特率与比特率相等,1位就是1个符号
      • 四进制中,1个符号等于2bit位

image-20250410161821798

工作模式

image-20250410180952671

方式功能(数据位+校验位)波特率SM0SM1
0同步移位串行:移位寄存器SYSclk/12半双工同步通讯,时钟信号TxD,数据信号RxD00
18位UART,无校验位,波特率可变定时器1溢出率+SMOD01
29位UART,波特率固定,8数据位+1校验位SYSclk系统时钟+SMOD10
39位UART,波特率可变定时器1溢出率+SMOD11
波特率设置

常见波特率:4800、9600、19200、38400、57600、115200;9600≈1.17kb/s

  • 方式1,波特率受SMOD控制位、定时器1的溢出频率
  • 设置波特率,9600为例
    • SMOD位于PCON 电源控制寄存器,不可按位寻址,设置为0,波特率=定时器1溢出率/32

image-20250410191314224

demo
c

image-20250410161157186

常用功能

时间控制

软件延时

通过执行无效命令,达到计时效果,拖住程序的运行

  • 借助工具生成,特定的软件延时函数
  • 定时长度(一般使用1ms,然后通过传参形式复用任意时间长度)
  • 选择系统频率(查单片机配置的晶振频率)
c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>   // _nop_() 所在头文件
void Delay500ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

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



void main(){
	do{
			P20 = ~P20;
			Delay500ms();
	}while(1); // 死循环,让它不会执行结束
}

image-20250220132849773

延时原理
  • 软件延时函数,生成为ASM汇编代码;芯片数据手册中,指令系统描述了每条汇编指令执行所需的时钟周期
  • 默认使用的是 12个时钟周期作为一个机器周期,可开启烧录工具的 双倍速模式-使用6个时钟周期作为一个机器周期
  • 注意
    • 开启 6T模式后,由于时钟周期缩短,延时函数对应减半

image-20250220134224945image-20250220134354446

image-20250220135229982

学习demo

流水灯

c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>
void Delay100ms(void)	//@11.0592MHz
{
	unsigned char data i, j;
	i = 180;
	j = 73;
	do{
		while (--j);
	} while (--i);
}


unsigned char tmp = 0x01;

void main(){
	do{
		if(tmp == 0x00 ){ tmp=0x01; }
	  	P2 = ~tmp;
		Delay100ms();
		tmp<<=1;
	}while(1);
}

数码管

  • 概念
    • 段选:确定显示的内容
    • 位选:确定显示的数字位置
  • 扩展
    • 优化电路:使用 74HC38译码器 较少引脚使用-但同时只能展示一个数字
    • 驱动灯管:74HC245N驱动器,提升输出电压,让数码管亮
  • 注意
    • 切换前显示的数字为置空,会导致下一位在动态切换时存在略微重影
c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>
void Delay100us(void) //@11.0592MHz
{
    unsigned char data i, j;

    _nop_();
    _nop_();
    i = 2;
    j = 15;
    do {
        while (--j);
    } while (--i);
}

typedef unsigned char u8;
unsigned int tmp = 0;

static u8 arr[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};

/**
 * @brief 数码管 指定位置->指定数字 显示
 * @param{u8} position 片选[0-7]
 * @param{u8} num 要显示的数字[0-9]
 */
void showNum(u8 position, u8 num)
{
    P0 = 0x00; // 置空,避免设置时,旧的数据保存在P0中切换时存在重影

    // 控制哪个数字亮
    P22 = position % 2;
    P23 = position / 2 % 2;
    P24 = position / 2 / 2 % 2;
    
    P0 = arr[num]; // 控制数字的哪个位灭-默认输出高/亮
}

void main()
{
    do {
        // 循环显示 76543210
        if (tmp >= 8) {
            tmp = 0;
        }
        showNum(tmp, tmp);
        Delay100us();
        tmp += 1;
    } while (1);
}

image-20250224195053620

74HC138译码器

作用:将3位二进制的输入转换为8位的二进制输出,同一时刻只有一个为低电平;例如:74HC138

  • 用途
    • 数码管使用时,位提升GPIO的利用率,将两个数码管的阴极接到38译码器,实现同一时刻只点亮一个数字

image-20250224201001906image-20250224201101370image-20250225194651856

245驱动器

245驱动芯片:由于51单片机高电平的驱动能力很微弱【为保证输入模式时-输入后引脚内部能感知-因此内部默认给出的高电平较小】,不足以点亮数码管,因此可使用 74HC245N作为驱动芯片

  • 74HC245N驱动器
    • 官方定义:双向的8路总线收发器

image-20250225191218934

image-20250225194638220

74HC595?

直接代替 74HC245N驱动器 和 74HC38译码器

按键

  • 介绍
    • 相当于开关,按下接通/断开,松开断开/接通
    • 12导通,34导通的目的:避免按键长期使用后出现松动

image-20250307155526555

Demo
c
/* 点击按键控制led灯的亮灭 - 采用按键检测 与 逻辑分离 */
// mian.c
#include <STC89C5xRC.H> // 锟斤拷锟斤拷STC89C52锟斤拷头锟侥硷拷
#include "Int_Key.h"

void main(){
    do {
        if (Int_Key_IsDownP30()) P20 = ~P20;
    } while (1);
}


// Int_Key.h
#ifndef _INT_KEY_H
#define _INT_KEY_H

bit Int_Key_IsDownP30();
...
#endif /* _INT_KEY_H */

// Int_Key.c
#include <STC89C5xRC.H> // 锟斤拷锟斤拷STC89C52锟斤拷头锟侥硷拷
#include "Com_Util.h"

bit Int_Key_IsDownP30(){
    if (P30 == 0) {
        Com_Util_Delay10ms();
        if (P30 == 0) {
            while (P30 == 0);
            return 1; // 1: Key down, 0: Key up
        }
    }
    return 0;
}
消抖
  • 问题
    • 由按键的物理特性限制,按键内部弹簧和触电具有弹性,当按键被按下或释放是,弹性材料发生震动,从而导致触电在短时间内反复接触和断开,因此一次按键实际按下多次,从而看起来像“失灵”。
    • 独立按键-在使用时,独立按键通过其中的金属弹片控制电路。在按下和抬起过程中,硬件接触之间不是瞬间完成的,存在抖动变化的过程。
延时消抖
c
// 利用延时10ms后的状态做判断依据,10ms后是低-表示按下-翻转led,10ms后是高-表示抬起-无需操作
void main()
{
    do {
        if (P31 == 0) {
            Com_Util_Delay10ms();
            if (P31 == 0) {
                while (P31 == 0);
                P20 = ~P20;
            }
        }
    } while (1);
}

image-20250311175755745

image-20231031091013470

矩阵按键

思路:将P20引脚置0,分别获取 24、25、26、27谁为1表示被按下,再一次类推将21、22、23分别置0检测

c
// Int_KeyMatrix.c文件
#include "Int_KeyMatrix.h"

u8 Int_KeyMatrix_CheckSW()
{
    u8 num1, num2;
    int i;
    num2 = 0;
    for (i = 0; i < 4; i++) {
        P1 |= 0xf0;
        if (i == 0) P17 = 0;
        if (i == 1) P16 = 0;
        if (i == 2) P15 = 0;
        if (i == 3) P14 = 0;
        num1 = P1 & 0x0F; // 0000 1111
        if (num1 == 0x0e) num2 = 4;
        if (num1 == 0x0d) num2 = 3;
        if (num1 == 0x0b) num2 = 2;
        if (num1 == 0x07) num2 = 1;
        if (num2) return (num2 + 4 * i);
    }

    return 0;
}


// main.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>
#include "Int_KeyMatrix.h"
void main()
{
    u8 key;
    while (1) {
        key = Int_KeyMatrix_CheckSW();
        // 数码管展示指定数字
        showNum(0, key % 10); // 个位 
        if(key / 10 % 10)  showNum(1, key / 10 % 10); // 十位
    }
}

image-20250317175034602

image-20250318154839711

蜂鸣器

  • 分类
    • 有源蜂鸣器 - 自带震荡电路
      • 震荡频率固定,声音固定
    • 无源蜂鸣器 - 不自带震荡电路
      • PWM可调节
无源蜂鸣器
  • 蜂鸣器旁边二极管的作用:释放断电时的瞬时高压,保护三极管防击穿!
c
// Int_Buzzer.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Int_Buzzer.h"
#include "Com_Util.h"

void Int_Buzzer_Buzz(void)
{
    u8 count = 100;
    while (count > 0) {
        P25 = ~P25;
        Com_Util_Delay1ms();
        count -= 1;
    }
}

image-20250318201105107image-20250319183255945

点阵LED

74HC238译码器

类似于 74HC138,与其相反,将3位二进制的输入转换为8位的二进制输出,同一时刻只有一个为高电平

74HC595

常用的 8位串行输入 转 并行输出芯片

  • SCK 移位寄存器 Shift Register
    • 用于接收串行数据输入,芯片会根据SCK时钟信号(上升沿)将串行输入逐位移入移位寄存器
  • RCK 存储寄存器 Stroage Register
    • 与输出引脚相连,因此存储寄存器上的数据就是芯片的输出数据
    • 会根据RCK时钟信号(上升沿),将移位寄存器接收的串行数据一次性加载到存储寄存器并进行输出
  • 脚G非(OE)
    • 74HC595开关,输出使能端,低电平开启,默认高电平
  • SRCLR非
    • 移位寄存器清零端,低电平有效,默认高电平
  • QH`
    • 串行数据输出脚,由于移位寄存器移入数据导致 被移除的数据

image-20250327200311734

c
// demo - 未封装处理【略】
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Com_Util.h"

void main()
{
    unsigned int temp;
    int i;
    do {
        P36  = 0;
        temp = 0x01;
        for (i = 0; i < 8; i++) {
            /* code */
            P34 = 1; // 切换扫描行
            // 触发移位+存储寄存器
            P36 = ~P36;
            P36 = ~P36;
            P35 = ~P35;
            P35 = ~P35;

            // 设置要显示的灯光
            P0 = 0x00;
            // 设置延时
            Com_Util_Delay1000ms();
        }
    } while (1);
}
访客总数 总访问量统计始于2024.10.29