51单片机的学习上(结合中科协的个人自用笔记)

1.什么是单片机

一、基础定义

单片机全称单片微型计算机 ,英文为 Micro Controller Unit(MCU),是将计算机核心硬件集成在单一芯片上的微型控制器。

二、硬件构成

芯片内部集成完整计算机基础硬件:

  • 运算核心:CPU(负责数据运算、逻辑处理)
  • 存储模块:RAM(随机存储器,临时存数据)、ROM(只读存储器,存储程序)
  • 外设模块:定时器、中断系统、通信接口(串口、I2C 等)

三、核心功能与任务

主要完成三类工作:

  1. 信息采集:搭配传感器,获取环境数据(如温度、光照、距离)
  2. 数据处理:依靠 CPU 运算,对采集到的信息进行判断、计算
  3. 设备控制:输出信号控制电机、LED、继电器等硬件执行器

四、与普通计算机的对比

对比维度 单片机 普通计算机
性能 性能较弱,算力有限 性能强劲,可处理复杂任务
体积成本 体积小、成本极低 体积大、成本高
适用场景 工业控制、智能家电、小型电子设备 办公、大型软件、高性能运算
系统特性 单芯片即可构成完整控制系统 需搭配主板、外设等组成整机

五、学习意义

单片机结构精简、原理直观,是理解计算机底层原理、硬件架构、嵌入式控制逻辑的最佳入门载体,也是电子信息、自动化、物联网等专业的核心基础。

2. 为什么叫51单片机

一、51单片机的由来

51单片机是对兼容英特尔8051指令系统的单片机的统称。1980年,Intel推出了MCS-51系列,其中8051芯片凭借其简洁的架构和低成本迅速占领市场,成为单片机领域的"行业标杆"。由于8051的成功,许多厂商开始基于这一架构开发自己的单片机,这些产品都被统称为"51单片机"。

二、STC89C52(典型 51 单片机)参数

  1. 基础属性 :属于51 单片机系列 ,由国产 STC(宏晶科技)出品,为8 位单片机。
  2. 存储配置
    • RAM(运行内存):512 字节,临时存储程序变量,断电丢失数据
    • ROM(程序存储):8K Flash,存储程序代码,可反复擦写,断电保存数据
  3. 硬件参数 :开发板常用12MHz 工作频率,时序简单,适合入门学习。
  4. 应用场景:LED 控制、传感器采集、电机驱动、小型智能设备开发,是嵌入式入门最常用型号。

3.LED的介绍和点亮LED

一、基础信息

  • 中文名:发光二极管
  • 英文名:Light Emitting Diode
  • 简称:LED
  • 用途:照明、广告灯、设备指示灯、屏幕背光等
  • 电气特性:单向导电 ,和普通二极管一致,正向导通发光,反向截止

二、正负极判断(3 种方法,图里全部标出)✅

  1. 引脚长短(最常用)
    • 长脚 = 正极 (+)
    • 短脚 = 负极 (-)
  2. 内部电极(图右侧红框)
    • 内部小块电极 → 正极
    • 内部大块碗状电极 → 负极
  3. 电路符号(图左侧)
    • 三角形一侧为正极 ,竖线一侧为负极 ,电流从三角流向竖线

三、51 单片机接线重点(必记)

  1. LED 必须串联220Ω 限流电阻,防止电流过大烧毁;
  2. 接法分 2 种:
    • 灌电流接法:单片机 IO 口接负极,正极经电阻接 5V(最常用)
    • 拉电流接法:IO 口接正极,负极接地
  3. 点亮逻辑:给负极低电平 / 正极高电平,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 语言标准规范)

核心结构

  1. .c 源文件 :存放模块的函数实现、变量定义,实现具体功能逻辑
  2. .h 头文件 :存放外部可调用的函数声明、宏定义、结构体定义、全局变量声明,作对外接口
  3. 调用方式 :其他源文件通过 #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配对,相当于代码块的大括号

拓展常用预编译指令(补充表格未列出的)

  1. #ifdef:如果宏已定义,执行对应代码
  2. #if:判断表达式真假,为真则执行代码
  3. #else:条件不成立时执行(类似 if-else)
  4. #elif:多重条件判断(类似 else if)
  5. #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 温度传感器

补充说明

  1. 同步 vs 异步
    • 同步通信(I²C/SPI):依赖时钟信号(SCL/SCLK)实现收发同步,抗干扰性强、速度更快。
    • 异步通信(UART/1-Wire):无时钟线,依靠预设波特率同步,硬件连接更简单
  2. 全双工 vs 半双工
    • 全双工(UART/SPI):收发可同时进行,效率更高
    • 半双工(I²C/1-Wire):同一时间只能收或发,需分时复用总线
  3. 此外还有CAN (工业抗干扰)、USB (高速设备通信)等接口,适用于更复杂的场景。
  4. 总线:连接各个设备的数据传输线路(类似于一条马路,把路边住户连接起来 使住户可以相互交流)

四、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×816×16 点阵(单片机开发中最常用
  • 大规模应用:由多个小点阵模块拼接而成,实现更大尺寸、更高分辨率的显示效果

二、显示原理

1. 核心原理:和数码管的 "亲戚关系"

LED 点阵屏的底层逻辑和数码管是相通的:

  • 数码管是把 LED 排成 "8" 字形用来显示数字
  • LED 点阵屏是把 LED 排成规则的矩阵 (比如 8×8),用来显示文字 / 图案,本质上都是多个 LED 的组合显示

2. 关键电路:共阴 / 共阳两种接法

和数码管一样 ,点阵屏也分两种接法,直接决定了驱动电平:

类型 公共端接法 点亮条件 典型应用
共阳点阵 所有 LED 的阳极统一接高电平(行线) 行线接高,列线接低,LED 点亮 左侧的单色点阵电路
共阴点阵 所有 LED 的阴极统一接低电平(行线) 行线接低,列线接高,LED 点亮 右侧的双色点阵电路(红 / 绿)

3. 动态扫描:为什么能同时显示所有点?

点阵屏无法同时给所有 LED 供电,所以必须用逐行 / 逐列扫描的方式,利用人眼的「视觉暂留效应」实现 "同时显示":

  1. 逐行扫描:先选中第 1 行,给这一行需要点亮的列送对应电平,点亮这一行的部分 LED;
  2. 快速切换到第 2 行,重复上述操作;
  3. 以每秒几十次以上的速度循环扫描所有行,人眼就会看到稳定的图像,而不是闪烁的光点。

74HC595实现了拓展I/0口由三根线拓展八根 但是这样速度会有点慢 这是必要的

这是恒压输出的 所以多个灯一起亮会变得比较暗

用74HC595控制引脚把它的行数据给输进来

三、补充

一、sfr:特殊功能寄存器声明

sfr 是用来给单片机的 8 位特殊功能寄存器定义名字和地址的关键字。

  • 作用:把寄存器的物理地址和一个变量名绑定,让你可以用变量名直接操作寄存器,不用每次都写地址。
  • 例子:sfr P0 = 0x80;
    • 声明了 P0 这个名字,对应物理地址 0x80 的寄存器(也就是 P0 端口)。
    • 之后你写 P0 = 0xFF;,就等价于给地址 0x80 写入数据 0xFF

二、sbit:特殊位声明

sbit 是用来直接操作寄存器中某一个特定位 的关键字,专门用于位寻址

  • 两种用法:
    1. 直接写位地址:sbit P0_1 = 0x81;
    2. 通过寄存器名 + 位号:sbit P0_1 = P0^1;
  • 例子里的 P0_1 就代表 P0 端口的第 1 位,之后你写 P0_1 = 1;,就能单独把 P0.1 这个引脚置高。

三、可位寻址 vs 不可位寻址

这部分是理解 C51 位操作的关键

  1. 为什么会有 "可位寻址"? 单片机里的寄存器是 8 位的,位的数量是寄存器的 8 倍,单片机无法给每个位都分配独立地址。所以规定:只有地址能被 8 整除的寄存器,才是可位寻址的 (比如0x800x880x90等),这些寄存器的每一位都有对应的位地址 ,可以直接用sbit操作。
  2. 不可位寻址的寄存器怎么操作某一位? 不能直接用sbit只能用位运算来 "屏蔽" 其他位 ,只修改目标位:
    • 置 1:寄存器 |= (1 << 位号);
    • 清 0:寄存器&= ~(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;
			}
			
		}

	}
}
相关推荐
颖火虫盟主15 小时前
Lua 协程:从 API 到底层原理再到 Skynet 架构的完整学习路径
学习·架构·lua
段一凡-华北理工大学15 小时前
工业领域的Hadoop架构学习~系列文章01:Hadoop与工业4.0深度融合
大数据·hadoop·学习·架构·知识图谱·高炉炼铁·工业智能体
全球通史15 小时前
Jetson Nano 双摄像头芯片检测视觉系统:小尺度难定位问题解决,从零开始实现教程说明
嵌入式硬件·算法·ubuntu·性能优化
千寻girling15 小时前
机器学习 | 监督学习算法(了解) | 尚硅谷学习
开发语言·人工智能·后端·python·学习·算法·机器学习
崇山峻岭之间15 小时前
单片机RTC实验
单片机·嵌入式硬件·实时音视频
red_redemption15 小时前
自由学习记录(195)
学习
该用户已躺平@15 小时前
并网逆变器学习笔记11---并网逆变器学习---SSRF-PLL、DDSRF-PLL、DSOGI-PLL快速上手(结合deepseek脚本快速生成)
笔记·学习
海兰15 小时前
手把手elasticsearch学习之构建 HITL AI 代理
人工智能·学习·elasticsearch
和blue一起变得更好15 小时前
day4 element plus+vue3+vite实现简单学习任务管理
javascript·vue.js·学习