51单片机学习笔记(未完结)

一.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电路规范

  1. 开漏(Open Drain): 开漏输出模式中的输出引脚可以驱动低电平(0V),但在高电平状态时,变得高阻抗无法提供电压。 这意味着只有连接到外部上拉电阻时,输出引脚才能转为高电平状态。 这种结构允许多个设备通过共同的信号线进行通讯。

  2. 多主设备支持: 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-放大器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值