DS18B20是一种单总线数字温度传感器,测试温度范围-55℃-125℃,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。单总线,意味着没有时钟线,只有一根通信线。单总线读写数据是靠控制起始时间和采样时间来完成,所以时序要求很严格,这也是DS18B20驱动编程的难点。
DS18B20温度传感器靠VCC和GND两个引脚完成供电,另一个引脚P3^7则负责单总线数据传输通信。
主要由2部分组成:64位ROM、9字节暂存器,如图所示。
(1) 64 位ROM。它的内容是64 位序列号,它可以被看作是该DS18B20 的地址序列码,其作用是使每个DS18B20 都各不相同,这样就可以实现一根总线上挂接多个DS18B20 的目的。
(2) 9字节暂存器包含:温度传感器、上限触发TH高温报警器、下限触发TL低温报警器、高速暂存器、8位CRC产生器。
DS18B20的通信分为初始化,发送一个字节,接受一个字节三个阶段:
初始化阶段先将主机总线拉低一定时间等待存在的从机响应。
发送一个字节阶段先将主机总线拉低然后再将一个字节放到总线上,如果该字节是1主机就会拉高从机就会读出1,如果该字节是0主机将一直拉低从机将读出0。
接受一个字节阶段先将主机总线拉低然后读取总线上的高低电平,高电平则是1,低电平则是0。
按照数据帧原理,初始化时要跳过ROM,然后发送接受数据原理类似移位寄存器,将数据一位一位放到总线上便可以实现温度的发送与读取。
# include
sbit OneWire_IO = P3^7;//定义通信引脚DQ
unsigned char OneWire_Init()
{
unsigned char i,Ack;
EA=0;
OneWire_IO = 1;
OneWire_IO = 0;
i = 247;while (--i);//delay 500us
OneWire_IO = 1;
i = 32;while (--i);//delay 70us
Ack=OneWire_IO ;
i = 247;while (--i);//delay 500us
EA=1;
return Ack;
}
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
EA=0;
OneWire_IO = 0;
i = 4;while (--i);//delay 10us
OneWire_IO=Bit;
i = 24;while (--i);//delay 50us
OneWire_IO = 1;
EA=1;
}
unsigned char OneWire_ReceiveBit()
{
unsigned char i,Bit;
EA=0;
OneWire_IO = 0;
i = 2;while (--i);//delay 5us
OneWire_IO = 1;
i = 2;while (--i);//delay 5us
Bit=OneWire_IO;
i = 24;while (--i);//delay 50us
EA=1;
return Bit;
}
void OneWire_SendData(unsigned char Data)
{
unsigned char i;
for (i=0;i<8;i++)
{
OneWire_SendBit(Data&(0x01<<i));
}
}
unsigned char OneWire_ReceiveData()
{
unsigned char i,Data=0x00;
for (i=0;i<8;i++)
{
if (OneWire_ReceiveBit())
{
Data|=(0x01<<i);
}
}
return Data;
}
这个是单总线通信模块,一共封装为初始化,发送一个字节,接受一个字节,发送数据,接收数据五个模块。
# include
# include "OneWire.h"
void DS18B20_ConvertT()
{
OneWire_Init();
OneWire_SendData(0xCC);
OneWire_SendData(0x44);
}
float DS18B20_ReadT()
{
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendData(0xCC);
OneWire_SendData(0xBE);
TLSB=OneWire_ReceiveData();
TMSB=OneWire_ReceiveData();
Temp=(TMSB<<8)|TLSB;
T = Temp/16.0;
return T;
}
这是DS18B02温度传感器模块,要严格遵照数据帧的流程,需要将读取的传感器数据转换位可读的温度输出浮点数。
LCD1602 (Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符
显示容量: 16x2个字符, 每个字符为5*7点阵
LCD1602模块一共有16个引脚,由VDD和VSS来接正负极供电,VO引脚通过调节电压高低控制屏幕亮度,RS引脚为数据指令选择引脚,RW引脚为读写选择引脚,E为使能引脚,D0~D7引脚为数据输入输出引脚,A和K引脚给背光灯供电。
往这些地址中写入字符码,然后DDRAM会从CGROM对照表中找到字符显示在屏幕上。
通过十六进制和二进制转换,上边一行是高四位,左边一列是低四位位,来找到所要显示的字符 。
#include
//引脚配置:
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液晶屏显示模块。
直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极,当电极正接时,电机正转,当电极反接时,电机反转。直流电机主要由永磁体(定子)、线圈(转子)和换向器组成。
直流电机要通过杜邦线接到51单片机上,一个接步进电机模块VCC5V输出引脚,另一个接到步进电机模块的四个引脚之一,通过控制该引脚的高低电平来驱动电机。
上图中的大功率驱动通过利用二极管的单向导通性来控制直流电机的电流方向,所以直流电机只能向一个方向转;H桥驱动通过同时闭合Q1和Q4开关,或同时闭合Q2和Q3开关来控制电机两个转向。
PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域。
直流电机调速和呼吸灯亮度变化本质上都是通过PWM来控制高低电平占空比来实现线性变化。在一个脉冲计数时间内,高低电平占空比越大,意味着电机转速越快,呼吸灯亮度越高;反之则电机转速越慢,呼吸灯亮度越低。
通过上述原理我们在主函数里面调用定时器,通过中断函数来控制计时时间内调整脉冲宽度来实现高低电平的占空比。
#include
sbit LED=P2^0;
void Delay(unsigned int t)
{
while(t--);
}
void main()
{
unsigned char Time,i;
while(1)
{
for(Time=0;Time<100;Time++) //改变亮灭时间,由暗到亮
{
for(i=0;i<20;i++) //计次延时
{
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
}
for(Time=100;Time>0;Time--) //改变亮灭时间,由亮到暗
{
for(i=0;i<20;i++) //计次延时
{
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
}
}
}
这是LED呼吸灯程序,直接简单利用delay延迟也是可以实现的。
# include
# include "delay.h"
# include "Key.h"
# include "Timer0.h"
sbit Motor=P1^0;
unsigned char KeyNum,count,Num,T0Count,T0Count1;
void main()
{
Timer0Init();
while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
count++;
count%=4;
if(count==0)
{
Num=0;
}
else if(count==1)
{
Num=30;
}
else if(count==2)
{
Num=60;
}
else if(count==3)
{
Num=90;
}
}
}
}
void Timer0_Routine() interrupt 1
{
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
T0Count1++;
T0Count1%=100;
if(T0Count>=20)
{
Key_Loop();
T0Count=0;
}
if(T0Count1<Num)
{
Motor=1;
}
else
{
Motor=0;
}
}
这是直流电机调速程序,Timer_0模块和之前的一样,但这里定时器0同时为按键扫描和电机调速服务。
AD和DA为模拟信号和数字信号间的转换提供了一个桥梁,为传感器和计算机之间的通信也提供了另一个思路。
数字信号和模拟信号之前存在一定的数学比例关系,通过寄存器写入模拟量经过AD转换就可以得到数字量,寄存器读出数字量经过DA转换就可以输出模拟量来
实现单片机对外设的控制。
运算放大器主要通过外部元器件连接来组合成不同功能的电路,比如电压放大电路,从输出端接电阻到反向输入端来提供负反馈,进而通过计算从输出端得到想要的放大电压。
电压比较器可以理解为一个逻辑判断开关,通过判断IN+和IN-两个引脚的电压高低来决定开关是向GND闭合还是向VCC闭合,这样就可以决定输出电压的大小。
反向放大器则通过R2电阻为输入端提供负反馈,进而控制输出端的电压与输入端的电压之比为R1和R2阻值之比。
逐次逼近型AD转换器通过8各位来输入模拟量,这个模拟量要转化为一个未知的数字量,通过运放电路比较器可以将模拟量变为一个输出电压,该电压不断和DAC里的数字电压相比较,若DAC里的数字电压高就拉低,不断逼近该模拟量在运放电路所产生的电压,这就实现了模拟量和数字量转化。
PWM型DA转换器通过低通滤波器将交流分量过滤为直流方波,并通过计算直流方波的平均值来输出电压,这就实现了数字量和模拟量的转换。
AD模数转换时要遵照以上时序来写程序,与之前的DS1302时钟芯片模块和DS8B20模块类似。
# include
sbit XPT2046_CS=P3^5;
sbit XPT2046_DCLK=P3^6;
sbit XPT2046_DIN=P3^4;
sbit XPT2046_DOUT=P3^7;
unsigned int XPT2046_ReadAD(unsigned char Command)
{
unsigned char i;
unsigned int ADVAlue=0;
XPT2046_DCLK=0;
XPT2046_CS=0;
for(i=0;i<8;i++)
{
XPT2046_DIN=Command&(0x80>>i);
XPT2046_DCLK=1;
XPT2046_DCLK=0;
}
for(i=0;i<16;i++)
{
XPT2046_DCLK=1;
XPT2046_DCLK=0;
if(XPT2046_DOUT){ADVAlue|=(0x8000>>i);}
}
XPT2046_CS=0;
if(Command&0x04)
{
return ADVAlue>>8;
}
else
{
return ADVAlue>>4;
}
}
这是AD 转换模块的代码。
# include
# include "XPT2046.h"
# include "delay.h"
# include "LCD1602.h"
unsigned int ADValue;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"ADJ");
LCD_ShowString(1,6,"NTC");
LCD_ShowString(1,11,"RG");
while(1)
{
ADValue=XPT2046_ReadAD(XPT2046_XP_8);
LCD_ShowNum(2,1,ADValue,3);
ADValue=XPT2046_ReadAD(XPT2046_YP_8);
LCD_ShowNum(2,6,ADValue,3);
ADValue=XPT2046_ReadAD(XPT2046_VBAT_8);
LCD_ShowNum(2,11,ADValue,3);
delay(10);
}
}
这是主函数的代码。
红外遥控系统主要由红外发射装置和红外接收装置构成。
红外发射装置主要由键盘电路、红外编码芯片、电源和红外发射电路组成(遥控器)。
红外接收装置主要由红外接收电路、红外解码芯片、电源和应用电路组成,在单片机开发板上有红外接收电路,而且单片机充当解码芯片。
红外发射电路通过控制一定频率的发光二极管闪烁来产生红外信号向外发送给接收器,该频率下的闪烁频率与大自然中任意一种红外光频率都不一样。
红外信号的发射由红外发射电路中的红外发光二极管完成,通常情况下为了提高抗干扰能力与降低电源消耗,遥控器将遥控信号(二进制脉冲码)调制在载波(载波是传送信息的物理基础和承载工具)上经放大后发送至红外二极管,再由二极管转换为红外信号发送出去。
与之前定时器0的原理一样,定时器要先初始化,初始化时先设定定时器模式,再设定定时器的初值,接着设定中断优先级和清除标志位;而外部中断的初始化则直接设定定时器处置进行中断计时。
NEC协议是红外遥控协议的一种,由其编码的数据帧分别由引导码、用户码、用户码(或者是用户码的反码)、数据码(即按键码)和数据码的反码这五部分组成,最后还有一个停止位。引导码表示即将开始传输32位的二进制数据;引导码之后的部分长度为4字节一共32位;第一字节为用户码;第二字节可能是用户码,也可能是用户码的反码,具体由厂商决定;第三字节是当前按键的按键码;第四字节是按键码的反码;停止位主要起隔离作用,一般不进行判断,也不需要理会。每一字节的数据从低位到高位依次发送。
遵照NEC编码的时序来写红外遥控模块,红外遥控模块的底层是定时器1,所以要先架构定时器1模块。
#include
/**
* @brief 定时器1初始化,[email protected]
* @param 无
* @retval 无
*/
void Timer1_Init(void)
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x9C; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1=1;
EA=1;
PT1=0;
}
/*定时器中断函数模板
void Timer1_Routine() interrupt 3
{
static unsigned int T1Count;
TL1 = 0x9C; //设置定时初值
TH1 = 0xFF; //设置定时初值
T1Count++;
if(T1Count>=1000)
{
T1Count=0;
}
}
*/
这是定时器1模块。
# include
void Int0_Init()
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
//void Int0_Routine(void) interrupt 0
//{
//
//}
# include
# include "Timer0.h"
# include "Int0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4];
unsigned char IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
void IR_Init()
{
Timer0_Init();
Int0_Init();
}
unsigned char IR_GetDataFlag()
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetRepeatFlag()
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetAddress()
{
return IR_Address;
}
unsigned char IR_GetCommand()
{
return IR_Command;
}
void Int0_Routine(void) interrupt 0
{
if(IR_State==0)
{
Timer0_SetCounter(0);
Timer0_Run(1);
IR_State=1;
}
else if(IR_State==1)
{
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(13000<IR_Time&&IR_Time<14000)
{
IR_State=2;
}
else if(10750<IR_Time&&IR_Time<11750)
{
IR_RepeatFlag=1;
Timer0_Run(0);
IR_State=0;
}
else
{
IR_State=1;
}
}
else if (IR_State==2)
{
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if (IR_Time>620&&IR_Time<1620)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));
IR_pData++;
}
else if(IR_Time>1750&&IR_Time<2750)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
IR_pData++;
}
else
{
IR_pData=0;
IR_State=1;
}
if(IR_pData>=32)
{
IR_pData=0;
if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3]))
{
IR_Address=IR_Data[0];
IR_Command=IR_Data[2];
IR_DataFlag=1;
}
Timer0_Run(0);
IR_State=0;
}
}
}
红外遥控模块通过几个标志位和几种模式来控制红外接受模块的工作,其中需要一个模块来触发计时,触发计时后要将通过时序的要求在特定时间段内发送8个位给红外接收模块,还要设定重复发送模块来控制 告诉接收模块重复接受。
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。