硬件设备:
普中51单片机开发板,Windows电脑。
开发软件 :
Keil5:

Keil5 C51和Keil5 MDK的区别
两者都是Keil系列软件,但前者是用来开发51单片机的,后者是用来开发ARM系列,比如STM32的。
STC-ISP下载程序
开发板资料、原理图
单片机介绍
单片机,英文Micro Controller Unit,简称MCU
内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能。
单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机、LED等)的控制。
单片机跟计算机相比,单片机算是一个袖珍版计算机,一个芯片就能构成完整的计算机系统。但在性能上,与计算机相差甚远,但单片机成本低、体积小、结构简单,在生活和工业控制领域大有所用。
同时,学习使用单片机是了解计算机原理与结构的最佳选择
单片机的应用领域:
单片机的使用领域已十分广泛,如智能仪表、实时工控、通讯设备、导航系统、家用电器等。各种产品一旦用上了单片机,就能起到使产品升级换代的功效,常在产品名称前冠以形容词——“智能型”,如智能型洗衣机等。

51单片机
主要品牌及其产品
- Intel(英特尔):80C31、80C51、87C51,80C32、80C52、87C52等;
- Atmel(艾特梅尔):89C51、89C52、89C2051,89S51(RC),89S52(RC)等;
- Philips(飞利浦)、华邦、Dallas(达拉斯)、Siemens(西门子)等公司的许多产品;
- STC(宏晶):STC89C51、STC90C51、STC11系列、STC15系列、STC8系列等。
STC89C52单片机
所属系列:51单片机系列
公司:STC公司, 中国公司。
位数:8位
RAM:512字节
ROM:8K(Flash)
工作频率:12MHZ
命名规则

单片机内部拆解

单片机内部结构图

看门狗:防止程序跑飞的。

单片机管脚图

单片机上方有个缺口,压杆朝上。
·
最小系统

新建工程:
Keil5选择CPU时,选Atmel,STC和AT基本操作是一样的。选择AT89C51或AT89C52都行。内核都是一样的。不需要复制启动文件,因为Keil已经集成了。
LED发光二极管 light emitting diode
用来照明、广告灯、指引灯、屏幕。

排阻,简称RP,(Resistance Pack)102代表1000欧:10*10的2次方; 473为47*1000=47K欧。有时有4位数表示如1001,前三位为有效数字,第4位为倍率即100*10=1000欧。电位器标103即代表10*1000=10K欧。限流电阻。

点亮LED,转变为控制相应引脚高低电平。
Keil默认不生成.hex文件,需要配置。
//点亮一个LED灯,共地接法
#include <REGX51.H>
void main()
{
P2=0x01;
while(1)
{
}
}

//LED闪烁代码
#include <REGX51.H>
#include <INTRINS.H> //包含_nop_()函数原型
void Delayxms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void main(void)
{
P2=0xFF;
while(1)
{
P2=0xFF;
Delayxms(500);
P2=0x00;
Delayxms(500);
}
}

独立按键
•轻触按键:相当于是一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。

sfr全称为:special function register(翻译为:特殊功能寄存器)。
sbit:位寄存器。
头文件REGX.H 有位声明,REG.H没有位声明。
//按下按键LED亮,松手LED灭
#include <REGX52.H>
void main()
{
P2=0x00;
while(1)
{
if(P0_0==0)
{
P2_0=1;
}
else
{
P2_0=0;
}
}
}
按键的抖动
对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。

消抖:硬件消抖; 软件消抖。
检测松手:
//按键控制LED灯亮灭状态切换
void main()
{
P2=0x00;
while(1)
{
if(P0_0==0)
{
Delayxms(20);//消抖
while(P0_0==0); //松手检测
Delayxms(20);
P2_0=~P2_0;
}
}
}
//第一个按键控制向左流水灯,第二个按键控制向右流水灯
void main()
{
unsigned char LEDNum=0;
P2=0x01;
while(1)
{
if(P0_0==0)
{
Delayxms(200);
while(P0_0==0);
Delayxms(200);
LEDNum++;
if(LEDNum>=8)
LEDNum=0;
P2=0x01<<LEDNum;
}
if(P0_1==0)
{
Delayxms(200);
while(P0_0==0);
Delayxms(200);
if(LEDNum==0)
LEDNum=8;
LEDNum--;
if(LEDNum<0)
LEDNum=8;
P2=0x01<<LEDNum;
}
}
}
//独立按键获取键值
#include <REGX51.H>
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围0-8,无按键按下,返回0;
*/
unsigned char Key()
{
unsigned char KeyNumber=0;
if(P0_0==0){Delayxms(50);while(P0_0==0);Delayxms(50);KeyNumber=1;}
if(P0_1==0){Delayxms(50);while(P0_1==0);Delayxms(50);KeyNumber=2;}
if(P0_2==0){Delayxms(50);while(P0_2==0);Delayxms(50);KeyNumber=3;}
if(P0_3==0){Delayxms(50);while(P0_3==0);Delayxms(50);KeyNumber=4;}
if(P0_4==0){Delayxms(50);while(P0_4==0);Delayxms(50);KeyNumber=5;}
if(P0_5==0){Delayxms(50);while(P0_5==0);Delayxms(50);KeyNumber=6;}
if(P0_6==0){Delayxms(50);while(P0_6==0);Delayxms(50);KeyNumber=7;}
if(P0_7==0){Delayxms(50);while(P0_7==0);Delayxms(50);KeyNumber=8;}
return KeyNumber;
}
寄存器
特殊的存储器,以8个为一组,每个位后面都连着一根线通过驱动器控制相应电路。驱动器为了增大驱动能力。CPU直接读写寄存器,寄存器通过驱动器驱动IO口。
输出高低电平的问题转化为读写寄存器。
数码管
•LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件

数码管引脚定义

共阴、共阳接法。
位选、段选、段码。

74LS138、74HC138:三八译码器
其中LS或HC代表电压。

74HC245:双向数据缓冲器(又称为锁存器)
OE: output enable。 芯片使能
DIR: direction 方向
电容:标着104,容量为10 0000pF, nF uF mF F
//点亮动态数码管某一位
#include <REGX51.H>
//P2通过138译码器位选
//P0通过74HC573D段选
void main(void)
{
P2=0x02; //选第三位
//显示数字6,需要a对应P0_7 c对应P0_5 d_4 e_3 f_2 g_1亮, b_6 dp_0灭 0111 1101 7D
P0=0x7D;
while(1)
{
}
}
//指定动态数码管第几位显示什么数字,
//Location 从1到8,8个数码管从左往右1、2……8排序
void Nixie(unsigned char Location,unsigned char Number)
{
switch(Location)
{
case 8: P2_2=1; P2_1=1; P2_0=1;break;
case 7: P2_2=1; P2_1=1; P2_0=0;break;
case 6: P2_2=1; P2_1=0; P2_0=1;break;
case 5: P2_2=1; P2_1=0; P2_0=0;break;
case 4: P2_2=0; P2_1=1; P2_0=1;break;
case 3: P2_2=0; P2_1=1; P2_0=0;break;
case 2: P2_2=0; P2_1=0; P2_0=1;break;
case 1: P2_2=0; P2_1=0; P2_0=0;break;
default:
break;
};
P0=NixieTable[Number];
}
数码管消影:位选、段选,下一个位选,下一下段选
数码管驱动方式
•单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间
•专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可


LCD1602调试工具
使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。
本视频提供的LCD1602代码属于模块化的代码,使用者只需要知道所提供函数的作用和使用方法就可以很容易的使用LCD1602
| 函数 | 作用 |
| LCD_Init(); | 初始化 |
| LCD_ShowChar(1,1,’A’); | 显示一个字符 |
| LCD_ShowString(1,3,”Hello”); | 显示字符串 |
| LCD_ShowNum(1,9,123,3); | 显示十进制数字 |
| LCD_ShowSignedNum(1,13,-66,2); | 显示有符号十进制数字 |
| LCD_ShowHexNum(2,1,0xA8,2); | 显示十六进制数字 |
| LCD_ShowBinNum(2,4,0xAA,8); | 显示二进制数字 |
矩阵键盘
•在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式
•采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态

扫描
数码管扫描(输出扫描) 原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果
矩阵键盘扫描(输入扫描)原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果
以上两种扫描方式的共性:节省I/O口。
IO口弱上拉模式
开漏输出模式:
P1、P2、P3都是弱上拉,P0开漏输出。

Keil5模板功能

程序注释范例

最好创建一个注释模板。
注释编译器,根据特定格式,从代码中提取注释形成特定说明文档。
//20221129矩阵键盘获取键码
#include <REGX51.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){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=1;}
if(P1_6==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=5;}
if(P1_5==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=9;}
if(P1_4==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=13;}
P1=0xFF;
P1_2=0;
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=2;}
if(P1_6==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=6;}
if(P1_5==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=10;}
if(P1_4==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=14;}
P1=0xFF;
P1_1=0;
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=3;}
if(P1_6==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=7;}
if(P1_5==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=11;}
if(P1_4==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=15;}
P1=0xFF;
P1_0=0;
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=4;}
if(P1_6==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=8;}
if(P1_5==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=12;}
if(P1_4==0){Delayxms(20);while(P1_7==0);Delayxms(20);KeyNumber=16;}
Delayxms(100);
return KeyNumber;
}
//20221129矩阵键盘密码锁
#include <REGX51.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
unsigned int Password,Count;
//s10代表0 s11确认键 s12取消
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
if(KeyNum<=10 && Count<4) //如果键码S1~S10按下,输入密码
{
Password*=10; //密码左移1位
Password+=KeyNum%10;//获取1位密码
Count++;//计次加1
}
LCD_ShowNum(2,1,Password,4);//更新显示
}
if(KeyNum==11 && Count==4)//如果S11按键按下,判断是否是4位,如果是,则验证密码
{
if(Password==2345)
{
LCD_ShowString(1,14,"OK ");
}
else
{
LCD_ShowString(1,14,"ERR");
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4);//更新显示
}
}
if(KeyNum==12)//如果S12取消
{
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4);//更新显示
}
}
}
定时器
51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成
定时器作用:
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度……
(3)多任务切换定时……
STC89C52定时器资源
定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源。
注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的。
定时期框图
定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行。

定时器工作模式
STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
工作模式1框图

中断系统

中断程序流程

STC89C52系列单片机中断源
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)。
中断优先级个数:4个。
注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,例如中断源个数不同、中断优先级个数不同等等

定时器和中断系统

数字电路元件标示:

定时器相关寄存器
寄存器是连接软硬件的媒介。在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式。寄存器相当于一个复杂机器的“操作按钮”。
TCON:Timer Control, 定时器控制。
TF:Timer Flag,计数器到达最高值时,由硬件置1。
TR: Timer Run,TR=1,计算器开始运行。
EA:Enable All


STP-ISP定时器计算器自动生成定时器配置代码

头文件<intrins.h>,_cror_循环右移,_crol_循环左移函数
//定时器0初始化及中断函数模板
#include <REGX51.H>
/**
* @brief 定时器0初始化,1毫秒@12Mhz
* @param 无
* @retval 无
*/
//可以使用软件STP-ISP软件定时器计算器功能生成配置代码
void Timer0_Init()
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计
ET0=1;
EA=1;
PT0=0;
}
//Timer0中断函数模板,
//void Timer0_Routine() interrupt 1
//{
// static unsigned int T0Count;
// TL0 = 0x18; //设置定时初始值
// TH0 = 0xFC; //设置定时初始值
// T0Count++;
// if(T0Count>=1000)
// {
// T0Count=0;
// P2_0=~P2_0;
// }
//}
串口通信
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。
51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。

接口及引脚定义

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

电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+5V表示1,0V表示0。 TTL:transistor transistor logic。10米左右距离。
RS232电平:-3~-15V表示1,+3~+15V表示0。10米左右距离。
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号:can、usb都是差分信号)可传输最大1000米距离。
常见通信接口比较

全双工:通信双方可以在同一时刻互相传输数据
半双工:通信双方可以互相传输数据,但必须分时复用一根数据线
单工:通信只能有一方发送到另一方,不能反向传输
异步:通信双方各自约定通信速率
同步:通信双方靠一根时钟线来约定通信速率
总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)
51单片机的UART
STC89C52有1个UART
STC89C52的UART有四种工作模式:
模式0:同步移位寄存器
模式1:8位UART,波特率可变(常用)
模式2:9位UART,波特率固定
模式3:9位UART,波特率可变
串口参数及时序图
波特率:串口通信的速率(发送和接收各数据位的间隔时间)。比特率:
检验位:用于数据验证
停止位:用于数据帧间隔


串口模式图



//单片机UART.c代码20221130
#include <REGX52.H>
/**
* @brief 串口初始化4800bps@12.000MHz
* @param 无
* @retval 无
*/
void UART_Init()//
{
SCON=0x40;
PCON |= 0x80; //使能波特率倍速位SMOD
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF3; //设置定时初始值
TH1 = 0xF3; //设置定时重载值
ET1 = 0; //禁止定时器%d中断
TR1 = 1; //定时器1开始计时
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(TI==0);//等待发送完成
TI=0;
}
//单片机1秒向电脑发送一个数据20221130
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UART_Init();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delayxms(1000);
}
}
//串口接收数据代码
#include <REGX52.H>
/**
* @brief 串口初始化4800bps@12.000MHz
* @param 无
* @retval 无
*/
void UART_Init()//
{
SCON=0x50;
PCON |= 0x80; //使能波特率倍速位SMOD
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF3; //设置定时初始值
TH1 = 0xF3; //设置定时重载值
ET1 = 0; //禁止定时器%d中断
TR1 = 1; //定时器1开始计时
EA=1; //启动中断功能
ES=1; //全能串串口中断
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(TI==0);//等待发送完成
TI=0;
}
/
//串口中断处理函数模板
//void UART_Routine() interrupt 4
//{
// if(RI==1)//接收中断
// {
// RI=0;
// }
//
//}
波特率计算:
TL1 = 0xF3; //设置定时初始值
TH1 = 0xF3; //设置定时重载值
0xF3为234,至256有13个数。在12T模式下,时钟每1um计一次数,13微秒溢出一次。1/13us = 0.07692 Mhz。 76920/16=4807.69Hz 7.69/4800=0.16误差。
数据显示模式
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示。
文本模式/字符模式:以原始数据编码后的形式显示。
LED点阵屏
LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。
LED点阵屏分类
按颜色:单色、双色、全彩
按像素:8*8、16*16等(大规模的LED点阵通常由很多个小点阵拼接而成)。
显示原理


LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。
LED点阵屏与数码管一样,有共阴和共阳两种接法,不同的接法对应的电路结构不同。
LED点阵屏需要进行逐行或逐列扫描,才能使所有LED同时显示。
74HC595
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。


OE:output enable,低电平有效。
RCLK:register clock, 寄存器时钟,上升沿锁存。
SRCLR: serial clear, 串行清零,低电平有效。
SRCLK:串行时钟,上升沿移位。
SER:串行数据
QH’: 多片级联。
C51的sfr、sbit
•sfr(special function register):特殊功能寄存器声明
例:sfr P0 = 0x80;
声明P0口寄存器,物理地址为0x80
•sbit(special bit):特殊位声明
例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1;
声明P0寄存器的第1位
•可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作
DS1302介绍
DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。
RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片。
单片机本身时钟不精确、浪费CPU时间、掉电丢失。

DS3231, 比DS1302精度高,内部自精振。DS12C887.
引脚定义和应用电路

内部结构框图

寄存器定义


时序定义

BCD码
BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数
例:0001 0011表示13,1000 0101表示85,0001 1010不合法
在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)
//20221202 DS1302相关函数
#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[]={22,12,2,0,59,55,4}; //年月日时分秒星期
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
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;
Command |= 0x01;
DS1302_CE=1;
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_SetTime(void)
{
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,0x00); //打开写保护
}
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;
}
//main.c LCD1602显示时钟
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
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);
}
}
蜂鸣器
蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号。
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。

驱动电路
三极管驱动

集成电路驱动

ULN2003芯片

存储器介绍

存储器简化模型

AT24C02介绍
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。
存储介质:E2PROM。
通讯接口:I2C总线。
容量:256字节

| 引脚 | 功能 |
| VCC、GND | 电源(1.8V~5.5V ) |
| WP | 写保护(高电平有效) |
| SCL、SDA | I2C接口 |
| A0、A1、A2 | I2C地址 |

内部结构框图

I2C总线介绍
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线。
两根通信线︰SCL (Serial Clock) 、SDA(Serial Data)。
同步、半双工,带数据应答。
通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期
提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度。使用有专利费。

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

I2C时序结构
起始条件
SCL高电平期间,SDA从高电平切换到低电平。
终止条件
SCL高电平期间,SDA从低电平切换到高电平。

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

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

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

I2C数据帧
发送一帧数据

接收一帧数据

先发送再接收数据帧(复合格式)

AT24C02数据帧
字节写:在WORD ADDRESS处写入数据DATA


AT24C02的固定地址为1010,可配置地址本开发板上为000,所以SLAVE ADDRESS+W 为0xA0, SLAVE ADDRESS+R 为0xA1;
//I2C.c 20221204
#include <REGX52.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
/**
* @brief I2C发送一个字节
* @param Byte要发送的字节
* @retval 无
*/
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;
}
}
/**
* @brief I2C接收一个字节
* @param 无
* @retval 接收到的一个字节数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i, Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA==1){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
/**
* @brief I2C发送应答
* @param AckBit应答位,0为应答,1为非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit) //bit 是C51特有的数据类型
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief I2C接收应答位
* @param 无
* @retval AckBit接收到的应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
//AT24C02.c 20221204
#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0
/**
* @brief AT24C02写入一个字节
* @param WordAddress要写入字节的地址
* @param Data要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}
/**
* @brief AT24C02读取一个字节
* @param WordAddress要读出字节的地址
* @retval 读出的数据
*/
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;
}
//I2C AT24C02存入并读出一个数据
#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"
unsigned char Data;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Hello");
AT24C02_WriteByte(1,66); //地址范围0~255
Delayxms(5); //写周期为5ms,必须等待一下
Data = AT24C02_ReadByte(1);
LCD_ShowNum(2,1,Data,3);
while(1)
{
}
}
DS18B20温度传感器
DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点。(模拟温度传感器如热敏电阻。)
测温范围:-55°C 到 +125°C。
通信接口:1-Wire(单总线)。
其它特征:可形成总线结构、内置温度报警功能、可寄生供电(不需要VCC也可通过总线供电)。可以在一个通信线上挂多个设备。

引脚及应用电路

内部结构框图

64-BIT ROM:作为器件地址,用于总线通信的寻址。
SCRATCHPAD(暂存器):用于总线的数据交互。
EEPROM:用于保存温度触发阈值和配置参数。
存储器结构

单总线介绍
单总线(1-Wire BUS)是由Dallas公司开发的一种通用数据总线。
一根通信线:DQ。
异步、半双工。
单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线。
单总线电路规范
设备的DQ均要配置成开漏输出模式。
DQ添加一个上拉电阻,阻值一般为4.7KΩ左右。
若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路。


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

发送一位
主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us。

接收一位
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us。

发送一个字节
连续调用8次发送一位的时序,依次发送一个字节的8位(低位在前)。
接收一个字节
连续调用8次接收一位的时序,依次接收一个字节的8位(低位在前)。
DS18B20操作流程
初始化:从机复位,主机判断从机是否响应。
ROM操作:ROM指令+本指令需要的读写操作。
功能操作:功能指令+本指令需要的读写操作。

DS18B20数据帧
温度变换:初始化→跳过ROM →开始温度变换

温度读取:初始化→跳过ROM →读暂存器→连续的读操作

温度存储格式


//20221206单总线相关函数
#include <REGX52.H>
//引脚定义
sbit OneWire_DQ=P3^7;
/**
* @brief 单总线初始化
* @param 无
* @retval 从机响应位,0为响应,1为未响应
*/
unsigned char OneWire_Init(void)
{
unsigned char i;
unsigned char AckBit;
OneWire_DQ=1;
OneWire_DQ=0;
i = 247;while (--i); //Delay 500us
OneWire_DQ=1;
i = 32;while (--i); //Delay 70us
AckBit=OneWire_DQ;
i = 247;while (--i); //Delay 500us
return AckBit;
}
/**
* @brief 单总线发送一位
* @param Bit 要发送的位
* @retval 无
*/
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ=0;
i = 4;while (--i); //Delay 10us
OneWire_DQ=Bit;
i = 24;while (--i); //Delay 50us
OneWire_DQ=1;
}
/**
* @brief 单总线接收一位
* @param 无
* @retval 读取的位
*/
unsigned char OneWire_ReceiveBit(void)
{
unsigned char i;
unsigned char Bit;
OneWire_DQ=0;
i = 2;while (--i); //Delay 5us
OneWire_DQ=1;
i = 2;while (--i); //Delay 5us
Bit=OneWire_DQ;
i = 24;while (--i); //Delay 50us
return Bit;
}
/**
* @brief 单总线发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
OneWire_SendBit(Byte&(0x01<<i));
}
}
/**
* @brief 单总线接收一个字节
* @param 无
* @retval 接收的一个字节
*/
unsigned char OneWire_ReceiveByte(void)
{
unsigned char i;
unsigned char Byte=0x00;
for(i=0;i<8;i++)
{
if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
}
return Byte;
}
//20221206 DS18B20温度传感器相关函数
#include <REGX52.H>
#include "OneWire.h"
//DS18B20指令
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
/**
* @brief DS18B20开始温度变换
* @param 无
* @retval 无
*/
void DS18B20_ConvertT(void)
{
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
/**
* @brief DS18B20读取温度
* @param 无
* @retval 温度数值
*/
float DS18B20_ReadT(void)
{
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;
}
LCD1602
LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符。
显示容量:16×2个字符,每个字符为5*7点阵。

引脚及应用电路


内部结构框图

存储器结构

时序结构
写数据/指令

LCD1602指令集

LCD1602操作流程
•初始化:
发送指令0x38 //八位数据接口,两行显示,5*7点阵
发送指令0x0C //显示开,光标关,闪烁关
发送指令0x06 //数据读写操作后,光标自动加一,画面不动
发送指令0x01 //清屏
•显示字符:
发送指令0x80|AC //设置光标位置
发送数据 //发送要显示的字符数据
发送数据 //发送要显示的字符数据
……