51单片机学习笔记-2

模块化编程

  • 模块化编程:
    • 把各个模块的代码放到不同的.c文件中,任何自定义的变量,函数在调用前必须有定义或者声明。
    • .h文件中提供外部可调用的函数声明,使用方法为引用#include "xxx.h",所有的.h文件最好和main.c文件一个目录下。

如果没有定义__DELAY_H__,则定义__DELAY_H__,并且声明void Delay(unsigned int xms);__DELAY_H__只有在第一次编译的时候会被定义一次,之后就不满足#ifndef条件了。

  • C预编译(在代码真正编译之前的预先处理内容):
    • #include <REGX52.H>:相当于把REGX52.H文件的所有内容引用到此处。
    • #define PI 3.14: 定义PI,把下文遇到的所有PI都用3.14替代。
    • #define ABC: 定义ABC。
    • #ifndef __XX_H__..#endif: 如果没有定义__XX_H__,那么执行从#ifndef __XX_H__#endif内的所有内容。
    • 还有#ifdef,#if,#else,#elif,#undef等。
#define AAA    //定义AAA

#ifdef AAA      //判断是否定义AAA
sddsdsds        //如果存在AAA,此处在编译时就会出现missing';'的编译错误。否则根本不编译执行体中的内容。
#endif
  • 模块化编程demo:

步骤1,写Delay.c

步骤2,写Delay.h

步骤3,主函数中调用。

LCD1602实验

  • 原理图

  • lcd1602.c

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

  • lcd1602.h
#ifndef __LCD1602_H__      //ifndef:为了避免重复定义,当工程大的时候,就会有重复定义的风险
#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

  • keil编译器的一个注意事项:

    • 当出现诸如:error C141: syntax error near 'char'这一类编译错误时,原因通常是:keil编译器不允许声明或者定义在代码中进行,把声明放在主函数的第一行即可。如下:
  • C语言的没有直接的字符串类型,字符串是以字符数组的方式存储的,以'\0'结尾,使用案例如下:

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

矩阵键盘

  • 原理图

4x4的方式,可以减少IO口的占用。
采用逐行或者逐列的扫描,实现检测所有按键的效果。
其实几乎所有的显示器都是通过扫描方式实现的(靠显卡扫描像素点和RBG),

  • 矩阵键盘MatrixKey.c实现:
#include <REGX52.H>
#include "Delay.h"

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;}  //s1
  if(P1_6==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=5;}  //s5
  if(P1_5==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=9;}  //s9
  if(P1_4==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=13;} //s13

  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_7==0);Delay(20);KeyNumber=6;} 
  if(P1_5==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=10;}  
  if(P1_4==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=14;} 

  P1=0xFF;
  P1_3=0;
  if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}  
  if(P1_6==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=7;} 
  if(P1_5==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=11;}  
  if(P1_4==0){Delay(20);while(P1_7==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_7==0);Delay(20);KeyNumber=8;} 
  if(P1_5==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=12;}  
  if(P1_4==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=16;} 

  return KeyNumber;
}
  • 矩阵键盘密码锁案例:
#include <REGX52.h>
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNum;
unsigned int Password,Count;    //  定义一个4位密码,int可以保证装得下

void main()
{
  LCD_Init();
  LCD_ShowString(1,1,"Password:");
  while(1)
  {
    KeyNum=MatrixKey();
    if(KeyNum)
    {
      if(KeyNum<=10)    //数字输入,输入的密码范围S1~S10
      {
        if(Count<4)
        {
          //Password=KeyNum%10;    //取余,1..9取余就是1..9,10取余是0
          Password*=10;    //密码左移一位
          Password+=KeyNum%10;    //获取当前密码(设置的当前密码是个4位密码,int类型可以装的下。)
          Count++;
        }
        LCD_ShowNum(2,1,Password,4); //更新显示
      }
      if(KeyNum==11)    //确认键,S11当作密码确认使用
      {
        if(Password==2345)
        {
          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)    //取消键,S12
      {
        Password=0;
        Count=0;
        LCD_ShowNum(2,1,Password,4);
      }
    }
  }
}

蜂鸣器

  • 蜂鸣器原理图:

(有源)蜂鸣器本身的控制很简单,给BEEP一个电压即可(给的频率不同,发声就不一样,以此实现不同音阶);但是单片机IO口一般不直接驱动蜂鸣器,本开发板共用的ULN2003D的第五个口来驱动BEEP;但是大部分时候自己设计可以用三极管(PNP或者NPN都行)驱动蜂鸣器。

  • 蜂鸣器的驱动方式,三极管驱动或者其他:

NPN蜂鸣器需要自己供电,PNP则由开关电路供电。

  • ULN2003达林顿晶体管阵列,可以提供高驱动能力

ULN2003输入端给1,经过取反电压为0,外部电压可以经过蜂鸣器(NPN型)回到COM,实现输出。

  • 音符到周期转换表

  • 鸣响一个蜂鸣器:

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

unsigned int i;
sbit Buzzer=P2^5;    //如原理图,P2^5控制BEEP

void main()
{
while(1)
{
    if(P3_1==0)    //按键K1按下
    {
      for(i=0;i<1000;i++)    //循环1000次,1次1毫秒,周期2ms,相当于蜂鸣器以0.5KHZ的频率动作。
        {
          Buzzer=!Buzzer;
          Delay(1);
        }
    }   
  }
}

LED点阵屏使用

  • 74HC595(移位寄存器)模块原理图:
    • RCLK:使能输出,待数据移位结束后给脉冲即可使能输出。
    • SRCLK:移位脉冲,一次高电平数据移位一次。
    • SER:数据位,先进去的位是高位,最后进去的位是低位。

注意开发板上J24的短接帽要取下来。

  • 对74HC595编程:
    • SFR:特殊功能寄存器声明,比如sfr P0=0x80;,既声明P0口寄存器,物理地址为0x80。
    • sbit:特殊位声明,比如sbit P0_1=0x81;或者sbit P0_1 = P0^1;,用来声明P0寄存器的第1位。
    • 可寻址位/不可寻址位:在单片机系统中,操作任意寄存器或者某一位数据时,必须给出物理地址。又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍。单片机无法对所有位进行编码,所以每8个寄存器中,只有一个寄存器可以被位寻址,对于不可以被位寻址的寄存器,若想操作其中一位而不影响其他位时,可用&=|=^=的方法进行位操作。
#include <REGX52.H>

sbit RCK=P3^5;    //RCLK和系统寄存器重名了,所以用RCK
sbit SCK=P3^6;    //即SCLK
sbit SER=P3^4;   

//写法1:
void _74HC595_WriteByte(unsigned char Byte)  
{
  //按位与运算
  SER=Byte&0x80;    //0x80=2#10000000;Byte的最高位是1,SER就是1,最高位是0,SER就是0.
  SCK=1;    //移位脉冲执行一次,数据移位一次
  SCK=0;
  
  SER=Byte&0x40; 
  SCK=1;    
  SCK=0;

  SER=Byte&0x20; 
  SCK=1;    
  SCK=0;

  SER=Byte&0x10; 
  SCK=1;    
  SCK=0;
  //一共执行8次
  ...

  RCK=1;    //输出脉冲
  RCK=0;
}

//写法2,用for循环代替重复代码:
void _74HC595_WriteByte(unsigned char Byte)  
{
  unsigned char i;
 for(i=0;i<8;i++)
  {
    SER=Byte&(0x80>>i);    //右移指令 >> ,替代循环
    SCK=1;    
    SCK=0;
  }
  RCK=1;    //输出脉冲
  RCK=0;
}


void main()
{
  //初始化
  SCK=0;
  RCK=0;
  

  while(1)
  {

  }
}
  • 点阵屏原理图:

  • 驱动点阵屏

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

sbit RCK=P3^5;    
sbit SCK=P3^6;    
sbit SER=P3^4;

//
void _74HC595_WriteByte(unsigned char Byte)  
{
  unsigned char i;
 for(i=0;i<8;i++)
  {
    SER=Byte&(0x80>>i);    //右移指令 >> ,替代循环
    SCK=1;    
    SCK=0;
  }
  RCK=1;    //输出脉冲
  RCK=0;
}

//驱动点阵
//以列为单位扫描(类似于LED数码管)
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
  _74HC595_WriteByte(Data);
  P0=~(0x80>>Column);    //利用移位指令实现对应列扫描
  Delay(1);    //LED动态扫描扫描,始终免不了消影
  P0=0xFF;
}

void main()
{
  //初始化
  SCK=0;
  RCK=0;
  while(1)
  {
    //调用,实现点阵显示
    MatrixLED_ShowColumn(0,0x3c);
    MatrixLED_ShowColumn(1,0x42);
    MatrixLED_ShowColumn(2,0xa9);
    MatrixLED_ShowColumn(3,0x85);
	MatrixLED_ShowColumn(4,0x85);
    MatrixLED_ShowColumn(5,0xa9);
    MatrixLED_ShowColumn(6,0x42);
    MatrixLED_ShowColumn(7,0x3c);
  }
}
  • 可以用excel做出阵列扫描图:

这是一个笑脸的阵列扫描

  • 做流水动画:
    • 原理:把数据做成一个数组,在for循环中做偏移和延迟,实现流水动画。
    • 文字取模软件可以帮助自动生成数据内容。
#include <REGX52.H>
#include "Delay.h"

sbit RCK=P3^5;    
sbit SCK=P3^6;    
sbit SER=P3^4;

void _74HC595_WriteByte(unsigned char Byte)  
{
  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);
  P0=~(0x80>>Column);    
  Delay(1);    
  P0=0xFF;
}

 //数组用来存数据,长度32+8=40
unsigned char Animation[]={
0xFF,0x10,0x10,0x10,0xFF,0x00,0xFF,0x91,    //0..31,生成的数据
0x91,0x91,0x91,0x91,0xDE,0x00,0xFF,0x00,
0xFF,0x00,0xFF,0x81,0x81,0x81,0xFF,0x00,
0x00,0x00,0x38,0xCF,0x8E,0xC4,0x3E,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,    //清屏
};     

void main()
{
  //offset:偏移量;count:用来做延迟
  unsigned char i,offset=0,count=0;
	
  SCK=0;
  RCK=0;
	
  while(1)
  {
    for(i=0;i<8;i++)    //显示一块8*8的阵列图形
    {
      MatrixLED_ShowColumn(i,Animation[i+offset]);    
    }
     count++;
     if(count>10)    //用count计数来变相做延迟
      {
        count=0;
        offset++;    //屏幕显示左移一列
        if(offset>(40-8))    //数组边界不能超过Animation[i+offset],避免数组长度溢出
        {
          offset=0;
        }
      }
  }
}

DS1302实时时钟

  • 作为一种实时时钟芯片,内部也是一个集成芯片。

VCC1,备用电源,此处没用
VCC2,主电源
CE,芯片使能
IO数据输入输出
SCLK,串行时钟
X1,X2:32.768khz晶振,这个晶振的精度很高。

  • 使用芯片,最重要的是学会看芯片手册,比如寄存器地址,比如时序定义等等..

RTC就是实时时钟的意思,RTC这个图详细解析了Figure3中command的内容。
地址命令字得到相关寄存器和控制字操作。
控制字第7位常1;第6位,给1操作RAM,给0操作CK;第0位给1读,给0写;中间的字的含义详见图RTC。
写秒80H,读秒81H;写分82H,读分83H...
时间数据存储是以BCD码方式存储的,所以显示时或者显示16进制数,或者BTI;
BCD转十进制:DEC=BCD/16 * 10+BCD%16;(高4位取模,低4位取余);
十进制转BCD:BCD=DEC/10 * 16+DEC%10;(高4位取模,低4位取余);

时序的定义:不管读还是写,CE高电平应该覆盖整个过程;时钟上升沿写入数据;时钟的下降沿读出数据
数据先发最低位(最左边的位);命令字写入/读出结束后,紧跟的第二个字节是数据。
注意读的时候,上升沿有16个,下降沿只有15个;从命令字到数据之间的那个下降沿是无用的。

  • 手动读写时钟代码测试:
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"

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

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

void DS1302_WriteByte(unsigned char Command,Data)
{
  unsigned char i;
  DS1302_CE=1;
  for(i=0;i<8;i++)		//command
  {
    DS1302_IO=Command&£(0x01<<i);    //左移取出对应位
    DS1302_SCLK=1;    //做脉冲,手册对脉冲持续时间有要求,但其实因为51反应慢,不加延时也行。
    Delay(1);
    DS1302_SCLK=0;
   }
	
  for(i=0;i<8;i++)    //data
  {
    DS1302_IO=Data&£(0x01<<i);
    DS1302_SCLK=1;	
    Delay(1);
    DS1302_SCLK=0;
  }
   DS1302_CE=0;    //结束清零
}

unsigned char DS1302_ReadByte(unsigned char Command)
{
  unsigned char i,Data=0x00;
  DS1302_CE=1;
  //Command|=0x01;    //将指令转换为读指令
  for(i=0;i<8;i++)
  {
    DS1302_IO=Command&£(0x01<<i);		
    DS1302_SCLK=0;    //注意看时序图,这里以及下面都在按照时序图的顺序在做沿	
    DS1302_SCLK=1;	
   }
  for(i=0;i<8;i++)
  {
    DS1302_SCLK=1;	//注意看时序图,这里按照时序图的顺序在做沿	
    DS1302_SCLK=0;
    if(DS1302_IO){Data=Data|(0x01<<i);}    //把读出来的数据给Data
  }
  DS1302_CE=0;
  DS1302_IO=0;    //读出完成后记得把IO设置为0,否则读出数据会出错。
  return Data;
}

unsigned char Second,Minute;

void main()
{
  LCD_Init();
  LCD_ShowString(1,1,"RTC");
  DS1302_WriteByte(0x80,0x55);  //写秒
  while(1)
  {
    Second=DS1302_ReadByte(0x81);  //读秒
    Minute=DS1302_ReadByte(0x83);    //读分
    LCD_ShowNum(2,1,Second/16*10+Second%16,2);  //BCD转十进制显示
    LCD_ShowNum(2,3,Minute/16*10+Minute%16,2);
  }
}
  • 结构化DS1302工程代码(用数组来做DS1302时间,把函数写在子程序中):
#include <REGX52.H>

//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

//寄存器写入地址/指令定义
#define DS1302_SECOND    0x80
#define DS1302_MINUTE    0x82
#define DS1302_HOUR      0x84
#define DS1302_DATE      0x86
#define DS1302_MONTH     0x88
#define DS1302_DAY       0x8A
#define DS1302_YEAR      0x8C
#define DS1302_WP        0x8E

//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={25,03,27,12,59,55,6};

/**
  * @brief  DS1302初始化
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
  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 命令字/地址
  * @retval 读出的数据
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
  unsigned char i,Data=0x00;
  Command|=0x01;	//将指令转换为读指令
  DS1302_CE=1;
  for(i=0;i<8;i++)
  {
    DS1302_IO=Command&(0x01<<i);
    DS1302_SCLK=0;
    DS1302_SCLK=1;
  }
  for(i=0;i<8;i++)
  {
    DS1302_SCLK=1;
    DS1302_SCLK=0;
    if(DS1302_IO){Data|=(0x01<<i);}
  }
  DS1302_CE=0;
  DS1302_IO=0;	//读取后将IO设置为0,否则读出的数据会出错
  return Data;
}

/**
  * @brief  DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
  * @param  无
  * @retval 无
  */
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,0x80);
}

/**
  * @brief  DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
  * @param  无
  * @retval 无
  */
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;
}

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"

void main()
{
  LCD_Init();
  DS1302_Init();
  LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
  LCD_ShowString(2,1,"  :  :  ");
	
  DS1302_SetTime();//设置时间
	
  while(1)
  {
    DS1302_ReadTime();//读取时间
    LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年
    LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月
    LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日
    LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时
    LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分
    LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒
   }
}
posted @ 2025-03-21 09:51  你要去码头整点薯条吗  阅读(118)  评论(0)    收藏  举报