/*写程序要点首先定义引脚#define DQ P33//18b20的引脚
5.1 底层驱动
复位(输出0保持480us,输出1保持60us,读取当前电平,延时420us)写0(输出0保持60us+,输出1保持1us+).
写1(输出0保持1us+,输出1保持60us+)读0/1(输出0保持1us+,,输出1保持Tus+,读取当前电平,延时60us)
5.2 接口函数
写1字节读1字节
5.3 用户功能函数
温度读取换算函数
(复位-CCH-44H-等待-复位-CCH-BEH-读取2字节温度数据-换算)
观察所需要的延时 482us420us(用480us顶替) 60us1us@22.1184MHz
*/
#include "18b20.h"
u16 Temp=0; //温度数值
bit MinusFlag; //温度正负0:正数1:负数
//需要的延时 482us420us(用480us顶替) 60us1us@22.1184MHz
void Delay480us() //@22.1184MHz
{
unsigned long i;
i = 2653UL;
while (i) i--;
}
void Delay60us() //@22.1184MHz
{
unsigned long i;
_nop_();
_nop_();
i = 330UL;
while (i) i--;
}
void Delay1us() //@22.1184MHz
{
unsigned long i;
_nop_();
i = 4UL;
while (i) i--;
}
//复位(输出0保持480us,输出1保持60us,读取当前电平,延时420us)
//如果读取到当前电平为高,说明DB18B20未准备好,就继续等待,一直到低电平出现
void DS18b20_Reset(void) //复位
{
bit flag = 1;
while( flag ) //flag先置1进入循环
{
DQ = 0; //输出低电平
Delay480us();
DQ = 1; //输出高电平
Delay60us();
flag = DQ; //读取当前电平
Delay480us(); //等480us后DQ为0,表示DB18B20已经准备好
}
}
和时序图是严格一致的
接着来:
//写逻辑0(输出0保持60us+,输出1保持1us+)
void DS18b20_Write_0(void) //写逻辑0码
{
DQ = 0; //输出低电平
Delay60us();
DQ = 1; //输出高电平
Delay1us();
Delay1us();
}
//写逻辑1(输出0保持1us+,输出1保持60us+)读0/1(输出0保持1us+,,输出1保持Tus+,读取当前电平,延时60us)
void DS18b20_Write_1(void) //写逻辑1码
{
DQ = 0; //输出低电平
Delay1us();
Delay1us();
DQ = 1; //输出高电平
Delay60us();
}
底层驱动就这3个函数,接下来是5.2 接口函数
底层还有读DQ的电平:
//读DQ的状态(0/1)并返回(输出0保持1us+,,输出1保持Tus+,读取当前电平,延时60us)
bit DS18b20_Read(void) //读取DQ电平
{
bit state ;
DQ = 0; //输出低电平
Delay1us();
Delay1us();
DQ = 1; //输出低电平
Delay1us();
Delay1us();
state = DQ; //读取当前电平
Delay60us();
return state;
}
5.2 接口函数写1字节
//写1字节:例如发送0XCC= 1100 1100b & 0x01 C就是12,就是二进制1100
//从低位开始发送,0->,0->,1->,1->,0->,0->,1->,1->
void DS18b20_WriteByte(u8 dat) //写一个字节
{
u8 i;
for( i=0;i<8;i++ ) //循环八次
{
if( dat & 0x01 )//最低位与0x01(取最低位,最低位是1的话与0x01就=1,最低位是0的话与0x01就=0)
DS18b20_Write_1();
else
DS18b20_Write_0();
dat >>=1; //次低位,再次低位......直到8位完成
}
}
因为的单总线,上面的void DS18b20_WriteByte(u8 dat) //写一个字节
每次是输出0码,或者输出1码,8个码构成一个字节
//读1字节:例如我们读的是1000 0000 ,右移一位第二次读的放在高位,上次的高位变第二位
// 1000 0000
// x100 0000 再右移一位第三次读的放在高位,上次的高位变第二位
// xx10 0000 继续搞8次
u8 DS18b20_ReadByte(void) //读取一个字节
{
u8 i;
u8 dat=0;//这里一定先赋值0
for( i=0;i<8;i++ ) //循环八次
{
dat >>=1; //开始是0,右移还是0
if( DS18b20_Read() ) //如果读取到的是1,和0x80相或(自然最高位是1)
dat |= 0x80; //否则反正是0,直接搞下一位
}
return dat; //把读到的一个字节数据返回去
}
5.3 用户功能函数温度读取换算函数
(复位-CCH-44H-等待-复位-CCH-BEH-读取2字节温度数据-换算)//(复位-CCH-44H-等待-复位-CCH-BEH-读取2字节温度数据-换算)
u16 DS18b20_ReadTemp(void) //读取并且换算温度,并复制给Temp返回主函数用
{
u8 TempH;
u8 TempL;
u16 temperture;
DS18b20_Reset(); //1.复位
DS18b20_WriteByte(0XCC);//2.跳过ROM指令
DS18b20_WriteByte(0X44);//3.开始转化
while(!DQ); //4.等待DQ变成高电平,高电平才能离开执行下一步
//开始转化引脚DQ一直是低电平,转换结束后才变高电平,才往下执行
DS18b20_Reset(); //5.复位
DS18b20_WriteByte(0XCC);//6.跳过ROM指令
DS18b20_WriteByte(0XBE);//7.读取2字节的温度数值
TempL = DS18b20_ReadByte();//先读出的是低字节,因为低位在前,高位在后
TempH = DS18b20_ReadByte();
if( TempH & 0XF8 ) //有1出现就是负数,负数前5位是1111 1000,就是0xF8
{ //&是位与运算符,而&&是逻辑与运算符,&运算符会对两
//个操作数进行按位与运算,而&&运算符只有在两个操作
//数都为非零值时才返回真(1),否则返回假(0)。
/* if((Temp_Value&0XF8)==0XF8)这条语句用于判断DS18B20传感器读取的温度值是否为零下。具体来说,
Temp_Value数组的第2个元素中,如果高5位全为1(即二进制的11111000,对应的十六进制是0xF8),则表示
读取的温度值为零下。这是因为DS18B20的设计使得其温度数据的符号位由最高几位决定,当这些位均为1时,表
示温度为负。TempH & 0XF8 是简略写法,实际是(TempH&0XF8)==0XF8
*/
MinusFlag = 1; //标志位负数
temperture = ((TempH<<8) | TempL); //将温度换算成16位
temperture = (~temperture) +1; //按位取反+1
temperture = temperture*10*0.0625; //最终温度保留一位小数,本来是小数,u16是整数我们处理整数
} //方便,所以放大10倍,保留1位小数
else
{
MinusFlag = 0; //标志位正数
temperture = ((TempH<<8) | TempL); //将温度换算成16位,是12位的精度,最终值=乘以0.0625
temperture = temperture*10*0.0625; //最终温度保留一位小数,本来是小数,u16是整数我们处理整数
} //方便,所以放大10倍,保留1位小数
Temp = temperture;
return temperture; //保留一位小数的温度
}
看看主函数:
void main() //程序开始运行的入口
{
int dat = 0; //初始数值
sys_init(); //USB功能+IO口初始化
usb_init(); //usb库初始化
EUSB = 1;
Timer0_Init(); //定时器0初始化
EA = 1; //CPU开放中断,打开总中断。
while( DeviceState != DEVSTATE_CONFIGURED );
while(1) //死循环
{
delay_ms(2);
if( bUsbOutReady )
{
// USB_SendData(UsbOutBuffer,OutNumber);
usb_OUT_done();
}
if( TIM_10MS_Flag==1 ) //如果10ms到了
{
TIM_10MS_Flag = 0; //清空标志位
dat++;
if( dat>=30 ) //时间= 30*10ms
{
dat = 0;
DS18b20_ReadTemp(); //300ms到达,读取一次温度
if( MinusFlag==1 )
{
printf( "当前温度:-%.01f\r\n",(float)((float)Temp/10) );
}
else
printf( "当前温度:%.01f\r\n",(float)((float)Temp/10) );
}
}
}
}
最终我们下载烧录,看串口打印的结果是正常的
顺利完成本实验
今天我们继续学习冲哥的3STC32位单片机 第二十七集:软件模拟SPI
SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI是一种高口速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。
产生时钟的一侧称为主机,另一侧称为从机。同一时间只有一个主机,但是可以有多个从机。
SPI总线包括4条逻辑线,定义如下:MISO:Master input slave output 主机输入,从机输出(数据来自从机);MOSI:Masteroutput slave input主机输出,从机输入(数据来自主机);SCLK:Serial Clock串行时钟信号,由主机产生发送给从机;SS Slave Select 片选信号,由主机发送以控制与哪个从机通信,通常是低电平有效信号
21.3SPI通信方式
SPI的通信方式通常有3种:单主单从(一个主机设备连接一个从机设备)、互为主从(两个设备连接,设备和互为主机和从机)、单主多从(一个主机设备连接多个从机设备)
多数是单主多从,由SS的电平决定每一时刻和谁通信SPI数据模式:时钟极性(CKP/CPOL)
根据硬件制造商的命名规则不同,时钟极性通常写为CKP或CPOLCKOL可以配置为1或0。这意味着可以根据需要将时钟的默认状态(IDLE)设置为高或低。当然具体的配置必须参考设备的数据手册正确设置CKP=0:时钟空闲IDLE为低电平 0;CKP=.1:时钟空闲IDLE为高电平·1;时钟相位(CKE/CPHA)
顾名思义,时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿;CPHA = 0:在时钟信号SCK的第一个跳变沿采样;CPHA= 1:在时钟信号SCK的第二个跳变沿采样;时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据;