外接晶振为12MHz时,51单片机相关周期的具体值为:
振荡周期=1/12us;
状态周期=1/6us;
机器周期=1us;
指令周期=1~4us;
51单片机定时/计数器的工作由两个特殊功能寄存器控制。TMOD用于设置其工作方式;TCON用于控制其启动和中断申请。
1、工作方式寄存器TMOD
工作方式寄存器TMOD用于设置定时/计数器的工作方式,低四位用于T0,高四位用于T1。其格式如下:
GATE是门控位, GATE=0时,用于控制定时器的启动是否受外部中断源信号的影响。只要用软件使TCON中的TR0或TR1为1,就可以启动定时/计数器工作;GATA=1时,要用软件使TR0或TR1为1,同时外部中断引脚INT0/1也为高电平时,才能启动定时/计数器工作。即此时定时器的启动条件,加上了INT0/1引脚为高电平这一条件。
C/T :定时/计数模式选择位。C/T =0为定时模式;C/T =1为计数模式。
M1M0:工作方式设置位。定时/计数器有四种工作方式。
控制寄存器TCON
TCON的高4位用于控
制定时/计数器的启动和中断申请。其格式如下:
TF1(TCON.7):T1溢出中断请求标志位。T1计数溢出时由硬件自动置TF1为1。CPU响应中断后TF1由硬件自动清0。T1工作时,CPU可随时查询TF1的状态。所以,TF1可用作查询测试的标志。TF1也可以用软件置1或清0,同硬件置1或清0的效果一样。
TR1(TCON.6):T1运行控制位。TR1置1时,T1开始工作;TR1置0时,T1停止工作。TR1由软件置1或清0。所以,用软件可控制定时/计数器的启动与停止。
TF0(TCON.5):T0溢出中断请求标志位,其功能与TF1类同。
TR0(TCON.4):T0运行控制位,其功能与TR1类同。
定时/计数器的工作方式
1、方式1
方式1的计数位数是16位,由TL0作为低8位,TH0
作为高8位,组成了16位加1计数器 。计数个数与计数初值的关系为:X=2(16次方)-N
2、方式2
为自动重装初值的8位计数方式。 计数个数与计数初值的关系为:X=28-N
工作方式2特别适合于用作较精确的脉冲信号发生器。所以串口通信处用此方式。
计数器初值的计算
机器周期也就是CPU完成一个基本操作所需要的时间。
机器周期=1/单片机的时钟频率。
51单片机内部时钟频率是外部时钟的12分频。也就是说当外部晶振的频率输入到单片机里面的时候要进行12分频。比如说你用的是12MHZ的晶振,那么单片机内部的时钟频率就是12/12MHZ,当你使用12MHZ的外部晶振的时候。机器周期=1/1M=1us。
而我们定时1ms的初值是多少呢,1ms/1us=1000。也就是要计数1000个数,初值=65535-1000+1(因为实际上计数器计数到66636才溢出)=64536=FC18H
串口通信
比特率是每秒钟传输二进制代码的位数,单位是:位/秒(bps)。如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位、1个停止位、8个数据位),这时的比特率为:
10位×240个/秒 = 2400 bps
SCON 是一个特殊功能寄存器,用以设定串行口的工作方式、接收/发送控制以及设置状态标志:
SM0和SM1为工作方式选择位,可选择四种工作方式:
SM2,多机通信控制位,主要用于方式2和方式3。当接收机的SM2=1时可以利用收到的RB8来控制是否激活RI(RB8=0时不激活RI,收到的信息丢弃;RB8=1时收到的数据进入SBUF,并激活RI,进而在中断服务中将数据从SBUF读走)。当SM2=0时,不论收到的RB8为0和1,均可以使收到的数据进入SBUF,并激活RI(即此时RB8不具有控制RI激活的功能)。通过控制SM2,可以实现多机通信。
在方式0时,SM2必须是0。在方式1时,如果SM2=1,则只有接收到有效停止位时,RI才置1。
REN,允许串行接收位。由软件置REN=1,则启动串行口接收数据;若软件置REN=0,则禁止接收
TI,发送中断标志位。在方式0时,当串行发送第8位数据结束时,或在其它方式,串行发送停止位的开始时,由内部硬件使TI置1,向CPU发中断申请。在中断服务程序中,必须用软件将其清0,取消此中断申请。
RI,接收中断标志位。在方式0时,当串行接收第8位数据结束时,或在其它方式,串行接收停止位的中间时,由内部硬件使RI置1,向CPU发中断申请。也必须在中断服务程序中,用软件将其清0,取消此中断申请。
PCON中只有一位SMOD与串行口工作有关 :SMOD(PCON.7) 波特率倍增位。在串行口方式1、方式2、方式3时,波特率与SMOD有关,当SMOD=1时,波特率提高一倍。复位时,SMOD=0。
80C51串行口的工作方式
这里只介绍方式1:
方式1是10位数据的异步通信口。TXD为数据发送引脚,RXD为数据接收引脚,传送一帧数据的格式如图所示。其中1位起始位,8位数据位,1位停止位。
用软件置REN为1时,接收器以所选择波特率的16倍速率采样RXD引脚电平,检测到RXD引脚输入电平发生负跳变时,则说明起始位有效,将其移入输入移位寄存器,并开始接收这一帧信息的其余位。接收过程中,数据从输入移位寄存器右边移入,起始位移至输入移位寄存器最左边时,控制电路进行最后一次移位。当RI=0,且SM2=0(或接收到的停止位为1)时,将接收到的9位数据的前8位数据装入接收SBUF,第9位(停止位)进入RB8,并置RI=1,向CPU请求中断。
方式1的波特率 =(2SMOD/32)·(T1溢出率)
T1 溢出率 = fosc /{12×[256 -(TH1)]}
DS18B20温度传感器原理
DS18B20温度转换规则
DS18B20的核心功能是它可以直接读出数字的温度数值。温度传感器的精度为用户可编程的9,10,11或12位,分别以0.5℃,0.25℃,0.125℃和0.0625℃增量递增。在上电状态下默认的精度为12位。
这是12位转化后得到的12位数据,存储在DS18B20的两个8位的RAM中,高字节的前5位是符号位,如果测得的温度大于0,这5位为‘0’,只要将测到的数值乘以0.0625即可得到实际温度;如果温度小于0,这5位为‘1’,测到的数值需要先减1再取反再乘以0.0625即可得到实际温度。
(1).数据线拉到低电平“0”。
(2).延时480微妙(该时间的时间范围可以从480到960微妙)。
(3).数据线拉到高电平“1”。
(4).延时等待80微妙。如果初始化成功则在15到60微妙时间内产生一个由DS18B20所返回的低电平“0”.根据该状态可以来确定它的存在,但是应注意不能无限的进行等待,不然会使程序进入死循环,所以要进行超时判断。
(5).若CPU读到了数据线上的低电平“0”后,还要做延时,其延时的时间从发出的高电平算起(第(3)步的时间算起)最少要480微妙。
读时序
(1).将数据线拉低“0”。
(2).延时1微妙。
(3).将数据线拉高“1”,释放总线准备读数据。
(4).延时10微妙。
(5).读数据线的状态得到1个状态位,并进行数据处理。
(6).延时45微妙。
(7).重复1~7步骤,直到读完一个字节。
写时序
(1).数据线先置低电平“0”
(2).延时15微妙。
(3).按从低位到高位的顺序发送数据(一次只发送一位)。
(4).延时60微妙。
(5).将数据线拉到高电平。
(6).重复1~5步骤,直到发送完整的字节。
(7).最后将数据线拉高。
#include<reg51.h>
#define LCD1602_DATAPINS P0
typedef unsigned int uint;
typedef unsigned char uchar;
sbit LCD1602_E=P2^7;
sbit LCD1602_RW=P2^5;
sbit LCD1602_RS=P2^6;
sbit DSPORT=P3^7;
uchar CNCHAR[6] = "摄氏度";
void LcdInit();
void LcdWriteData(uchar dat);
void LcdWriteCom(uchar com);
void LcdDisplay(int);
void UsartConfiguration();
uchar init();
void writebyte(uchar datas);
void change_temper();
void read_tempercom();
uint read_temper();
uchar readbyte();
void DelayMs(unsigned int x);
void main(void)
{
UsartConfiguration();
LcdInit(); //初始化LCD1602
LcdWriteCom(0x88); //写地址 80表示初始地址
LcdWriteData('C');
while(1)
{
LcdDisplay(read_temper());
// Delay1ms(1000);//1s钟刷一次
}
}
void DelayMs(unsigned int x) //0.14ms误差 0us
{
unsigned char i;
while(x--)
{
for (i = 0; i<13; i++)
{}
}
}
void LcdDisplay(int temp) //lcd显示
{
unsigned char i, datas[] = {0, 0, 0, 0, 0}; //定义数组
float tp;
if(temp< 0) //当温度值为负数
{
LcdWriteCom(0x80); //写地址 80表示初始地址
SBUF='-';//将接收到的数据放入到发送寄存器
while(!TI); //等待发送数据完成
TI=0; //清除发送完成标志位
LcdWriteData('-'); //显示负
//因为读取的温度是实际温度的补码,所以减1,再取反求出原码
temp=temp-1;
temp=~temp;
tp=temp;
temp=tp*0.0625*100+0.5;
//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
//算由?.5,还是在小数点后面。
}
else
{
LcdWriteCom(0x80); //写地址 80表示初始地址
LcdWriteData('+'); //显示正
SBUF='+';//将接收到的数据放入到发送寄存器
while(!TI); //等待发送数据完成
TI=0; //清除发送完成标志位
tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量
//如果温度是正的那么,那么正数的原码就是补码它本身
temp=tp*0.0625*100+0.5;
//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
//算加上0.5,还是在小数点后面。
}
datas[0] = temp / 10000;
datas[1] = temp % 10000 / 1000;
datas[2] = temp % 1000 / 100;
datas[3] = temp % 100 / 10;
datas[4] = temp % 10;
LcdWriteCom(0x82); //写地址 80表示初始地址
LcdWriteData('0'+datas[0]); //百位
SBUF = '0'+datas[0];//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
LcdWriteCom(0x83); //写地址 80表示初始地址
LcdWriteData('0'+datas[1]); //十位
SBUF = '0'+datas[1];//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
LcdWriteCom(0x84); //写地址 80表示初始地址
LcdWriteData('0'+datas[2]); //个位
SBUF = '0'+datas[2];//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
LcdWriteCom(0x85); //写地址 80表示初始地址
LcdWriteData('.'); //显示 ‘.’
SBUF = '.';//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
LcdWriteCom(0x86); //写地址 80表示初始地址
LcdWriteData('0'+datas[3]); //显示小数点
SBUF = '0'+datas[3];//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
LcdWriteCom(0x87); //写地址 80表示初始地址
LcdWriteData('0'+datas[4]); //显示小数点
SBUF = '0'+datas[4];//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
for(i=0; i<6; i++)
{
SBUF = CNCHAR[i];//将接收到的数据放入到发送寄存器
while (!TI); //等待发送数据完成
TI = 0;
}
}
void UsartConfiguration()
{
SCON=0X50; //设置为工作方式1
TMOD=0X20; //设置计数器工作方式2
PCON=0X80; //波特率加倍
TH1=0XF3; //计数器初始值设置,注意波特率是4800的
TL1=0XF3;
// ES=1; //打开接收中断
// EA=1; //打开总中断
TR1=1; //打开计数器
}
/****************
温度传感器部分
****************/
uchar init()
{
uchar i=0;
DSPORT=0;
i = 70; //将总线拉低480us~960us
while(i--);//延时642us
DSPORT=1; //然后拉高总线,如果DS18B20做出反应会将在15us~60us后总线拉低
i=0;
while(DSPORT) //等待DS18B20拉低总线
{
DelayMs(1);
i++;
if(i>5)
{
return 0; //初始化失败
}
}
return 1;
}
void writebyte(uchar datas)
{
uchar i,j=1;
for(i=0;i<8;i++)
{
DSPORT=0; //每写入一位数据之前先把总线拉低1us
//j++; //此处延时1us,好像又影响不大
DSPORT=datas&0x01;
j=6;
while(j--); //延时68us,持续时间最少60us
DSPORT=1; //然后释放总线,至少1us给总线恢复时间才能接着写入第二个数值
datas>>=1;
}
}
uchar readbyte()
{
uchar dat=0,temp;
uint i ,j;
for(i=0;i<8;i++)
{
DSPORT=0; //先将总线拉低1us
j++;
DSPORT=1; //然后释放总线
j++; //此处延时有变化,等待6us
j++;
temp=DSPORT; //读取数据,从最低位开始读取
/*将byte左移一位,然后与上右移7位后的bi,注意移动之后移掉那位补0。*/
dat=(dat>>1)|(temp<<7);
j = 4; //读取完之后等待48us再接着读取下一个数
while(j--);; //好像没影响
}
return dat; //不是temp
}
void change_temper()
{
init();
DelayMs(1); //无影响
writebyte(0xcc); //跳过ROM操作命令
writebyte(0x44); //温度转换命令
}
void read_tempercom()
{
init();
//DelayMs(1);
writebyte(0xcc);
writebyte(0xbe); //发送读取温度命令
}
uint read_temper()
{
uchar tml,tmh;
uint t=0;
change_temper(); //先写入转换命令
read_tempercom(); //然后等待转换完后发送读取温度命令
tml=readbyte(); //读取温度值共16位,先读低字节
tmh=readbyte(); //再读高字节
t=tmh;
t<<=8; //移8位
t=tml|t;
return t;
}
/********************
LCD液晶部分
********************/
void LcdWriteCom(uchar com) //写入命令
{
LCD1602_E = 0; //使能
LCD1602_RS = 0; //选择发送命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //放入命令
DelayMs(1); //等待数据稳定
LCD1602_E = 1; //写入时序
DelayMs(5); //保持时间
LCD1602_E = 0;
}
void LcdWriteData(uchar dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //写入数据
DelayMs(1);
LCD1602_E = 1; //写入时序
DelayMs(5); //保持时间
LCD1602_E = 0;
}
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x38); //开显示
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
}
LCD显示部分可以参考我一篇文章
51单片机DS1302时钟LCD1602显示
以上是我在学习过程中的一点总结,用的是普中的51单片机·。