1.什么是单片机
一、基础定义
单片机全称单片微型计算机 ,英文为 Micro Controller Unit(MCU),是将计算机核心硬件集成在单一芯片上的微型控制器。
二、硬件构成
芯片内部集成完整计算机基础硬件:
- 运算核心:CPU(负责数据运算、逻辑处理)
- 存储模块:RAM(随机存储器,临时存数据)、ROM(只读存储器,存储程序)
- 外设模块:定时器、中断系统、通信接口(串口、I2C 等)
三、核心功能与任务
主要完成三类工作:
- 信息采集:搭配传感器,获取环境数据(如温度、光照、距离)
- 数据处理:依靠 CPU 运算,对采集到的信息进行判断、计算
- 设备控制:输出信号控制电机、LED、继电器等硬件执行器
四、与普通计算机的对比
| 对比维度 | 单片机 | 普通计算机 |
|---|---|---|
| 性能 | 性能较弱,算力有限 | 性能强劲,可处理复杂任务 |
| 体积成本 | 体积小、成本极低 | 体积大、成本高 |
| 适用场景 | 工业控制、智能家电、小型电子设备 | 办公、大型软件、高性能运算 |
| 系统特性 | 单芯片即可构成完整控制系统 | 需搭配主板、外设等组成整机 |
五、学习意义
单片机结构精简、原理直观,是理解计算机底层原理、硬件架构、嵌入式控制逻辑的最佳入门载体,也是电子信息、自动化、物联网等专业的核心基础。
2. 为什么叫51单片机
一、51单片机的由来
二、STC89C52(典型 51 单片机)参数
- 基础属性 :属于51 单片机系列 ,由国产 STC(宏晶科技)出品,为8 位单片机。
- 存储配置
- RAM(运行内存):512 字节,临时存储程序变量,断电丢失数据
- ROM(程序存储):8K Flash,存储程序代码,可反复擦写,断电保存数据
- 硬件参数 :开发板常用12MHz 工作频率,时序简单,适合入门学习。
- 应用场景:LED 控制、传感器采集、电机驱动、小型智能设备开发,是嵌入式入门最常用型号。
3.LED的介绍和点亮LED
一、基础信息
- 中文名:发光二极管
- 英文名:Light Emitting Diode
- 简称:LED
- 用途:照明、广告灯、设备指示灯、屏幕背光等
- 电气特性:单向导电 ,和普通二极管一致,正向导通发光,反向截止
二、正负极判断(3 种方法,图里全部标出)✅
- 引脚长短(最常用)
- 长脚 = 正极 (+)
- 短脚 = 负极 (-)
- 内部电极(图右侧红框)
- 内部小块电极 → 正极
- 内部大块碗状电极 → 负极
- 电路符号(图左侧)
- 三角形一侧为正极 ,竖线一侧为负极 ,电流从三角流向竖线

- 三角形一侧为正极 ,竖线一侧为负极 ,电流从三角流向竖线
三、51 单片机接线重点(必记)

- LED 必须串联220Ω 限流电阻,防止电流过大烧毁;
- 接法分 2 种:
- 灌电流接法:单片机 IO 口接负极,正极经电阻接 5V(最常用)
- 拉电流接法:IO 口接正极,负极接地
- 点亮逻辑:给负极低电平 / 正极高电平,LED 导通发光。
四、LED是怎么通过代码来点亮的?

单片机存在一个非常小的CPU 我们写入的程序会通过CPU------>寄存器------>驱动器 然后点亮LED
五、点亮步骤
先编写代码 如图所示



最后点亮成功
六、LED流水灯

流水灯plus版(自己设置参数来控制延时的时间)

4.独立按键控制LED亮灭
一、用独立按键先控制一个LED亮灭


这里是对单个LED进行分配 P2是八个LED为一体的


二、独立按键控制LED状态


三、用独立按键控制LED显示二进制

四、用独立按键控制LED移位
cpp
#include <REGX52.H>
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{ i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
unsigned char LEDNum;//¶¨ÒåÈ<<¾Ö±äÁ¿ ĬÈÏÊÇ0
void main()
{
P2=~0x01;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
//0000 0001 Ï൱ÓÚ0x01<<0
//0000 0010 Ï൱ÓÚ0x01<<1
//0000 0100 Ï൱ÓÚ0x01<<2
//0000 1000 Ï൱ÓÚ0x01<<3
LEDNum++;
if(LEDNum>=8)
{
LEDNum=0;
}
P2=~(0x01<<LEDNum);
}
if(P3_0==0)
{
Delay(20);
while(P3_0==0);
Delay(20);
if(LEDNum==0)
{
LEDNum=7;
}
else
LEDNum--;
P2=~(0x01<<LEDNum);
}
}
}
5.静态数码管显示
一、数码管介绍






二、给数码管显示数字


三、用子函数自己定义显示的位置和显示的数字
cpp
#include <REGX52.H>
unsigned char NiexieTable []={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};
//¶ÔÓ¦0 1 2 3 4 5 6 7 8 9 A B C D E F ¿Õ µÄ¶ÏÂë
//ÓÃ×Óº¯ÊýÊäÈë²ÎÊýÀ´¿ØÖƵãÁÁÊýÂë¹Ü
void NiexieTube(unsigned char Location,Number)
//Á½¸ö²ÎÊý·Ö±ðÊÇÏÔʾµÄÎ>>Öà ºÍÏÔʾµÄÊý×Ö
{
switch(Location)
{
//Ñ¡×î×ó±ßµÄΪÁÁµÆÎ>>ÖõÚÒ>>Î>> Ò²¾ÍÊÇY7
//µ<<ÊÇLEDµÄÅÅÐòÊÇ´ÓÓÒÍù×óLED1~8µÄ
//ËùÒÔ×î×ó±ßµÄÊÇLED8 Ò²¾ÍÊÇY7
case 1: P2_4=1;P2_3=1;P2_2=1;break;
case 2: P2_4=1;P2_3=1;P2_2=0;break;
case 3: P2_4=1;P2_3=0;P2_2=1;break;
case 4: P2_4=1;P2_3=0;P2_2=0;break;
case 5: P2_4=0;P2_3=1;P2_2=1;break;
case 6: P2_4=0;P2_3=1;P2_2=0;break;
case 7: P2_4=0;P2_3=0;P2_2=1;break;
case 8: P2_4=0;P2_3=0;P2_2=0;break;
//×îÓұߵÄÊÇLED1 Ò²¾ÍÊÇY0
}
P0=NiexieTable[Number];
}
void main()
{
NiexieTube(7,2);
while(1)
{
}
}
四、动态数码管显示

先一个一个依次亮:5 → 2 → 0 → 1 → 3 → 1 → 4(每个单独亮,会消失)
然后再变成:5201314 六个数码管一起常亮不消失(其实是会消失的 但是给的延迟时间特别短所以就会相当于一直显示)
6.模块化编程
一、模块化编程(多文件编程,C 语言标准规范)
核心结构
.c源文件 :存放模块的函数实现、变量定义,实现具体功能逻辑.h头文件 :存放外部可调用的函数声明、宏定义、结构体定义、全局变量声明,作对外接口- 调用方式 :其他源文件通过
#include "XXX.h"引入头文件,即可使用该模块功能

核心优势
✅ 可读性 :功能拆分到不同文件 ,每个文件职责单一 ,便于理解代码逻辑
✅ 可维护性 :修改某一模块只需改动对应 .c/.h 文件,不影响其他模块
✅ 可移植性 :通用模块可直接复制到其他项目,实现代码复用
✅ 便于协作 :多人可并行开发不同模块,降低代码冲突概率
二、简单示例
-
fun.h(头文件,声明接口)#ifndef FUN_H
#define FUN_H
void printHello(); // 函数声明
#endif -
fun.c(源文件,实现功能)#include "fun.h"
#include <stdio.h>
void printHello() {
printf("Hello 模块化编程\n");
} -
main.c(主程序,调用模块)#include "fun.h"
int main() {
printHello();
return 0;
}
编译运行:gcc main.c fun.c -o main,即可执行程序。
三、C 语言预编译指令详解💡
预编译是编译前的文本处理阶段 ,所有以 # 开头的指令都会在这一步执行,纯文本替换、条件判断,不做语法检查。下面结合表格逐条解析,并联系你之前学的模块化编程。
表格指令逐条解析
| 预编译指令 | 核心含义 & 通俗解释 |
|---|---|
#include <REGX52.H> |
文件包含 :把头文件REGX52.H的全部内容直接复制粘贴 到当前代码位置;<>用于系统 / 库头文件,""用于自定义头文件 |
#define PI 3.14 |
宏定义(带值) :全文本替换,代码中所有PI都会被替换成3.14;只是简单字符串替换,无计算逻辑 |
#define ABC |
空宏定义 :仅标记ABC已被定义,不做文本替换,常配合条件编译使用 |
#ifndef __XX_H__ |
条件编译 :if not defined,判断宏__XX_H__是否未定义,未定义则执行下方代码 |
#endif |
条件编译结束标记 ,和#ifndef/#ifdef/#if配对,相当于代码块的大括号 |
拓展常用预编译指令(补充表格未列出的)
#ifdef:如果宏已定义,执行对应代码#if:判断表达式真假,为真则执行代码#else:条件不成立时执行(类似 if-else)#elif:多重条件判断(类似 else if)#undef:取消已定义的宏
结合之前的模块化编程
头文件防护完整逻辑(预编译实现)
#ifndef __DELAY_H__ // 如果__DELAY_H__没定义
#define __DELAY_H__ // 立刻定义它
void Delay(unsigned int xms); // 放入函数声明
#endif // 结束条件编译
- 第一次
#include "delay.h":宏未定义 → 执行声明 - 第二次重复
#include "delay.h":宏已定义 → 跳过声明,避免重复定义报错
预编译本质(一句话)
就是编译器帮你自动复制、替换、裁剪代码文本,为正式编译做准备。
举个简单例子
#define PI 3.14
int main(){
float s=PI*2*2;
return 0;
}
预编译后变成:
int main(){
float s=3.14*2*2;
return 0;
}
四、模块化编程实现动态数码管

主函数
延时函数和延时函数头文件

动态数码管函数和头文件


五、LCD1602调试工具


main.c
cpp
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
void main()
{
LCD_Init();//初始化
LCD_ShowChar(1,1,'A');//几行几列 显示哪个字符
LCD_ShowString(1,3,"Hello");//几行几列开始 向后显示字符串
LCD_ShowNum(1,9,123,2);//行列 数字 数字个数(左对齐)范围0~65535
//如果是 123 2 会输出23 如果是123 4 会输出0123
LCD_ShowSignedNum(1,13,-66,2);//输出有符号数字 符号位不算进字数里
LCD_ShowHexNum(2,1,0xA8,2);//显示16进制数 也不包括0x符号位
LCD_ShowBinNum(2,4,0xAA,8);//显示2进制数 10101010
while(1)
{
}
}
//验证1+1=2
int Result;
void main()
{
LCD_Init();//初始化
LCD_ShowNum(1,1,Result,3);//会显示002 说明1+1=2
while(1)
{
}
}
//观察是否每秒加1
int Result=0;
void main()
{
LCD_Init();
while(1)
{
Result++;
Delay(100);
LCD_ShowNum(1,1,Result,3);
}
}
LCD1602.h
cpp
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
LCD1602.C
cpp
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
7. 矩阵键盘(基于LCD1602显示)


逐列扫描
要控制一列的按键
给列分布高低电平(P13 P12 P11 P10) 控制一整列的按键
要控制列中单个按键
就由上面四个P17 P16 P15 P14 四个分布的高低电平来控制
一、矩阵键盘
MatrixKey.c
cpp
#include <REGX52.H>
#include "Delay.h"
//给上注释更加专业
/**
* @brief 矩阵键盘读取按键键码 //简介
* @param 无 //参数
* @retval KeyNumber 按下按键的键码值 //返回值
如果按键按下不放 程序会停留再此函数 松手的一瞬间 返回按键键码 没有按下按键返回0
*/
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;
P1=0xFF;
P1_3=0;//对矩阵键盘第一列进行扫描
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}//检测松手并消抖 S1的检测
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}//检测松手并消抖 S5的检测
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}//检测松手并消抖 S9的检测
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}//检测松手并消抖 S13的检测
P1=0xFF;
P1_2=0;//对矩阵键盘第二列进行扫描
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}//检测松手并消抖 S2的检测
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}//检测松手并消抖 S6的检测
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}//检测松手并消抖 S10的检测
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}//检测松手并消抖 S14的检测
P1=0xFF;
P1_1=0;//对矩阵键盘第三列进行扫描
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}//检测松手并消抖 S3的检测
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}//检测松手并消抖 S7的检测
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}//检测松手并消抖 S11的检测
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}//检测松手并消抖 S15的检测
P1=0xFF;
P1_0=0;//对矩阵键盘第四列进行扫描
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}//检测松手并消抖 S4的检测
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}//检测松手并消抖 S8的检测
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}//检测松手并消抖 S12的检测
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}//检测松手并消抖 S16的检测
return KeyNumber;
}
MatrixKey.h
cpp
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char MatrixKey();
#endif
main.c
cpp
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "MatrixKey.h"
unsigned char KeyNum;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"MatrixKey:");
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
LCD_ShowNum(2,1,KeyNum,2);
}
}
}
这通过矩阵键盘显示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
二、矩阵键盘的应用------电子密码锁
这里仅对 main.c 进行修改
核心思路 S1~S10 代表1 2 3 4 5 6 7 8 9 0 S11代表确认 S12代表取消
cpp
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "MatrixKey.h"
int Count;
unsigned char KeyNum;
unsigned int Password;//定义密码 单片机会默认初始化为0
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
if(KeyNum<=10)//如果按键S1~S10按下输入密码
//S1~S10 定义为1 2 3 4 5 6 7 8 9 0
{
if(Count<4)//确保输入四位后就不让输入了
{
Password*=10;
Password+=KeyNum%10;//获取一位密码4
Count++;//计次加一
}
LCD_ShowNum(2,1,Password,4);//四位密码 更新显示
}
if(KeyNum==11)//如果S11按下 就代表确认
{
if(Password==1107)//定义正确密码
{
LCD_ShowString(1,10,"Right");//输入正确为Right
Password=0; //密码清零
Count=0; //计次清零
LCD_ShowNum(2,1,Password,4);//四位密码 更新显示
}
else
{
LCD_ShowString(1,10,"Error");//输入错误显示Error
Password=0; //密码清零
Count=0; //计次清零
LCD_ShowNum(2,1,Password,4);//四位密码 更新显示
}
}
if(KeyNum==12) //如果S12按键按下,代表取消
{
Password=0; //密码清零
Count=0; //计次清零
LCD_ShowNum(2,1,Password,4);//四位密码 更新显示
}
}
}
}
8.定时器
一、定时器的介绍
- 定时器:51 内部硬件资源,用于定时 / 计数,替代软件延时,提升 CPU 效率
- STC89C52 :3 个定时器(T0、T1、T2)
- T0/T1:全 51 通用兼容
- T2:该型号专属新增
定时器工作流程 :时钟(提供脉冲)→计数单元(累加计数)→中断系统(触发中断,执行定时任务) 原理类比:内部小闹钟 ,计数溢出触发中断提醒。
定时器工作模式
T0/T1 四种工作模式
- 模式 0:13 位
- 模式 1:16 位(常用)
- 模式 2:8 位自动重装
- 模式 3:双 8 位计数器

时钟 :SYSclk(系统时钟,即晶振周期,本开发本的为11.0592)连接到+12分频 一个周期就是一微秒 C/T C就是counter计数 如果给0就counter 给1就Timer
计数单元:TL TH 十六位计数 最大为65535 接收脉冲进行加数字
模式 1 要点
16 位定时器,由TH0+TL0组成;12T 模式时钟 12 分频,溢出触发 TF0 中断。
二、中断系统
实现中断操作的部件叫做中断系统
- 中断:CPU 暂停当前任务,处理紧急事件,完成后返回原任务
- 中断源 :向 CPU 发中断请求的来源,按优先级响应
- 中断嵌套 :处理低级中断时,可响应更高优先级中断
- 分类:支持嵌套 = 多级中断系统;不支持 = 单级中断系统

- 中断源数量 :STC89C52 有 8 个 中断源(外部中断 0、定时器 0、外部中断 1、定时器 1、串口、定时器 2、外部中断 2、外部中断 3)
- 中断优先级 :共 4 个 优先级
- C 语言中断号规则 中断查询序号 = 中断号,格式:
函数() interrupt 数字0:外部中断 0 | 1:定时器 0 | 2:外部中断 1 | 3:定时器 1 | 4:串口 | 5:定时器 2 | 6:外部中断 2 |7:外部中断 3 - 核心注意点中断资源与单片机型号绑定,不同型号中断源数量、优先级数量存在差异



单片机通过配置寄存器来控制内部线路的连接



三、用按键控制LED流水灯模式
寄存器在头文件里面有声明

TMOD负责控制和确定T0和T1的功能和工作模式

不可位寻址的寄存器只能整体赋值
可位寻址的寄存器可以单独赋值
还有Delay.c 和 Delay.h
Key.h
cpp
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
#endif
Key.c(四个独立按键)
cpp
#include <REGX52.H>
#include "Delay.h"
//四个独立按键控制流水灯
/**
* @brief 获取四个独立按键键码
* @param 无
* @retval 按下按键的键码,范围0~4,无按键按下时返回0
*/
unsigned char Key()
{
unsigned char KeyNumber=0;
if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
return KeyNumber;
}
Timer0.c(定时器)
cpp
#include <REGX52.H>
/**
* @brief 定时器0初始化 //1毫秒@11.0592MHz
* @param 无
* @retval 无
*/
void Timer0Init(void) //1毫秒@11.0592MHz
{ //第一段可以删除或者注释 因为定时器就是12T模式的
//AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
//生成的初始化函数没有中断函数我们要自己加上
ET0=1;//T0的溢出中断允许位
//=1 允许中断 =0禁止中断
EA=1;//CPU的总中断允许控制位
//=1 允许中断 =0禁止中断
PT0 =0;//定时器0中中断优先级控制位
//PT0=1 定时器0中断为最低优先级中断 开关拨向上面
//PT0=1 定时器0中断为较低优先级中断 开关拨向下面
}
/* 定时器中断函数 一秒模板
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;//内部静态局部变量
TH0=0xfc;//中断之后值会溢出 溢出就变成0
TL0=0X66;//所以我们要重新赋初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
Timer0.h
cpp
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0Init(void);
#endif
main.c
cpp
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>
unsigned char KeyNum,LEDMode;
//实现模块化后 可以注释掉
/*
void Timer0Init(void) //1毫秒@11.0592MHz
{ //第一段可以删除或者注释 因为定时器就是12T模式的
//AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
//生成的初始化函数没有中断函数我们要自己加上
ET0=1;//T0的溢出中断允许位
//=1 允许中断 =0禁止中断
EA=1;//CPU的总中断允许控制位
//=1 允许中断 =0禁止中断
PT0 =0;//定时器0中中断优先级控制位
//PT0=1 定时器0中断为最低优先级中断 开关拨向上面
//PT0=1 定时器0中断为较低优先级中断 开关拨向下面
}
*/
/*
void Timer0_Init()//初始化函数
{
//1.配置TMOD
TMOD=0x01; //TMOD是工作模式
//不影响高四位 定义低四位
TMOD&=0XF0; //把TMOD低四位清零 高四位保持不变
TMOD|=0X01; //把TMOD的第四位置1 高四位保持不变
//0000 0(GATE)0(C/T)0(M1)1(M0) 第一种工作模式
//这里我们只用一个定时器所以不对TF1 和 TR1 操作定义
//2.配置TCONT
TF0=0;//TFO代表定时器T0溢出中断标志
//=0代表清零
TR0=1;//TR0控制定时器T0是否开启
//=1代表开启
//因为GATE已经等于0了过非门后为1 IE0和 IT0外部中断引脚就不用配置
//因为他们一起进入的是或门 只要有一个是1 那结果就是1
//3.给定时器寄存器赋初值 初值 = 65536 - ( 定时时间 * 晶振频率 / 12 )
TH0=0xfc;//代表定时器0高八位寄存器
TL0=0x66;//代表定时器0低八位寄存器
//举个1234十进制的例子 1234/100=12 1234%100=34
//256的二进制对应的是10进制的100 所以这里取高八位和低八位就是这样取
//4.打开中断系统的开关 ET0 EA PT0
ET0=1;//T0的溢出中断允许位
//=1 允许中断 =0禁止中断
EA=1;//CPU的总中断允许控制位
//=1 允许中断 =0禁止中断
PT0 =0;//定时器0中中断优先级控制位
//PT0=1 定时器0中断为最低优先级中断 开关拨向上面
//PT0=1 定时器0中断为较低优先级中断 开关拨向下面
}
*/
void main()
{
P2=0XFE;
Timer0Init();//用生成的初始化函数
//Timer0_Init();//上电的时候初始化
while(1)
{
KeyNum=Key();//获取独立按键键码
if(KeyNum) //如果按键按下
{
if(KeyNum==1) ///如果K1按下
{
LEDMode++; //模式切换
if(LEDMode>=2)LEDMode=0;
}
//Mode 在1 0 1 0 1 0反复 这时候独立按键1相当于切换模式
}
}
}
//5.中断函数 中断号控制中断 上面的没执行 直接跳转到中断函数
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;//内部静态局部变量
TH0=0xfc;//中断之后值会溢出 溢出就变成0
TL0=0X66;//所以我们要重新赋初值
T0Count++;
if(T0Count>=500)
{
T0Count=0;
if(LEDMode==0)
P2=_crol_(P2,1);//左移函数(将右边挤出的数字补到左边) 在头文件#include <INTRINS.H>中定义了
if(LEDMode==1)
P2=_cror_(P2,1);//右移函数
}
}
四、定时器时钟
这里要用到LCD1602, Delay,Timer0 都是前面的
main.c
cpp
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec=18,Min=59,Hour=19;//秒 分 时
void main()
{
LCD_Init();
Timer0Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,1," : :");
while(1)
{
LCD_ShowNum(2,1,Hour,2);
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,7,Sec,2);
}
}
// 定时器中断函数 一秒模板
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;//内部静态局部变量
TL0=0X66;//中断之后值会溢出 溢出就变成0
TH0=0xfc;//所以我们要重新赋初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
Hour++;
if(Hour>=24)
{
Hour=0;
}
}
}
}
}
9.串口通信
一、串口核心要点简述

- 定义 :成本低、连接简单的**双向通信接口,**支持设备间点对点数据传输。
- 单片机应用 :可实现单片机与电脑 、其他单片机 、各类外设模块(蓝牙、传感器等)的通信,大幅扩展功能。
- 51 单片机串口 :自带 UART(通用异步收发器),无需额外芯片即可实现串口通信。
- 配套硬件:常见模块包括 USB 转串口模块(电脑 - 单片机通信)、串口传感器模块、蓝牙透传模块(无线串口通信)。
- 关键前提:通信双方波特率、数据位、停止位、校验位参数必须一致,硬件需 TX-RX 交叉连接并共地。
二、串口硬件电路核心要点简述

- 双向通信连接 :设备间的串口通信需交叉连接,即设备 1 的TXD(发送端) 接设备 2 的RXD(接收端) ,设备 1 的RXD(接收端) 接设备 2 的TXD(发送端) ,同时必须连接共地(GND),保证电平参考一致。
- 单向通信简化 :仅需单向传输数据 时,可只连接一条通信线 (发送端 TXD 到接收端 RXD),无需交叉连接。
- 电平匹配处理 :当设备间串口电平标准(如 TTL 与 RS232 )不一致时,需额外添加电平转换芯片,避免设备损坏或通信失败。

- 供电与共地:除信号线外,VCC 和 GND 的连接是基础,GND 共地是串口通信稳定的前提,否则会出现信号干扰、乱码等问题。
三、常见嵌入式通信接口对比

| 接口名称 | 核心引脚 | 通信方式 | 关键特点 | 典型场景 |
|---|---|---|---|---|
| UART | TXD、RXD | 全双工、异步 | 点对点通信,无需时钟线 | 单片机与电脑、蓝牙模块通信 |
| I²C | SCL、SDA | 半双工、同步 | 可挂载多个设备,仅需 2 线 | 读取 EEPROM、温湿度传感器 |
| SPI | SCLK、MOSI、MISO、CS | 全双工、同步 | 可挂载多个设备,高速传输 | 驱动 OLED 屏幕、Flash 芯片 |
| 1-Wire | DQ | 半双工、异步 | 单总线,可挂载多个设备 | 读取 DS18B20 温度传感器 |
补充说明
- 同步 vs 异步
- 同步通信(I²C/SPI):依赖时钟信号(SCL/SCLK)实现收发同步,抗干扰性强、速度更快。
- 异步通信(UART/1-Wire):无时钟线,依靠预设波特率同步,硬件连接更简单。
- 全双工 vs 半双工
- 全双工(UART/SPI):收发可同时进行,效率更高。
- 半双工(I²C/1-Wire):同一时间只能收或发,需分时复用总线。
- 此外还有CAN (工业抗干扰)、USB (高速设备通信)等接口,适用于更复杂的场景。
- 总线:连接各个设备的数据传输线路(类似于一条马路,把路边住户连接起来 使住户可以相互交流)
四、51单片机的UART



一、关键参数
- 波特率:串口通信的速率,决定数据位之间的发送 / 接收间隔,通信双方必须设置一致。
- 校验位:用于数据传输过程中的错误校验(如奇偶校验),提升通信可靠性。
- 停止位:标记数据帧的结束,用于帧与帧之间的间隔,让接收端做好准备。
二、数据帧格式与时序
-
8 位数据格式(1 帧共 10 位)
- 组成:1 个起始位 + 8 个数据位(D0~D7,先传低位 LSB) + 1 个停止位
- 特点:最常用的格式,无校验位,传输效率高,适合大多数普通通信场景。
-
9 位数据格式(1 帧共 11 位)
- 组成:1 个起始位 + 9 个数据位(含 D0~D7 + 第 9 位 TB8/RB8) + 1 个停止位
- 特点:多出的第 9 位可作为校验位或地址 / 控制标志,常用于多机通信,51 单片机中对应
TB8(发送)和RB8(接收)寄存器。




SCON寄存器

五、串口向电脑发送数据
这里不需要中断函数 这里只是串口向电脑发送数据
需要Delay.h Delay.c
UART.h
cpp
#ifndef __UART_H__
#define __UART_H__
void UART_Init();
void UART_SendByte(unsigned char Byte);
#endif
UART.c
cpp
#include <REGX52.H>
/**
* @brief 串口初始化 4800bps@11.0592MHz
* @param 无
* @retval 无
*/
//串口的配置
void UART_Init()//4800bps@11.0592MHz
{ //1.配置UART的SCON寄存器
SCON=0X40;//0100 0000 前两个01代表SM0为0 SM1为1 这样就是方式1
PCON &= 0x7F;//波特率寄存器配置
//定时器配置(这里规定只能用定时器1)
TMOD &= 0x0F; //设置定时器模式2 这里是把高八位置为0
TMOD |= 0x20; //设置定时器模式2 把高八位变成0010 实现模式二
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
//我们这里不需要中断
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/**
* @brief 串口发送一个字节数据
* @param Byte要发送的一个字节数据 无符号字符型
* @retval 无
*/
//2.SBUF寄存器初始化不需要配置 就是写数据
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;//写入数据到SBUG中 会自动发送
while(TI==0);//传输结束后TI会置为1
TI=0;//要我们自己复位为0
}
main.c
cpp
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
//实现模块化后可以注释掉
/*
//串口的配置
void UART_Init()//4800bps@11.0592MHz
{ //1.配置UART的SCON寄存器
SCON=0X40;//0100 0000 前两个01代表SM0为0 SM1为1 这样就是方式1
PCON &= 0x7F;//波特率寄存器配置
//定时器配置(这里规定只能用定时器1)
TMOD &= 0x0F; //设置定时器模式2 这里是把高八位置为0
TMOD |= 0x20; //设置定时器模式2 把高八位变成0010 实现模式二
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
//我们这里不需要中断
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
//2.SBUF寄存器初始化不需要配置 就是写数据
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;//写入数据到SBUG中 会自动发送
while(TI==0);//传输结束后TI会置为1
TI=0;//要我们自己复位为0
}
*/
unsigned char Sec;
void main()
{
UART_Init();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(100);
}
}
六、电脑通过串口控制LED
这里需要用到中断函数 来接受和发送
UART.h
cpp
#ifndef __UART_H__
#define __UART_H__
void UART_Init();
void UART_SendByte(unsigned char Byte);
#endif
UAR.c
cpp
#include <REGX52.H>
/**
* @brief 串口初始化 4800bps@11.0592MHz
* @param 无
* @retval 无
*/
//串口的配置
void UART_Init()//4800bps@11.0592MHz
{ //1.配置UART的SCON寄存器
SCON=0X50;//0101 0000 前两个01代表SM0为0 SM1为1 这样就是方式1
//后面的1是REN 允许串行接受状态 会触发中断
PCON &= 0x7F;//波特率寄存器配置
//定时器配置(这里规定只能用定时器1)
TMOD &= 0x0F; //设置定时器模式2 这里是把高八位置为0
TMOD |= 0x20; //设置定时器模式2 把高八位变成0010 实现模式二
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
//我们这里不需要中断
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
//打开中断的两个开关
EA=1;//启动总中断
ES=1;//启动串口中断
}
/**
* @brief 串口发送一个字节数据
* @param Byte要发送的一个字节数据 无符号字符型
* @retval 无
*/
//2.SBUF寄存器初始化不需要配置 就是写数据
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;//写入数据到SBUG中 会自动发送
while(TI==0);//传输结束后TI会置为1
TI=0;//要我们自己复位为0
}
/*串口中断函数模板(使用时需要移入主函数)
void UART_Routine() interrupt 4
//interrupt 4这个小尾巴就代表了中断子函数 4是串口中断号
{
if(RI==1)//如果接收中断 说明数据已经存到SBUF中
{
RI=0;//手动复位
}
}
*/
Delay.h
Delay.c
main.c
cpp
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4
//interrupt 4这个小尾巴就代表了中断子函数 4是串口中断号
{
if(RI==1)//如果接收中断 说明数据已经存到SBUF中
{
P2=~SBUF;//读取SBUF 放到P2口上
UART_SendByte(SBUF);//再把数据发给电脑端
RI=0;//手动复位
}
}
10.LED点阵屏
一、LED点阵屏介绍
1. 核心概念
LED 点阵屏是一种模块化显示设备,核心原理是通过矩阵排列的 LED 灯珠亮灭组合,来呈现文字、图片、视频等信息。它由显示模块、控制系统和电源系统组成,制作安装简单,被广泛应用于公共场合,比如:
- 公交 / 地铁报站器
- 户外广告屏
- 交通提示牌、公告栏

2. 主要分类
2.1 按颜色划分
| 类型 | 组成 | 特点 | 典型应用 |
|---|---|---|---|
| 单色 | 单种颜色 LED (多为红色) | 成本低、亮度高,只能显示单一颜色 | 简易文字屏、数字显示牌 |
| 双色 | 红 + 绿两种 LED | 可显示红、绿、黄(红 + 绿混合)三种颜色 | 交通提示屏、简易广告屏 |
| 全彩 | 红 + 绿 + 蓝三种 LED | 可显示超过 1600 万种颜色,色彩丰富 | 户外大屏、视频广告屏 |
2.2 按像素规模划分
- 常见基础规格:
8×8、16×16点阵(单片机开发中最常用) - 大规模应用:由多个小点阵模块拼接而成,实现更大尺寸、更高分辨率的显示效果
二、显示原理


1. 核心原理:和数码管的 "亲戚关系"
LED 点阵屏的底层逻辑和数码管是相通的:
- 数码管是把 LED 排成 "8" 字形 ,用来显示数字;
- LED 点阵屏是把 LED 排成规则的矩阵 (比如 8×8),用来显示文字 / 图案,本质上都是多个 LED 的组合显示。
2. 关键电路:共阴 / 共阳两种接法
和数码管一样 ,点阵屏也分两种接法,直接决定了驱动电平:
| 类型 | 公共端接法 | 点亮条件 | 典型应用 |
|---|---|---|---|
| 共阳点阵 | 所有 LED 的阳极统一接高电平(行线) | 行线接高,列线接低,LED 点亮 | 左侧的单色点阵电路 |
| 共阴点阵 | 所有 LED 的阴极统一接低电平(行线) | 行线接低,列线接高,LED 点亮 | 右侧的双色点阵电路(红 / 绿) |
3. 动态扫描:为什么能同时显示所有点?
点阵屏无法同时给所有 LED 供电,所以必须用逐行 / 逐列扫描的方式,利用人眼的「视觉暂留效应」实现 "同时显示":
- 逐行扫描:先选中第 1 行,给这一行需要点亮的列送对应电平,点亮这一行的部分 LED;
- 快速切换到第 2 行,重复上述操作;
- 以每秒几十次以上的速度循环扫描所有行,人眼就会看到稳定的图像,而不是闪烁的光点。
74HC595实现了拓展I/0口由三根线拓展八根 但是这样速度会有点慢 这是必要的
这是恒压输出的 所以多个灯一起亮会变得比较暗
用74HC595控制引脚把它的行数据给输进来

三、补充
一、sfr:特殊功能寄存器声明
sfr 是用来给单片机的 8 位特殊功能寄存器定义名字和地址的关键字。
- 作用:把寄存器的物理地址和一个变量名绑定,让你可以用变量名直接操作寄存器,不用每次都写地址。
- 例子:
sfr P0 = 0x80;- 声明了
P0这个名字,对应物理地址0x80的寄存器(也就是 P0 端口)。 - 之后你写
P0 = 0xFF;,就等价于给地址0x80写入数据0xFF。
- 声明了
二、sbit:特殊位声明
sbit 是用来直接操作寄存器中某一个特定位 的关键字,专门用于位寻址。
- 两种用法:
- 直接写位地址:
sbit P0_1 = 0x81; - 通过寄存器名 + 位号:
sbit P0_1 = P0^1;
- 直接写位地址:
- 例子里的
P0_1就代表 P0 端口的第 1 位,之后你写P0_1 = 1;,就能单独把 P0.1 这个引脚置高。
三、可位寻址 vs 不可位寻址
这部分是理解 C51 位操作的关键:
- 为什么会有 "可位寻址"? 单片机里的寄存器是 8 位的,位的数量是寄存器的 8 倍,单片机无法给每个位都分配独立地址。所以规定:只有地址能被 8 整除的寄存器,才是可位寻址的 (比如
0x80、0x88、0x90等),这些寄存器的每一位都有对应的位地址 ,可以直接用sbit操作。 - 不可位寻址的寄存器怎么操作某一位? 不能直接用
sbit,只能用位运算来 "屏蔽" 其他位 ,只修改目标位:- 置 1:
寄存器|= (1 << 位号); - 清 0:
寄存器&= ~(1 << 位号); - 翻转:
寄存器^= (1 << 位号);
- 置 1:
四、LED点阵显示图形
这里还要用到延时函数
main.c
cpp
#include <REGX52.H>
#include "Delay.h"
//74HC595 串转并
//这里对应的就是那三根线 用来拓展成八根 然后控制行
sbit RCK =P3^5;//为了方便操作 将P3^5特殊命名为RCK (原理图中的RCLK-上升沿锁存)
sbit SCK =P3^6;//将P3^6特殊命名为SCK(原理图中的SRCLK-上升沿移位)
sbit SER =P3^4;//将P3^4特殊命名为RET(放入数据)
#define MATRIX_LED_PORT P0//将P0口定义
/**
* @brief 74HC595写入一个字节
* @param 要写入的字节
* @retval 无
*/
void _74HC595_WriteByte(unsigned char Byte)
{
//为了方便我们用for循环将八位移进去
unsigned char i;
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i);//实现八位移进去
SCK=1;//排序一位一位排从高到低
SCK=0;
}
RCK=1;//把数据传过去给I/0口
RCK=0;//清零
/*
SER=Byte&0x80;//最高位是0还是1
SCK=1;//高电平就是一个上升沿就把数据移进去
SCK=0;//清零为下一位做准备
SER=Byte&0x40;//次高位
SCK=1;
SCK=0;
SER=Byte&0x20;//依次循环八次就可以把八位移进去
SCK=1;
SCK=0;*/
}
/**
* @brief LED点阵屏显示一列数据
* @param Column 要选择的列 范围:0~7,0在最左边
* @param Data选择列显示的数据,高位在上 1为亮 0为灭
* @retval 无
*/
//参考数码管 把每一行看成Location 每一列看成断码
//1.控制列
void MatrixLED_ShowColumn(unsigned char Column,Data)//Column代表列0 1 2 3 4 5 6 7
{
_74HC595_WriteByte(Data);
MATRIX_LED_PORT=~(0x80>>Column);//控制列相当于位选
Delay(1);//消影
MATRIX_LED_PORT=0XFF;
}
void main()
{
SCK=0;//初始化为低电平
RCK=0;//初始化为低电平
while(1)
{
//全红爱心
/*MatrixLED_ShowColumn(0,0x30);
MatrixLED_ShowColumn(1,0x78);
MatrixLED_ShowColumn(2,0x7C);
MatrixLED_ShowColumn(3,0x3E);
MatrixLED_ShowColumn(4,0x3E);
MatrixLED_ShowColumn(5,0x7C);
MatrixLED_ShowColumn(6,0x78);
MatrixLED_ShowColumn(7,0x30);*/
//笑脸
MatrixLED_ShowColumn(0,0x3C);
MatrixLED_ShowColumn(1,0x42);
MatrixLED_ShowColumn(2,0xA9);
MatrixLED_ShowColumn(3,0x85);
MatrixLED_ShowColumn(4,0x85);
MatrixLED_ShowColumn(5,0xA9);
MatrixLED_ShowColumn(6,0x42);
MatrixLED_ShowColumn(7,0x3C);
}
}
五、LED点阵屏显示动画
这里延时函数可用可不用
我们这里采用Count计数扫描次数来控制延时
这里实现了模块化
生成图像可以运用工具 生成对应的16进制码
Matrix.h
cpp
#ifndef __MATRIX_LED_H__
#define __MATRIX_LED_H__
void _74HC595_WriteByte(unsigned char Byte);
void MatrixLED_ShowColumn(unsigned char Column,Data);
void MatrixLED_Init();
#endif
Matrix.c
cpp
#include <REGX52.H>
#include "Delay.h"
//74HC595 串转并
//这里对应的就是那三根线 用来拓展成八根 然后控制行
sbit RCK =P3^5;//为了方便操作 将P3^5特殊命名为RCK (原理图中的RCLK-上升沿锁存)
sbit SCK =P3^6;//将P3^6特殊命名为SCK(原理图中的SRCLK-上升沿移位)
sbit SER =P3^4;//将P3^4特殊命名为RET(放入数据)
#define MATRIX_LED_PORT P0//将P0口定义
/**
* @brief 74HC595写入一个字节
* @param 要写入的字节
* @retval 无
*/
void _74HC595_WriteByte(unsigned char Byte)
{
//为了方便我们用for循环将八位移进去
unsigned char i;
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i);//实现八位移进去
SCK=1;//排序一位一位排从高到低
SCK=0;
}
RCK=1;//把数据传过去给I/0口
RCK=0;//清零
/*
SER=Byte&0x80;//最高位是0还是1
SCK=1;//高电平就是一个上升沿就把数据移进去
SCK=0;//清零为下一位做准备
SER=Byte&0x40;//次高位
SCK=1;
SCK=0;
SER=Byte&0x20;//依次循环八次就可以把八位移进去
SCK=1;
SCK=0;*/
}
/**
* @brief 点阵屏初始化
* @param 无
* @retval 无
*/
void MatrixLED_Init()
{
SCK=0;
RCK=0;
}
/**
* @brief LED点阵屏显示一列数据
* @param Column 要选择的列 范围:0~7,0在最左边
* @param Data选择列显示的数据,高位在上 1为亮 0为灭
* @retval 无
*/
//参考数码管 把每一行看成Location 每一列看成断码
//1.控制列
void MatrixLED_ShowColumn(unsigned char Column,Data)//Column代表列0 1 2 3 4 5 6 7
{
_74HC595_WriteByte(Data);
MATRIX_LED_PORT=~(0x80>>Column);//控制列相当于位选
Delay(1);//消影
MATRIX_LED_PORT=0XFF;
}
main.c
cpp
#include <REGX52.H>
#include "Delay.h"
#include "Matrix LED.h"
//用一个数组来存一个长条形动画
/*
//定义code 放在flash里面能存放更多内存
unsigned char code Animation[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//前面加八个0x00
0x7E,0x10,0x10,0x7E,0x00,0x00,0x1C,0x2A,
0x2A,0x1A,0x00,0x00,0x7E,0x02,0x02,0x02,
0x00,0x7E,0x02,0x02,0x02,0x00,0x3C,0x42,
0x42,0x3C,0x00,0x00,0x7D,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//后面加8个0x00 对接起来
};
*/
//坤坤矩阵
unsigned char code Animation[]=
{
0x00,0x0E,0x1B,0x7F,0x7F,0x1B,0x0E,0x00,
0x00,0x04,0x0C,0x18,0x7F,0x7F,0x1B,0x0E,
0x00,0x08,0x0C,0x05,0x7F,0x7F,0x1E,0x0C,
0xC0,0x00,0x41,0x22,0x1A,0x7C,0x7D,0x1A,
0x00,0x60,0x61,0x12,0x0A,0x7E,0x7F,0x08,
0x00,0x06,0x0E,0x08,0x0B,0x3E,0x3E,0x0B,
0x00,0x03,0x0B,0x08,0x0B,0x3E,0x3E,0x0B,
0x00,0x06,0x0E,0x08,0x09,0x3F,0x3E,0x0B,
0x00,0x0E,0x13,0x7E,0x7E,0x1F,0x0E,0x00,
0x08,0x13,0x7E,0x7E,0x12,0x1D,0x0C,0x00,
0x09,0x12,0x7E,0x7F,0x10,0x13,0x03,0x00,
0x09,0x12,0x7E,0x7F,0x10,0x1C,0x0C,0x00,
0x00,0x0C,0x15,0x7E,0x7E,0x17,0x0E,0x00,
0x00,0x30,0x38,0x0D,0x7E,0x7F,0x1D,0x08,
0xC0,0xC0,0x30,0x1B,0x7C,0x7F,0x08,0x00,
0x00,0x00,0x37,0xF8,0xFA,0x34,0x00,0x00,
0x00,0x00,0x1B,0x7C,0x7C,0x1A,0x01,0x00,
0x00,0x00,0x09,0x1E,0x7C,0x7F,0x18,0x00,
0x00,0x00,0x1B,0x7C,0x7C,0x1B,0x00,0x00,
0x00,0x00,0x09,0x1E,0x7C,0x7F,0x18,0x00,
0x00,0x00,0x1B,0x7C,0x7C,0x1B,0x00,0x00,
0x08,0x10,0x16,0x7C,0x7F,0x10,0x08,0x00,
0x00,0x10,0x13,0x7C,0x7E,0x18,0x00,0x00,
0x00,0x08,0x13,0x7C,0x7F,0x10,0x10,0x00,
0x00,0x08,0x12,0xFC,0xFD,0x3A,0x00,0x00,
0x00,0x08,0x12,0xFC,0xFC,0x22,0x10,0x00,
0x00,0x00,0x61,0x12,0x7C,0x7F,0x10,0x60,
0x00,0x00,0x20,0x11,0x12,0x7C,0x7E,0x19,
0x00,0x00,0x00,0x31,0x0A,0x7C,0x7E,0x0D,
0x00,0x00,0x20,0x11,0x12,0x7C,0x7E,0x19,
0x00,0x00,0x00,0x31,0x0A,0x7C,0x7E,0x0D,
0x00,0x00,0x20,0x11,0x12,0x7C,0x7E,0x19,
0x00,0x00,0x00,0x31,0x0A,0x7C,0x7E,0x0D,
0x00,0x00,0x20,0x11,0x12,0x7C,0x7E,0x19,
0x00,0x00,0x00,0x31,0x0A,0x7C,0x7E,0x0D,
};
void main()
{
unsigned char i,Offset=0,Count=0;//定义偏移量
MatrixLED_Init();
while(1)
{
//全红爱心
/*MatrixLED_ShowColumn(0,0x30);
MatrixLED_ShowColumn(1,0x78);
MatrixLED_ShowColumn(2,0x7C);
MatrixLED_ShowColumn(3,0x3E);
MatrixLED_ShowColumn(4,0x3E);
MatrixLED_ShowColumn(5,0x7C);
MatrixLED_ShowColumn(6,0x78);
MatrixLED_ShowColumn(7,0x30);*/
//笑脸
/*
MatrixLED_ShowColumn(0,0x3C);
MatrixLED_ShowColumn(1,0x42);
MatrixLED_ShowColumn(2,0xA9);
MatrixLED_ShowColumn(3,0x85);
MatrixLED_ShowColumn(4,0x85);
MatrixLED_ShowColumn(5,0xA9);
MatrixLED_ShowColumn(6,0x42);
MatrixLED_ShowColumn(7,0x3C);*/
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i, Animation[i+Offset]);
}
Count++;
if(Count>3)//扫描五遍 之后换下一帧
{
Count=0;
Offset+=8;
if(Offset>25)//我们自己取的宽度是32 一次读8个宽度(32-8)
{
Offset=0;
}
}
}
}
