51单片机学习笔记-3

定时器/计数器资源

  • STC89C52定时器/计数器一共三个(T0,T1,T2)。T1,T0是传统51兼容的定时器/计数器,T2是此CPU增加的资源。

  • TO和T1的四种工作模式:

    • 模式0:13位定时器/计数器
    • 模式1:16位定时器/计数器(常用)
    • 模式2:8位自动重装
    • 模式3:2个8位计数器
  • 模式1的工作原理:

工作机制:每来一个脉冲TL0/TH0(0-65535)就加1,大于65535就溢出了,TF0标识位响应溢出标志,此时产生中断TR0决定是否启动暂停。
工作分为3部分:SYSclk部分通过晶振给脉冲。C/T部分用来配置定时器/计数器模式。Interrupt中断系统用来处理定时器/计数器中断事件。

  • 中断:

    • 中断有优先级,优先级高的先执行,高优先级中断可以打断低优先级中断。
    • 中断嵌套,高优先级中断打断低优先级中断,实现嵌套。

  • 定时器以及中断的相关寄存器使用

使用中断或者定时器主要的使用方法就是明白如何配置寄存器。

  • 定时器的脉冲计算

    • a.首先确定脉冲来源,如果是定时器,脉冲来源一定来自于系统时钟,此时定时器每12个时钟(12T/单倍数)或者每6个时钟(6T/双倍数)得到一个计数脉冲,计数值加1。如果是计数器,脉冲来源来自于外部引脚(T0是P3.4,T1是P3.5)。
    • b.做定时器时系统晶振一般是12MHZ,那么相当于每一个计数脉冲的计数间隔位1us。如果用11.0592MHZ,这时候除不尽,有误差。
    • c.T0模式1的定时器是int(TL0,TH0)长度的,大小是0~65535每产生一次溢出标志位TF0的最大时间为65535us。
    • d.64535离计数器溢出只差1000,1000us为1ms,所以计时基准时间可以设置为1ms
    • e. 程序中实际使用:计算高8位时:TH0=64535/256;计算低8位时:TL0=64535%256可以使得定时器计数从64535~65535,1000us准时触发中断TF0
  • 中断和T0使用练习

配置中断和定时器寄存器的关系如图

#include <REGX52.H>

void Timer0_Init()    //配置定时器0
{
  //TMOD不可位寻址,只能整体赋值
  //TMOD=0x01; //0000 0001, 配置TMOD: M1,M0为0,1;表示16位定时/计数器。TL0,TH0全用。

  //缩写:
  TMOD&=0xF0;
  TMOD|=0x01;

  //TCON可位寻址,意味着可以单独赋值
  TF0=0;    //T0的中断溢出标志位,=1表示有溢出。
  TR0=1;      //=1允许T0计数
  TH0=64535/256;    //赋值定时器高8位
  TL0=64535%256;    //赋值定时器低8位

  ET0=1;    //中断允许控制寄存器打开
  EA=1;    //CPU的总中断允许控制位,=1CPU放开中断。各个中断源被EA总控,其次还可以用ET单独控制各自的中断源。
  PT0=0;    //中断优先级控制寄存器
}

main()
{
  Timer0_Init();  //初始化定时器
  while(1)
  {}
}

unsigned int T0Count;
void Timer0_Routine() interrupt 1  //interrupt1表示T0的中断号
{
  TH0=64535/256;    //每次进中断都需要重新赋初值64535,
  TL0=64535%256;    

  T0Count++;
  if(T0Count>=1000)  //控制灯1秒闪烁1次
  {
    T0Count=0;
    P2_0=~P2_0;
  }
}
  • 因为TMOD不能位寻址,那么在同时存在多个定时器时,配置TMOD的时候TMOD高低位之间肯定会相互影响,如何解决这个问题(与或式赋值法):
TOMD=TMOD&0xF0;  //把TMOD的低四位清零。高四位保持不变(1与上任何一个数都等于1)
TOMD=TMOD|0x01; //把TMOD的低四位置1。高四位保持不变(0或上任何一个数保持不变)

//缩写:
TMOD&=0xF0;
TMOD|=0x01;
  • STC-ISP可以自动配置定时器:

AUXR &=0X7FSTC89C52没有AUXR这个寄存器,AUXR &=0X7F可以删除掉。

串口通信

  • UART(universal asynchronous receiver transmitter,通用异步收发器):可用来实现串口通信。

  • 串口硬件:

    • 基础串口包含TXD/RXD两个通信线,他们交叉连接。(如DB9通常使用2(RXD),3(TXD),5(GND)。)
    • 当串口两头电平标准不一样时,需要叫电平转换芯片
      • TTL电平:5V表示1,0V表示0
      • RS232电平:-3~ -15V表示1,+3~ +15V表示1。
      • 485电平:两线压差+2~ +6V表示1,-2~ -6V表示0(差分信号)。
    • 常见通信接口比较:
  • 串口术语:

    • 全双工:可以在同一时刻内互传数据,如RS485协议。
    • 半双工:通信双方可以互传数据,但是必须分时复用同一根数据线,如RS232协议。
    • 单工:单向通信(比如遥控器)。
    • 异步: 通信双方各自约定通信速率。
    • 同步: 通信双方靠一根时钟线来约束通信速率。
    • 总线: 连接各个设备的数据传输线路。
  • 51单片机的UART:

    • STC89C52有1个UART资源:P3.0/RxD,P3.1/TxD
    • UART有四种工作模式:
      • 模式0:同步移位寄存器。
      • 模式1:8位UART,波特率可变(常用)。
      • 模式2:9位UART,波特率固定。
      • 模式3:9位UART,波特率可变。
    • 时序图:
  • 串口使用的相关寄存器:

  • 串口模式图

  • 串口和中断系统:

  • 利用STC-ISP自动计算波特率:

AUXR 89C52没有用上,可以删掉。
举个例子,如果发送波特率为4800,接收波特率为9600,那么会接收到两次发送的数据。

  • 串口通信配置,串口只能使用定时器T1,且配置为8位自动重装模式。禁止定时器1中断是因为它只需要溢出的波特率发生器(T1溢出率),而不需要生成中断:
#include <REGX52.H>
#include "Delay.h"

//初始化
void UART_Init()
{
    //串口配置
    SCON=0x40;
    PCON=0;

    //定时器T1配置8位自动重装
    //9600bps@11.0592MHz
    PCON &= 0x7F;	//波特率不倍速
    SCON = 0x50;	//8位数据,可变波特率  -> 为了接收数据,使能REN(bit4)
    TMOD &= 0x0F;	//清除定时器1模式位
    TMOD |= 0x20;	//设定定时器1为8位自动重装方式
    TL1 = 0xFD;		//设定定时初值
    TH1 = 0xFD;		//设定定时器重装值
    ET1 = 0;		//禁止定时器1中断
    TR1 = 1;		//启动定时器1

    //为了接收数据,启动中断
    EA=1;    //启动所有中断
    ES=1;    //启动串口中断
}

//发送数据
void UART_SendByte(unsigned char Byte)
{
  SBUF=Byte;    //发生数据的方法很简单,只需要给SBUF赋值即可。
  while(TI==0);  //检查是否发送完成,发完硬件会自动配置。
  TI=0;    //软件复位。
}

//接收数据,需要利用中断,在中断函数中进行数据处理
void UART_Routine() interrupt 4
{
  if(RI==1)
  {
    P2=SBUF;  //读出的数据用来点灯,直观看效果
    RI=0;      //软件复位
  }
}

void main()
{
  UART_Init();
  UART_SendByte(0x66);
  while(1)
  {

  }
}

I2C总线

  • 存储器:
    • RAM(易失型存储器):SRAM(比DRAM更快)/DRAM(内存条为DRAM)
    • ROM(非易失型存储器):Mask ROM/PROM/EPPROM/E2PPROM/Flash/硬盘,软盘,光盘
  • 存储器的简化模型(地址总线,数据总线):

_基本工作原理:假如给地址总线的第一行加一个电压,该行和数据总线焊接上的交叉点则也会有电压(表示为1),没有没有被焊接上的交叉点的数据总线列上则不会有电压(表示为0),整个数据总线的高低电压的不同则可以表示为二进制数据。其他的地址总线行同理。
_Mask ROM:利用了最基本的地址总线数据总线交叉原理。意味着在最开始制造该ROM的时候就已经焊接好了对应的交叉点(交叉点用二极管联通,避免电压反向导通干扰其他地址总线)。数据内容出厂已经确定,不支持后期再更改。(Mask ROM的内容只可读,出厂即被固化。)
_PROM: 用一对二极管代替最原始的单个二极管,如图,蓝色的二极管比较特殊,它在特殊情况下会被击穿从而形成导线(比如加高电压到某个阈值)。利用这个特性,做成了可以被写入一次的PROM。因为蓝色的那个二极管击穿后就不会再恢复了,所以它只能被写入一次。
_EPROM: 把不可恢复的蓝色二极管替换成在某种条件下可以恢复的特殊材质二极管(比如光照几分钟),则得到了可擦除ROM。
_E2PROM: 用一种新的材料,把光照恢复的触发条件变成电可恢复的触发条件,从而制造出E2PROM。
_平时常提到的单片机烧录,说法来源就是早期的时候写入数据需要加入编程电压烧毁二极管。
_存储器材质除了二极管,还有一种使用熔丝的方式,也可用来控制通断。
_地址总线通常接在译码器(比如74HC138)之后,可以节省需要控制的管脚的数量。

  • AT24C02:
    • 掉电不丢失
    • E2PROM存储介质
    • I2C通讯接口
    • 256KB容量


开发板原理图上没有接上拉电阻是因为P21/P20的IO口已经接过上拉电阻了。

  • I2C总线:
    • 两个通信线:SCL(Serial Clock),SDA(Serial Data)
    • 同步,半双工(SDA数据线只有一个,负责数据来回),带数据应答

开漏输出:不同于上拉电阻模式,开漏输出的含义接通即给高电平,断开则引脚悬空
开漏输出和上拉模式结合,避免多机通信导致的干扰(单一时间主机其实之和一个从机通信,其他从机通过开漏输出模式断开和主机的连接)。

  • I2C的时序结构:

S:Start,启动;P:Stop,停止。

  • I2C发送一个字节时序:
  • I2C接收一个字节时序:

紫色线是从机控制的部分

  • I2C的发送应答和接收应答:

应答作为数据的第9位。SA:主机发出的发送应答;RA:从机发出的接收应答

  • 发送的数据帧结构:

地址的A2/A1/A0是可以在芯片硬件管脚上配置的;A6/A5/A4/A3固定不变(1010);最后一位R/W(低)决定读或者写从机。
发送的数据帧就像一节火车:开始+从机地址(+W写)+从机接收应答+主机发第一个字节的数据+接收应答+主机发第二个字节的数据+..+主机发第n个字节的数据+接收应答+停止位。

  • 接收的数据帧结构:

接收的数据帧就像一节火车:开始+从机地址(+R读)+从机接收应答+从机发第一个字节的数据+发送应答+从机发第二个字节的数据+..+从机发第n个字节的数据+发送应答+停止位。

  • 先发送再接收数据帧格式:
  • AT24C02数据读写帧结构(继承自I2C帧结构,所以看起来很相似):
    • 翻译AT24C02手册上的图,使之可以与上图AT24C02数据帧对应上:

起始条件: Start位
MSB/LSB: 高有效位/低有效位;
1010: 从机地址A6/A5/A4/A3的固定值
ACK:应答,0有效
R/W: 选择读写模式
停止条件: Stop位

  • I2C应用实例
#include <REGX52.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);}
		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;
}
#include <REGX52.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;
}
#include <REGX52.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,"        ");
		}
	}
}

DS18B20温度传感应用

  • 模拟传感器和数字传感器

单看热敏电阻,它就相当于模拟传感器。为了采集温度值,首先需要一个分压电路,采集电压测量点的电压变化,经过AD转换,最后在CPU中把数字量经过公式换算回温度值。
关于数字传感器:把上述电路集成在芯片内部,芯片内部自己采集并运算好温度值存入它内部的RAM中,CPU仅仅需要通过单总线的协议即可得到实际的温度值,并且单总线的协议方式还可以级联多个为数字传感器,实现一次单个IO口采集多个温度值。本例子的DS18B20就是这么一类数字传感器。

  • 原理图如下:

VCC: 3 ~ 5.5V
I/0: 单总线接口(需要上拉电阻);P37本身已经接了上拉电阻所以这里没有画出上拉电阻。
GND
DS18B20除了数据温度以外,本身还具备温度报警的功能。
ROM中存了DS18B20总线地址,地址ID号全球唯一。

  • DS18B20存储器结构:

  • 单总线协议(1-wire BUS)

    • 寄生供电,相当于从机在主机侧取电;强上拉:直接把VCC接到DQ上。
    • 初始化相当于I2C中的Start;注意此处使用的绝对时间(一线制没办法靠相对时间);主机先拉低BUS(写IO),释放后BUS会被电阻弱上拉高,等到一段时间后,检查该信号是否被从机拉低(读IO),拉低则从机有响应。
    • 注意发送时拉低不能超过120us,因为超过480us后就被认作初始化时序了。
    • 从机并不是太在意时序,主机释放总线后,IO读出来的数就是从机发回来的数。

  • DS18B20操作指令


  • 代码

#include <REGX52.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  单总线接收一个字节
  * @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;
}
#include <REGX52.H>
#include "OneWire.h"

//DS18B20指令
#define DS18B20_SKIP_ROM			0xCC
#define DS18B20_CONVERT_T			0x44
#define DS18B20_READ_SCRATCHPAD 	0xBE

/**
  * @brief  DS18B20开始温度变换
  * @param  无
  * @retval 无
  */
void DS18B20_ConvertT(void)
{
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_CONVERT_T);
}

/**
  * @brief  DS18B20读取温度
  * @param  无
  * @retval 温度数值
  */
float DS18B20_ReadT(void)
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=(TMSB<<8)|TLSB;
	T=Temp/16.0;
	return T;
}
posted @ 2025-03-22 00:20  你要去码头整点薯条吗  阅读(188)  评论(0)    收藏  举报