13-1 DS18B20温度传感器
2023 04 27
通信接口:1-Wire(单总线)
其它特征:可形成总线结构、内置温度报警功能、可寄生供电
引脚 | 功能 |
---|---|
VDD | 电源(3.0V ~ 5.5V) |
GND | 电源地 |
DQ | 单总线接口 |
64-BIT ROM:作为器件地址,用于总线通信的寻址
SCRATCHPAD(暂存器):用于总线的数据交互
EEPROM:用于保存温度触发阈值和配置参数
单总线 1-Wire BUS
一根通信线:DQ
异步、半双工
采用寄生供电时还可以免去一根VSS线路,只需要DQ+GND两根线
线路规范:
-
设备的DQ均要配置成开漏输出模式
-
DQ添加一个上拉电阻,阻值一般为4.7KΩ左右
-
若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路
单总线时序结构
-
初始化:主机将总线拉低至少480us,然后释放总线,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线
-
发送一位:主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us
-
接收一位:主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us
-
发送一个字节:连续调用8次发送一位的时序,依次发送一个字节的8位(低位在前)
-
接收一个字节:连续调用8次接收一位的时序,依次接收一个字节的8位(低位在前)
DS1802操作流程
-
初始化:从机复位,主机判断从机是否响应
-
ROM操作:ROM指令+本指令需要的读写操作
-
功能操作(RAM操作):功能指令+本指令需要的读写操作
ROM****指令 | 功能指令 |
---|---|
SEARCH ROM [F0h] 搜索ROM | CONVERT T [44h] 温度更新 |
READ ROM [33h] 读ROM | WRITE SCRATCHPAD [4Eh] 写暂存器 |
MATCH ROM [55h] 匹配ROM | READ SCRATCHPAD [BEh] 读暂存器 |
SKIP ROM [CCh] 跳过ROM(只有一个从机的情况下使用) | COPY SCRATCHPAD [48h] 配置持久化 |
ALARM SEARCH [ECh] 报警搜索 | RECALL E2 [B8h] 使用配置 |
READ POWER SUPPLY [B4h] 读是否为寄生供电 |
DS18B20数据帧:
-
温度变换:初始化→跳过ROM →开始温度变换
-
温度读取:初始化→跳过ROM →读暂存器→连续的读操作
温度存储格式:
其实就是补码啦
13-2 DS18B20温度读取
2023 04 27
温度转换需要时间
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
float T;
unsigned int i;
int main(){
LCD_Init();
LCD_ShowString(1, 1, "temperature:");
while(1){
Delay(1000);
DS18B20_ConvertT();
T = DS18B20_ReadT();
if (T < 0) {// 显示符号
LCD_ShowChar(2, 1, '-');
}
else{
LCD_ShowChar(2, 1, '+');
}
LCD_ShowNum(2, 2, T, 3);// 显示整数部分
LCD_ShowChar(2, 5, '.');
LCD_ShowNum(2, 6, ((unsigned long) (T * 10000)) % 10000, 4);// 显示小数部分
}
}
DS18B20.c
#include <REGX52.H>
#include "OneWire.h"
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
/**
* @brief DS18B20温度转换 温度转换需要时间
*/
void DS18B20_ConvertT(){
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
/**
* @brief 读取温度
* @return 读取的温度值,默认精度为2^-4
*/
float DS18B20_ReadT(){// 51单片机对浮点数的处理能力较弱,尽量不要用float
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;
}
DS18B20.h
#ifndef __DS19B20_H__
#define __DS19B20_H__
void DS18B20_ConvertT();
float DS18B20_ReadT();
#endif
OneWire.c
#include <REGX52.H>
sbit OneWire_DQ=P3^7;
/**
* @brief 1Wire总线初始化
* @return AckBit,0~1,0表示从机存在,1表示不存在
*/
unsigned char OneWire_Init(){
unsigned char i,AckBit;
OneWire_DQ = 1;
OneWire_DQ = 0;
i = 227;while (--i);// 拉低500us @11.0592MHz
OneWire_DQ = 1;
i = 29;while (--i);// 放权70us
AckBit = OneWire_DQ;// 读取响应值
i = 227;while (--i);// 等待500us @11.0592MHz
return AckBit;
}
/**
* @brief 1Wire总线发送一位
* @param Bit 发送位
*/
void OneWire_SendBit(unsigned char Bit){
unsigned char i;
OneWire_DQ = 0;
i = 4;while (--i);// Delay 10us
OneWire_DQ = Bit;
i = 29;while (--i);// Delay 70us
OneWire_DQ = 1;
}
/**
* @brief 1Wire总线读取一位
* @return 0~1
*/
unsigned char OneWire_ReceiveBit(){
unsigned char i,Bit;
OneWire_DQ = 0;
i = 2;while (--i);// Delay 5us
OneWire_DQ = 1;
i = 4;while (--i);// Delay 10us
Bit = OneWire_DQ;
i = 20;while (--i);// Delay 50us
return Bit;
}
/**
* @brief 1Wire总线发送一个字节
* @param Byte 要发送的字节
*/
void OneWire_SendByte(unsigned char Byte){
unsigned char i;
for(i=0;i<8;i++){
OneWire_SendBit(Byte & (0x01 << i));// 从低位到高位
}
}
/**
* @brief 1Wire总线读取一个字节
* @return 读取到的字节
*/
unsigned char OneWire_ReceiveByte(){
unsigned char i,Byte = 0;// Byte没有直接赋值,记得赋初值0
for(i=0;i<8;i++){
if(OneWire_ReceiveBit()) Byte |= (0x01 << i);
}
return Byte;
}
OneWire.h
#ifndef __ONE_WIRE_H__
#define __ONE_WIRE_H__
unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit();
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte();
#endif
13-3 DS18B20温度报警器
2023 04 28
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "Key.h"
#include "AT24C02.h"
#include "Timer0.h"
float T;
unsigned int i;
char TLow, THigh;
unsigned char KeyNum;
int main() {
LCD_Init();
Timer0Init();
LCD_ShowString(1, 1, "T:");
LCD_ShowString(2, 1, "TH:");
LCD_ShowString(2, 9, "TL:");
THigh = AT24C02_ReadByte(0);
TLow = AT24C02_ReadByte(1);
if (THigh > 125) THigh = 125;
else if (THigh < -55) THigh = -55;
if (TLow > 125) TLow = 125;
else if (TLow < -55) TLow = -55;
if (THigh < TLow) THigh = TLow;
DS18B20_ConvertT();
Delay(1000);
while (1) {
KeyNum = Key();
DS18B20_ConvertT();
T = DS18B20_ReadT();
if (T < 0) {// 显示符号
LCD_ShowChar(1, 3, '-');
} else {
LCD_ShowChar(1, 3, '+');
}
LCD_ShowNum(1, 4, (T < 0 ? -T : T), 3);// 显示整数部分
LCD_ShowChar(1, 7, '.');
LCD_ShowNum(1, 8, ((unsigned long) ((T < 0 ? -T : T) * 10000)) % 10000, 4);// 显示小数部分
// 阈值判断及显示
if (KeyNum) {
if (KeyNum == 1) {
if (THigh < 125) THigh++;
AT24C02_WriteByte(0,THigh);
} else if (KeyNum == 2) {
if (THigh > TLow) THigh--;
AT24C02_WriteByte(0,THigh);
} else if (KeyNum == 3) {
if (TLow < THigh) TLow++;
AT24C02_WriteByte(1,TLow);
} else if (KeyNum == 4) {
if (TLow > -55) TLow--;
AT24C02_WriteByte(1,TLow);
}
KeyNum = 0;
}
LCD_ShowSignedNum(2, 4, THigh, 3);
LCD_ShowSignedNum(2, 12, TLow, 3);
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;
static unsigned char K;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if (T0Count >= 20) {
K = Key();
if (K) KeyNum = K;
T0Count = 0;
}
}
Key.c
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @return 按下按键的键码,范围:0~4
*/
unsigned char Key() {
static unsigned char KeyNumber = 0;
static unsigned char K;
if (KeyNumber) {
if (KeyNumber == 1 && P3_1 == 1) {K = KeyNumber; KeyNumber = 0; return K;}
else if (KeyNumber == 2 && P3_0 == 1) {K = KeyNumber; KeyNumber = 0; return K;}
else if (KeyNumber == 3 && P3_2 == 1) {K = KeyNumber; KeyNumber = 0; return K;}
else if (KeyNumber == 4 && P3_3 == 1) {K = KeyNumber; KeyNumber = 0; return K;}
return 0;
} else {
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 0;
}
}
在OneWire中加入了EA=0;EA=1;因为在1Wire(单总线)通信时对时序有着严格的要求,中断会导致时序出错。
14-1 LCD1602
2023 04 28
引脚 | 功能 |
---|---|
VSS | 地 |
VDD | 电源正极(4.5~5.5V) |
VO | 对比度调节电压 |
RS | 数据/指令选择,1为数据,0为指令 |
RW | 读/写选择,1为读,0为写 |
E | 使能,1为数据有效,下降沿执行命令 |
D0~D7 | 数据输入/输出 |
A | 背光灯电源正极 |
K | 背光灯电源负极 |
时序结构
-
写数据/指令
-
初始化:
-
发送指令0x38 //八位数据接口,两行显示,5*7点阵
-
发送指令0x0C //显示开,光标关,闪烁关
-
发送指令0x06 //数据读写操作后,光标自动加一,画面不动
-
发送指令0x01 //清屏
-
-
显示字符:
- 发送指令0x80|AC //设置光标位置
- 发送数据 //发送要显示的字符数据
- 发送数据 //发送要显示的字符数据
- ……
14-2 LCD1602液晶显示屏
2023 04 28
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延时函数,11.0592MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay(){
unsigned char i, j;
// _nop_();
i = 2;
j = 199;
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){
Column--;
if(Line == 2) Column |= 0x40;
Column |= 0x80;
LCD_WriteCommand(Column);
}
/**
* @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){
LCD_SetCursor(Line, Column);
while(*String){
LCD_WriteData(*String);
String++;
}
}
/**
* @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 int K = 1;
LCD_SetCursor(Line, Column);
while(--Length) K *= 10;
while(K){
LCD_WriteData((Number / K) % 10 + '0');
K /= 10;
}
}
/**
* @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){
LCD_SetCursor(Line, Column);
if(Number < 0){
LCD_WriteData('-');
Number = -Number;
}else{
LCD_WriteData('+');
}
LCD_ShowNum(Line, Column+1, Number, Length);
}
/**
* @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 int i,k;
LCD_SetCursor(Line, Column);
for (i = Length; i >= 1; i--) {
k = (Number >> ((i-1) * 4)) & 0x000F;
if(k < 10){
LCD_WriteData(k + '0');
}else{
LCD_WriteData(k - 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 int i;
LCD_SetCursor(Line, Column);
for (i = Length; i >= 1; i--) {
LCD_WriteData(((Number >> (i-1)) & 0x0001) + '0');
}
}
LCD1602.h
#ifndef __LCD1602_H__
#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
15-1 直流电机驱动 PWM
PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域
PWM重要参数:
频率(越高越好 ) = 1 / TS 占空比 = TON / TS 精度 = 占空比变化步距
(瞎眼的PWM就是这么来的!捏妈!)
15-2 LED呼吸灯
主函数实现呼吸灯
#include <REGX52.H>
sbit LED = P2^0;
void Delay(unsigned int t){
while(t--);
}
int main(){
unsigned char Time,i,State = 1;
while(1){
Time += State;
if(Time >= 100) State = -1;
if(Time <= 1) State = 1;
for(i=0;i<10;i++){
LED=0;
Delay(Time);
LED=1;
Delay(100 - Time);
}
}
}
15-3 直流电机调速
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Timer0.h"
unsigned char KeyNum, Compare, Speed;
sbit Motor = P1 ^ 0;
int main() {
Timer0Init();
Nixie(1, 0);
while (1) {
KeyNum = Key();
if (KeyNum) {
if (KeyNum == 1) {
Speed++;
Speed %= 4;
}
Nixie(1, Speed);
switch (Speed) {
case 0: Compare = 0;break;
case 1: Compare = 20;break;
case 2: Compare = 50;break;
case 3: Compare = 100;break;
}
KeyNum = 0;
}
}
}
// 中断函数 100us@11.0592HMz
void timer0_Routine() interrupt 1
{
static unsigned int T0Count,KeyCount;
static unsigned char K;
TL0 = 0xA4; //设置定时初值
TH0 = 0xFF; //设置定时初值
T0Count++;
if(T0Count % 10 == 0) KeyCount ++;
if (KeyCount % 20 == 0) {
K = Key();
if (K) KeyNum = K;
KeyCount = 0;
}
T0Count %= 100;
Motor = !(T0Count >= Compare);
}