本篇文章属于学习笔记,来源于B站教学视频,相关代码工程请从源地址自行下载。这位Up讲解得很好,适合同学们一起学习,在这里推荐给大家。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。
51单片机入门教程-2020版 程序全程纯手打 从零开始入门
1.开发软件:keil5C51。可自行到官网下载。(注意要下51单片机版本)
2、烧录软件stc-isp
3.开发板:普中A2标准板。
单片机(Single-Chip Microcomputer)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的300M的高速单片机。
单片机的发展先后经历了4位、8位、16位和32位等阶段。8位单片机由于功能强,被广泛用于工业控制、智能接口、仪器仪表等各个领域,8位单片机在中、小规模应用场合仍占主流地位,代表了单片机的发展方向,在单片机应用领域发挥着越来越大的作用。 80年代初,Intel公司推出了8位的MCS-51系列的单片机。
MCS-51单片机的逻辑部件,包括一个8位CPU及片内振荡器、 80514B掩膜ROM、87514KBEPROM、8031无ROM、特殊功能寄存器SFR128BRAM、定时器/计数器T0及T1、并行I/O接口:P0、P1、P2、P3;串行接口:TXD、RXD;中断系统:INT0,INT1。
通过下面的图,我们可以看到LED正极接着电源,那我们可以控制P2的八根引脚的高低电压来控制电路的导通从而控制LED的亮灭。并且通过串联电阻来限流,避免LED烧坏。而单片机需要通过CPU控制寄存器的值,进而通过驱动器加大控制力度,由控制电路输出高低电平(对应寄存器1/0)。因此,程序需要在对应的寄存器上写1或0,即可控制LED的亮灭。
//LED D1亮
#include
void main()
{
//P2=0xEE;//1111 1110
while()
{
P2=0xEE;//1111 1110
}
}
//延时500ms控制LED灯闪烁
#include
#include
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2=0xFE;
Delay500ms();
P2=0xFF;
Delay500ms();
}
}
//流水灯
#include
#include
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2=0xFE;//1111 1110
Delay500ms();
P2=0xFD;//1111 1101
Delay500ms();
P2=0xFB;//1111 1011
Delay500ms();
P2=0xF7;//1111 0111
Delay500ms();
P2=0xEF;//1110 1111
Delay500ms();
P2=0xDF;//1101 1111
Delay500ms();
P2=0xBF;//1011 1111
Delay500ms();
P2=0x7F;//0111 1111
Delay500ms();
}
}
//LED流水灯升级(控制延时时间)
#include
#include
void Delay1ms(unsigned int xms) //@11.0592MHz
{
while(xms){
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
while(1)
{
P2=0xFE;//1111 1110
Delay1ms(500);
P2=0xFD;//1111 1101
Delay1ms(500);
P2=0xFB;//1111 1011
Delay1ms(500);
P2=0xF7;//1111 0111
Delay1ms(500);
P2=0xEF;//1110 1111
Delay1ms(500);
P2=0xDF;//1101 1111
Delay1ms(500);
P2=0xBF;//1011 1111
Delay1ms(500);
P2=0x7F;//0111 1111
Delay1ms(500);
}
}
//按位与控制LED循环亮灭
#include
#include
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2=0xFF;
Delay500ms();
while(P2!=0)
{
P2=(P2<<1);
Delay500ms();
}
//P2=0xFF;
}
}
因为按键的抖动,消除抖动可以在编写代码是延时抖动时间,使他处于稳点位置。
//按键控制LED
#include
void main()
{
//P2=0xFE;
//P2_0=1;
while(1)
{
if(P3_1==0){
P2_0=0;
}
else{
P2_0=1;
}
}
}
//按键控制led状态
#include
void Delay1ms(unsigned int xms) //@11.0592MHz
{
while(xms)
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
while(1)
{
if(P3_1==0)
{
Delay1ms(20);
while(P3_1==0);
Delay1ms(20);
P2_0=~P2_0;
}
}
}
//按键控制led二进制亮灭
#include
void Delay(unsigned int xms) //@11.0592MHz
{
while(xms--)
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main(){
unsigned char LEDNum = 0;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++;
P2 = ~LEDNum;
}
}
}
//按键控制led移位
#include
void Delay(unsigned int xms) //@11.0592MHz
{
while(xms--)
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main(){
unsigned char LEDNum = 0;
P2 = ~0x01;
//unsigned char LEDNum2 = 0;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++;
if(LEDNum>7)
LEDNum=0;
//LEDNum++;
P2 = ~(0x01<<LEDNum);
//LEDNum1++;
}
if(P3_0==0)
{
Delay(20);
while(P3_0==0);
Delay(20);
if(LEDNum==0)
LEDNum=7;
else
LEDNum--;
P2 = ~(0x01<<LEDNum);
}
}
}
数码管的接法,有共阳和共阴之分。共阴时,拉高电压即可点亮。共阳时,拉低电平点亮。
//
#include
const unsigned char NixieTable[]={0x3F,0x06,0x5B,
0x4F,0x66,0x6D,
0x7D,0x07,0x7F,0x6F};
void Nixie(unsigned char Location,Number)
{
switch(Location)
{
case 1:
P2_4=1;P2_3=1;P2_2=1;break;
case 2:
P2_4=1;P2_3=1;P2_2=0;break;
case 3:
P2_4=1;P2_3=0;P2_2=1;break;
case 4:
P2_4=1;P2_3=0;P2_2=0;break;
case 5:
P2_4=0;P2_3=1;P2_2=1;break;
case 6:
P2_4=0;P2_3=1;P2_2=0;break;
case 7:
P2_4=0;P2_3=0;P2_2=1;break;
case 8:
P2_4=0;P2_3=0;P2_2=0;break;
}
P0 = NixieTable[Number];
}
void main()
{
// const unsigned char NixieTable[]={0x3F,0x06,0x5B,
// 0x4F,0x66,0x6D,
// 0x7D,0x07,0x7F,0x6F};
//注意:函数传参都是拷贝过去的。
while(1)
{
Nixie(3,2);
}
}
消影是因为位选和段选显示数据窜位。加了一个延时函数并且位选清零。
#include
const unsigned char NixieTable[]={0x3F,0x06,0x5B,
0x4F,0x66,0x6D,
0x7D,0x07,0x7F,0x6F};
void Delay(unsigned int xms) //@11.0592MHz
{
while(xms--)
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void Nixie(unsigned char Location,Number)
{
switch(Location)
{
case 1:
P2_4=1;P2_3=1;P2_2=1;break;
case 2:
P2_4=1;P2_3=1;P2_2=0;break;
case 3:
P2_4=1;P2_3=0;P2_2=1;break;
case 4:
P2_4=1;P2_3=0;P2_2=0;break;
case 5:
P2_4=0;P2_3=1;P2_2=1;break;
case 6:
P2_4=0;P2_3=1;P2_2=0;break;
case 7:
P2_4=0;P2_3=0;P2_2=1;break;
case 8:
P2_4=0;P2_3=0;P2_2=0;break;
}
P0 = NixieTable[Number];
Delay(1);
P0=0x00;
}
void main()
{
// const unsigned char NixieTable[]={0x3F,0x06,0x5B,
// 0x4F,0x66,0x6D,
// 0x7D,0x07,0x7F,0x6F};
//注意:函数传参都是拷贝过去的。
while(1)
{
Nixie(1,1);
//Delay(100);
Nixie(2,2);
//Delay(100);
Nixie(3,3);
//Delay(100);
}
}
单片机直接扫描: 硬件设备简单,但会耗费大量的单片机CPU时间。
专用驱动芯片: 内部自带显存、扫描电路,单片机只需告诉它显示什么即可。
读取第一行然后快速循环扫描,也可以按列扫描。
例:当P1_7 = 0是相当于接地,当S1按下时候,强上拉使P1_3低电压输入数据为0,即P1_3=0。
MartixKey.c
#include
#include "Delay.h"
unsigned char MartixKey()
{
unsigned char KeyNum=0;
P1 = 0xFF;
P1_7=0;
if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=1;}
if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=2;}
if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=3;}
if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=4;}
P1 = 0xFF;
P1_6=0;
if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=5;}
if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=6;}
if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=7;}
if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=8;}
P1 = 0xFF;
P1_5=0;
if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=9;}
if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=10;}
if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=11;}
if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=12;}
P1 = 0xFF;
P1_4=0;
if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=13;}
if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=14;}
if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=15;}
if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=16;}
return KeyNum;
}
main.c
#include
#include "LCD1602.h"
#include "Delay.h"
#include "MartixKey.h"
unsigned char KeyNum=0;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"hello world!");
while(1)
{
KeyNum = MartixKey();
if(KeyNum)
{
LCD_ShowNum(2,1,KeyNum,2);
}
}
}
main.c
#include
#include "LCD1602.h"
#include "Delay.h"
#include "MartixKey.h"
unsigned char KeyNum = 0;
unsigned int Password ,count;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password!");
while(1)
{
KeyNum = MartixKey();
if(KeyNum)
{
if(KeyNum<=10)//如果按键s1-s10,输入密码。
{
if(count<4)
{
Password *= 10; //密码左移一位
Password += KeyNum%10;//输入一位密码
}
count++;//计数加1
}
if(KeyNum==11)//如果按下S11,确认
{
if(Password==1234)
LCD_ShowString(1,11,"Right!");//如果密码等于1234,则正确
else
LCD_ShowString(1,11,"Error!");
}
if(KeyNum==12)//如果按下S12,重新输入
{
Password=0;
count=0;
}
LCD_ShowNum(2,1,Password,4);//更新显示
}
}
}
51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
我们之前控制的led,按键矩阵按键等都是单片机IO口控制的外设
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度
(3)操作系统的任务切换。
一些高级的单片机,它里面就有专门的一个系统的滴答定时器,用来计时让操作系统来执行多任务。
CPU的多任务就是把多个任务分成一段一段的时间片,把他们交叉组合在一起,然后只要一条线的执行
就能实现多个任务的同时执行。
在切换某个任务的执行,这个过程就是定时器实现的。
STC89C52系列单片机的定时器0和定时器1,与传统8051的定时器完全兼容
当在定时器1做波特率发生器时,定时器0可以当两个8位定时器用。
定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,
当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,
产生“响铃提醒”,使程序跳转到中断服务函数中执行。
STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
此模式下,定时器配置为16位定时器/计数器,由TLO的8位和THO的8位所构成。
TLO的8位溢出向THO进位,THO计数溢出置位TCON中的溢出标志位TFO。
SYSclk:系统时钟,即晶振周期 本开发板上的晶振为11.0592MHz
12Mhz: 时钟周期:
1/12Mhz,1单位是秒所以12Mhz要转为秒为12000000hz 1/12000000≈0.00000008s
机器周期:
12×时钟周期=0.00000008s×12=0.000001s
转为us就是1us
11.0592Mhz:
时钟周期:1/11.0592Mhz,1单位是秒所以11.0592Mhz要转为秒为11059200hz1/11059200≈0.00000009s
机器周期:12×时钟周期=0.00000009s×12=0.00000109s
转为us就是1.09us
此模式下,定时器配置为16位定时器/计数器,由TL0的8位和TH0的8位所构成。
TL0的8位溢出向TH0进位,TH0计数溢出置位TCON中的溢出标志位TF0。
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号:
注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,
例如中断源个数不同、中断优先级个数不同等等
单片机通过配置寄存器来控制内部线路的连接,通过内部线路的不同连接方式来实现不同电路,不同电路完成不同功能
寄存器是连接软硬件的媒介
在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,
另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式
main:
#include
#include "Timer0.h"
#include "Key.h"
#include
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);
}
}
Timer0.c:
#include
/**
* @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;
}
}
*/
main:
#include
#include "LCD1602.h"
#include "Delay.h"
#include "Timer0.h"
unsigned char Sec, Min=18, Hour=0;
void main()
{
LCD_Init();
Timer0Init();
LCD_ShowString(1,1,"abc");
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_Routie () interrupt 1
{
static unsigned int T0count;
T0count++;
TL0 = 0x66;
TH0 = 0xFC;
if(T0count>1000)
{
T0count=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
Hour++;
if(Hour>=24)
{
Hour=0;
}
}
}
}
}
串口,即串行接口,串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。
串口、UART口、COM口、USB口是指的物理接口形式(硬件)。
与之相对应的另一种接口叫并口,并行接口。
串口:串口是一个泛称,UART,TTL,RS232,RS485,I2C,SPI都遵循类似的通信时序协议,因此都被通称为串口。
两者的区别是,传输一个字节(8个位)的数据时,串口是将8个位排好队,逐个地在1条连接线上传输,
而并口则将8个位一字排开,分别在8条连接线上同时传输。
串口、RS-232与TTL
前面讲过,RS-232是一个串行通信接口标准,它规定了逻辑“1”为-3 ~ -15V,逻辑“0”为+3 ~+15V,
符合该标准的串口也叫RS-232串口,比如电脑的COM口。
那么,还有不符合RS-232标准的串口?答案是肯定的,那就是单片机(如stm32)的UART/USART,
这个也叫串口,但它不遵循RS-232标准,使用的是TTL电平(Transistor-TransistorLogic)
该电平的逻辑“1”为+5V,逻辑“0”为0V,称为TTL串口。
**需要注意的是,串口、UART/USART通常指的是硬件接口,而RS-232指的是属于物理层范畴的串行通信接口标准,简而言之,RS-232就是个标准。**
UART接口:通用异步收发器(Universal Asynchronous Receiver/Transmitter),UART是串口收发的逻辑电路,这部分可以独立成芯片,也可以作为模块嵌入到其他芯片里,单片机、SOC、PC里都会有UART模块。
COM口:特指台式计算机或一些电子设备上的D-SUB外形(一种连接器结构,VGA接口的连接器也是D-SUB)的串行通信口,应用了串口通信时序和RS232的逻辑电平。
USB口:通用串行总线,和串口完全是两个概念。虽然也是串行方式通信,但由于USB的通信时序和信号电平都和串口完全不同,因此和串口没有任何关系。USB是高速的通信接口,用于PC连接各种外设,U盘、键鼠、移动硬盘、当然也包括“USB转串口”的模块。(USB转串口模块,就是USB接口的UART模块)
串口通讯(Serial Communication),是指外设和计算机间,通过数据信号线、地线等,按位进行传输数据的一种通讯方式。
串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称作UART)
是一种串行异步收发协议,应用十分广泛。UART工作原理是将数据的二进制位一位一位的进行传输。
在UART通讯协议中信号线上的状态位高电平代表’1’低电平代表’0’。
当然两个设备使用UART串口通讯时,必须先约定好传输速率和一些数据位。
51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。
main:
#include
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UartInit();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(1); // 必要的延时,避免误差导致乱码,没误差的时候可以不需要
}
}
#include
/**
* @brief 初始化函数
* @param 无
* @retval 无
*/
void UartInit(void) //[email protected]
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
//AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
//8AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA = 1;
ES = 1;
}
/**
* @brief 向缓冲区赋一个数据
* @param Byte 赋的值
* @retval 无
*/
void UART_SENT(unsigned char Byte)
{
SBUF = Byte;
while(TI==0);
TI=0;
}
#include
#include "Delay.h"
#include "Uart.h"
unsigned int set;
void main()
{
UartInit();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI!=0)
{
P2 = ~SBUF;
UART_SENT(SBUF);
RI=0;
}
}
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,
8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。
#include
#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);
}
}
#include
#include "MatrixLED.h"
unsigned char Animation[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x42,0x46,0x4A,0x52,0x62,0x42,0x00,
0x00,0x3C,0x42,0x42,0x42,0x42,0x3E,0x02,
0x00,0x7E,0x08,0x08,0x08,0x08,0x7E,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,}
void main()
{
//MatrixLED_Init();
//_74HC595_WriteByte(0x12);
unsigned char i, Offset=2, Count=0;
MatrixLED_Init();
while(1)
{
for(i=0 ;i<8 ;i++)
{
MatrixLED_ShowColumn(i,Animation[i+Offset]);
}
Count++;
if(Count>10)//扫描十遍
{
Count=0;
Offset++;
if(Offset>32)
{
Offset=0; //防止数组溢出
}
}
}
}
晶振结构示意图
我们给石英晶体电压时,石英晶体就会产生机械形变,形变时候又会产生电压,以此不断重复给石英晶体交变的电压
就能产生稳定的震动频率。
当输入的信号频率等于谐振频率时候,LC串联阻抗看作0
反相器兼具把信号放大的功能
感兴趣的可以点击去看原视频介绍。
晶振工作原理
驱动CE为高启动所有数据传输。
CE输入有两个功能
第一:CE打开控制逻辑,允许访问地址/命令序列的移位寄存器。
第二,CE信号提供了终止单字节或多字节CE数据传输的方法。
对于数据输入,数据必须在时钟上升沿有效,数据位在时钟下降沿输出。
如果CE输入低,所有的数据传输终止,I/O引脚进入高阻抗状态。
在上电时, CE必须为逻辑0直到 VCC大于2.0V,同样, SCLK 必须为逻辑0当 CE 变成逻辑1状态。
在输入一个写命令字节的8个SCLK周期之后,在接下来的8个SCLK周期的上升边缘输入一个数据字节。
额外的SCLK周期被忽略,如果他们无意中发生。数据从第0位开始输入。
在输入一个read命令字节的8个SCLK周期之后
一个数据字节被输出到下一个8个SCLK周期的下降边缘。
注意,要传输的第一个数据位发生在命令字节的最后一位写入后的第一个下降沿上。
只要CE保持高,额外的SCLK周期就会重新传输数据字节。
秒寄存器的第7位被定义为时钟停止(CH)标志。
当此位设置为逻辑1时。时钟振荡器停止,DS1302被置于低功率待机模式,电流漏小于100nA。
当此位写入逻辑0时,时钟开始计时。 也即上电状态。
控制寄存器的第7位是写保护位。前7位(从0到6位)被迫为0,在读时总是读0。
在对时钟或RAM进行任何写操作之前,第7位必须是0。
当值高时,写保护位阻止对任何其他寄存器的写操作也即初始上电状态。因此,WP位应该在试图写入设备之前被清除。
电路上电的初始态WP是1,这时是不能改写上面任何一个时间寄存器的,只有首先将WP改写为0,才能进行其它寄存器的写操作。
main:
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
void main()
{
LCD_Init();
DS1301_Init();
//DS1302_WriteByte(0x80,0x03);
DS1302_SetTime();
while(1)
{
// Second = DS1302_ReadByte(0x81);
// LCD_ShowNum(1,2,Second/16*10+Second%16,3);
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);
}
}
DS1302.c:
#include
sbit DS1302_SCK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8e
unsigned char DS1302_TIME[]={22,12,31,23,59,55,1};
/**
* @brief 初始化函数,初始化芯片使能和串行时钟线
* @param 无
* @retval 无
*/
void DS1301_Init(void)
{
DS1302_SCK = 0;
DS1302_CE = 0;
}
/**
* @brief 写入数据
* @param Command ,Date//命令字,数据
* @retval 无
*/
void DS1302_WriteByte(unsigned char Command ,Date)
{
unsigned char i;
DS1302_CE = 1;
for(i=0;i<8;i++)
{
DS1302_IO=Command & (0x01<<i);
DS1302_SCK = 1;
DS1302_SCK = 0;
}
for(i=0;i<8;i++)
{
DS1302_IO=Date & (0x01<<i);
DS1302_SCK = 1;
DS1302_SCK = 0;
}
DS1302_CE = 0;
}
/**
* @brief 读数据
* @param 命令字/地址
* @retval Date
*/
unsigned char DS1302_ReadByte(unsigned char Command )
{
unsigned char i,Date=0x00;
Command|=0x01;
DS1302_CE = 1;
for(i=0;i<8;i++)
{
DS1302_IO=Command & (0x01<<i);
DS1302_SCK = 0;
DS1302_SCK = 1;
}
for(i=0;i<8;i++)
{
DS1302_SCK = 1;
DS1302_SCK = 0;
if(DS1302_IO)
Date |= (0x01<<i);
}
DS1302_CE = 0;
DS1302_IO = 0;
return Date;
}
/**
* @brief 转为BCD码并且设置数据,
* @param 无
* @retval 无
*/
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);
}
/**
* @brief 读取数据并转为十进制数
* @param 无
* @retval 无
*/
void DS1302_ReadTime(void)
{
unsigned char Temp = 0x00;
Temp=DS1302_ReadByte(DS1302_YEAR);
DS1 302_TIME[0]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_MONTH);
DS1302_TIME[1]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DATE);
DS1302_TIME[2]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_TIME[3]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_TIME[4]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_TIME[5]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DAY);
DS1302_TIME[6]=Temp/16*10+Temp%16;
}
main:
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;
void TimeShow()
{
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);
}
void TimeSet()
{
if(KeyNum==2)//按键2按下
{
TimeSetSelect++;//设置选择位加1
TimeSetSelect%=6;//越界清零
}
if(KeyNum==3)//按键3按下
{
DS1302_TIME[TimeSetSelect]++;//时间设置位数值加1
if(DS1302_TIME[0]>99){DS1302_TIME[0]=0;}//年越界判断
if(DS1302_TIME[1]>12){DS1302_TIME[1]=1;}//月越界判断
if( DS1302_TIME[1]==1 || DS1302_TIME[1]==3 || DS1302_TIME[1]==5 || DS1302_TIME[1]==7 ||
DS1302_TIME[1]==8 || DS1302_TIME[1]==10 || DS1302_TIME[1]==12)//日越界判断
{
if(DS1302_TIME[2]>31){DS1302_TIME[2]=1;}//大月
}
else if(DS1302_TIME[1]==4 || DS1302_TIME[1]==6 || DS1302_TIME[1]==9 || DS1302_TIME[1]==11)
{
if(DS1302_TIME[2]>30){DS1302_TIME[2]=1;}//小月
}
else if(DS1302_TIME[1]==2)
{
if((DS1302_TIME[0]%4==0 && DS1302_TIME[0]%100!=0) || (DS1302_TIME[0]%400))
{
if(DS1302_TIME[2]>29){DS1302_TIME[2]=1;}//闰年2月
}
else
{
if(DS1302_TIME[2]>28){DS1302_TIME[2]=1;}//平年2月
}
}
if(DS1302_TIME[3]>23){DS1302_TIME[3]=0;}//时越界判断
if(DS1302_TIME[4]>59){DS1302_TIME[4]=0;}//分越界判断
if(DS1302_TIME[5]>59){DS1302_TIME[5]=0;}//秒越界判断
}
if(KeyNum==4)//按键4按下
{
DS1302_TIME[TimeSetSelect]--;//时间设置位数值减1
if(DS1302_TIME[0]<0){DS1302_TIME[0]=99;}//年越界判断
if(DS1302_TIME[1]<1){DS1302_TIME[1]=12;}//月越界判断
if( DS1302_TIME[1]==1 || DS1302_TIME[1]==3 || DS1302_TIME[1]==5 || DS1302_TIME[1]==7 ||
DS1302_TIME[1]==8 || DS1302_TIME[1]==10 || DS1302_TIME[1]==12)//日越界判断
{
if(DS1302_TIME[2]<1){DS1302_TIME[2]=31;}//大月
if(DS1302_TIME[2]>31){DS1302_TIME[2]=1;}
}
else if(DS1302_TIME[1]==4 || DS1302_TIME[1]==6 || DS1302_TIME[1]==9 || DS1302_TIME[1]==11)
{
if(DS1302_TIME[2]<1){DS1302_TIME[2]=30;}//小月
if(DS1302_TIME[2]>30){DS1302_TIME[2]=1;}
}
else if(DS1302_TIME[1]==2)
{
if((DS1302_TIME[0]%4==0 && DS1302_TIME[0]%100!=0) || (DS1302_TIME[0]%400))
{
if(DS1302_TIME[2]<1){DS1302_TIME[2]=29;}//闰年2月
if(DS1302_TIME[2]>29){DS1302_TIME[2]=1;}
}
else
{
if(DS1302_TIME[2]<1){DS1302_TIME[2]=28;}//平年2月
if(DS1302_TIME[2]>28){DS1302_TIME[2]=1;}
}
}
if(DS1302_TIME[3]<0){DS1302_TIME[3]=23;}//时越界判断
if(DS1302_TIME[4]<0){DS1302_TIME[4]=59;}//分越界判断
if(DS1302_TIME[5]<0){DS1302_TIME[5]=59;}//秒越界判断
}
//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1," ");}
else {LCD_ShowNum(1,1,DS1302_TIME[0],2);}
if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4," ");}
else {LCD_ShowNum(1,4,DS1302_TIME[1],2);}
if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7," ");}
else {LCD_ShowNum(1,7,DS1302_TIME[2],2);}
if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1," ");}
else {LCD_ShowNum(2,1,DS1302_TIME[3],2);}
if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4," ");}
else {LCD_ShowNum(2,4,DS1302_TIME[4],2);}
if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7," ");}
else {LCD_ShowNum(2,7,DS1302_TIME[5],2);}
}
void main()
{
LCD_Init();
Timer0Init();
DS1301_Init();
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");
//DS1302_WriteByte(0x80,0x03);
DS1302_SetTime();
//TimeShow();
while(1)
{
// TimeShow();
KeyNum = Key();
if(KeyNum == 1)
{
if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
else if(MODE==1){MODE=0;DS1302_SetTime();}
}
switch(MODE)
{
case 0: TimeShow();break;
case 1: TimeSet();break;
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500)//每500ms进入一次
{
T0Count=0;
TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
}
}
main:
#include
#include "Key.h"
#include "Delay.h"
#include "Nixie.h"
#include "Buzzer.h"
unsigned char KeyNum;
void main()
{
Nixie(1,0);
while(1)
{
KeyNum = Key();
if(KeyNum)
{
Nixie(1,1);
Buzzer_Time(1000);
}
}
}
Buzzer:
#include
#include
sbit Buzzer = P2^5;//取别名
/**
* @brief 蜂鸣器专用延时:500us
* @param 无
* @retval 无
*/
void Buzzer_Delay500us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 227;
while (--i);
}
/**
* @brief 蜂鸣器响铃时间
* @param unsigned int ms:响铃时间参数
* @retval 无
*/
void Buzzer_Time(unsigned int ms)
{
unsigned int i;
for(i=0;i<ms*2;i++)
{
Buzzer = !Buzzer;
Buzzer_Delay500us();
}
}
#include
#include "Delay.h"
#include "Timer0.h"
//蜂鸣器端口定义
sbit Buzzer=P2^5;
//播放速度,值为四分音符的时长(ms)
#define SPEED 500
//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P 0
#define L1 1
#define L1_ 2
#define L2 3
#define L2_ 4
#define L3 5
#define L4 6
#define L4_ 7
#define L5 8
#define L5_ 9
#define L6 10
#define L6_ 11
#define L7 12
#define M1 13
#define M1_ 14
#define M2 15
#define M2_ 16
#define M3 17
#define M4 18
#define M4_ 19
#define M5 20
#define M5_ 21
#define M6 22
#define M6_ 23
#define M7 24
#define H1 25
#define H1_ 26
#define H2 27
#define H2_ 28
#define H3 29
#define H4 30
#define H4_ 31
#define H5 32
#define H5_ 33
#define H6 34
#define H6_ 35
#define H7 36
//索引与频率对照表
unsigned int FreqTable[]={
0,
63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
};
//乐谱
unsigned char code Music[]={
//音符,时值,
//1
P, 4,
P, 4,
P, 4,
M6, 2,
M7, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
M7, 4+4+4,
M3, 2,
M3, 2,
//2
M6, 4+2,
M5, 2,
M6, 4,
H1, 4,
M5, 4+4+4,
M3, 4,
M4, 4+2,
M3, 2,
M4, 4,
H1, 4,
//3
M3, 4+4,
P, 2,
H1, 2,
H1, 2,
H1, 2,
M7, 4+2,
M4_,2,
M4_,4,
M7, 4,
M7, 8,
P, 4,
M6, 2,
M7, 2,
//4
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
M7, 4+4+4,
M3, 2,
M3, 2,
M6, 4+2,
M5, 2,
M6, 4,
H1, 4,
//5
M5, 4+4+4,
M2, 2,
M3, 2,
M4, 4,
H1, 2,
M7, 2+2,
H1, 2+4,
H2, 2,
H2, 2,
H3, 2,
H1, 2+4+4,
//6
H1, 2,
M7, 2,
M6, 2,
M6, 2,
M7, 4,
M5_,4,
M6, 4+4+4,
H1, 2,
H2, 2,
H3, 4+2,
H2, 2,
H3, 4,
H5, 4,
//7
H2, 4+4+4,
M5, 2,
M5, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
H3, 4+4+4+4,
//8
M6, 2,
M7, 2,
H1, 4,
M7, 4,
H2, 2,
H2, 2,
H1, 4+2,
M5, 2+4+4,
H4, 4,
H3, 4,
H3, 4,
H1, 4,
//9
H3, 4+4+4,
H3, 4,
H6, 4+4,
H5, 4,
H5, 4,
H3, 2,
H2, 2,
H1, 4+4,
P, 2,
H1, 2,
//10
H2, 4,
H1, 2,
H2, 2,
H2, 4,
H5, 4,
H3, 4+4+4,
H3, 4,
H6, 4+4,
H5, 4+4,
//11
H3, 2,
H2, 2,
H1, 4+4,
P, 2,
H1, 2,
H2, 4,
H1, 2,
H2, 2+4,
M7, 4,
M6, 4+4+4,
P, 4,
0xFF //终止标志
};
unsigned char FreqSelect,MusicSelect;
void main()
{
Timer0Init();
while(1)
{
if(Music[MusicSelect]!=0xFF) //如果不是停止标志位
{
FreqSelect=Music[MusicSelect]; //选择音符对应的频率
MusicSelect++;
Delay(SPEED/4*Music[MusicSelect]); //选择音符对应的时值
MusicSelect++;
TR0=0;
Delay(5); //音符间短暂停顿
TR0=1;
}
else //如果是停止标志位
{
TR0=0;
while(1);
}
}
}
void Timer0_Routine() interrupt 1
{
if(FreqTable[FreqSelect]) //如果不是休止符
{
/*取对应频率值的重装载值到定时器*/
TL0 = FreqTable[FreqSelect]%256; //设置定时初值
TH0 = FreqTable[FreqSelect]/256; //设置定时初值
Buzzer=!Buzzer; //翻转蜂鸣器IO口
}
}
code是keil C51里面的关键字,一般用于定义常量数组,意思是告诉编译说把这个数组放在ROM存储。
code的作用是告诉单片机,定义的数据要放在ROM(程序存储区)里面,写入后就不能再更改。
因为C语言中没办法详细描述存入的是ROM还是RAM(寄存器),所以在软件中添加了这一个语句起到代替汇编指令的作用,
对应的还有data是存入RAM的意思。
程序可以简单的分为code(程序)区,和data (数据)区,
code区在运行的时候是不可以更改的,data区放全局变量和临时变量,是要不断的改变的,
cpu从code区读取指令,对data区的数据进行运算处理。因此code区存储在什么介质上并不重要,象以前的计算机程序存储在卡片上,code区也可以放在rom里面,也可以放在ram里面,也可以放在flash里面(但是运行速度要慢很多,主要读flash比读ram要费时间),因此一般的做法是要将程序放到flash里面,然后load到 ram里面运行的;DATA区就没有什么选择了,肯定要放在RAM里面,放到rom里面改动不了。
unsigned char code array[];
//表示分配一个指向code区的指针,指针在默认存储区
code unsigned char array[];
//表示分配一个指向默认存储区的指针,指针在code区
c51中的存储类型:
SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。
FLASH存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据(NVRAM的优势),U盘和MP3里用的就是这种存储器。在过去的20年里,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,然而近年来Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,它用作存储Bootloader以及操作系统或者程序代码,或者直接当硬盘使用(U盘)。
ROM、RAM、DRAM、SRAM和FLASH的区别
注意:地址总线和数据总线不是连接交叉的,他们之间有个高度差。
I2C硬件接口是开漏模式,这个接口只能输出低电平,要实现高电平就要靠上拉电阻去拉高。
上拉电阻是将总线的拉成高电平,当连接在总线上的任意一个设备输出低电平时,总线被拉低就是输出了低电平
“线与”的意思就是连接在总线上的设备只要有一个输出低电平(0)总线就为低电平(0),只有全部设备都为高阻态时总线才是高电平(1)
main:
#include
#include "Key.h"
#include "LCD1602.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," ");
}
}
}
AT24C02:
#include
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0
/**
* @brief AT24C02写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress, Date)
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Date);
I2C_ReceiveAck();
I2C_Stop();
}
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Date;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS+1);
I2C_ReceiveAck();
Date = I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Date;
}
I2C:
#include
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);}
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;
}
main
#include
#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调用一次数秒表驱动函数
}
}
Nixie:
#include
#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:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=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;}
}
Key:
#include
#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(P3_1==0){KeyNumber=1;}
if(P3_0==0){KeyNumber=2;}
if(P3_2==0){KeyNumber=3;}
if(P3_3==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;
}
}
#include
#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调用一次按键驱动函数
}
}
#include
//引脚定义
sbit OneWire_DQ=P3^7;
/**
* @brief 单总线初始化
* @param 无
* @retval 从机响应位,0为响应,1为未响应
*/
unsigned char OneWire_Init(void)
{
unsigned char i;
unsigned char AckBit;
OneWire_DQ=1;
OneWire_DQ=0;
i = 247;while (--i); //Delay 500us
OneWire_DQ=1;
i = 32;while (--i); //Delay 70us
AckBit=OneWire_DQ;
i = 247;while (--i); //Delay 500us
return AckBit;
}
/**
* @brief 单总线发送一位
* @param Bit 要发送的位
* @retval 无
*/
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ=0;
i = 4;while (--i); //Delay 10us
OneWire_DQ=Bit;
i = 24;while (--i); //Delay 50us
OneWire_DQ=1;
}
/**
* @brief 单总线接收一位
* @param 无
* @retval 读取的位
*/
unsigned char OneWire_ReceiveBit(void)
{
unsigned char i;
unsigned char Bit;
OneWire_DQ=0;
i = 2;while (--i); //Delay 5us
OneWire_DQ=1;
i = 2;while (--i); //Delay 5us
Bit=OneWire_DQ;
i = 24;while (--i); //Delay 50us
return Bit;
}
/**
* @brief 单总线发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
OneWire_SendBit(Byte&(0x01<<i));
}
}
/**
* @brief 单总线接收一个字节
* @param 无
* @retval 接收的一个字节
*/
unsigned char OneWire_ReceiveByte(void)
{
unsigned char i;
unsigned char Byte=0x00;
for(i=0;i<8;i++)
{
if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
}
return Byte;
}
#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
main:
#include
#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模块(第五章)。