Skip to content

51 单片机

参考视频:江协科技https://www.bilibili.com/video/BV1Mb411e7re

参考资料:STC官网STC单片机专业供应商 (stcmicro.com)

1. 简介

1.1 概念

什么是单片机

单片机种类

单片机应用

1.2 环境准备

驱动安装

打开普中科技给的资源包里,会有驱动的

image-20240306151115356

image-20240306151134092

image-20240306151149859

如果失败,点击安装失败解决方法

如果驱动已经安装成功了,能够检查到。只要没有黄的感叹号,都是连接成功了

image-20240306125948625

辅助软件安装stc-isp

这边能显示串口号,就说明连接成功了

image-20240307104023201

Keil5 安装

官网下载:Keil Product Downloads

Keil公司是一家业界领先的微控制器(MCU)软件开发工具的独立供应商。Keil公司由两家私人公司联合运营,分别是德国慕尼黑的Keil Elektronik GmbH和美国德克萨斯的Keil Software Inc。Keil公司制造和销售种类广泛的开发工具,包括ANSI C编译器、宏汇编程序、调试器、连接器、库管理器、固件和实时操作系统核心(real-time kernel)。有超过10万名微控制器开发人员在使用这种得到业界认可的解决方案。Keil公司在2005年被ARM公司收购。KEIL软件为适应51内核和ARM内核,KEIL版本分为KEIL C51和MDK-ARM。KEIL MDK目前最新的版本为9.61。

image-20240306151354969

随便写写,都写上就行

image-20240306151507025

Keil5破解

image-20240306144408364

image-20240306144427480

image-20240306144549623

image-20240306144621195

image-20240306144658683

1.3 开发板简介

image-20240307201724588

序号板载资源
1数码管模块2个四位一体共阴数码管,可显示8位数字字符,如时钟、密码等
2LCD1602接口兼容LCD1602/LCD9648液晶屏,可显示数字字符等
3LCD12864接口兼容LCD9648/MiniLCD12864/带字库LCD12864液晶,可显示汉字/图像等
48*8LED点阵可显示数字/字符/图形等
5LED模块8个LED,可实现流水灯等花样显示
6矩阵按键4*4矩阵按键,可作计算器、密码锁等应用的输入装置
7红外接收头NEC协议,可实现遥控
8DS18B20温度传感器可实现温度采集控制等
9NRF24L01接口可实现2.4G远程遥控通信
10独立按键4个按键,可作按键输入控制
11MicroUSB接口可作电源输入、程序下载等
12USB转TTL模块CH340C芯片,可作电脑USB与单片机串口下载和通信
133.3V电源模块ASM1117-3.3芯片,将5V转为3.3V供用户使用
14电源开关系统电源控制开关
15ADC/DAC模块XPT2046芯片作为ADC,LM358+PWM作为DAC,可采集外部模拟信号和输出模拟电压
16EEPROM模块AT24C02芯片,可存储256字节数据,掉电不丢失
17复位按键系统复位
18无源蜂鸣器可作报警提示、音乐等
19DS1302时钟模块DS1302芯片,可作时钟发生
20步进电机驱动模块ULN2003芯片,可作直流电机、28BYJ48步进电机驱动
21STC89Cxx单片机座及IO固定单片机,并将所有I0引出,方便用户二次开发
22TFTLCD模块接口与3号接口组合可连接TFTLCD触摸屏,可实现酷炫GUI人机
2374HC595扩展I0,控制LED点阵
2474HC245驱动数码管段选显示
2574HC138驱动数码管位选显示

2. 入门-LED灯

概念

2-1 点亮一个LED灯

LED灯为啥低点位才亮

由于单片机的I/O口的结构决定了它灌电流能力较强,所以都采用低电平点亮led的方式。其实,采用低电平驱动LED,只是为了简化单片机接口的设计,如果采用接口元件,则高电平驱动和低电平驱动是同样的效果,另外,低电平驱动也简化了控制代码,避免了单片机上电复位时端口置高电平后对led的影响。

LED具有单向导电性,当LED的正端接了高电位,负端连接了低电位,且正负端电位差超过1.8V以上时,LED就会亮起来。

image-20240307202350272

创建项目

image-20240306145440719

image-20240306145545249

选择合适的编译器

image-20240306145631963

image-20240306145712353

image-20240306145736828

创建成功如下所示

image-20240306145744892

编写代码

image-20240306145757522

image-20240306145804796

image-20240306145927039

如果你觉得字体太小,可以按照以下操作

image-20240306150033700

编写代码

c
#include <REGX52.h>

void main()
{
	P2=0xFE; //1111 1110 0表示低点位,LED在低点位的情况下才亮灯
}

解释一下P0啥意思。C51中的sfr,sbit

  • sfr (special function register)特殊功能寄存器声明

    例:sfr PO =0x80;物理地址为0x80声明P0口寄存器,

  • sbit (special bit) :特殊位声明

    例:sbit PO_1=0x81;或 sbit p0_1=POA1;

    声明PO寄存器的第1位

可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码故每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“=”“^=”的方法进行位操作

image-20240413161929215

点击编译,输出hex文件

image-20240306150335884

编译成功,

image-20240306151656087

查看是否成功输出hex文件,在文件夹的Objects下

image-20240306150359471

上传代码至单片机

image-20240306150703117

重启单片机后,会发现灯亮了

image-20240306150758001

2-2 LED闪烁

根据板子-生成延时代码

image-20240307104809609

编写代码

c
#include <REGX52.H>
#include <INTRINS.H>

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()
{
	while(1){
		
		P2=0xFE;
		Delay500ms();
		P2=0xFF;
		Delay500ms();
	}
	
}

2-3 LED流水灯

c
#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}



void main()
{
	while(1){
		P2=0xFE; // 1111 1110
		Delay1ms(500);
		P2=0xFD; // 1111 1101
		Delay1ms(500);
		P2=0xFB; // 1111 1011
		Delay1ms(500);
		P2=0xF7; // 1111 0111
		Delay1ms(500);
		P2=0xEF; // 1110 1111
		Delay1ms(500);
		P2=0xDF; // 1101 1111
		Delay1ms(500);
		P2=0xBF; // 1011 1111
		Delay1ms(500);
		P2=0x7F; // 0111 1111
		Delay1ms(500);
	}
	
}

3、 独立按键

3-1 独立按键控制LED亮灭

独立按键

image-20240307202410746

c
#include<REGX52.h>

void main()
{
   while(1){
		
		 if(P3_1==0){ // 当第一个独立按键被按下时
			 p2_0=0; // 第一个灯就会亮
		 }else{
			 p2_0=1;
		 }
		 
	 }
}

简化版

c
#include<REGX52.h>

void main()
{
   P2_0=P3_1;
}

3-2 独立按键控制LED状态

注意事项

image-20240307111939335

所以需要20ms来延时抖动

c
#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}


void main()
{
	while(1){
		if(P3_1==0){
			Delay1ms(20);
			while(P3_1==0);
			Delay1ms(20);
			P2_0=~P2_0;
		}
	}
	
}

3-3 独立按键控制LED显示二进制

c
#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}


void main()
{
	while(1){
		if(P3_1==0){
			Delay1ms(20);
			while(P3_1==0);
			Delay1ms(20);
			P2--;
		}
	}
	
}

3-4 独立按键控制LED移位

c
#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}


void main()
{
	
	unsigned char LEDnumber;
	
	P2=~0x01;
	
	while(1){
		if(P3_1==0){
			Delay1ms(20);
			while(P3_1==0);
			Delay1ms(20);
			LEDnumber++;
			if(LEDnumber>=8){
				LEDnumber=0;
			}
			P2=~(0x01<<LEDnumber);
		}
		
		if(P3_0==0){
			Delay1ms(20);
			while(P3_0==0);
			Delay1ms(20);
			
			if(LEDnumber==0){
				LEDnumber=7;
			
			}else{
				
				LEDnumber--;
			}
			P2=~(0x01<<LEDnumber);
		}
	}
	
	
	
}

4、数码管

概念

什么是数码管

LED数码管: 数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件

  • 按照发光二极管段数可以7段数码管和8段数码管,二者区别呢?就是8段数码管多了一个dp管,表示小数点。

  • 按照发光二极管连接方式,可以分为2种,第一种是,所有的阴极连在一块,叫做共阴极,还有一种是,所有的阳极连在一块,叫做共阳极

    • 共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V
    • 共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM) 的数码管,共阴数码管在应用时应将公共极 COM 接到地线GND上

image-20240307201532149

数码管引脚定义-显示原理

image-20240307203119383

电路设计

image-20240307202331685

为什么需要38译码器

image-20240307205230809

这里我们看到U5 CBA的配置。这里用来把3位数字转换成8位 比如 5 代表3位 101 转换成8位 0001 0000

CBA就表示3位 101 C表示第一位的1 B表示第二位的0 A表示第三位的1

表示选中了LED5

为什么需要74HC245

4-1 静态数码管显示6

image-20240307215230062

让数码管显示6

c
#include <REGX52.H>
void main()
{
	
	P2_4=1;
	P2_3=0;
	P2_2=1;
	P0=0x7D; // 
}

理由如下

image-20240307221243386

image-20240307220519887

  • dp表示 数码管上的小数点,因为不需要显示,所以对应的是0
  • P07对应高位,P0寄存器正确的读法是 P07-P00 。对应的数据位 0111 1101 对应的16进制位 7D
c
#include <REGX52.H>
#include <INTRINS.H>
unsigned char  NixmeTable[]={
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
	0x77,0x7C,0x39,0x5E,0x79,0x71,0x00
};

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

void Nixie(unsigned char Location,number)
{
	switch(Location){
		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;
	}
	
	P0=NixmeTable[number];
}


void main()
{
	
    Nixie(7,2);
    while(1){
        
    }
	
}

4-2 动态数码管显示

c
#include <REGX52.H>
#include <INTRINS.H>
unsigned char  NixmeTable[]={
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
	0x77,0x7C,0x39,0x5E,0x79,0x71,0x00
};

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

void Nixie(unsigned char Location,number)
{
	switch(Location){
		
		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;
	}
	
	P0=NixmeTable[number];
}


void main()
{
	
	while(1){
		Nixie(2,1);
		Nixie(3,2);
		Nixie(4,3);
	}
	
}

这种情况下会出现消影问题

位选 段选 位选 段选 位选 段选 位选 段选 位选 段选

正常情况下, 是一段位选 段选 ,一段 位选 段选 。但是程序运行太快,会出现一段的 段选 位选 ,即橘色的部分。这样子,数字就会串号了

image-20240308181759643

解决办法

位选 段选 清零 位选 段选 清零 位选 段选 清零 位选 段选 清零 位选 段选

c
#include <REGX52.H>
#include <INTRINS.H>
unsigned char  NixmeTable[]={
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
	0x77,0x7C,0x39,0x5E,0x79,0x71,0x00
};

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

void Nixie(unsigned char Location,number)
{
	switch(Location){
		
		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;
	}
	
	P0=NixmeTable[number];
	Delay1ms(5);
	P0=0x00;
}


void main()
{

	while(1){
		Nixie(2,1);
		Nixie(3,2);
		Nixie(4,3);
	}	
}

单片机直接扫描:硬件设备简单,但是会耗费大量的单片机CPU时间。

专用驱动芯片,内部自带显存,扫描电路,单片机只需要告诉他显示什么即可

5、模块化

5-1 模块化编程

首先进行新建工程

创建Delay.c 文件

image-20240308202245247

c
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

然后创建头文件

image-20240308202400583

创建.h文件后,会发现文件里面啥都没有。所以需要添加已经存在的文件,才能显示

image-20240308202134695

image-20240308202506950

最后发现工程目录为

image-20240308202200360

main文件夹内容

c
#include <REGX52.h>
#include "Delay.h"

void main()
{
	Delay1ms(10);
}

所有函数如下

image-20240308191643714

5-2 LCD1602调试工具

模块化LCD

LCD1602.c代码

c
#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');
	}
}

LCD1602.h

c
#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

main.c

c
#include <REGX52.h>
#include "LCD1602.h"

void main()
{
	
	LCD_Init();
	LCD_ShowString(1,1,"A");
	while(1)
	{
		
	}
}

注意事项

  1. LCD板子没有插进入,千万别返回来擦进入
  2. 延时函数,LCD1602.h里面的函数得写对
  3. 变阻器,需要自己调节,调整亮度
  4. 程序需要重新下载,才能显示

image-20240308213918955

下面是我的错误尝试。引以为戒

image-20240308214306857

6、矩阵键盘

基本概念

  • 在键盘中按键数量较多时,为了减少I/0口的用,通常将按键排列成矩阵形式
  • 采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态

image-20240309110909498

6-1 矩阵键盘显示

项目目录结构如下

image-20240310152719108

MatrixKey.c

c
#include <REGX52.h>
#include "Delay.h"

unsigned char MatrixKey()
{
	
	unsigned char KeyNumber=0;
	
	P1=0xFF;
	P1_3=0;
	if(P1_7==0){Delay1ms(20);while(P1_7==0);Delay1ms(20);KeyNumber=1;}
	if(P1_6==0){Delay1ms(20);while(P1_6==0);Delay1ms(20);KeyNumber=5;}
	if(P1_5==0){Delay1ms(20);while(P1_5==0);Delay1ms(20);KeyNumber=9;}
	if(P1_4==0){Delay1ms(20);while(P1_4==0);Delay1ms(20);KeyNumber=13;}
	
	// 
	P1=0xFF;
	P1_2=0;
	if(P1_7==0){Delay1ms(20);while(P1_7==0);Delay1ms(20);KeyNumber=2;}
	if(P1_6==0){Delay1ms(20);while(P1_6==0);Delay1ms(20);KeyNumber=6;}
	if(P1_5==0){Delay1ms(20);while(P1_5==0);Delay1ms(20);KeyNumber=10;}
	if(P1_4==0){Delay1ms(20);while(P1_4==0);Delay1ms(20);KeyNumber=14;}
	
	P1=0xFF;
	P1_1=0;
	if(P1_7==0){Delay1ms(20);while(P1_7==0);Delay1ms(20);KeyNumber=3;}
	if(P1_6==0){Delay1ms(20);while(P1_6==0);Delay1ms(20);KeyNumber=7;}
	if(P1_5==0){Delay1ms(20);while(P1_5==0);Delay1ms(20);KeyNumber=11;}
	if(P1_4==0){Delay1ms(20);while(P1_4==0);Delay1ms(20);KeyNumber=15;}
	
	P1=0xFF;
	P1_0=0;
	if(P1_7==0){Delay1ms(20);while(P1_7==0);Delay1ms(20);KeyNumber=4;}
	if(P1_6==0){Delay1ms(20);while(P1_6==0);Delay1ms(20);KeyNumber=8;}
	if(P1_5==0){Delay1ms(20);while(P1_5==0);Delay1ms(20);KeyNumber=12;}
	if(P1_4==0){Delay1ms(20);while(P1_4==0);Delay1ms(20);KeyNumber=16;}
	
	
	return KeyNumber;
	
}

matrixKey.h

c
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__


unsigned char MatrixKey();

#endif

main.c

c
#include <REGX52.h>
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNum;
void main()
{
	
	LCD_Init();
	LCD_ShowString(1,1,"A");
	while(1)
	{
		KeyNum=MatrixKey();
		if(KeyNum){
			LCD_ShowNum(2,1,KeyNum,2);
		}
	}
}

6-2 矩阵键盘密码锁

image-20240311144518457

main.c代码如下

c
#include <REGX52.h>
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNum;
unsigned int Password,Count;
void main()
{
	
	LCD_Init();
	LCD_ShowString(1,1,"Password");
	while(1)
	{
		KeyNum=MatrixKey();
		if(KeyNum){
			if(KeyNum<=10)
			{
				if(Count<4){
					Password*=10;
					Password+=KeyNum%10;
					Count++;
				}
			}
			LCD_ShowNum(2,1,Password,4);
			
			
			if(KeyNum==11)
			{
				 if(Password==2345){
				   LCD_ShowString(1,14,"OK ");
					 Password=0;
					 Count=0;
				 }else{
					 LCD_ShowString(1,14,"ERR");
					 Password=0;
					 Count=0;
				 }
				 LCD_ShowNum(2,1,Password,4);
			}
			
			if(KeyNum==12)
			{
			   	Password=0;
					Count=0;
				  LCD_ShowNum(2,1,Password,4);
			}
		}
	}
}

7、定时器

基本概念

51单片机的定时器属于单片机的内部资源,其电路的连接河运转均在单片机内部完成

  • 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
  • 代替长时间的Delay,提高CPU的运行效率和处理速度

STC89C52定时器资源

定时器个数:3个(T0,T1,T2).T2是此型号单片机增加的资源

注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的

注意点:STC89C52RC芯片内置了2个定时器/计数器(Timer0和Timer1),这两个模块可以用于实现定时和计数功能。

image-20240311145402348

定时器工作模式

STC89C52的T0和T1均有四种工作模式

  • 模式0:13位定时器/计数器

    image-20240311184902184

    在模式0下,定时器/计数器的计数值为TH0和TL0组成的13位数值,当计数器溢出时,TH0和TL0将重新装载初值。在这种模式下,定时器/计数器的计数精度相对较低,适用于计数范围比较小的场合。

  • 模式1: 16位定时器/计数器(常用)

    在模式1下,定时器/计数器的计数值为TH0和TL0组成的16位数值,当计数器溢出时,TH0和TL0将重新装载初值。在这种模式下,定时器/计数器的计数精度较高,适用于计数范围比较大的场合。

  • 模式2:8位自动重装模式

    在模式2下,定时器/计数器的计数值为TH0组成的8位数值,当计数器溢出时,TH0将重新装载初值,并置位TF0标志位。在下一个计数周期,定时器/计数器将继续计数,直到再次溢出,重复上述操作。在这种模式下,定时器/计数器的计数精度较高,同时能够自动重新装载初值,适用于需要周期性定时的场合。

  • 模式3:两个8位计数器

    在模式3下,定时器/计数器的计数值为TH0和TL0组成的16位数值,当计数器溢出时,TH0和TL0将重新装载初值,并置位TF0标志位。在下一个计数周期,定时器/计数器将继续计数,直到再次溢出,重复上述操作。在这种模式下,定时器/计数器的计数精度较高,同时能够自动重新装载初值,适用于需要周期性定时的场合,计数范围较大。

中断系统

中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的

当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。

当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序) ,发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。

image-20240311192904888

STC89C52中断资源

STC89C52系列单片机提供了8个中断请求源,它们分别是: 外部中断0(INTO)、定时器0中断、外部中断1(INT1)定时器1中断、串口(UART)中断、定时器2中断、外部中断2(INT2)外部中断3(NT3)。所有的中断都具有4个中断优先级。

  • 中断源个数:8个(外部中断0,定时器0中断,外部中断1,定时器1中断,串口中断,外部中断2,外部中断3

  • 中断优先级个数4

  • 中断号

    image-20240311194319714

  • 注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等

  • 寄存器是连接软硬件的

  • 媒介在单片机中寄存器就是一段特殊的RAM存储器一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式寄存器相当于一个复杂机器的“操作按钮

7-1 按键控制LED流水灯模式

使用Timer0-模式1 实现1秒定时

将TMOD寄存器的低四位设置为0001,表示使用Timer0的模式1,即16位定时器模式。

设置Timer0的初值,即将TL0和TH0寄存器清零,并将计数值赋给TH0寄存器。

开始计时,即打开定时器中断开关(ET0)和总中断开关(EA)。

在中断服务程序中,每当Timer0计数到0时,重新设置初值,并执行定时操作。

下面是一段示例代码,实现每隔1秒LED闪烁一次的功能:

c
#include <REGX52.h>
void Timer0_Init()
{

	// 首先配置定时器的工作模式,TMOD:定时器/计数器模式控制
   TMOD=0x01; // 0000 0001
	
	// TCON:控制寄存器,作用是控制定时器的启,停止,标志定时器溢出和中断情况
	TF0=0;
	TR0=1;
	TH0=64535/256;
	TL0=64535%256;
	
  // 两个寄存器TH0、TL0为二进制八位,单独可计256次
	// 低八位计满 256次后高8位进1
	// 所以除以256可得高八位得次数,取余就是低八位的次数
	// 合并在一起就是所赋的初始值
	// 现在想把123 存储在2个寄存器里面,该怎么存呢??
	// 123 -> 123/100=1  123%100=23
	// 这样子123 就被存储在2个地方了
	
	// 下面开始配置中断
	ET0=1;
	EA=1;
	PT0=0;
}

void main()
{
	Timer0_Init();
	// 如果不加 while的话,灯是不会闪动的
	while(1){
	}
}
unsigned int T0Count;

void Timer0_Routine() interrupt 1
{
	TH0=64535/256;
	TL0=64535%256;
	T0Count++;
	if(T0Count>=1000){
		T0Count=0;
		P2_0=~P2_0; // 指示灯开始闪烁 每个一秒钟
	}
}

或者不知道怎么改?

image-20240401112959328

微妙,毫秒啥的,单位记得配置正确

c
#include <REGX52.h>

void Timer0_Init(void)		//1微秒@12.000MHz
{
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	// 下面开始配置中断
	ET0=1;
	EA=1;
	PT0=0;
}


void main()
{
	Timer0_Init();
	// 如果不加 while的话,灯是不会闪动的
	while(1){
	}
}
unsigned int T0Count;

void Timer0_Routine() interrupt 1
{
    TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	T0Count++;
	if(T0Count>=1000){
		T0Count=0;
		P2_0=~P2_0; // 指示灯开始闪烁 每个一秒钟
	}
}

使用Timer1-模式2 实现1秒定时

在STC89C52RC芯片中,Timer1可以用来实现计数器功能。具体步骤如下:

将TMOD寄存器的高四位设置为0010,表示使用Timer1的模式2,即8位自动重装计数器模式。

设置Timer1的初值,即将TL1和TH1寄存器清零,并将计数值赋给TH1寄存器。

开始计数,即打开定时器中断开关(ET1)和总中断开关(EA)。

在中断服务程序中,每当Timer1计数到0时,将计数器的值加1。

下面是一段示例代码,实现每隔1秒计数器加1,当计数器的值达到10时,停止计数:

c
#include <reg52.h>
 
sbit led = P1^0; // LED连接到P1.0
unsigned char counter = 0; // 计数器
 
void timer1_isr() interrupt 3 { // Timer1中断服务程序
    TH1 = 0x3C; // 重新设置初值
    TL1 = 0xAF;
    counter++; // 计数器加1
    if(counter == 10) { // 计数器达到10时停止计数
        ET1 = 0; // 关闭Timer1中断开关
        led = 0; // 关闭LED
    }
}
 
void main() {
    TMOD = 0x20; // 设置Timer1为模式2
    TH1 = 0x3C; // 设置Timer1的初值为15536
    TL1 = 0xAF;
    EA = 1; // 打开总中断开关
    ET1 = 1; // 打开Timer1中断开关
    TR1 = 1; // 启动Timer1
    while(1) {
        led = 1; // 点亮LED
    }
}

在上述代码中,TH1和TL1寄存器的初值为15536,即0x3CAF。这是因为STC89C52RC的时钟频率为11.0592MHz,而Timer1的计数时钟频率为时钟频率的1/12,即921.6kHz。当计数器达到0时,计数器的值为65536,即0x10000,需要重新设置初值为0x3CAF,这样计数器的溢出时间就是1秒。

下面的代码呢?是实现流水灯模式

代码结构如下

image-20240410114602184

main.c

c
#include <REGX52.h>
#include <Timer0.h>
#include <Key.h>
#include <INTRINS.h>


unsigned char KeyNum,LEDMode;
void main()
{
	
	P2=0xFE;
	Timer0_Init();
	// 如果不加 while的话,灯是不会闪动的
	while(1){
		
		KeyNum=Key();
		if(KeyNum){
			if(KeyNum==1){
				if(KeyNum==1){
					LEDMode++;
					if(LEDMode>=2){
						LEDMode=0;
					}
				}
			}
			if(KeyNum==2) P2_2=~P2_2;
			if(KeyNum==3) P2_3=~P2_3;
			if(KeyNum==4) P2_4=~P2_4;
		}
	}
}
unsigned int T0Count;

void Timer0_Routine() interrupt 1
{
 	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	T0Count++;
	if(T0Count>=1000){
		T0Count=0;
		if(LEDMode==0){
			P2=_crol_(P2,1);
		}
		
		if(LEDMode==1){
			P2=_cror_(P2,1);
		}
	}
}

delay.c

c
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

Time.c

c
#include <REGX52.h>

void Timer0_Init(void)		//1微秒@12.000MHz
{
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD=0x01; 		//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时

	// 下面开始配置中断
	ET0=1;  // 打开Timer0中断开关
	EA=1; // 打开总中断开关
	PT0=0;
}

7-2 定时器时钟

c
#include <REGX52.h>
#include <Delay.h>
#include <LCD1602.h>
#include <Timer0.h>

unsigned char Sec=55,Min=59,Hour=23;

void main()
{
	
	LCD_Init(); //初始化
	Timer0_Init();
	
	
	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);
		
	}
	
}

unsigned int T0Count;


void Timer0_Routine() interrupt 1
{
 	TL0 = 0x66;				//设置定时初始值
	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;
		}	
	}
}

8、串口

基本知识

概念

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。

单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。

51单片机内部自带UART(Universal Asynchronous ReceiverTransmitter,通用异步收发器),可实现单片机的串口通信

台式机上的串口

image-20240410123018472

硬件电路

  • 简单双阿串口通信有两根通信线(发送端TXD和接收端RXD
  • TXD与RXD要交叉连接
  • 当只需单向的数据传输时,可以直接一根通信线
  • 当电平标准不一致时,需要加电平转换芯片

image-20240410123915175

电平标准

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电 压与数据的对应关系,串口常用的电平标准有如下三种 TTL电平:+5V表示1,0V表示0 RS232电平:表示1,表示0 RS485电平:两线压差表示1,​表示0(差分信号)

常见通信接口比较

名称引脚定义通信方式特点
UARTTXD,RXD全双工,异步点对点通信
CSCL,SDA半双工,同步可挂载多个设备
SPISCLK,MOSI,MISO,CS全双工,同步可挂载多个设备
1-WireDQ半双工,异步可挂载多个设备

以外还有CAD,USB等

  • 全双工:通信双方可以在同一时刻互相传输数据
  • 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线单工:通信只能有一方发送到另一方,不能反向传输
  • 异步:通信双方各自约定通信速率
  • 同步:通信双方靠一根时钟线来约定通信速率
  • 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)

51单片机的UART

STC89C52有一个UART

STC89C52的UART有四种工作模式

  • 模式0:同步移位寄存器
  • 模式1:8位UART,波特率可变 (常用)
  • 模式2:9位UART,波特率固定
  • 模式3:9位UART,波特率可变

串口参数及时序图

  • 波特率:串口通信的速率(发送和接受各数据位的间隔时间)
  • 检验位:用于数据验证
  • 停止位:用于数据帧间隔

image-20240411185140806

串口模式图

image-20240411190029259

  • SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器
  • STC89C52系列单片机内部集成有一个功能很强的全双工串行通信口,与传统8051单片机的串口完全兼容。设有2个互相独立的接收、发送缓冲器,可以同时发送和接收数据。发送缓冲器只能写入而不能读出,接收缓冲器只能读出而不能写入,,因而两个缓冲器可以共用一个地址码(99H)。两个缓冲器统称串行通信特殊功能寄存器SBUF。
  • 串行通信设有4种工作方式,其中两种方式的波特率是可变的,另两种是固定的,以供不同应用场合选用。波特率由内部定时器/计数器产生,用软件设置不同的波特率和选择不同的工作方式。主机可通过查询或中断方式对接收/发送进行程序处理,使用十分灵活。
  • STC89C52系列单片机串行口对应的硬件部分对应的管脚是P3.0/RxD和P3.1/TxD
  • STC89C52系列单片机的串行通信口,除用于数据通信外,还可方便地构成一个或多个并行IO口,或作串一并转换,或用于扩展串行外设等。

串口寄存器

image-20240411191334197

PCON:电源控制寄存器(不可位寻址)

SFR nameAddressbitB7B6B5B4B3B2B1B0
PCON87HnameSMODSMOD0-POFGF1GF0PDIDL
  • SMOD:波特率选择位。当用软件置位SMOD,即SMOD=1,则使串行通信方式1、2、3的波特率加倍:SMOD=0,则各工作方式的波特率加倍。复位时SMOD=0。
  • SMOD0:帧错误检测有效控制位。当SMODO=1,SCON寄存器中的SMO/FE位用于FE(帧错误检测)功能:当SMOD0=0,SCON寄存器中的SMO/FE位用于SMO功能,和SM1一起指定串行口的工作方式。复位时SMODO=0

8-1 串口向电脑发送数据(每隔一秒)

如果不想手动了解的话,直接自动生成

image-20240412112508584

但是需要把

image-20240412112925941

这两行删除,这个配置是属于高级配置 ,不属于51单片机的

代码如下

c
#include <REGX52.h>
#include <Delay.h>

void UART_Init()
{
	SCON=0x40;
	PCON &= 0x7F;		//波特率不倍速
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0x0F; 		//设置定时器模式 TMOD &= 0x0F(清除定时器1模式位)
	TMOD |= 0x20; // 设置定时器1 为8位自动重装模式
	TL1 = 0xFA;			//设置定时初始值
	TH1 = 0xFA;			//设置定时重载值
	ET1 = 0;			//禁止定时器中断
	TR1 = 1;			//定时器1开始计时

}


void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	TI=0;
	
}

void main()
{
	
	UART_Init();
	UART_SendByte(0x66);
	
	
	while(1)
	{
		
	}
}

image-20240413102956855

HEX模式/十六进制模式/二进制模式:以原始数据的形式显示 文本模式/字符模式:以原始数据编码后的形式显示

复位键是哪个?

image-20240413103130302

如果想实现,每隔一秒钟,发送一个数据,就不需要复位键了。代码如下

c
#include <REGX52.h>
#include <Delay.h>

void UART_Init()
{
	SCON=0x40;
	PCON &= 0x7F;		//波特率不倍速
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0x0F; 		//设置定时器模式 TMOD &= 0x0F(清除定时器1模式位)
	TMOD |= 0x20; // 设置定时器1 为8位自动重装模式
	TL1 = 0xFA;			//设置定时初始值
	TH1 = 0xFA;			//设置定时重载值
	ET1 = 0;			//禁止定时器中断
	TR1 = 1;			//定时器1开始计时

}


void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	TI=0;
	
}

unsigned char Sec;
void main()
{
	
	UART_Init();
	
	
	
	while(1)
	{
		
		UART_SendByte(Sec);
		Sec++;
		Delay1ms(1000);
		
	}
}

接下来,开始模块串口化

image-20240413104258538

UART.c文件

c
#include <REGX52.h>

void UART_Init()
{
	SCON=0x50;
	PCON &= 0x7F;		//波特率不倍速
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0x0F; 		//设置定时器模式 TMOD &= 0x0F(清除定时器1模式位)
	TMOD |= 0x20; // 设置定时器1 为8位自动重装模式
	TL1 = 0xFA;			//设置定时初始值
	TH1 = 0xFA;			//设置定时重载值
	ET1 = 0;			//禁止定时器中断
	TR1 = 1;			//定时器1开始计时

}


void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	TI=0;
	
}

UART.h文件

c
#ifndef __UART_H__
#define __UART__H__

UART_Init();
void UART_SendByte(unsigned char Byte);

#endif

main.c代码改成

c
#include <REGX52.h>
#include <Delay.h>
#include <UART.h>


unsigned char Sec;
void main()
{
	
	UART_Init();
	
	while(1)
	{
		
		UART_SendByte(Sec);
		Sec++;
		Delay1ms(1000);
		
	}
}

8-2 电脑通过串口控制灯

首先修改UART.c文件。修改内容如下

SCON=0x50; 让模式变成既能收,也能读取

EA=1;ES=1; 开启UART中断

c
#include <REGX52.h>

void UART_Init()
{
	SCON=0x50;
	PCON &= 0x7F;		//波特率不倍速
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0x0F; 		//设置定时器模式 TMOD &= 0x0F(清除定时器1模式位)
	TMOD |= 0x20; // 设置定时器1 为8位自动重装模式
	TL1 = 0xFA;			//设置定时初始值
	TH1 = 0xFA;			//设置定时重载值
	ET1 = 0;			//禁止定时器中断
	TR1 = 1;			//定时器1开始计时
	
	EA=1;
	ES=1;
	

}


void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	TI=0;
	
}

main.c文件

c
#include <REGX52.h>
#include <Delay.h>
#include <UART.h>


unsigned char Sec;


void UART_Routine() interrupt 4
{
	
	// 如果是接受中断
	if(RI==1){
		P2=SBUF;
		UART_SendByte(SBUF); // 串口给电脑发送消息
		RI=0; // 必须由软件复位
	}
}


void main()
{
	
	UART_Init();
	
	while(1)
	{
		


	}
}

操作界面如下

image-20240413110229179

计算波特率

image-20240413110814605

c
	PCON &= 0x7F;		//波特率不倍速
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0x0F; 		//设置定时器模式 TMOD &= 0x0F(清除定时器1模式位)
	TMOD |= 0x20; // 设置定时器1 为8位自动重装模式
	TL1 = 0xFA;			//设置定时初始值
	TH1 = 0xFA;			//设置定时重载值
	ET1 = 0;			//禁止定时器中断
	TR1 = 1;			//定时器1开始计时

我的晶振板子 为11.0592hz。配置的数值为上。设置的值就是0xFA。为啥呢??

K就是寄存器的长度。用定时器T1以工作方式2作波特率发生器,采用的是8位自动重载

则K=8,SMOD取为1。就是系统时钟

对应的十进制是250,每隔250溢出一次。256-250=6。每计数6 就溢出一次。

image-20240413112540205这个就是溢出率,T1的溢出率就是 1/6=0.1666MHz

我们11.0592hz就0.9216微秒

波特率为19200时,对应初值为: 253 = 11111101B = FDH 波特率为9600时,对应初值为: 250 = 11111010B = FAH 波特率为4800时,对应初值为: 244 = 11110100B = F4H 波特率为2400时,对应初值为: 232 = 11101000B = E8H 波特率为1200时,对应初值为: 208 = 11010000B = DOH

image-20240413111021451

9、LED点阵屏

基本概念

LED点阵屏由若千个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等

LED点阵屏分类

  • 按颜色:单色、双色、全彩
  • 按像素:​等(大规模的LED点阵通常由很多个小点阵拼接而成)

内部结构

image-20240413144636071

8*8 点阵共由 64 个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,当对应的某一行置 1 电平,某一列置 0 电平,则相应的二极管就亮:如要将第一个点点亮,则 1 脚接高电平 a 脚接低电平,则第一个点就亮了;如果要将第一行点亮,则第1脚要接高电平,而 (a、b、c、d、e、f、g、h ) 这些引脚接低电平,那么第一行就会点亮:如要将第一列点亮,则第 a 脚接低电平,而(1、2、3、4、5、6、7、8) 接高电平,那么第一列就会点亮。由此可见,LED点阵的使用也是非常简单的。

由于单片机中的I口十分的紧张,所以呢,使用了以下这个模块进行转换。使用3根线,来操作一下这么多的线

image-20240413144904166

  • 74HC595是串行输入输出的移位寄存器,可用三根输入串行数据,八根输出并行数据,在多级级联后,可输出16位、24位、32位。
  • OE(output enable)加横线,表示连接低电平有效,接入低电平才可以输出,74HC595才可以工作相当于开关,OE端连接在跳线帽,需要与GND连接才有效。
  • RCLK(register clock)寄存器时钟,连接到P35
  • SRCLR 加横线 (serial clear)串形清零端接入VCC,表示不清空数据
  • SRCLk(serial clock)串行时钟
  • SER 串行数据 一一输入数据

要想正确的使用LED点阵

image-20240413152828720

  • 。OE上面加一个横线,一般表示低电平有效。OE- output enable.输出使能。如果OE接了低电平,才可以输出。OE接低电平,这个芯片才工作。所以跳线帽 街道j24的2,3。GND表示地线,和GND连接。OE才是低电平
  • RCLK register clock。寄存器时钟,
  • SRCLR 串行清零端 serial clear。把里面的数据清空
  • SRCLK 串行时钟,serial clock
  • SER 串行数据

跳线帽的解接法

image-20240413153126607

原理图

image-20240413152308545

比如我们SER串行数据为10100000,每当SERCLK来一个上升沿,就是低电平向高电平的跳跃,那么SER的高位移入,以此类推,当八位全部移入时,RCLK来一个上升沿,就会将这八位数据全部送入输出端。

注意事项:普中科技的开发板,没有额外的LED灯,需要设置P0_7=0;只能使用点阵屏的第一列进行测试

c
#include <REGX52.h>
#include <Delay.h>

// 新取一个名字。方便操作
sbit RCK=P3^5;
sbit SCK=P3^6;
sbit SER=P3^4;

void _75HC595_WriteByte(unsigned char Byte)
{
	
	// 两个同为1,结果才为 1, 否则位0
	// 如果想把Byte的第八位先取出来。
//  SER=Byte&0x80; // 0x80-> 1000 0000。
//  SCK=1;
//	SCK=0;
//	
//	// 复制第7位
//	SER=Byte&0x40; // 0x40-> 0100 0000。
//  SCK=1;
//	SCK=0;
	
	  unsigned char i;
	  for(i=0;i<8;i++)
	  {
			SER=Byte&(0x80>>i);
			SCK=1;//上升沿,使数据下移
	    SCK=0;//清零,进入下一轮
		}
		
		RCK=1;
		RCK=0;
}

void main()
{
	SCK=0;
	RCK=0;
	P0_7=0; // 普中科技的开发板,没有额外的LED灯,只能使用点阵屏的第一列进行测试
  _75HC595_WriteByte(0x11);
	while(1)
	{
		


	}
}

image-20240413172715347

9-1 LED点阵画一个笑脸

c
#include <REGX52.h>
#include <Delay.h>

// 新取一个名字。方便操作
sbit RCK=P3^5;
sbit SCK=P3^6;
sbit SER=P3^4;
#define MATRIX_LED_PORT		P0
void _75HC595_WriteByte(unsigned char Byte)
{
	
	// 两个同为1,结果才为 1, 否则位0
	// 如果想把Byte的第八位先取出来。
//  SER=Byte&0x80; // 0x80-> 1000 0000。
//  SCK=1;
//	SCK=0;
//	
//	// 复制第7位
//	SER=Byte&0x40; // 0x40-> 0100 0000。
//  SCK=1;
//	SCK=0;
	
	  unsigned char i;
	  for(i=0;i<8;i++)
	  {
			SER=Byte&(0x80>>i);
			SCK=1;//上升沿,使数据下移
	    SCK=0;//清零,进入下一轮
		}
		
		RCK=1;
		RCK=0;
}
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
   
   _75HC595_WriteByte(Data);
	MATRIX_LED_PORT=~(0x80>>Column);
	Delay1ms(1);
	MATRIX_LED_PORT=0xFF;

}
void main()
{
	SCK=0;
	RCK=0;
	// P0_7=0; // 普中科技的开发板,没有额外的LED灯,只能使用点阵屏的第一列进行测试
	while(1)
	{
		
    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);
	}
}

10、DS1302

基本概念

DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时且具有闺年补偿等多种功能

RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片

原理图

image-20240414125229254

引脚名作用
VCC2主电源
VCC1备用电源
GND电源地
X1,X232.768KHz晶振
CE(chip enable)芯片使能
IO数据输入/输出
SCLK串行时钟

内部的结构框图

image-20240414130732980

31x8的寄存器内部定义

image-20240414130945626

WP:Write Protect 写保护。如果为1 ,写入的操作是无效的。最下面一行的寄存器就是用来涓流充电的

地址命令字节。第7位必须是1,第6表示操作RAM还是Clock.给1就是操作RAM,给0就是操作时钟。

5,4,3,2,1 就是地址位,比如我要操作秒,都给00000,最后一位,就是读还是取的意思。给1就是读取,给0就是写的意思。如下图所示

如果想给时钟进行写操作,就是1000 0000-> 80。就是表中第一行的数据

image-20240414131859917

时序定义

image-20240414133358380

上升沿,和下降沿就是电平升高和下降的时候。上升沿发送数据,下降沿读取数据。

下面讲解一下,发送数据的流程

第一步:CE至为高电平,

第二步:SCLK 发送一个上升沿

第三步:IO发送一位数据。

第四步:SCLK 发送一个上升沿,IO发送一位数据。循环往复

10-1 DS1302时钟展示

项目结构如下

image-20240414171135034

具体的代码如下所示DS1302.c

c
#include <REGX52.h>

sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

#define DS1302_SECOND   0x80
#define DS1302_MINUTE   0x82
#define DS1302_HOUR     0x84
#define DS1302_DATE     0x86
#define DS1302_MONTH    0x88
#define DS1302_DAY      0x8A
#define DS1302_YEAR     0x8C
#define DS1302_WP       0x8E


unsigned char DS1302_Time[]={24,4,11,14,30,11,7};


void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;

	
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	
	for(i=0;i<8;i++)
	{
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	
	DS1302_CE=0;
	
}




unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	DS1302_CE=1;
	Command|=0x01; // 把写地址变成 读地址 写地址和读地址就差最低位的。
	// 例如 ,秒钟的读取地址是 0x81 写地址为0x80。0x80|0x01=0x81 这样子就可以实现写地址转 读取地址
	
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;
	}
	
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	
	DS1302_CE=0;
	DS1302_IO=0;
	return Data;

}

void DS1302_Init()
{
  DS1302_CE=0;
	DS1302_SCLK=0;
	DS1302_WriteByte(0x8e,0x00);// 关闭写保护
}


void DS1302_SetTime()
{
  DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
  
}

void DS1302_ReadTime(void)
{
  unsigned char Temp;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
	
}

DS1302.h

c
#ifndef __DS1302_H__
#define __DS1302_H__

unsigned char DS1302_Time[];
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime(void);
void DS1302_SetTime();


#endif

main.c文件

c
#include <REGX52.h>
#include <Delay.h>
#include <LCD1602.h>
#include <DS1302.h>

unsigned char Second;

void main()
{
	
	LCD_Init();
	DS1302_Init();
	
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();
	
	while(1)
	{
		DS1302_ReadTime();
	  
		LCD_ShowNum(1,1,DS1302_Time[0],2);
		LCD_ShowNum(1,4,DS1302_Time[1],2);
		LCD_ShowNum(1,7,DS1302_Time[2],2);
		
		LCD_ShowNum(2,1,DS1302_Time[3],2);
		LCD_ShowNum(2,4,DS1302_Time[4],2);
		LCD_ShowNum(2,7,DS1302_Time[5],2);
		
	}
}

额外的知识

BCD码

BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数

例:0001 0011表示13,1000 0101表示85,0001 1010不合法

在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法(2位BCD)

BCD码转十进制:

十进制转BCD码:(2位BCD)

10-2 DS1302 可调时钟

可以参照如下代码51单片机-DS1302可调时钟(实现代码)_如何用ds1302实现星期随日期的改动而改动-CSDN博客

项目结构如下

image-20240415173519983

main.c

c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"
 
unsigned char KeyNumber,MODE,TimeSetSelect,TimeSetFlashFlag;
unsigned char YEAR(unsigned char year,month);
void TimeShow();
void TimeSet();
	
 
void main(){
	LCD_Init();
	DS1302_Init();
	Timer0_Init();
	
	DS1302_SetTime();
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	
	
	while(1){
		KeyNumber = Key();
		if(KeyNumber==1){
			if(MODE==0)MODE=1;
			else if(MODE==1){MODE=0;DS1302_SetTime();}
		}
		switch(MODE)
		{
			case 0: TimeShow();break;
			case 1: TimeSet();break;
		}
		
	}
}
//显示
void TimeShow(){
	DS1302_ReadTime();	
	LCD_ShowNum(1,1,DS1302_Time[0],2);
	LCD_ShowNum(1,4,DS1302_Time[1],2);
	LCD_ShowNum(1,7,DS1302_Time[2],2);
	LCD_ShowNum(2,1,DS1302_Time[3],2);
	LCD_ShowNum(2,4,DS1302_Time[4],2);
	LCD_ShowNum(2,7,DS1302_Time[5],2);
}
//修改
void TimeSet(){
	//选择需要更改的位
	if(KeyNumber == 2)
	{
		TimeSetSelect++;
		TimeSetSelect%=6;
	}
	//增加更改位的数,并判断是否越界
	if(KeyNumber == 3)
	{
		DS1302_Time[TimeSetSelect]++;
		if(DS1302_Time[0]>99){DS1302_Time[0]=0;}
		if(DS1302_Time[1]>12){DS1302_Time[1]=1;}
		
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==31)
		{
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==30)
		{
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==29)
		{
			if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
		}
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==28)
		{
			if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
		}
		
		if(DS1302_Time[3]>24){DS1302_Time[3]=0;}
		if(DS1302_Time[4]>59){DS1302_Time[4]=0;}
		if(DS1302_Time[5]>59){DS1302_Time[5]=0;}
	}
	//减小更改位的数,并判断是否越界
	if(KeyNumber == 4)
	{
		DS1302_Time[TimeSetSelect]--;
		if(DS1302_Time[0]>100){DS1302_Time[0]=99;}
		if(DS1302_Time[1]<1){DS1302_Time[1]=12;}
		
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==31)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=31;}
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==30)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=30;}
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==29)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=29;}
			if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
		}
		if(YEAR(DS1302_Time[0]+2000,DS1302_Time[1])==28)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=28;}
			if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
		}
		
		if(DS1302_Time[3]>100){DS1302_Time[3]=24;}
		if(DS1302_Time[4]>100){DS1302_Time[4]=59;}
		if(DS1302_Time[5]>100){DS1302_Time[5]=59;}
	}
	//显示更改后的数据
	if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
	else{LCD_ShowNum(1,1,DS1302_Time[0],2);}
	
	if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
	else{LCD_ShowNum(1,4,DS1302_Time[1],2);}
	
	if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
	else{LCD_ShowNum(1,7,DS1302_Time[2],2);}
	
	if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
	else{LCD_ShowNum(2,1,DS1302_Time[3],2);}
	
	if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
	else{LCD_ShowNum(2,4,DS1302_Time[4],2);}
	
	if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
	else{LCD_ShowNum(2,7,DS1302_Time[5],2);}
 
	/*LCD_ShowNum(2,10,TimeSetSelect,2);
	LCD_ShowNum(2,13,TimeSetFlashFlag,2);*/
}
 
//闰年判断,月份天数判断
unsigned char YEAR(unsigned char year,month){
	if(year%400==0||year%4==0&&year%100!=0)
	{
		if(month==2)
			return 29;
	}
	else
	{
		if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)
			return 31;
		else if(month==4||month==6||month==9||month==11)
			return 30;
		else
			return 28;
	}
	return 0;
}
//定时器
void Timer0_Routine() interrupt 1
{
	static unsigned int count0 = 0;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	count0++;
	if(count0 == 300){
		count0 = 0;
		TimeSetFlashFlag=!TimeSetFlashFlag;
	}
		
}

DS1302.c

c
#include <REGX52.h>

sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

#define DS1302_SECOND   0x80
#define DS1302_MINUTE   0x82
#define DS1302_HOUR     0x84
#define DS1302_DATE     0x86
#define DS1302_MONTH    0x88
#define DS1302_DAY      0x8A
#define DS1302_YEAR     0x8C
#define DS1302_WP       0x8E


unsigned char DS1302_Time[]={24,4,11,14,30,11,7};


void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;

	
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	
	for(i=0;i<8;i++)
	{
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	
	DS1302_CE=0;
	
}




unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	DS1302_CE=1;
	Command|=0x01; // 把写地址变成 读地址 写地址和读地址就差最低位的。
	// 例如 ,秒钟的读取地址是 0x81 写地址为0x80。0x80|0x01=0x81 这样子就可以实现写地址转 读取地址
	
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;
	}
	
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	
	DS1302_CE=0;
	DS1302_IO=0;
	return Data;

}

void DS1302_Init()
{
  DS1302_CE=0;
	DS1302_SCLK=0;
}


void DS1302_SetTime()
{
	DS1302_WriteByte(DS1302_WP,0x00);// 关闭写保护
  DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);// 开启写保护
  
}

void DS1302_ReadTime(void)
{
  unsigned char Temp;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
	
}

DS1302.h

c
#ifndef __DS1302_H__
#define __DS1302_H__

unsigned char DS1302_Time[];
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime(void);
void DS1302_SetTime();


#endif

Key.c

c
#include <REGX52.h>
#include "Delay.h"

unsigned char Key()
{
	unsigned char KeyNumber=0;
	if(P3_1==0){Delay1ms(20);while(P3_1==0);Delay1ms(20);KeyNumber=1;}
  if(P3_0==0){Delay1ms(20);while(P3_0==0);Delay1ms(20);KeyNumber=2;}
	if(P3_2==0){Delay1ms(20);while(P3_2==0);Delay1ms(20);KeyNumber=3;}
	if(P3_3==0){Delay1ms(20);while(P3_3==0);Delay1ms(20);KeyNumber=4;}

	return KeyNumber;
}

Key.h

c
#ifndef __KEY_H__
#define __KEY_H__


unsigned char Key();

#endif

LED1602.c

c
#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 data i, j;
	i = 2;
			j = 199;
			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');
	}
}

LED1602.h

c
#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

Time0.c

c
#include <REGX52.h>

void Timer0_Init(void)		//1微秒@12.000MHz
{
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD=0x01; 		//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时

	// 下面开始配置中断
	ET0=1;  // 打开Timer0中断开关
	EA=1; // 打开总中断开关
	PT0=0;
}

Time0.h

c
#ifndef __TIMER0_H__
#define __TIMER0_H__


void Timer0_Init(void);

#endif

Delay.c

c
#include <INTRINS.H>

void Delay1ms(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

Delay.h

c
#ifndef __DELAY_H__
#define __DELAY_H__


void Delay1ms(unsigned int xms);

#endif

11、蜂鸣器

基本概念

蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号

蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器

  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声调整提供振荡脉冲的频率,可发出不同频率的声音

原理图

image-20240415174831874

使用步进电机来驱动蜂鸣器

image-20240415182845740

额外知识

image-20240415184620119

image-20240415184922066

image-20240415185848993

10-1 蜂鸣器提示音

运行代码之前,需要把红外的线给拔掉

image-20240415203729396

因为独立按键和红外接收模块的线冲突了。使用的都是32的管子

image-20240415203839606

项目结构如下

image-20240415202454555

main.c

c
#include <REGX52.h>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"

sbit Buzzer=P2^5;

unsigned char KeyNum;
unsigned int i;


void main()
{

	while(1)
	{
    KeyNum=Key();
		if(KeyNum)
		{
			
			for(i=0;i<500;i++)
      {
			  Buzzer=!Buzzer;
				Delay1ms(1);
			}
			Nixie(1,KeyNum);
		}
	}
}

Nixie.c

c
#include <REGX52.h>
#include <Delay.h>


unsigned char  NixmeTable[]={
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
	0x77,0x7C,0x39,0x5E,0x79,0x71,0x00
};

void Nixie(unsigned char Location,number)
{
	switch(Location){
		
		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;
	}
	
	P0=NixmeTable[number];
//	Delay1ms(5);
//	P0=0x00;
}

Nixie.h

c
#ifndef __NIXIE_H__
#define __NIXIE_H__

void Nixie(unsigned char Location,number);

#endif

注意点:新版本的普中开发板子对应是的 P2^5

然后呢?数码管的最后两行注释掉

模块化Buzzer.c

c
#include <REGX52.h>
#include "Delay.h"
#include <INTRINS.H>

sbit Buzzer=P2^5;


unsigned int i;

void Buzzer_Delay500us(void)	//@11.0592MHz
{
	unsigned char data i;
  _nop_();
	i = 227;
	while (--i);
}


void Buzzer_Time(unsigned int ms)
{

	for(i=0;i<ms*2;i++)
	{
		Buzzer=!Buzzer;
		Buzzer_Delay500us();
	}

}

Buzzer.h

c
#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int ms);

#endif

main.c

c
#include <REGX52.h>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Buzzer.h"

unsigned char KeyNum;

void main()
{

	while(1)
	{
    KeyNum=Key();
		if(KeyNum>0)
		{
			Buzzer_Time(500);
			Nixie(1,KeyNum);
		}
	}
}

10-2 蜂鸣器播放音乐

c
#include <REGX52.h>
#include <INTRINS.H>
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"

sbit Buzzer=P2^5;

unsigned int i;

unsigned int FreqTable[] = { //中央C以及高八度低八度的音调由低到高
	63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
	64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
	65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,0
};//最后一个是休止符

unsigned char Music[] = {12,12,19,19,21,21,19,17,17,16,16,14,14,12,0,0xFF};
unsigned char Music2[] ={4,4,4,4,4,4,8,4,4,4,4,4,4,8,4};

//小星星简谱音调对应的数组下标(前两句)
unsigned char FreqSelect, MusicSelect;


void main()
{

	Timer0_Init(); // 中断器初始化
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)
		{
		  FreqSelect = Music[MusicSelect];
			MusicSelect ++;
			Delay1ms(125 * Music2[MusicSelect]); //音符时长,可修改
			TR0 = 0; //定时器关闭,自然停顿,否则两个连音之间没有了间隔
			Delay1ms(5);
			TR0 = 1; //定时器开启
		}else
		{
			// 如果到结尾了,重复播放
		  MusicSelect=0;
			FreqSelect=0;
		}
	}
}

void Timer0_Routine() interrupt 1
{
  if(FreqTable[FreqSelect] != 0) //若不为休止符
	{
		TL0 = FreqTable[FreqSelect] % 256;		//设置定时初值
		TH0 = FreqTable[FreqSelect] / 256;		//设置定时初值
		Buzzer = !Buzzer;
	}
}

12、AT24C02介绍

AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机

运行时想要永久保存的数据信息

  • 存储介质:E2PROM
  • 通讯接口:12C总线
  • 容量:256字节

image-20240416194905695

电路

引脚功能
VCC,GND电源(1.8v-5.5v)
WP写保护(高电平有效)
SCL,SDAI2C接口
E0,E1,E2I2C地址

image-20240416201941881

内部结构框图

image-20240416203530206

I2C总线介绍

I2C总线 (InterIC BUS) 是由Philips公司开发的一种通用数据总线

两根通信线:SCL (Serial Clock) 、SDA(Serial Data)

同步、半双工,带数据应答

通用的12C总线,可以使各种设备的通信标准统一,对于厂家来说使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用着来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度

I2C电路规范

  • 所有12C设备的SCL连在一起,SDA连在一起
  • 设备的SCL和SDA均要配置成开漏输出模式
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KQ左右
  • 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相千扰的问题

image-20240416204501542

SDA,SCL是半双工,1想象成下面开关断了,SDA,SCL靠VDD拉

当SDA,SCL为0的时候,想象成下面开关连接了,选择被控IC

如果cpu控制的base基级是0,那么out口会被拉低,电压因而为零,即使外界有接的vcc,vcc的电流也会被导通到ground,out端口输出仍然是0

I2C时序结构

起始条件:SCL高电平期间,SDA从高电平切换到低电平 终止条件:SCL高电平期间,SDA从低电平切换到高电平

image-20240416212120197

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位。所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次。即可发送一个字节

image-20240416212336306

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前)。然后拉高SCL,主机将在SCL高电平期间读取数据位。所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次。即可接收一个字节(主机在接收之前,需要释放SDA)

image-20240416212038279

发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

image-20240416212757814

发送一帧数据

image-20240416214109590

接受一帧数据

image-20240416215436794

先发送数据再接受数据帧

image-20240416215837448

亮268的可以看一下是不是ack=I2C_receiveack();这句错了

要是是3个灯 检查一下模块化中 start 的代码有没有问题

12-1 AT24C02教程

项目目录如下:

image-20240419210721294

I2C.c

c
#include <REGX52.h>

sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;


void I2C_Start()
{
	// 起始条件
	I2C_SDA=1;
	I2C_SCL=1;
	
	
	// 
	I2C_SDA=0;
	I2C_SCL=0;
	
}

void I2C_Stop()
{

  I2C_SDA=0;
	I2C_SCL=1;
  I2C_SDA=1;
}


void I2C_SendByte(unsigned char Byte)
{
	
	unsigned char i;
	for(i=0;i<8;i++)
	{
		I2C_SDA=Byte&(0x80>>i);
		I2C_SCL=1;
		I2C_SCL=0;
	}
}


unsigned char I2C_ReceiveByte()
{

	unsigned char Byte=0x00;
	unsigned char i;
	I2C_SDA = 1;

	for(i=0;i<8;i++)
	{
		I2C_SCL=1;
		if(I2C_SDA){Byte|=(0x80>>i);}
		I2C_SCL=0;
	}
	
	return Byte;
	

}

void I2C_SendAck(unsigned char AckBit)
{
	
	I2C_SDA=AckBit;
	I2C_SCL=1;
	I2C_SCL=0;


}


unsigned char I2C_ReceiveAck()
{
	unsigned char AckBit=0;
	I2C_SDA=1;
	I2C_SCL=1;
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit; //返回值为0 表示有应答,返回值为1,表示无应答
}

I2C.h

c
#ifndef __I2C_H__
#define __I2C_H__

void I2C_Start();
void I2C_Stop();
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte();

void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck();
	

#endif

AT24C02.c

c
#include <REGX52.h>
#include "I2C.h"


#define  AT24C02_AddRESS     0xA0


void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	unsigned char Ack;
	I2C_Start();
  I2C_SendByte(AT24C02_AddRESS);
	Ack=I2C_ReceiveAck();
	if(Ack==0) P2=0x00;
	I2C_SendByte(WordAddress);
	Ack=I2C_ReceiveAck();
  I2C_SendByte(Data);
	Ack=I2C_ReceiveAck();
	I2C_Stop();
	
}

unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(AT24C02_AddRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_Start();
	I2C_SendByte(AT24C02_AddRESS|0x01);
	I2C_ReceiveAck();
	Data=I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	return Data;

}

AT24C02.h

c
#ifndef __AT24C02_H__
#define __AT24C02_H__


void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);

#endif

main.c

c
#include <REGX52.h>
#include <INTRINS.H>
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
#include "LCD1602.h"
#include "AT24C02.h"

unsigned char Data,KeyNum;
unsigned int Num=1;

void main()
{

	
	LCD_Init();
	// LCD_ShowString(1,1,"Hello");
//	AT24C02_WriteByte(234,66);
//	Delay1ms(5);
//	Data=AT24C02_ReadByte(234);

  LCD_ShowNum(1,1,Num,5);
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)
		{
		  Num++;
		  LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==2)
		{
		  Num--;
		  LCD_ShowNum(1,1,Num,5);
		}
		
		if(KeyNum==3)
		{
		  AT24C02_WriteByte(0,Num%256);
			Delay1ms(5);
			AT24C02_WriteByte(1,Num/256);
			Delay1ms(5);
			LCD_ShowString(2,1,"Write OK");
			Delay1ms(1000);
			LCD_ShowString(2,1,"         ");
		}
		
		if(KeyNum==4)
		{
		  Num=AT24C02_ReadByte(0);
			Num|=AT24C02_ReadByte(1)<<8;
      LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK");
			Delay1ms(1000);
			LCD_ShowString(2,1,"         ");
		}
		
		
	}
}

12-2 秒表(定时器扫描数码管)

项目目录结构。其他的文件没有进行改动。

image-20240419224722430

main.c

c
#include <REGX52.h>
#include <INTRINS.H>
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
#include "NiXie.h"
#include "AT24C02.h"

unsigned char Temp,KeyNum;
unsigned int Num=1;
unsigned char Min,Sec,MinSec;
unsigned char RunFlag;
void main()
{
	Timer0_Init();

	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1) RunFlag=!RunFlag;
	  if(KeyNum==2) 
		{
		  Min=0;
			Sec=0;
			MinSec=0;
		}
		
		if(KeyNum==3) 
		{
		  AT24C02_WriteByte(0,Min);
			Delay1ms(5);
			AT24C02_WriteByte(1,Sec);
			Delay1ms(5);
			AT24C02_WriteByte(2,MinSec);
			Delay1ms(5);
		}
		
		if(KeyNum==4) 
		{
		  Min=AT24C02_ReadByte(0);
			Sec=AT24C02_ReadByte(1);
			MinSec=AT24C02_ReadByte(2);
		}
		
		Nixie_SetBuf(1,Min/10);
		Nixie_SetBuf(2,Min%10);
		Nixie_SetBuf(3,17); // 我这里17表示横杠,视频中为11
		Nixie_SetBuf(4,Sec/10);
		Nixie_SetBuf(5,Sec%10);
		Nixie_SetBuf(6,17); // 我这里17表示横杠,视频中为11
		Nixie_SetBuf(7,MinSec/10);
		Nixie_SetBuf(8,MinSec%10);
	}
}

void Sec_Loop(void)
{
	if(RunFlag==0) return; // 表示不跑
  MinSec++;
	if(MinSec>=100)
	{
	  MinSec=0;
		Sec++;
		if(Sec>=60)
		{
		  Sec=0;
			Min++;
			if(Min>=60)
			{
			  Min=0;
				Sec=0;
				MinSec=0;
			}
		}
	}
}


void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2,T0Count3;
  TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	T0Count1++;

	if(T0Count1>=20){
		T0Count1=0;
		Key_Loop();
	}
	T0Count2++;
	if(T0Count2>=2){
		T0Count2=0;
		Nixie_Loop();
	}
	T0Count3++;
		if(T0Count3>=10){
		T0Count3=0;
		Sec_Loop();
	}
}

Key.c

c
#include <REGX52.h>
#include "Delay.h"

unsigned char Key_KeyNumber;

unsigned char Key()
{
	unsigned char Temp=0;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}
unsigned char Key_GetState()
{
	unsigned char KeyNumber=0;
	if(P3_1==0){KeyNumber=1;}
  if(P3_0==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}

	return KeyNumber;
}

void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	LastState=NowState;
	NowState=Key_GetState();
	if(LastState==1 && NowState==0)
	{
		// 按键按下。
		Key_KeyNumber=1;	
	}
	if(LastState==2 && NowState==0)
	{
		// 按键按下。
		Key_KeyNumber=2;	
	}
	if(LastState==3 && NowState==0)
	{
		// 按键按下。
		Key_KeyNumber=3;	
	}
	if(LastState==4 && NowState==0)
	{
		// 按键按下。
		Key_KeyNumber=4;	
	}
	
	
	
}

Key.h

c
#ifndef __KEY_H__
#define __KEY_H__


unsigned char Key();
void Key_Loop(void);
#endif

Nixie.c

c
#include <REGX52.h>
#include <Delay.h>


unsigned char  NixmeTable[]={
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
	0x77,0x7C,0x39,0x5E,0x79,0x71,0x00,0x40
};

unsigned char Nixie_Buf[9]={0,16,16,16,16,16,16,16,16};

void Nixie_SetBuf(unsigned char Location,number)
{
  Nixie_Buf[Location]=number;
}

void Nixie(unsigned char Location,number)
{
	
	P0=0x00;
	switch(Location){
		
		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;
	}
	P0=NixmeTable[number];
}


void Nixie_Loop()
{
	static unsigned char i=1;
	Nixie(i,Nixie_Buf[i]);
	i++;
	if(i>=9) i=1;

}

Nixie.h

c
#ifndef __NIXIE_H__
#define __NIXIE_H__

void Nixie(unsigned char Location,number);
void Nixie_Loop();
void Nixie_SetBuf(unsigned char Location,number);
#endif

13、DS18B20温度传感器

概念

DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点

测温范围:-55°C 到 +125°C

通信接口:1-Wire(单总线

其它特征:可形成总线结构、内置温度报警功能、可寄生供电

电子路

image-20240420180900443

引脚功能
VCC电源(3.0V-5.5V)
GND电源地
DQ/IO单总线接口

内部结构

image-20240420194606489

64-BITROM:作为器件地址,用于总线通信的寻址

SCRATCHPAD(暂存器):用于总线的数据交互

EEPROM:用于保存温度触发阈值和配置参数

暂存器内部结构

image-20240420195323482

单总线

单总线(1-Wire BUS)是由Dalas公司开发的一种通用数据总线

一根通信线:DO

异步、半双工

单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线

单总线电路规范

设备的DQ均要配置成开漏输出模式

DO添加一个上拉电阻,阻值一般为4.7KQ左右

若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路

image-20240420220527404

时序结构

初始化:主机将总线拉低至少480us,然后释放总线,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线

image-20240420230213537

发送一位:主机将总线拉低60~120us,然后释放总线,表示发送0

主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)

读取电平,整个时间片应大于60us

image-20240422191057731

接收一位:主机将总线拉低1~15us一然后释放总线,并在拉低后15us内读取总线电平(量贴近15us的末尾)

读取为低电平则为接收0,读取为高电平则为接收1,整个时间片应大于60us

image-20240422202643505

发送字节的

image-20240422212114246

操作流程

  • 初始化:从机复位,主机判断从机是否响应
  • ROM操作:ROM指令+本指令需要的读写操作
  • 功能操作:功能指令+本指令需要的读写操作

image-20240422224039983

13-1 DS18B20温度读取

项目结构如下

image-20240423122824732

main.c

c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"

float T;

void main()
{
	DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
	Delay1ms(1000);			//等待转换完成
	LCD_Init();
	LCD_ShowString(1,1,"Temperature:");
	while(1)
	{
		DS18B20_ConvertT();	//转换温度
		T=DS18B20_ReadT();	//读取温度
		if(T<0)				//如果温度小于0
		{
			LCD_ShowChar(2,1,'-');	//显示负号
			T=-T;			//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(2,1,'+');	//显示正号
		}
		LCD_ShowNum(2,2,T,3);		//显示温度整数部分
		LCD_ShowChar(2,5,'.');		//显示小数点
		LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示温度小数部分
	}
}

OneWire.c

c
#include <REGX52.h>
#include <INTRINS.H>


sbit OneWire_DQ=P3^7;

void Delay480us(void)	//@11.0592MHz
{
	unsigned char data i;

	_nop_();
	i = 218;
	while (--i);
}

void Delay500us(void)	//@11.0592MHz
{
	unsigned char data i;

	_nop_();
	i = 227;
	while (--i);
}


unsigned char OneWire_Init(void)
{
	unsigned char data i;
	unsigned char AckBit;
	OneWire_DQ=1;
	OneWire_DQ=0;
	_nop_();i = 227;while (--i);// 至少延时480微秒,这里写延时500微秒
	OneWire_DQ=1;
	_nop_();i = 29;while (--i); // 至少延时60微秒,这里写延时70微秒
	AckBit=OneWire_DQ;
	_nop_();i = 227;while (--i);// 延时500微秒
	return AckBit;

}


void OneWire_SendBit(unsigned char Bit)
{
	unsigned char data i;
	OneWire_DQ=0;
	i = 4;while (--i); // 延时10微秒
	OneWire_DQ=Bit;
	i = 22;while (--i); // 延时50微秒
	OneWire_DQ=1;
	
}


unsigned char OneWire_ReceiveBit(void)
{
	// 1.这块的意思就是,先拉低,然后delay5us,在释放,在delay5us
	// 2.如果此时读到0,则代表着从机继续拉低电平,如果读到1,则代表从机没有继续拉低
	unsigned char data i;
	unsigned char Bit;
	OneWire_DQ=0;
	i = 2;while (--i); // 延时5微秒
	OneWire_DQ=1;
	i = 2;while (--i); // 延时5微秒
	Bit=OneWire_DQ;
	i = 22;while (--i); // 延时50微秒
	return Bit;
	
}


void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OneWire_SendBit(Byte&(0x01<<i));
	}
}

unsigned char OneWire_ReceiveByte()
{
	unsigned char i;
	unsigned char Byte=0x00;
	
	for(i=0;i<8;i++)
	{
		if(OneWire_ReceiveBit())
		{
		 Byte|=(0x01<<i);
		}
	}
	return Byte;
}

OneWire.h

c
#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__

unsigned char OneWire_Init(void);

void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);

void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte();
#endif

DS18B20.h

c
#ifndef __DS18B20_H__
#define __DS18B20_H__
void DS18B20_ConvertT();
float DS18B20_ReadT();

#endif

DS18B20.c

c
#include <REGX52.h>
#include <OneWire.h>

#define DS18B20_SKIP_ROM           0xCC
#define DS18B20_CONVERT_T          0x44
#define DS18B20_READ_SCRATCHPAD    0xBE

void DS18B20_ConvertT()
{
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_CONVERT_T);

}

float DS18B20_ReadT()
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
  OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
  OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=(TMSB<<8)| TLSB;
	T=Temp/16.0;
	return T;
}

13-2 DS18B20 温度报警

OneWire.c

c
#include <REGX52.h>
#include <INTRINS.H>


sbit OneWire_DQ=P3^7;

void Delay480us(void)	//@11.0592MHz
{
	unsigned char data i;

	_nop_();
	i = 218;
	while (--i);
}

void Delay500us(void)	//@11.0592MHz
{
	unsigned char data i;

	_nop_();
	i = 227;
	while (--i);
}


unsigned char OneWire_Init(void)
{
	unsigned char data i;
	unsigned char AckBit;
	EA=0;
	OneWire_DQ=1;
	OneWire_DQ=0;
	_nop_();i = 227;while (--i);// 至少延时480微秒,这里写延时500微秒
	OneWire_DQ=1;
	_nop_();i = 29;while (--i); // 至少延时60微秒,这里写延时70微秒
	AckBit=OneWire_DQ;
	_nop_();i = 227;while (--i);// 延时500微秒
	EA=1;
	return AckBit;

}


void OneWire_SendBit(unsigned char Bit)
{
	unsigned char data i;
	EA=0;
	OneWire_DQ=0;
	i = 4;while (--i); // 延时10微秒
	OneWire_DQ=Bit;
	i = 22;while (--i); // 延时50微秒
	OneWire_DQ=1;
	EA=1;
}


unsigned char OneWire_ReceiveBit(void)
{
	// 1.这块的意思就是,先拉低,然后delay5us,在释放,在delay5us
	// 2.如果此时读到0,则代表着从机继续拉低电平,如果读到1,则代表从机没有继续拉低
	unsigned char data i;
	unsigned char Bit;
	EA=0;
	OneWire_DQ=0;
	i = 2;while (--i); // 延时5微秒
	OneWire_DQ=1;
	i = 2;while (--i); // 延时5微秒
	Bit=OneWire_DQ;
	i = 22;while (--i); // 延时50微秒
	EA=1;
	return Bit;
	
}


void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OneWire_SendBit(Byte&(0x01<<i));
	}
}

unsigned char OneWire_ReceiveByte()
{
	unsigned char i;
	unsigned char Byte=0x00;
	
	for(i=0;i<8;i++)
	{
		if(OneWire_ReceiveBit())
		{
		 Byte|=(0x01<<i);
		}
	}
	return Byte;
}

14、LCD1602

LCD1602I(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符。

还可以有8个自定义字符

显示容量:16x2个字符,每个字符为5*7点阵

电路接口

image-20240423173302764

名称

引脚功能
VSS
VDD电源正极(4.5-5.5v)
VO对比度调节电压
RS数据/指令选择,1为数据,0为指令
RW读/写选择,1为读,0为写
E使能,1为数据有效,下降沿执行命令
D0-D7数据输入/输出
A背光灯电源正极
K背光灯电源负极

15、直流电机驱动(PWM)

概念

  • 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机主要由永磁体(定子)、线(转子)和换向器组成
  • 除直流电机外,常见的电机还有步进电机、舵机、无刷电机、空心杯电机等

image-20240423181632631

PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的 系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所 需要的模拟参量,常应用于电机控速、开关电源等领域

PWM重要参数,频率 =,占空比=。精度=占空比变化步距

image-20240423182515039

15-1 LED呼吸灯

main.c

c
#include <REGX52.H>

sbit LED=P2^0;

void Delay(unsigned char t)
{
	while(t--);
}

void main()
{
	unsigned char Time,i;
	while(1)
	{
		for(Time=0;Time<100;Time++)
		{
			for(i=0;i<20;i++)
			{
				LED=0;
				Delay(Time);
				LED=1;
				Delay(100-Time);
			}
		}
		
		for(Time=100;Time>0;Time--)
		{
			for(i=0;i<20;i++)
			{
				LED=0;
				Delay(Time);
				LED=1;
				Delay(100-Time);
			}
		}
	}

}

15-2 直流电机调速

产生PWM的方法

image-20240423185013405

16、AD/DA

基本概念

AD(Analog to Digital)模拟-数字转换,将模拟信号转换为计算机可操作的数字信号

DA(Digital to Analog)数字-模拟转换,将计算机输出的数字信号转换为模拟信号

AD/DA转换打开了计算机与模拟信号的大门,极大的提高了计算机 系统的应用范围,也为模拟信号数字化处理提供了可能

image-20240423202421881

AD转换通常有多个输入通道,用多路选择开关连接至AD转换器 以实现AD多路复用的目的,提高硬件利用率 AD/DA与单片机数据传送可使用并口(速度快、原理简单), 也可 使用串口(接线少、使用方便) 可将AD/DA模块直接集成在单片机内,这样直接写入/读出寄存器 就可进行AD/DA转换,单片机的I0口可直接复用为AD/DA的通道

运算放大器

运算放大器(简称“运放”)是具有很高放大倍数的放大电路单元:内部集成了差分放大器、电压放大器、功率放大器三级放大电路是一个性能完备、功能强大的通用放大电路单元,由于其应用十分广泛,现已作为基本的电路元件出现在电路图中

运算放大器可构成的电路有:电压比较器、反相放大器、同相放大器、电压跟随器、加法器、积分器、微分器等

运算放大器电路的分析方法:虚短、虚断(负反馈条件下

image-20240423211125325

运算放大器应用

image-20240424212525335

image-20240424212744582

image-20240424213322318

image-20240424214131931

image-20240424220114439

AD/DA性能评价指标

分辨率:指AD/DA数字量的精细程度,通常用位数表示。例如,对于5V电源系统来说,8位的AD可将5V等分为256份,即数字量变化最小一个单位时,模拟量变化5V/256=0.01953125V,所以,8位AD的电压分辨率为0.01953125V,AD/DA的位数越高,分辨率就越高

转换速度:表示AD/DA的最大采样/建立频率,通常用转换频率或者转换时间来表示,对于采样/输出高速信号,应注意AD/DA的转换速度

16-1 AD模数转换

项目结构

image-20240425124102938

main.c

c
#include <REGX52.H>
#include <Delay.h>
#include <LCD1602.h>
#include <XPT2046.h>

unsigned int ADValue;

void main()
{
	
	LCD_Init();
	
	LCD_ShowString(1,1,"ADJ NTC RG");
	
	while(1)
	{
		
		ADValue=XPT2046_ReadAD(XPT2046_XP_8);
		LCD_ShowNum(2,1,ADValue,3);
		Delay(10);
		
		ADValue=XPT2046_ReadAD(XPT2046_YP_8);
		LCD_ShowNum(2,5,ADValue,3);
		
		ADValue=XPT2046_ReadAD(XPT2046_VBAT_8);
		LCD_ShowNum(2,9,ADValue,3);
	
	}


}

XPT2046.c

c
#include <REGX52.H>


sbit XPT2046_CS=P3^5;
sbit XPT2046_DCLK=P3^6;
sbit XPT2046_DIN=P3^4;
sbit XPT2046_DOUT=P3^7;

unsigned int XPT2046_ReadAD(unsigned char Command)
{
	unsigned char i;
	unsigned int ADValue=0;
	XPT2046_DCLK=0;
	XPT2046_CS=0;
	
	
	for(i=0;i<8;i++)
	{
		XPT2046_DIN=Command&(0x80>>i);
		XPT2046_DCLK=1;
	  XPT2046_DCLK=0;
	}

	

	
	for(i=0;i<16;i++)
	{
		XPT2046_DCLK=1;// 第一个上升沿发送命令字到芯片
	  XPT2046_DCLK=0; // 第二个下降沿读取ad值
		if(XPT2046_DOUT) {ADValue|=(0x8000>>i);}
	}

	XPT2046_CS=1;
	
	if(Command&0x08)
	{
		return ADValue>>8;
	}else
	{
	  return ADValue>>4;
	}
}

XTP2046.h

c
#ifndef __XPT2046_H__
#define __XPT2046_H__

#define XPT2046_XP_8    0x9C
#define XPT2046_YP_8    0xDC
#define XPT2046_VBAT_8  0xAC
#define XPT2046_AUX_8   0xEC

#define XPT2046_XP_12    0x94
#define XPT2046_YP_12    0xD4
#define XPT2046_VBAT_12  0xA4
#define XPT2046_AUX_12   0xE4


unsigned int XPT2046_ReadAD(unsigned char Command);

#endif

16-2 DA数模转换

main.c

c
#include <REGX52.H>
#include <Delay.h>
#include <Key.h>
#include <Timer0.h>
#include <Nixie.h>

unsigned char Counter,Compare;
unsigned char i;


sbit DA=P2^1;

void main()
{
	Timer0_Init();
	
	while(1)
	{
		for(i=0;i<100;i++)
		{
			Compare=i;
			Delay(10);
		}
		for(i=100;i>0;i--)
		{
			Compare=i;
		}
	
	}

}

void Timer0_Routine() interrupt 1
{
	// 每隔100微秒进来一次 
	TL0 = 0xA4;				//设置定时初始值
	TH0 = 0xFF;				//设置定时初始值
	Counter++;
	Counter%=100;

	if(Counter<Compare)
	{
	  DA=1;
	}else
	{
	  DA=0;
	}
}

17、红外遥控

概念

基本概念

红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出

通信方式:单工,异步

红外LED波长:940nm

通信协议标准:NEC标准

原理

红外遥控是一种非接触,无线控制技术。具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等特点。红外遥控系统一般有红外发射装置和红外接收设备两大部分组成。

  • 红外发射装置就是我们常见的遥控器,由键盘电路,红外编码电路,电源电路和红外发射电路组成。
  • 红外接收设备是由红外接收电路,红外解码,电源和应用电路组成。红外接收装置有三个引脚,VDD,GND和数据输出VOUT。通常正对接收头凸起处看,从左到右引脚顺序为VOUT,GND,VDD

红外遥控模块

通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为38kHz,这是由发射端所使用的455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取12,所以为38kHz。也有一些遥控系统采用36kHz、40 kHz、56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码) 调制在38KHz的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。

硬件电路

image-20240425152055604

基本发送与接受

  • 空闲状态1红外LED不亮,接收头输出高电平
  • 发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平
  • 发送高电平:红外LED不亮,接收头输出高电平

image-20240425152830391

NEC编码

image-20240425153637300

二进制脉冲码的形式有多种,其中最为常用的是NEC Protocol 的PWM码(脉冲宽度调制)和 Philips RC-5 Protoco 的 PPM码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率,才可以选取一体化红外接收头和制定解码方案。这里针对NEC编码形式做一个详细介绍。NEC编码形式有以下特点

  • 8 位地址和 8 位指令长度
  • 地址和命令 2次传输(确保可靠性)
  • PWM 脉冲位置调制,以发射红外载波的占空比代表“ 0”和“1”
  • 载波频率为 38Khz
  • 位时间为 1.125ms 或 2.25ms

NEC码的位定义

一个脉冲对应 560us 的连续载波,一个逻辑1传输需要 2.25ms(560us脉冲+1680us 低电平) ,一个逻辑 0的传输需要 1.125ms (560us脉冲+560us 低电平)。而红外接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为: 逻辑 1应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以通过计算高电平时间判断接收到的数据是0还是1。NEC码位定义时序图如下 逻辑0和逻辑1的时序图

特别标注一下,上图逻辑0和逻辑1的时序图对应的是接收端的时序图。

NEC遥控指令的数据格式

NEC遥控指令的数据格式为:引导码、地址码、地址反码、控制码控制反码。引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成地址码、地址反码、控制码、控制反码均是8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性 (可用于校验)。数据格式如下

NEC遥控指令数据格式

特别标注一下,上图为发送端的时序图。

NEC码还规定了连发码(由 9ms 低电平+2.5m 高电平+0.5ms 低电平+97.94ms 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数。

设计思路

红外发射装置只需要按键按下即可产生红外信号,我们只需要针对红外接收设备编写程序即可。上面介绍了,红外接收设备在收到脉冲的时候为低电平,在没有脉冲的时候为高电平。根据“0”和“1”的时序图可知,我们只需要监测红外接收设备的数据输出引脚的高电平持续时间就可以判断接收到的是“0”还是“1”。

另外,没有按键按下时,也就是没有发红外信号,没有脉冲,红外接收设备的数据输出引脚一直为高电平。只有接收到脉冲时,说明有按键按下,此时红外接收设备的数据输出引脚为低电平。因此,可以利用外部中断的下降沿出发来判断是否有按键按下,在中断中测量高电平持续时间来判断接收到的是“0”还是“1”。

NEC编码实际采样图

示波器实际采样的图

image-20240425155029708

51单片机的建码

image-20240425155527572

外部中断

  • STC89C52有4个外部中断

  • STC89C52的外部中断有两种触发方式

    下降沿触发和低电平触发

  • 中断号

    image-20240425155841841

17-1 红外遥控

项目结构如下

image-20240425193824917

main.c

c++
#include <REGX52.H>
#include <Delay.h>
#include <LCD1602.h>
#include <Int0.h>
#include <Timer0.h>
#include <IR.h>

unsigned char Num;
unsigned char Address;
unsigned char Command;

void main()
{
	LCD_Init();
	IR_Init();
	
	LCD_ShowString(1,1,"ADDR  CMD NUM");
	LCD_ShowString(2,1,"00    00  00");
	
	while(1)
	{
		
		if(IR_GetDataFlag() || IR_GetRepeatFlag())
		{
			Address=IR_GetAddress();
			Command=IR_GetCommand();
			
			LCD_ShowHexNum(2,1,Address,2);
			LCD_ShowHexNum(2,7,Command,2);
			
			
			if(Command==IR_VOL_MINUS)
			{
				Num--;
			}
			if(Command==IR_VOL_ADD)
			{
				Num++;
			}
			
			LCD_ShowNum(2,12,Num,3);
		}
		
	}
}

Timer0.c

c++
#include <REGX52.h>

void Timer0_Init(void)		//100微秒@12.000MHz
{
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD= 0x01; 		//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 0;				//定时器0不计时
}

void Timer0_SetCounter(unsigned char Value)
{
	TH0=Value /256;
	TL0=Value%256;
}

unsigned int Timer0_GetCounter(void)
{
	return (TH0<<8)|TL0;
}

void Timer0_Run(unsigned char Flag)
{
	TR0=Flag;
}

Timer0.h

c
#ifndef __TIMER0_H__
#define __TIMER0_H__


void Timer0_Init(void);
void Timer0_SetCounter(unsigned char Value);
unsigned int Timer0_GetCounter(void);
void Timer0_Run(unsigned char Flag);


#endif

Int0.c

c
#include <REGX52.H>

void Int0_Init(void)
{

	IT0=1; // 1表示下降沿触发,0 表示低电频触发,只要是低电频,一直触发中
	IE0=0; // IE0是硬件置1软件清0的,外部中断0有下降沿时硬件自动置1的
	EX0=1;
	EA=1;
	PX0=1; // 中断的优先级

}

//void Int0_Routine() interrupt 0
//{
//	Num++;
//}

Int0.h

c
#ifndef __INT0_H__
#define __INT0_H__


void Int0_Init(void);

#endif

IR.c

c
#include <REGX52.H>
#include <LCD1602.h>
#include <Int0.h>
#include <Timer0.h>


unsigned int IR_Time;
unsigned char IR_State;// 用来记录是开始还是发送中 ,还是repeat。为状态机

unsigned char IR_Data[4];
unsigned char IR_pData;

unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;


void IR_Init(void)
{
	Int0_Init();
	Timer0_Init();
}

unsigned char IR_GetDataFlag(void)
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}


unsigned char IR_GetRepeatFlag(void)
{
  if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

unsigned char IR_GetAddress(void)
{
	return IR_Address;
}

unsigned char IR_GetCommand(void)
{
	return IR_Command;
}

void Int0_Routine() interrupt 0
{
	if(IR_State==0)
	{
		Timer0_SetCounter(0);
		Timer0_Run(1);
		IR_State=1;
	}
	else if(IR_State==1)
	{
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		// 芯片是11.0592Mhz的定时器每次记一个数是1.085us不是1us
		// 13580/ 1.085=12442
		if(IR_Time> 12442 -500 && IR_Time<12442+500) 
		{
			IR_State=2;
			// 11250/1.085=10368
		}else if(IR_Time>10368-500 && IR_Time<10368+500)
		{
			IR_RepeatFlag=1;
			Timer0_Run(0);
			IR_State=0;
		}else
		{
			IR_State=1;
		}
	
	}else if(IR_State==2)
	{
		// 开始解码
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		// 1120
		if(IR_Time> 1058 -500 && IR_Time<1058+500) 
		{
			// 收到0之后
			// pdata默认0,pd/8确认某字节,
			// 例如7/8=0,第0个字节,7%8第8位数,0x01左移8位,注意是1在移动,
			// 取反后就能对应位置0,其他为1,保证数据不变化
			// &与 相应位置0不影响其他位
			IR_Data[IR_pData/8] &=~(0x01<<(IR_pData%8));
			IR_pData++;
			// unsigned char IR_Data[4];//用于存储数据分别为地址码,
			// 地址反码,命令,命令反码,unsigned char 类型为8位数据
		}else if(IR_Time>2126-500 && IR_Time<2126+500) // 2250
		{
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
			// IR_Data[IR_pData/8] |=(0x01<<(IR_pData%8));检查这个 没有取反~符号
			IR_pData++;
		}else{
			IR_pData=0;
		  IR_State=1;
		}
		if(IR_pData>=32)
		{
			IR_pData=0;
			if((IR_Data[0] == ~IR_Data[1]) && (IR_Data[2] == ~IR_Data[3]))
			{
				
				IR_Address=IR_Data[0];
				IR_Command=IR_Data[2];
				IR_DataFlag=1;
			}
			
			IR_State=0;
			Timer0_Run(0); // 暂停计时器
		}
	}
}

IR.h

c
#ifndef __IR_H__
#define __IR_H__


#define IR_POWER       0x45
#define IR_MODE        0x46
#define IR_MUTE        0x47
#define IR_START_STOP  0x44
#define IR_PREVIOUS    0x40
#define IR_NEXT        0x43
#define IR_EQ          0x07
#define IR_VOL_MINUS   0x15
#define IR_VOL_ADD     0x09
#define IR_0           0x16
#define IR_RPT         0x19
#define IR_USD         0x0D
#define IR_1           0x0C
#define IR_2           0x18
#define IR_3           0x5E
#define IR_4           0x08
#define IR_5           0x1C
#define IR_6           0x5A
#define IR_7           0x42
#define IR_8           0x52
#define IR_9           0x4A


void IR_Init(void);
unsigned char IR_GetCommand(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetDataFlag(void);


#endif

17-2 红外遥控电机调速

项目结构如下

image-20240425200929554

代码

main.c

c
#include <REGX52.H>
#include <Delay.h>
#include <Key.h>
#include <Timer1.h>
#include <Nixie.h>
#include <Montor.h>
#include <IR.h>


unsigned char Speed,KeyNum,Command;

void main()
{
	Motor_Init();
	IR_Init();
	
	while(1)
	{
		if(IR_GetDataFlag())
		{
			
			Command= IR_GetCommand();
			
			if(Command==IR_0){Speed=0;}
			if(Command==IR_1){Speed=1;}
			if(Command==IR_2){Speed=2;}
			if(Command==IR_3){Speed=3;}
			
			if(Speed==0){Motor_SetSpeed(0);}
			if(Speed==1){Motor_SetSpeed(50);}
			if(Speed==2){Motor_SetSpeed(75);}
			if(Speed==3){Motor_SetSpeed(100);}
		}
		Nixie(1,Speed);
	}
}

Delay.c

c
#include <INTRINS.H>

void Delay(unsigned int xms)	//@11.0592MHz
{
	unsigned char data i, j;
	
	while(xms){
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);
			xms--;
	}
}

Delay.h

c
#ifndef __DELAY_H__
#define __DELAY_H__


void Delay(unsigned int xms);

#endif

Nixie.c

c
#include <REGX52.h>
#include <Delay.h>


unsigned char  NixmeTable[]={
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
	0x77,0x7C,0x39,0x5E,0x79,0x71,0x00
};

void Nixie(unsigned char Location,number)
{
	switch(Location){
		
		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;
	}
	
	P0=NixmeTable[number];
	Delay(5);
	P0=0x00;
}

Nixie.h

c
#ifndef __NIXIE_H__
#define __NIXIE_H__

void Nixie(unsigned char Location,number);

#endif

Key.c

c
#include <REGX52.h>
#include "Delay.h"

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;
}

Key.h

c
#ifndef __KEY_H__
#define __KEY_H__


unsigned char Key();

#endif

Timer1.c

c
#include <REGX52.h>

void Timer1_Init(void)		//100微秒@12.000MHz
{
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD &= 0x0F; 		//设置定时器模式
	TMOD |= 0x10;
	TL1 = 0xA4;				//设置定时初始值
	TH1 = 0xFF;				//设置定时初始值
	TF1 = 0;				//清除TF0标志
	TR1 = 1;				//定时器0开始计时

	// 下面开始配置中断
	ET1=1;  // 打开Timer0中断开关
	EA=1; // 打开总中断开关
	PT1=0;
}

/**
void Timer1_Routine() interrupt 3
{
	// 每隔100微秒进来一次 
	static unsigned int T0Count;
	TL0 = 0xA4;				//设置定时初始值
	TH0 = 0xFF;				//设置定时初始值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
	}
}

*/

Timer1.h

c
#ifndef __TIMER1_H__
#define __TIMER1_H__


void Timer1_Init(void);

#endif

Int0.c

c
#include <REGX52.H>

void Int0_Init(void)
{

	IT0=1; // 1表示下降沿触发,0 表示低电频触发,只要是低电频,一直触发中
	IE0=0; // IE0是硬件置1软件清0的,外部中断0有下降沿时硬件自动置1的
	EX0=1;
	EA=1;
	PX0=1; // 中断的优先级

}

//void Int0_Routine() interrupt 0
//{
//	Num++;
//}

Int0.h

c
#ifndef __INT0_H__
#define __INT0_H__


void Int0_Init(void);

#endif

IR.c

c
#include <REGX52.H>
#include <Int0.h>
#include <Timer0.h>


unsigned int IR_Time;
unsigned char IR_State;// 用来记录是开始还是发送中 ,还是repeat。为状态机

unsigned char IR_Data[4];
unsigned char IR_pData;

unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;


void IR_Init(void)
{
	Int0_Init();
	Timer0_Init();
}

unsigned char IR_GetDataFlag(void)
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}


unsigned char IR_GetRepeatFlag(void)
{
  if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

unsigned char IR_GetAddress(void)
{
	return IR_Address;
}

unsigned char IR_GetCommand(void)
{
	return IR_Command;
}

void Int0_Routine() interrupt 0
{
	if(IR_State==0)
	{
		Timer0_SetCounter(0);
		Timer0_Run(1);
		IR_State=1;
	}
	else if(IR_State==1)
	{
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		// 芯片是11.0592Mhz的定时器每次记一个数是1.085us不是1us
		// 13580/ 1.085=12442
		if(IR_Time> 12442 -500 && IR_Time<12442+500) 
		{
			IR_State=2;
			// 11250/1.085=10368
		}else if(IR_Time>10368-500 && IR_Time<10368+500)
		{
			IR_RepeatFlag=1;
			Timer0_Run(0);
			IR_State=0;
		}else
		{
			IR_State=1;
		}
	
	}else if(IR_State==2)
	{
		// 开始解码
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		// 1120
		if(IR_Time> 1058 -500 && IR_Time<1058+500) 
		{
			// 收到0之后
			// pdata默认0,pd/8确认某字节,
			// 例如7/8=0,第0个字节,7%8第8位数,0x01左移8位,注意是1在移动,
			// 取反后就能对应位置0,其他为1,保证数据不变化
			// &与 相应位置0不影响其他位
			IR_Data[IR_pData/8] &=~(0x01<<(IR_pData%8));
			IR_pData++;
			// unsigned char IR_Data[4];//用于存储数据分别为地址码,
			// 地址反码,命令,命令反码,unsigned char 类型为8位数据
		}else if(IR_Time>2126-500 && IR_Time<2126+500) // 2250
		{
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
			// IR_Data[IR_pData/8] |=(0x01<<(IR_pData%8));检查这个 没有取反~符号
			IR_pData++;
		}else{
			IR_pData=0;
		  IR_State=1;
		}
		if(IR_pData>=32)
		{
			IR_pData=0;
			if((IR_Data[0] == ~IR_Data[1]) && (IR_Data[2] == ~IR_Data[3]))
			{
				
				IR_Address=IR_Data[0];
				IR_Command=IR_Data[2];
				IR_DataFlag=1;
			}
			
			IR_State=0;
			Timer0_Run(0); // 暂停计时器
		}
	}
}

IR.h

c
#ifndef __IR_H__
#define __IR_H__


#define IR_POWER       0x45
#define IR_MODE        0x46
#define IR_MUTE        0x47
#define IR_START_STOP  0x44
#define IR_PREVIOUS    0x40
#define IR_NEXT        0x43
#define IR_EQ          0x07
#define IR_VOL_MINUS   0x15
#define IR_VOL_ADD     0x09
#define IR_0           0x16
#define IR_RPT         0x19
#define IR_USD         0x0D
#define IR_1           0x0C
#define IR_2           0x18
#define IR_3           0x5E
#define IR_4           0x08
#define IR_5           0x1C
#define IR_6           0x5A
#define IR_7           0x42
#define IR_8           0x52
#define IR_9           0x4A


void IR_Init(void);
unsigned char IR_GetCommand(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetDataFlag(void);


#endif

Montor.c

c
#include <REGX52.H>
#include <Timer1.h>


unsigned char Counter,Compare;
sbit Motor=P1^0;


void Motor_Init()
{
	Timer1_Init();
}

void Motor_SetSpeed(unsigned char Speed)
{
	Compare=Speed;
}

void Timer1_Routine() interrupt 3
{
	// 每隔100微秒进来一次 
	TL1 = 0xA4;				//设置定时初始值
	TH1 = 0xFF;				//设置定时初始值
	Counter++;
	Counter%=100;
	
		
	if(Counter<Compare)
	{
	  Motor=1;
	}else
	{
	  Motor=0;
	}
}

Montor.h

c
#ifndef __MONTOR_H__
#define __MONTOR_H__


void Motor_Init();
void Motor_SetSpeed(unsigned char Speed);


#endif

Timer0.c

c
#include <REGX52.h>

void Timer0_Init(void)		//100微秒@12.000MHz
{
	// AUXR &= 0x7F;		第一行需要删除	//定时器时钟12T模式
	TMOD= 0x01; 		//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 0;				//定时器0不计时
}

void Timer0_SetCounter(unsigned char Value)
{
	TH0=Value /256;
	TL0=Value%256;
}

unsigned int Timer0_GetCounter(void)
{
	return (TH0<<8)|TL0;
}

void Timer0_Run(unsigned char Flag)
{
	TR0=Flag;
}

Timer0.h

c
#ifndef __TIMER0_H__
#define __TIMER0_H__


void Timer0_Init(void);
void Timer0_SetCounter(unsigned char Value);
unsigned int Timer0_GetCounter(void);
void Timer0_Run(unsigned char Flag);


#endif