一.STC89系列单片机介绍
1.1 STC89C51RC简介
位数:8位
RAM:512字节
ROM:8K(Flash)
工作频率:12MHz
TTL规范(+5V等价于逻辑“1”,0V等价于逻辑“0” )
I/O口工作模式:弱上拉强下拉:输出1驱动能力有限,输出0驱动能力较强
1.2 内部框图

二.点亮LED
LED模块原理图
2.1点亮一个LED
原理:单片机上电,所有I/O默认高电平。当P2口给低电平时导通,LED点亮。
ps:以16进制赋值
//点亮D1
void main()
{
P2=0xFE;
while(1)
{
}
}
2.2 LED闪烁
原理:通过延时函数,实现LED亮灭闪烁
void main()
{
while(1)
{
P2=0xFE;
Delay500ms();
P2=0xFF;
Delay500ms();
}
}
2.3 LED流水灯
原理:依次给每个P2口低电平使其导通,并通过延时函数,实现交替闪烁。
void main()
{
while(1)
{
P2=0xFE; //1111 1110
Delayxms(500);
P2=0xFD; //1111 1101
Delayxms(500);
P2=0xFB; //1111 1011
Delayxms(500);
P2=0xF7; //1111 0111
Delayxms(500);
P2=0xEF; //1110 1111
Delayxms(500);
P2=0xDF; //1101 1111
Delayxms(500);
P2=0xBF; //1011 1111
Delayxms(500);
P2=0x7F; //0111 1111
Delayxms(500);
}
}
三.独立按键控制
按键模块原理图
3.1独立按键控制LED亮灭
原理:按键按下电路导通,IO口读取电平为GND低电平
#include <STC89C5xRC.H>
void main()
{
while(1)
{
if(P30==0)
{
P20=0;
}
else
{
P20=1;
}
}
}
3.2 独立按键控制LED状态
原理:按键按下时对LED状态取反,实现按一次按键LED状态改变。
void main()
{
while(1)
{
if(P31==0)
{
Delay(20);
while(P31==0);
Delay(20);
P20=~P20;
}
}
}
扩展:Delay函数目的是为了消除按键抖动,检测松手,才改变LED灯的状态
3.3 独立按键控制LED显示二进制
原理:LEDNum初始值为0 //00000000
第一次按下按键:
LEDNum++ //P2=00000001
~LEDNum //P2=111111110
第二次按下
LEDNum++=00000001+1=00000010
~LEDNum=11111101 //P2=11111101
.........
(只对LEDNum修改赋值给P2)
void main()
{
unsigned char LEDNum=0; // char max num is 255
while(1)
{
if(P31==0)
{
Delay(20);
while(P31==0);
Delay(20);
LEDNum++;
P2=~LEDNum;
}
}
}
3.4 独立按键控制LED移位
原理:P31按键按下时
LEDNum从当前数+1
P2=000000001,1每次向左左移LEDNum位并取反
即 P2=~(00000001) -> ~(00000010) -> ~(00000100)..~(100000000)
P30按键按下
LEDNum从当前数-1
1每次右移
#include <STC89C5xRC.H>
void Delay(unsigned int xms); // must statement
unsigned char LEDNum; // The global variable
void main()
{
P2=~0x01; //int P2
while(1)
{
if(P31==0)
{
Delay(20);
while(P31==0);
Delay(20);
LEDNum++;
if(LEDNum>=8)
LEDNum=0;
P2=~(0x01<<LEDNum); // 0x01 of P2 need shift to the left LEDNum, and get the not
}
if(P30==0)
{
Delay(20);
while(P30==0);
Delay(20);
if(LEDNum==0)
LEDNum=7;
else
LEDNum--;
P2=~(0x01<<LEDNum);
}
}
}
四.数码管控制
数码管模块
其中双向数据缓冲器74HC245中 DIR接高电平将数据从A端送到B端。接低电平读取B段数据。OE芯片使能引脚。
一位数码管引脚图
四位一体数码管引脚图
3.1静态数码管显示
选片原理:
通过74HC138译码器
ABC Y
000 Y0 低电平有效 LED1有效
001 Y1 LED2有效
....
数码管原理:
#include <STC89C5xRC.H>
unsigned char NixieTable[]={ //0~9段码
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:
P24=1;P23=1;P22=1;break;
case 2:
P24=1;P23=1;P22=0;break;
case 3:
P24=1;P23=0;P22=1;break;
case 4:
P24=1;P23=0;P22=0;break;
case 5:
P24=0;P23=1;P22=1;break;
case 6:
P24=0;P23=1;P22=0;break;
case 7:
P24=0;P23=0;P22=1;break;
case 8:
P24=0;P23=0;P22=0;break;
}
P0=NixieTable[Number];
}
void main()
{
Nixie(2,3); //LED2显示3
while(1)
{
}
}
3.2 动态数码管显示
原理:原则每一次只能位选一个LED显示一个数字,如果不消影会串位。
过程:位选 段选 清零 ->下一次位选 段选 清零
此外,如果单片机直接扫描会消耗大量CPU时间。
1.可以通过专用驱动芯片TM1640,内部自带显存,扫描电路。
2.也可以用74HC595移位寄存器,通过3根线控制八个数码管
#include <STC89C5xRC.H>
void Nixie(unsigned char Location,Number)
{
switch(Location)
{
case 1:
P24=1;P23=1;P22=1;break;
case 2:
P24=1;P23=1;P22=0;break;
case 3:
P24=1;P23=0;P22=1;break;
case 4:
P24=1;P23=0;P22=0;break;
case 5:
P24=0;P23=1;P22=1;break;
case 6:
P24=0;P23=1;P22=0;break;
case 7:
P24=0;P23=0;P22=1;break;
case 8:
P24=0;P23=0;P22=0;break;
}
P0=NixieTable[Number];
Delay(1); // Shadow elimination
P0=0x00; // reset
}
void main()
{
while(1)
{
Nixie(1,1);
Nixie(2,2);
Nixie(3,3);
}
}
五.模块化及调试工具
5.1模块化编程

模块化编程框图
注意事项
与main.c放在同一个目录下
5.2 LCD1602调试工具
LCD1602接口模块
API(封装好的函数),使用之前需要初始化
LCD1602驱动代码
#include <STC89C5xRC.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');
}
}
#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
六.矩阵键盘
矩阵键盘模块
与数码管类似 需要不断扫描
6.1矩阵键盘
原理:如像读取S1按键,先将P1全部拉高(弱上拉),P13置低电平;当S1按键按下时,电路导通,读取到P17口从高电平变为低电平(强下拉)。
#include <STC89C5xRC.H>
#include "Delay.h"
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;
P1=0xFF;
P13=0;
if(P17==0){Delay(20);while(P17==0);Delay(20);KeyNumber=1;}
if(P16==0){Delay(20);while(P16==0);Delay(20);KeyNumber=5;}
if(P15==0){Delay(20);while(P15==0);Delay(20);KeyNumber=9;}
if(P14==0){Delay(20);while(P14==0);Delay(20);KeyNumber=13;}
P1=0xFF;
P12=0;
if(P17==0){Delay(20);while(P17==0);Delay(20);KeyNumber=2;}
if(P16==0){Delay(20);while(P16==0);Delay(20);KeyNumber=6;}
if(P15==0){Delay(20);while(P15==0);Delay(20);KeyNumber=10;}
if(P14==0){Delay(20);while(P14==0);Delay(20);KeyNumber=14;}
P1=0xFF;
P11=0;
if(P17==0){Delay(20);while(P17==0);Delay(20);KeyNumber=3;}
if(P16==0){Delay(20);while(P16==0);Delay(20);KeyNumber=7;}
if(P15==0){Delay(20);while(P15==0);Delay(20);KeyNumber=11;}
if(P14==0){Delay(20);while(P14==0);Delay(20);KeyNumber=15;}
P1=0xFF;
P10=0;
if(P17==0){Delay(20);while(P17==0);Delay(20);KeyNumber=4;}
if(P16==0){Delay(20);while(P16==0);Delay(20);KeyNumber=8;}
if(P15==0){Delay(20);while(P15==0);Delay(20);KeyNumber=12;}
if(P14==0){Delay(20);while(P14==0);Delay(20);KeyNumber=16;}
return KeyNumber;
}
#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "Delay.h"
#include "MatrixKey.h"
unsigned char KeyNum;
void main()
{
LCD_Init();
LCD_ShowString(1,3,"MatrixKey:");
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
LCD_ShowNum(2,3,KeyNum,2);
}
}
}
为什么要加入判断语句:如果不加判断语句if,while循环会不断快速执行,导致按键按下后在下一次循环中Matrixkey()函数会给KeyNum赋0而不需要判断直接通过LCD_ShowNum()函数不断打印0。
加上判断语句if()后,会先判断是否不为0,再执行打印操作,不会重复打印0导致按下时效果不明显。
6.2 实操矩阵键盘密码锁
#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "Delay.h"
#include "MatrixKey.h"
unsigned char KeyNum;
unsigned int Password, Count;
void main()
{
LCD_Init();
// LCD_ShowChar(1,1,'B');
LCD_ShowString(1,1,"Password:");
// LCD_ShowNum(1,9,123,3);
// LCD_ShowSignedNum(1,13,-66,2);
// LCD_ShowHexNum(2,1,0xA8,2);
// LCD_ShowBinNum(2,4,0xAA,8);
// LCD_ShowChar(2,13,'A');
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
if(KeyNum<=10) // 如果S1~S10按键按下,输入密码
{
if(Count<4) // 密码四位数
{
Password*=10; // 密码左移一位,这样就可以依次输入密码
Password+=KeyNum%10; // 获取一位密码,加入到原密码,此时10为0
Count++;
}
}
LCD_ShowNum(2,1,Password,4);
if(KeyNum==11) // 确认
{
if(Password==1234)
{
LCD_ShowString(1,14,"OK ");
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4);
}
else
{
LCD_ShowString(1,14,"ERR");
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4);
}
}
if(KeyNum==12) //取消
{
Password=0;
Count=0;
LCD_ShowString(1,14," ");
LCD_ShowNum(2,1,Password,4);
}
}
}
}
七.定时器
定时器介绍:51单片机的定时器属于单片机的内部资源,其他电路的连接和运转均在单片机内部完成
定时器作用:
1.用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
2.代替长时间的Delay,提高CPU的运行效率和处理速度
.......
内部工作原理
工作模式
模式1工作框图
TH/TL:16位寄存器(0~65535)TH高八位,TL控制第八位。
TF0:溢出标志位,每来一个脉冲,寄存器+1。当加到65535时,下一个脉冲置中断标志位,申请中断(中断系统:CPU对外界紧急事件的实时处理能力)。
TR0:控制定时器启动和暂停。
SYSclk:系统时钟,即晶振周期。本开发板晶振12Mhz
T0PIN:时钟源之一,来源外部接口.
÷12/÷6:分频,12分频(12Mhz/12)或者6分频(12Mhz/6)
C/T:
中断资源
定时器和中断系统(传统51单片机)
EA:总中断允许位。EA=1断开,EA=1连接。
ET0:T0的溢出中断允许位。ET0=1,允许T0中断,ET0=0,禁止T0中断。
PT0:PT0=1,选择高优先级,PT=1,选择低优先级。
定时器相关定时器
7.1 按键控制LED流水灯模式&定时器时钟
Timer0.c
#include <STC89C5xRC.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式,只改变T0,避免T1改变
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //高位设置定时初值 65535/256
TH0 = 0xFC; //低位设置定时初值 65535%256
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count; //静态变量,拥有局部作用域,全局生命周期
TL0 = 0x18; //设置定时初值,像沙漏,重置沙漏时间
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0Init(void);
#endif
main.c
#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>
unsigned char KeyNum,LEDMode;
void main()
{
P2=0xFE;
Timer0Init();
while(1)
{
KeyNum=Key(); //获取独立按键键码
if(KeyNum) //如果按键按下
{
if(KeyNum==1) //如果K1按键按下
{
LEDMode++; //模式切换,按1下按键是模式1,按2下是模式0,默认模式0
if(LEDMode>=2)LEDMode=0;
}
}
}
}
void Timer0_Routine() interrupt 1 //中断函数标识,含优先级
{
static unsigned int T0Count; //静态变量,拥有局部作用域,全局生命周期
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++; //T0Count计次,对中断频率进行分频
if(T0Count>=500)//分频500次,500ms
{
T0Count=0;
if(LEDMode==0) //模式判断
P2=_crol_(P2,1); //LED输出(循环左移函数,即使流水灯循环左移)
if(LEDMode==1)
P2=_cror_(P2,1);
}
}
key.c
#include <STC89C5xRC.H>
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0
*/
unsigned char Key()
{
unsigned char KeyNumber=0;
if(P31==0){Delay(20);while(P31==0);Delay(20);KeyNumber=1;}
if(P30==0){Delay(20);while(P30==0);Delay(20);KeyNumber=2;}
if(P32==0){Delay(20);while(P32==0);Delay(20);KeyNumber=3;}
if(P33==0){Delay(20);while(P33==0);Delay(20);KeyNumber=4;}
return KeyNumber;
}
key.h
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
#endif
7.2 定时器时钟
#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Delay.h"
#include "LCD1602.h"
unsigned char Sec, Min=59, Hour=23;
void main()
{
Timer0Init();
LCD_Init();
LCD_ShowString(1,1,"Clock:");
while(1)
{
LCD_ShowNum(2,1,Hour,2);
LCD_ShowString(2,3,":");
LCD_ShowNum(2,4,Min,2);
LCD_ShowString(2,6,":");
LCD_ShowNum(2,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1 //中断函数标识,含优先级
{
static unsigned int T0Count; //静态变量,拥有局部作用域,全局生命周期
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++; //T0Count计次,对中断频率进行分频
if(T0Count>=1000)//1000ms
{
T0Count=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
Hour++;
if(Hour>=24)
{
Hour=0;
}
}
}
}
}
八.串口通信
8.1串口通信介绍
硬件电路
电平标准常用通信接口
51单片机的UART
串口参数及时序图
串口模式图
串口和中断系统
8.2 串口向电脑发送数据
相关寄存器
B7:当PCON寄存器中的SMOD0/PCON.6位置1时,该位用于帧错误检测。由软件清零。
当PCON寄存器中的SMOD0/PCON.6位置0时,该位用于与SM1一起配置串行通信的工作模式。
B7B6:配置串行通信的工作模式 00方式0,01方式1,10方式2,11方式3
B5:允许方式2或方式3多机通信。
B4:允许/禁止串行接收控制位。1:允许接收。0:禁止接收。
B3:方式2或方式3,为要发送的第9位数据。
B2:方式2或方式3.是接收的第9位数据。
B1:发送中断请求标志位。在方式0中,当发送数据第8位结束时,由内部硬件自动置1,向主机请求中断,中断后必须软件复位。在其他方式中,则在停止位开始发送时由内部硬件置位,必须软件复位
B0:接收中断请求标志位。
uart.c
#include <STC89C5xRC.H>
void UartInit(void) //4800bps@11.0592MHz
{
PCON &= 0x80; //波特率不倍速
SCON = 0x40; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFA; //设置定时初始值
TH1 = 0xFA; //设置定时重载值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //定时器1开始计时
}
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte; // 根据硬件原理,操作寄存器
while(TI==0); // 操作寄存器,检测是否完成
TI=0; // 按要求重新赋值为0
}
uart.h
#ifndef __UART_H__
#define __UART_H__
void UartInit(void);
void UART_SendByte(unsigned char Byte);
#endif
main.c
#include <STC89C5xRC.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UartInit();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(1); // 必要的延时,避免误差导致乱码,没误差的时候可以不需要
}
}
8.3 电脑通过串口控制LED
#include <STC89C5xRC.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UartInit();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=~SBUF; // 读寄存器的数据
UART_SendByte(SBUF);
RI=0; // 手动复位
}
}
九.点阵屏
9.1点阵屏介绍
点阵屏模块
SER:存储当前数据,SERCLK来一个上升沿时,将SER中数据移入寄存器。RCLK来一个上升沿时并行输出寄存器中的数据
显示原理
C51的sfr,sbit
9.2 LED点阵屏显示图形
#include <STC89C5xRC.H>
#include "Delay.H"
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4; //SER
#define MATRIX_LED_PORT P0
void _74HC595_WriteByte(unsigned char Byte)
{
// SER=Byte&0x80; //一般是0、1赋值,不过,如果非0,都会当作1
// SCK=1;
// SCK=0;
// SER=Byte&0x60;
// 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)
{
_74HC595_WriteByte(Data);
// if(Column==0){P0=~0x80;}
// if(Column==1){P0=~0x40;}
MATRIX_LED_PORT=~(0x80>>Column);
Delay(1);
MATRIX_LED_PORT=0xFF;
}
void main()
{
SCK=0;
RCK=0;
while(1)
{
// _74HC595_WriteByte(0xAA);
MatrixLED_ShowColumn(0,0x80);
MatrixLED_ShowColumn(1,0x40);
MatrixLED_ShowColumn(2,0x20);
MatrixLED_ShowColumn(3,0x10);
}
}
9.3 LED显示点阵屏动画
MatrixLED.C
#include <STC89C5xRC.H>
#include "Delay.H"
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4; //SER
#define MATRIX_LED_PORT P0
void _74HC595_WriteByte(unsigned char Byte)
{
// SER=Byte&0x80; //一般是0、1赋值,不过,如果非0,都会当作1
// SCK=1;
// SCK=0;
// SER=Byte&0x60;
// 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_Init()
{
SCK=0;
RCK=0;
}
void MatrixLED_ShowColumn(unsigned char Column, Data)
{
_74HC595_WriteByte(Data);
// if(Column==0){P0=~0x80;}
// if(Column==1){P0=~0x40;}
MATRIX_LED_PORT=~(0x80>>Column);
Delay(1);
MATRIX_LED_PORT=0xFF;
}
MatrixLED.h
#ifndef __MATRIXLED_H__
#define __MATRIXLED_H__
void MatrixLED_ShowColumn(unsigned char Column, Data);
void MatrixLED_Init();
#endif
main.c
#include <STC89C5xRC.H>
#include "Delay.H"
#include "MatrixLED.H"
unsigned char Animation[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 为了显示更好看,避免第一列直接显示字符本身
0xFF,0x10,0x10,0x10,0xFF,0x00,0x1E,0x29,0x29,0x29,0x18,0x00,0xFE,0x01,0x02,0x00,
0xFE,0x01,0x02,0x00,0x0E,0x11,0x11,0x0E,0x00,0x7D,0x7D,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 为了显示更好看
};
void main()
{
unsigned char i, Offset=1, Count=0;
while(1)
{
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i,Animation[i+Offset]);
}
Count++;
if(Count>10)
{
Count=0;
Offset++;
if(Offset>40)
{
Offset=0; //防止数组溢出
}
}
}
}
将数据放在flash里面,用来避免内存被消耗过多,但这种数据是不能更改的,方法如下:
将
unsigned char Animation[]
改为
unsigned char code Animation[ ]
十.DS1302实时时钟
10.1 DS1302时钟介绍
RTC:实时时钟,是一种集成电路,通常称为时钟芯片(DS1302是其中一种)
单片机定时器计时的缺点:
1.精度不高
2.占用CPU时间
3.无法掉电运行
DS1302 时钟模块
引脚定义和应用电路
内部结构框图
寄存器定义 (时分秒存储方式为BCD码)
CH:=1时钟暂停。
WP:写保护
命令字B7:固定为1
B6:=1:操作RAM,=0;操作时钟
B5~B1:地址
B0:读/写 1读0写
时序定义
单字节读:CE置1,在上升沿时单片机传入命令字(读+地址),在下降沿时读出数据(由时钟芯片写入)。CE清0。
单字节写:CE置1。上升沿,第一字节单片机传入命令字(写+地址),第二个字节单片机写入数据。CE清0。
10.2 DS1302时钟
DS1302.C
#include <STC89C5xRC.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[]={19,11,16,12,59,55,6};
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
void DS1302_WriteByte(unsigned char Command, Data)//一个比特一个比特写入
{
unsigned char i;
DS1302_CE=1;
// DS1302_IO=Command&0x01;
// DS1302_SCLK=1; // 速度慢可以不加延时,有些速度快的芯片需要增加延时
// DS1302_SCLK=0;
//
// DS1302_IO=Command&0x02;
// DS1302_SCLK=1;
// DS1302_SCLK=0;
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; //完成一次操作,释放IO
}
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;
}
// DS1302_SCLK=0;
// DS1302_SCLK=1;
// if(DS1302_IO)
// {
// Data=Data|0x01;
// }
// DS1302_SCLK=0;
// DS1302_SCLK=1;
// if(DS1302_IO)
// {
// Data=Data|0x02;
// }
for(i=0;i<8;i++)
{
DS1302_SCLK=1; //重复置1是去掉一个周期,为的是满足时序
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0; // 如果不加这一行,将显示全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);//十进制转BCD码
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; //BCD码转十进制
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
#ifndef __DS1302_H__
#define __DS1302_H__
extern unsigned char DS1302_TIME[]; // 声明数组
void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command, Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);
#endif
main.c
#include <STC89C5xRC.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[]={19,11,16,12,59,55,6};
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
void DS1302_WriteByte(unsigned char Command, Data)
{
unsigned char i;
DS1302_CE=1;
// DS1302_IO=Command&0x01;
// DS1302_SCLK=1; // 速度慢可以不加延时,有些速度快的芯片需要增加延时
// DS1302_SCLK=0;
//
// DS1302_IO=Command&0x02;
// DS1302_SCLK=1;
// DS1302_SCLK=0;
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; //完成一次操作,释放IO
}
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;
}
// DS1302_SCLK=0;
// DS1302_SCLK=1;
// if(DS1302_IO)
// {
// Data=Data|0x01;
// }
// DS1302_SCLK=0;
// DS1302_SCLK=1;
// if(DS1302_IO)
// {
// Data=Data|0x02;
// }
for(i=0;i<8;i++)
{
DS1302_SCLK=1; //重复置1是去掉一个周期,为的是满足时序
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0; // 如果不加这一行,将显示全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;
}
十二.AT24C02 E2PROM(I2C总线)
存储器介绍
RAM:掉电丢失
ROM:掉电不丢失
存储器简化模型
12.1 AT24C02介绍
引脚及应用电路
I2C总线介绍
同步:通信双方靠一根时钟线(SCL)来约定通信速率。
半双工:通信双方可以相互传输数据,但必须分时复用一根数据线(SDA)。
I2C电路规范
-
开漏(Open Drain): 开漏输出模式中的输出引脚可以驱动低电平(0V),但在高电平状态时,变得高阻抗无法提供电压。 这意味着只有连接到外部上拉电阻时,输出引脚才能转为高电平状态。 这种结构允许多个设备通过共同的信号线进行通讯。
-
多主设备支持: I2C总线通常是由多个设备(主设备和从设备)连接到同一条数据线上。 由于开漏输出,只要有一个设备拉低了信号线,其他设备的输入引脚就能感知到这个低电平信号。
I2C时序结构
I2C数据帧
主机发送完每个字节,主机释放SDA,从机需要发送接收应答给主机接收(0应答,1非应答)。
S:R:读控制位,相当于把总线控制权交给从机
RA:从机发送的接收应答
R:BYRE:从机发送数据,主机读。
SA:0:主机发送发送应答。
AT24C02数据帧
12.2 AT24C02数据存储
I2C.c
#include <STC89C5xRC.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){Byte|=(0x80>>i);} //如果SDA读取是1 BYTE对应位就是1,默认是0
I2C_SCL=0;
}
return Byte;
}
/**
* @brief I2C发送应答
* @param AckBit 应答位,0为应答,1为非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief I2C接收应答位
* @param 无
* @retval 接收到的应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
I2C.h
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);
#endif
AT24C02.c
#include <STC89C5xRC.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;
}
AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
mian.c
#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1)
{
KeyNum=Key();
if(KeyNum==1) //K1按键,Num自增
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2) //K2按键,Num自减
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3) //K3按键,向AT24C02写入数据
{
AT24C02_WriteByte(0,Num%256);
Delay(5);
AT24C02_WriteByte(1,Num/256);
Delay(5);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
if(KeyNum==4) //K4按键,从AT24C02读取数据
{
Num=AT24C02_ReadByte(0);
Num|=AT24C02_ReadByte(1)<<8;
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read OK ");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}
12.3 秒表(定时器扫描按键数码管)
main.c
#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag;
void main()
{
Timer0_Init();
while(1)
{
KeyNum=Key();
if(KeyNum==1) //K1按键按下
{
RunFlag=!RunFlag; //启动标志位翻转
}
if(KeyNum==2) //K2按键按下
{
Min=0; //分秒清0
Sec=0;
MiniSec=0;
}
if(KeyNum==3) //K3按键按下
{
AT24C02_WriteByte(0,Min); //将分秒写入AT24C02
Delay(5);
AT24C02_WriteByte(1,Sec);
Delay(5);
AT24C02_WriteByte(2,MiniSec);
Delay(5);
}
if(KeyNum==4) //K4按键按下
{
Min=AT24C02_ReadByte(0); //读出AT24C02数据
Sec=AT24C02_ReadByte(1);
MiniSec=AT24C02_ReadByte(2);
}
Nixie_SetBuf(1,Min/10); //设置显示缓存,显示数据
Nixie_SetBuf(2,Min%10);
Nixie_SetBuf(3,11);
Nixie_SetBuf(4,Sec/10);
Nixie_SetBuf(5,Sec%10);
Nixie_SetBuf(6,11);
Nixie_SetBuf(7,MiniSec/10);
Nixie_SetBuf(8,MiniSec%10);
}
}
/**
* @brief 秒表驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Sec_Loop(void)
{
if(RunFlag)
{
MiniSec++;
if(MiniSec>=100)
{
MiniSec=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
}
}
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count1,T0Count2,T0Count3;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count1++;
if(T0Count1>=20)
{
T0Count1=0;
Key_Loop(); //20ms调用一次按键驱动函数
}
T0Count2++;
if(T0Count2>=2)
{
T0Count2=0;
Nixie_Loop();//2ms调用一次数码管驱动函数
}
T0Count3++;
if(T0Count3>=10)
{
T0Count3=0;
Sec_Loop(); //10ms调用一次数秒表驱动函数
}
}
key.c
#include <STC89C5xRC.H> #include "Delay.h" unsigned char Key_KeyNumber; /** * @brief 获取按键键码 * @param 无 * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下 */ unsigned char Key(void) { unsigned char Temp=0; Temp=Key_KeyNumber; Key_KeyNumber=0; return Temp; } /** * @brief 获取当前按键的状态,无消抖及松手检测 * @param 无 * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下 */ unsigned char Key_GetState() { unsigned char KeyNumber=0; if(P31==0){KeyNumber=1;} if(P30==0){KeyNumber=2;} if(P32==0){KeyNumber=3;} if(P33==0){KeyNumber=4;} return KeyNumber; } /** * @brief 按键驱动函数,在中断中调用 * @param 无 * @retval 无 */ 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; } }
NIxie.c
#include <STC89C5xRC.H>
#include "Delay.h"
//数码管显示缓存区
unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};
//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};
/**
* @brief 设置显示缓存区
* @param Location 要设置的位置,范围:1~8
* @param Number 要设置的数字,范围:段码表索引范围
* @retval 无
*/
void Nixie_SetBuf(unsigned char Location,Number)
{
Nixie_Buf[Location]=Number;
}
/**
* @brief 数码管扫描显示
* @param Location 要显示的位置,范围:1~8
* @param Number 要显示的数字,范围:段码表索引范围
* @retval 无
*/
void Nixie_Scan(unsigned char Location,Number)
{
P0=0x00; //段码清0,消影
switch(Location) //位码输出
{
case 1:P24=1;P23=1;P22=1;break;
case 2:P24=1;P23=1;P22=0;break;
case 3:P24=1;P23=0;P22=1;break;
case 4:P24=1;P23=0;P22=0;break;
case 5:P24=0;P23=1;P22=1;break;
case 6:P24=0;P23=1;P22=0;break;
case 7:P24=0;P23=0;P22=1;break;
case 8:P24=0;P23=0;P22=0;break;
}
P0=NixieTable[Number]; //段码输出
}
/**
* @brief 数码管驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Nixie_Loop(void)
{
static unsigned char i=1;
Nixie_Scan(i,Nixie_Buf[i]);
i++;
if(i>=9){i=1;}
}
十三.DS18B20(单总线)
13.1 DS18B20介绍
引脚及应用电路
内部结构框图
原理:DS18B20内部有一个模拟的温度传感器,用一些控制器吧它读出来放入RAM,再通过通讯协议将RAM中数据读出,实现温度读取
PARASITE POWER CIRCUIT:寄生电源电路。功能:可以省去VDD供电,电源由DQ提供。
64-BIT ROM AND 1-Wire PORT:64位ROM与单总线接口。
MEMORY CONTROL LOGIC:内存的控制逻辑,掌管内部的RAM。
SCRATCHPAD:暂存器,就是一个RAM,内部存入温度数据等一些参数。
TEMPERATURE SENSOR:温度传感器,让它开始温度转换时,传感器工作将数据放入RAM。
存储器结构
单总线介绍
异步:通信双方各自约定速率
发送一位
接收一位
解释:主机拉低15us后释放总线,如果从机想发送0,总线还是被从机拉低,主机读取。如果从机想发送1,主机拉低后释放后总线自动拉高,会认为从机没有跟着一起拉低,从机想发送1。
发送一个字节
DS18B20操作流程
READ ROM:读取ROM
MATCH ROM:总线上有多个设备时,进行选址
SKIP ROM:单个设备时,直接进行选址
ALARM SEARCH:当某个设备处于报警状态,可以通过指令进行搜索获取哪个设备报警
CONVERT T:温度变换。读取温度之前需要进行温度变换。将温度传感器读出的数值放入暂存器中。
WRITE SCRATCHPAD:将字节写入到暂存器Byte234
READ SCRATCHPAD:主机读取暂存器中的数据
COPY SCRATCHPAD:从机接收到指令会将暂存器中的Byte234写入EEPROM
RECALL E2:发送这条指令,从机会把EEPROM中的数据覆盖到暂存器中的Byte234
READ POWER SUPPLY:读取设备供电模式。
前5位S:符号位。负数全位1,正数全为0。
BIT0-3:小数位
BIT4-10:整数部分,二进制补码形式存储
13.2 DS18B20温度读取
Onewire.c
#include <STC89C5xRC.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 单总线接收一个字节,默认为全0,当接收bit为1时,对应位置1。
* @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;
}
Onewire.h
#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(void);
#endif
DS18B20.c
#include <REGX52.H>
#include "OneWire.h"
#define DS18B20_SKIP_ROM 0XCC//跳过ROM
#define DS18B20_CONVERT_T 0X44
#define DS18B20_READ_SCRATCHPAD 0XBE//调用时可将芯片中的数据读出来
/*
温度变换:初始化→跳过ROM →开始温度变换
*/
void DS18B20_CovertT(void)
{
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_CONVERT_T);//开始温度变换
}
/*
温度读取:初始化→跳过ROM →读暂存器→连续的读操作
*/
float DS18B20_ReadT(void)
{
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//读暂存器
//将控制全交给主机
TLSB=OneWire_ReceiveByte();//读操作
TMSB=OneWire_ReceiveByte();//读操作
/*
读出温度的两个字节数据
*/
Temp=(TMSB<<8)|TLSB;//将两个字节合并为一个字节,强转为有符号型
T=Temp/16.0;//
return T;//
}
DS18B20.h
#ifndef _DS18B20_H_
#define _DS18B20_H_
void DS18B20_CovertT(void);
float DS18B20_ReadT(void);
#endif
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
float T;
void main()
{
DS18B20_ConvertT();
Delay(1000);
LCD_Init();
LCD_ShowString(1,1,"Temperature:");
while(1)
{
DS18B20_ConvertT();
T=DS18B20_ReadT();
if(T<0)
{
LCD_ShowChar(2,1,'-');
T=-T;
}
else
{
LCD_ShowChar(2,1,'+');
}
LCD_ShowNum(2,2,T,3); //自动省略T的小数部分
LCD_ShowChar(2,5,'.');
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4); //先将小数部分移至整数,再取余得到小数部分
}
}
13.3 DS18B20温度报警器
#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"
float T,TShow;
char TLow,THigh;
unsigned char KeyNum;
void main()
{
DS18B20_ConvertT(); //上电先转换一次温度,防止第一次读数据错误
Delay(1000); //等待转换完成
THigh=AT24C02_ReadByte(0); //读取温度阈值数据
TLow=AT24C02_ReadByte(1);
if(THigh>125 || TLow<-55 || THigh<=TLow)
{
THigh=20; //如果阈值非法,则设为默认值
TLow=15;
}
LCD_Init();
LCD_ShowString(1,1,"T:");
LCD_ShowString(2,1,"TH:");
LCD_ShowString(2,9,"TL:");
LCD_ShowSignedNum(2,4,THigh,3);
LCD_ShowSignedNum(2,12,TLow,3);
Timer0_Init();
while(1)
{
KeyNum=Key();
/*温度读取及显示*/
DS18B20_ConvertT(); //转换温度
T=DS18B20_ReadT(); //读取温度
if(T<0) //如果温度小于0
{
LCD_ShowChar(1,3,'-'); //显示负号
TShow=-T; //将温度变为正数
}
else //如果温度大于等于0
{
LCD_ShowChar(1,3,'+'); //显示正号
TShow=T;
}
LCD_ShowNum(1,4,TShow,3); //显示温度整数部分
LCD_ShowChar(1,7,'.'); //显示小数点
LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);//显示温度小数部分
/*阈值判断及显示*/
if(KeyNum)
{
if(KeyNum==1) //K1按键,THigh自增
{
THigh++;
if(THigh>125){THigh=125;}
}
if(KeyNum==2) //K2按键,THigh自减
{
THigh--;
if(THigh<=TLow){THigh++;}
}
if(KeyNum==3) //K3按键,TLow自增
{
TLow++;
if(TLow>=THigh){TLow--;}
}
if(KeyNum==4) //K4按键,TLow自减
{
TLow--;
if(TLow<-55){TLow=-55;}
}
LCD_ShowSignedNum(2,4,THigh,3); //显示阈值数据
LCD_ShowSignedNum(2,12,TLow,3);
AT24C02_WriteByte(0,THigh); //写入到At24C02中保存
Delay(5);
AT24C02_WriteByte(1,TLow);
Delay(5);
}
if(T>THigh) //越界判断
{
LCD_ShowString(1,13,"OV:H");
}
else if(T<TLow)
{
LCD_ShowString(1,13,"OV:L");
}
else
{
LCD_ShowString(1,13," ");
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=20)
{
T0Count=0;
Key_Loop(); //每20ms调用一次按键驱动函数
}
}
十四.LCD1602
14.1 LCD1602介绍
LCD1602介绍
引脚及应用电路
内部结构框图
存储器结构
时序结构
操作流程
14.2 LCD1602功能函数代码
#include <REGX52.H>
//引脚定义
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_E=P2^7;
#define LCD_DataPort P0 //将LCD1602的接口替换成P0.
//延时函数
void LCD_Delay() //@12.000MHz 1ms
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
//写命令
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_E=1;//这里置高又置低,高电平速度太快,反应不过来,因此进行延时
LCD_Delay();
LCD_E=0;
LCD_Delay();
}
//写数据
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_E=1;//这里置高又置低,高电平速度太快,反应不过来,因此进行延时
LCD_Delay();
LCD_E=0;
LCD_Delay();
}
//初始化
void LCD_Init(void)
{
LCD_WriteCommand(0x38);
LCD_WriteCommand(0x0C);
LCD_WriteCommand(0x06);
LCD_WriteCommand(0x01);
}
//设置光标地址(该函数需要重复利用,所以写成一个函数)
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1)); //因为写入显示地址时要求最高位D7恒定高电平1.
}
else
{
LCD_WriteCommand(0x80|(Column-1)+0x40);//LCD存储是每行40,所以第二行第一位是在第一位的基础上加0x40.
}
}
//显示一个字符
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
//LCD1602指定位置显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
//返回值=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;
}
//显示数字
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
//LCD_WriteData(Number); // 比如要写入789,不能直接写入789,需要转化为字符
for(i=Length;i>0;i--)
{
LCD_WriteData('0'+Number/LCD_Pow(10,i-1)%10); //+0x30或者+'0'.转化为数字格式,不然是ASCII码
}
}
//显示有符号十进制数字
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('0'+Number1/LCD_Pow(10,i-1)%10);
}
}
//显示十六进制数
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
unsigned char SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16; //十六进制除以16
if(SingleNumber<10)
{
LCD_WriteData('0'+SingleNumber); //小于10,直接+'0'
}
else
{
LCD_WriteData('A'+SingleNumber-10);//由于大于10,ASCII码值并不是从A开始,需要变化偏移量
}
}
}
//显示二进制数
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('0'+Number/LCD_Pow(2,i-1)%2);
}
}
十五.直流电机
15.1 直流电机介绍
电机驱动电路
PWM介绍
15.2 LED呼吸灯(PWM控制原理)
#include <STC89C5xRC.H>
sbit LED=P2^0;
void Delay(unsigned int 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; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
}
for(Time=100;Time>0;Time--) //改变亮灭时间,由亮到暗
{
for(i=0;i<20;i++) //计次延时
{
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
}
}
}
15.3 直流电机调速
产生PWM方法
main.c
#include <STC89C5xRC.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Timer0.h"
sbit Motor=P1^0;
unsigned char Counter,Compare; //计数值和比较值,用于输出PWM
unsigned char KeyNum,Speed;
void main()
{
Timer0_Init();
while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
Speed++;
Speed%=4;
if(Speed==0){Compare=0;} //设置比较值,改变PWM占空比
if(Speed==1){Compare=50;}
if(Speed==2){Compare=75;}
if(Speed==3){Compare=100;}
}
Nixie(1,Speed);
}
}
void Timer0_Routine() interrupt 1 //每隔100us进一次中断
{
TL0 = 0x9C; //设置定时初值
TH0 = 0xFF; //设置定时初值
Counter++;
Counter%=100; //计数值变化范围限制在0~99
if(Counter<Compare) //计数值小于比较值
{
Motor=1; //输出1
}
else //计数值大于比较值
{
Motor=0; //输出0
}
}
Timer0.c
#includ <REGX52.H>
void Timer0_Init(void) //100微秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x9C; //设置定时初始值
TH0 = 0xFF; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
十六.AD/DA
16.1 AD/DA介绍
硬件电路模型
硬件电路
运算放大器介绍
功能1-比较器
功能2-放大器
例