AutoLeaders控制组—51单片机学习笔记(二)

一.模块化编程和LCD调试使用

1.模块化编程

模块化编程就是将不同的代码模块放在不同的头文件中,在主函数文件中调用,这样就会使代码清晰很多。
拿动态数码管显示做案例,可以把Delay函数和显示数字做成两个模块;
使用<双括号>调用头文件是在文件库里面搜索调用,使用“双引号”调用头文件是在此源文件的目录里调用(基本都是自定义的);
建立Delay模块:
首先在目录中创建Delay.c文件,在文件中写入定义的Delay函数,如下:

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

然后再创建Delay.h文件声明已经定义的Delay函数,其中需要用到预编译的知识,如下:

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);



#endif

在主函数中就可以直接调用Delay.h函数,注意:任何自定义的变量、函数在调用前都必须有定义或声明(同一个.c)。
所以在要想使用.c文件里的函数必须先建立相连的.h文件里面的函数声明,然后在主函数中使用前再次声明。

2.LCD1602调试工具

LCD调试工具已经由博主写好,我们只需要掌握如何操作这些函数即可。
函数如下:
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);显示二进制数字
让液晶屏从0开始每秒计数加一,代码如下:
先定义一个Result来计数,然后让LCD液晶屏显示Result递加的结果,这就可以用到显示数字函数LCD_ShowNum,第一个数字代表行,第二个数字代表列,第三表示要显示的数字(从要开始的第一行开始显示),最后一个数字表示要显示的位数(从显示数的最后一位开始算)

#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
int Result=0;
void main()
{
	LCD_Init();
	while(1)
	{
		Result++;
		Delay(1000);
		LCD_ShowNum(1,1,Result,3);
	}
}

二.矩形键盘

1.矩形键盘的扫描

如下图,矩形键盘是4*4排列的,P17–P14四个端口是连接四行的,P13–P10是连接四列的,其扫描的原理是假如给第一行低电平,给其他行高电平(防止影响低电平检测),然后按下S2,那么S2这一行和这一列的P17和P12口就会联通,所以只需判断P12是否为低电平,就可以判断S2是否被按下。如此,依次分别给每一行低电平进行检测,就可以判断出来按下了哪几个键。你说妙不妙!
在这里插入图片描述
根据这个原理,我们可以构造函数来判断按键按下的模块,代码如下:

/**
  * @brief  矩阵键盘读取按键键码
  * @param  无
  * @retval KeyNumer 按下键吗的键码值 
	如果按键按下不妨,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键时,返回0
  */
unsigned char MatrixKey()
{
	unsigned char KeyNumber=0;//每执行一次就会清零
	
	P1=0xFF;
	P1_3=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}	
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}	
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}	
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}	
	
	P1=0xFF;
	P1_2=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}	
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}	
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}	
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
	
	P1=0xFF;
	P1_1=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}	
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}	
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}	
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
	
	P1=0xFF;
	P1_0=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}	
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}	
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}	
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
	return KeyNumber;
}

这里是一列一列开始扫描的,和行扫描一个道理
然后在主函数就可以写一个判断按键按下的代码:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"MatrixKey:");
	while(1)
	{
		KeyNum=MatrixKey();
		if(KeyNum)//每执行一次MatrixKey函数KeyNum就会清零
		{
			LCD_ShowNum(2,1,KeyNum,2);
		}
	}
	
	
}

注意这里必须加if(KeyNum)条件判断,因为每循环执行一次MatrixKey函数,KeyNum的值就会被清零,当有按键按下时KeyNum才会被赋值一个数,也就显示极短的一下,所以液晶屏上看着总是显示0。

2.矩形键盘密码锁

S1-S10表示1234567890,S11表示确认,S12表示取消重输,输错了显示2B,输对了显示NB,不多说,注释都在下面,懂得都懂,直接上代码:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
unsigned int Password,cnt;

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Password:");
	while(1)
	{
		KeyNum=MatrixKey();
		if(KeyNum)//每执行一次MatrixKey函数KeyNum就会清零
		{
			if(KeyNum<=10)//如果S1~S10按键按下,输入密码
			{
				if(cnt<4)//防止输多
				{
					Password*=10;
				  Password+=KeyNum%10;//获取一位密码
				  cnt++;
				}
				
			}
			LCD_ShowNum(2,1,Password,4);
			if(KeyNum==11)//按下S11键确认
			{
       if(Password==2004)//如果密码正确
         {Password=0;//密码清零
					cnt=0;//计数清零
				  LCD_ShowString(2,14,"NB");
				 }
			 else
         {Password=0;//密码清零
					cnt=0;//计数清零
				  LCD_ShowString(2,14,"2B");
				 }
		  }
			if(KeyNum==12)
			{
				Password=0;//密码清零
				cnt=0;//计数清零
				LCD_ShowNum(2,1,Password,4);
			}
		}
	}
	
	
}

三.控制按键控制LED流水灯模式&定时器

1.中断系统

STC89C5X 系列单片机提供了 8 个中断请求源,它们分别是:外部中断O(INTO)、外部中断 1(INT1)、外部中断 2(INT2)、外部中断 3(INT3)、定时器 0中断、定时器 1 中断、定时器 2 中断、串口(UART)中断。
一般来说都具有下面五个中断。定时器0/1中断T0/1,外部中断INT0/1,串行口中断(UART)包含接收中断TI和发送中断RI,当串行口接收完一帧串行数据时置位 RI 或当串行口发送完一帧串行数据时置位 TI,向 CPU 申请中断。
在这里插入图片描述

2.定时器

STC89C5X 单片机内有两个可编程的定时/计数器 T0、T1 和一个特殊功能定时器 T2。定时/计数器的实质是加 1 计数器(16 位),由高 8 位和低 8 位两个寄存器 THx 和 TLx 组成。它随着计数器的输入脉冲进行自加 1,也就是每来一个脉冲,计数器就自动加 1,当加到计数器为全 1 时(12MHZ记满65536),再输入一个脉冲就使计数器回零,且计数器的溢出使相应的中断标志位置 1,向 CPU 发出中断请求(定时/计数器中断允许时)。由溢出时计数器的值减去计数初值才是加 1 计数器的计数值。
TMOD:定时器/计数器模式控制;
TOCN:控制寄存器,作用是控制定时器的启、停,标志定时器溢出和中断情况。
配置定时器和中断系统,如下:

void Timer0Init(void)//配置定时器和中断系统 1毫秒@11.0592MHz
{
//   TMOD=0X01;  
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值,低8位寄存器
	TH0 = 0xFC;		//设置定时初值,高8位寄存器
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
    //后三行设置中断总开关并打开定时器0
    ET0=1;
	EA=1;
	PT0=0;
}	

频率为11.0592的1s定时器模板:

void Timer0_Routine() interrupt 1
{
	  static unsigned int T0Count;
		TL0 = 0x66;		//设置定时初值
	    TH0 = 0xFC;		//设置定时初值
		T0Count++;
		if( T0Count>=1000)
		{
			T0Count=0;
		}
	
	
}

控制LED流水灯,此处关键是_cro_函数使用,_crol_左移_cror_右移,它与<<不同,_cro_函数移到最高位后会从最低位开始移。

unsigned char KeyNum,LEDMode;

void main()
{
	P2=0xFE;
	Timer0Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum)
		{
			if(KeyNum==1)
			{
				LEDMode++;
				if(LEDMode==2)LEDMode=0;
			}
		}
		
	}
	
}
void Timer0_Routine() interrupt 1
{
	  static unsigned int T0Count;
		TL0 = 0x66;		//设置定时初值
	    TH0 = 0xFC;		//设置定时初值
		T0Count++;
		if( T0Count>=500)
		{
			T0Count=0;
			if(LEDMode==0)
				P2=_crol_(P2,1);
			if(LEDMode==1)
				P2=_cror_(P2,1);
		}
	
	
}

设置一个定时器定时小时、分钟、秒,如下:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hour;
void main()
{
	LCD_Init();
	Timer0Init();
	LCD_ShowString(1,1,"Clock:");
	
	while(1)
	{
		LCD_ShowNum(2,1,Hour,2);
		LCD_ShowNum(2,3,Min,2);
		LCD_ShowNum(2,5,Sec,2);
	}
	
	
}
void Timer0_Routine() interrupt 1
{
	  static unsigned int T0Count;
		TL0 = 0x66;		//设置定时初值
	    TH0 = 0xFC;		//设置定时初值
		T0Count++;
		if( T0Count>=1000)
		{
			T0Count=0;
			Sec++;
				if(Sec==60)
			{
				Min++;
				Sec=0;
					if(Min==60)
				{
					Hour++;
					Min=0;
				}
			}
		
		}
}

四.串口向电脑传输数据

1.串口初始化和串口发送一个字节数据

用串口发送一个字节数据很简单,只需要调用 UART_SendByte函数,将数据写入SBUF寄存器即可

void UART_Init()//4800bps@11.0592MHz
{
	  SCON=0x40;
		PCON &= 0x7F;
		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;//软件复位
}

2.电脑通过串口发送数据

利用电脑通过串口发送数据需要先设置中断

void UART_Routine() interrupt 4//设置中断函数 interrupt是小尾巴
{
	if(RI==1)//如果发送数据,则RI内置为1
	{
		P2=~SBUF;
		UART_SendByte(SBUF);
		RI=0;
	}
	
}

波特率(Baud Rate)单位bps是用于衡量串口通信速度的单位,它表示每秒钟发送的比特数。如果一个串口的波特率为9600,就表示该串口在一秒钟内可以发送9600个比特的数据。
定时器1产生波特率, 串口一般使用定时器1,模式2,八位自动重装模式,来产生溢出率,从而产生波特率。而且在配置定时器相关的寄存器时不用配置定时器中断,只是使用定时器1来产生波特率的功能。
下图为计算不同定时器模式下的波特率
在这里插入图片描述
STC-ISP串口助手收发数据有两种模式,分别是HEX模式和文本模式。
文本模式显示的是可打印的字符,A b ? *之类
HEX模式则对应的其ascii码,以十六进制显示

五.DS1302时钟

DS1302 涓流充电计时芯片包含一个实时时钟/日历和 31 字节的静态 RAM.通过简单的串行
接口与微处理器通讯.这个实时时钟/日历提供年月日,时分秒信息.对于少于 31 天的月份月末
会自动调整,还有闰年校正.由于有一个 AM/PM 指示器,时钟可以工作在 12 小时制或者 24
小时制。
VCC1是涓流充电器:在主电源VCC2给时钟供电时,VCC1不供电并且VCC2给VCC1充电,一旦VCC2断电,VCC1给时钟持续稳定供电,这样时钟的计时工作不受总电源的控制(不同与定时器)所以保证了时钟的持续。
But,我手中的这个ST89C52中的这个时钟没有接VCC1电源(没有备用电源),所以断电就真啥也没了~
DS1302各引脚的名称和作用
在这里插入图片描述
x1,x2 接外部晶振,通过内部电路输出1HZ标准计时频率;

右下角:内部寄存器,内部时间存在这里;
在这里插入图片描述

1)完成时钟显示的基本流程:
在哪里 写入 什么
在哪里 读出 什么

2)CE:芯片使能 这里的CE相当于一个中介开关,CE高电平有效。
SCLK上升沿读入数据,下降沿输出数据
I/O引脚中R/W控制读或写,A0到A4告诉我们是哪一块地址,R/C控制RAM或时钟,最高位则默认为1。

3)单字节写:
首先把CE置高电平(开关打开),开始写,第二步命令字发两个字节,第一个:命令字;第二个字节数据
命令字:第一位先发R/(–W)设置到I/O口上(内部芯片就是这样设计的)
上升沿数据写入;依次循环,直到最高位被写入;
CE置高电平后,第一个写入的就是命令字(告诉它在哪里读,还是在哪里写)操作完成后CE置低电平

单字节读:
写入完成命令字后,下降沿把I/O口线释放掉,就不再操作I/O口了,就开始读出数据
每一个下降沿来一个数据,把数据一个一个读出来;注意单字节读D1到D7是不用操作I/O口的

在这里插入图片描述
4)BCD码(Binary Coded Decimal‎),用4位二进制数来表示1位十进制数
例:0001 0011表示13,1000 0101表示85,0001 1010不合法
在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
BCD码转十进制:DEC=BCD/1610+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/10
16+DEC%10; (2位BCD)

DS1302.c时钟模块如下:

#include <REGX52.H>

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

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

unsigned char DS1302_Time[]={23,11,16,12,59,55,6};

void DS1302_Init()
{
	//上电时引脚都自动置1,所以要先初始化置0
	DS1302_CE=0;
	DS1302_SCLK=0;
}
/**
  * @brief  DS1302写一个字节
  * @param  Command 命令字/地址
  * @param  Data 要写入的数据,已经存在后八位寄存器里了
  * @retval 无
  */
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;
}

/**
  * @brief  DS1302读一个字节
  * @param  Command 命令字/地址,读完之后就知道我要显示哪种类型的数据(h?min?s?)
  * @param  返回已经已经存入,后八位寄存器里的数据(这里由DS1302自己控制)
  * @retval 无
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	//Command|=0x01;	//将指令转换为读指令
	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;//过滤掉一个脉冲,先置1
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}//记录输出数据,每当输出1时,Data的这一位记录为1
	}
	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,0x80);//打开写保护
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值