本来这次想做LCD背光灯的调节的,但是没有调通,时间很紧迫,就转向了其它东西,昨天调了一下DHT11,今天又调了一下DS18B20,还算有个安慰,本来是想用1-wire子系统做的,但是时间上有点紧,要看源代码好长时间去领悟,所以,先自己用以前的笨方法先实现.
先来介绍一下DS18B20:
DS18B20 是由 DALLAS 半导体公司推出的一种的 “一线总线”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。一线总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络,从而为测量系统的构建引入全新概念,测量温度范围为-55~+125℃ ,精度为±0. 5℃。
现场温度直接以“一线总线”的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现 9~l2 位的数字值读数方式。它工作在3—5. 5 V 的电压范围,采用多种封装形式,从而使系统设计灵活、方便,设定分辨率及用户设定的报警温度存储在EEPROM 中,掉电后依然保存。其内部结构如图所示:
ROM 中的 64 位序列号是出厂前被光记好的,它可以看作是该 DS18B20 的地址序列码,每个DS18B20 的 64 位序列号均不相同。 64 位 ROM 的排列是:前 8 位是产品家族码,接着 48 位是DS18B20 的序列号,最后 8 位是前面 56 位的循环冗余校验码(CRC=X8+X5+X4 +1)。 ROM 作用是使每一个 DS18B20 都各不相同,这样就可实现一根总线上挂接多个。
所有的单总线器件要求采用严格的信号时序,以保证数据的完整性。 DS18B20 共有 6 种信号类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。这里我们简单介绍这几个信号的时序:
1).复位脉冲和应答脉冲
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少 480us,以产生复位脉冲。接着主机释放总线,4.7K 的上拉电阻将单总线拉高,延时 15~60 us,并进入接收模式(Rx)。接着 DS18B20 拉低总线 60~240 us,以产生低电平应答脉冲,若为低电平,再延时 480 us。
2 ) 写时序
写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在 2 次独立的写时序之间至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线,延时 2us。
3 )读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的 15us 之内采样总线状态。典型的读时序过程为:主机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然后延时 50us。
在了解了单总线时序之后,我们来看看 DS18B20 的典型温度读取过程, DS18B20 的典型温度读取过程为:
复位-->发 SKIP ROM 命令(0XCC)-->发开始转换命令(0X44)-->延时-->复位-->发送 SKIP ROM 命令(0XCC)->发读存储器命令(0XBE)-->连续读出两个字节数据(即温度)-->结束。DS18B20 的介绍就到这里,更详细的介绍,请大家参考 DS18B20的数据手册
下面,来讲一下驱动的设计思路:
1).DS18B20的初始化
初始化时序图;
设计分析:
主机首先发出一个480-960微秒的低电平脉冲,然后释放总线变为高电平,并在随后的480微秒时间内对总线进行检测,如果有低电平出现说明总线上有器件已做出应答。若无低电平出现一直都是高电平说明总线上无器件应答。
作为从器件的DS18B20在一上电后就一直在检测总线上是否有480-960微秒的低电平出现,如果有,在总线转为高电平后等待15-60微秒后将总线电平拉低60-240微秒做出响应存在脉冲,告诉主机本器件已做好准备.若没有检测到就一直在检测等待。
初始化代码;
/* 1.复位信号 */
void DS18B20_Rst(void)
{
gpio_request(DQ_IN_GPIO, "DQ_OUT");
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(750); //拉低750us
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(15); //拉高15us
gpio_free(gpio_get_value(DQ_IN_GPIO));
}
DS18B20 写操作
主机发出各种操作命令都是向DS18B20写0和写1组成的命令字节, 接收数据时也是从DS18B20读取0或1的过程。因此首先要搞清主机是如何进行写0、写1、读0和读1的。
写周期最少为60微秒,最长不超过120微秒。写周期一开始做为主机先把总线拉低1微秒表示写周期开始。随后若主机想写0,则将总线置为低电平,若主机想写1,则将总线置为高电平,持续时间最少 60微秒直至写周期结束,然后释放总线为高电平至少1微秒给总线恢复。而DS18B20则在检测到总线被拉底后等待15微秒然后从15us到45us开始对总线采样,在采样期内总线为高电平则为1,若采样期内总线为低电平则为0 。
写数据代码:
/* 3.写时序,一个字节 */
void DS18B20_Write_Byte(int dat)
{
int j,testb;
gpio_request(DQ_IN_GPIO, "DQ_OUT");
for(j = 0; j < 8; j++){
testb = dat & 0x1;
dat >>= 1;
if(testb){ // 写1时序
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(2);
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(60);
} else { // 写0时序
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(60);
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(2);
}
}
gpio_free(DQ_IN_GPIO);
}
DS18B20 读操作
对于读数据操作时序也分为读0时序和读1时序两个过程。读周期是从主机把单总线拉低1微秒之后就得释放单总线为高电平,以让DS18B20把数据传输到单总线上。作为从机DS18B20在检测到总线被拉低1微秒后,便开始送出数据,若是要送出0就把总线拉为低电平直到读周期结束。若要送出1则释放总线为高电平。主机在一开始拉低总线1微秒后释放总线,然后在包括前面的拉低总线电平1微秒在内的15微秒时间内完成对总线进行采样检测,采样期内总线为低电平则确认为0 。采样期内总线为高电平则确认为1。完成一个读时序过程,至少需要60微秒才能完成。
/* 4.读一bit数据 */
int DS18B20_Read_Bit(void)
{
int data;
gpio_request(DQ_IN_GPIO, "DQ_OUT");
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(2);
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(12);
// gpio_free(DQ_IN_GPIO);
// gpio_request(DQ_IN_GPIO, "DQ_IN");
gpio_direction_input(DQ_IN_GPIO);
if(gpio_get_value(DQ_IN_GPIO))
data = 1;
else
data = 0;
udelay(50);
gpio_free(DQ_IN_GPIO);
return data;
}
/* 5.读一个字节的数据 */
int DS18B20_Read_Byte(void)
{
int i, j, dat = 0;
for(i = 0; i < 8; i++)
{
j = DS18B20_Read_Bit();
//低位先行,j左移7位,把最低位赋给dat,同时dat左移1位,把低位右移,循环8次你懂的
dat = (j << 7) | (dat >> 1);
}
return dat;
}
DS18B20 的寄存器简介
1. DS18B20 内部结构主要由四部分组成:
1), 64 位光刻 ROM光刻ROM中的64位序列号是出厂前被光刻好的,它可以看作是该DS18B20的地址序列码。64 位光刻ROM的排列是:开始8位(地址:28H)是产品类型标号,接着的48位是该DS18B20自身的序列号,并且每个DS18B20的序列号都不相同,因此它可以看作是该DS18B20的地址序列码;最后8位则是前面56位的循环冗余校验码(CRC=X8+X5+X4+1)。由于每一个DS18B20的ROM数据都各不相同,因此微控制器就可以通过单总线对多个DS18B20进行寻址,从而实现一根总线上挂接多个DS18B20的目的。
2), 温度传感器
3), 非挥发的温度报警触发器TH和TL
4), 配置寄存器。
2. DS18B20的存储器由一个高速暂存RAM和一个非易失性、 电可擦除(E2)RAM组成.
我们看一下第4个字节,配置寄存器:
出厂设置默认R0和R1都配置为1,也就是温度计分辨率是12bit.
1.8 读取温度操作
1. 读取温度操作
DS18B20 经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第0和第1个字节。所以当我们只想简单的读取温度值的时候,只用读取暂存器中的第0和第1个字节就可以了。读取一次D18B20温度的操作步骤 如下:
a) 初始化DS18B20
b) 跳过ROM操作(ROM里面可以读取DS18B20的地址、型号,还有 配置分辨率等,我们只使用一个DS18B2,所以不用读取地址型号,配置直接使用默认的12位分辨率就好了.
c) 发送温度转换命令。
d) 跳过ROM操作
e) 发送读取温度命令
f) 读取温度值。
2 补码介绍
什么是补码
a) 正数的补码是正数本身
b) 负数的补码是原码取反,然后再加 1。DS18B20存储的温度值是以补码的形式存储的,所以读出来的温度值是实际温度值的补码,要把的转换为原码。
1)正温度的话,原码就是补码本身,所以在12位分辨率下,温度的计算公式是:
温度值=读取值*0.0625
2)负温度的话,原码是补码减1再取反,所以在12位分辨率下,计算公式为:
温度值= -(读取值取反再加1)*0.0625
/* 7.处理温度信息 */
int DS18B20_Get_temp(void)
{
int tem = 0,
TL = 0,
TH = 0;
DS18B20_Start();
DS18B20_Rst();
DS18B20_CheckAck();
DS18B20_Write_Byte(0xcc);
DS18B20_Write_Byte(0xbe);
TL = DS18B20_Read_Byte();
TH = DS18B20_Read_Byte();
if(TH > 7){
TH = ~TH;
TL = ~TL;
temp = 0;
}else{
temp = 1;
}
tem = TH;
tem <<= 8;
tem += TL;
/**
* 内核不支持浮点运算,如果在内核里做计算会丢失小数部分
* 所以计算留给用户程序来完成,这里直接传输原始数据即可
*/
if(temp){
return tem;
}else
return ~tem + 1;
}
下面来一个完整的驱动代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DQ_IN_GPIO EXYNOS4_GPX3(2) // 操作DS18B20的GPIO
/*********************************************DS18B20************************************************/
/* 1.复位信号 */
void DS18B20_Rst(void)
{
gpio_request(DQ_IN_GPIO, "DQ_OUT");
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(750); //拉低750us
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(15); //拉高15us
gpio_free(gpio_get_value(DQ_IN_GPIO));
}
/* 2.应答信号 */
int DS18B20_CheckAck(void)
{
int retry = 0;
gpio_request(DQ_IN_GPIO, "DQ_IN");
gpio_direction_input(DQ_IN_GPIO);
while(gpio_get_value(DQ_IN_GPIO) & (retry < 200))
{
retry++;
udelay(1);
};
if(retry >= 200){
gpio_free(gpio_get_value(DQ_IN_GPIO));
return -1;
}
retry = 0;
while(!gpio_get_value(DQ_IN_GPIO) & (retry < 240))
{
retry++;
udelay(1);
}
if(retry >= 240){
gpio_free(gpio_get_value(DQ_IN_GPIO));
return -1;
}
gpio_free(gpio_get_value(DQ_IN_GPIO));
return 0;
}
/* 3.写时序,一个字节 */
void DS18B20_Write_Byte(int dat)
{
int j,testb;
gpio_request(DQ_IN_GPIO, "DQ_OUT");
for(j = 0; j < 8; j++){
testb = dat & 0x1;
dat >>= 1;
if(testb){ // 写1时序
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(2);
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(60);
} else { // 写0时序
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(60);
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(2);
}
}
gpio_free(DQ_IN_GPIO);
}
/* 4.读一bit数据 */
int DS18B20_Read_Bit(void)
{
int data;
gpio_request(DQ_IN_GPIO, "DQ_OUT");
gpio_direction_output(DQ_IN_GPIO, 0);
udelay(2);
gpio_direction_output(DQ_IN_GPIO, 1);
udelay(12);
// gpio_free(DQ_IN_GPIO);
// gpio_request(DQ_IN_GPIO, "DQ_IN");
gpio_direction_input(DQ_IN_GPIO);
if(gpio_get_value(DQ_IN_GPIO))
data = 1;
else
data = 0;
udelay(50);
gpio_free(DQ_IN_GPIO);
return data;
}
/* 5.读一个字节的数据 */
int DS18B20_Read_Byte(void)
{
int i, j, dat = 0;
for(i = 0; i < 8; i++)
{
j = DS18B20_Read_Bit();
//低位先行,j左移7位,把最低位赋给dat,同时dat左移1位,把低位右移,循环8次你懂的
dat = (j << 7) | (dat >> 1);
}
return dat;
}
/* 6.温度转换 */
void DS18B20_Start(void)
{
DS18B20_Rst();
DS18B20_CheckAck();
DS18B20_Write_Byte(0xcc);
DS18B20_Write_Byte(0x44);
}
/* 7.处理温度信息 */
int DS18B20_Get_temp(void)
{
int tem = 0,
TL = 0,
TH = 0;
DS18B20_Start();
DS18B20_Rst();
DS18B20_CheckAck();
DS18B20_Write_Byte(0xcc);
DS18B20_Write_Byte(0xbe);
TL = DS18B20_Read_Byte();
TH = DS18B20_Read_Byte();
if(TH > 7){
TH = ~TH;
TL = ~TL;
temp = 0;
}else{
temp = 1;
}
tem = TH;
tem <<= 8;
tem += TL;
/**
* 内核不支持浮点运算,如果在内核里做计算会丢失小数部分
* 所以计算留给用户程序来完成,这里直接传输原始数据即可
*/
if(temp){
return tem;
}else
return ~tem + 1;
}
/*****************************************************************************************************/
int
ds18b20_open(struct inode *inode, struct file *filp)
{
// 硬件初始化
/*do nothing*/
return 0;
}
ssize_t
ds18b20_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret = -1,
temp_buf;
temp_buf = DS18B20_Get_temp();
ret = copy_to_user(buf, &temp_buf, count);
if(ret > 0){
printk("copy to user failed !\n");
return -EFAULT;
}
return count;
}
int
ds18b20_release(struct inode *inode, struct file *filp)
{
// 释放引脚
gpio_free(DQ_IN_GPIO);
return 0;
}
const struct file_operations ds18b20_fops = {
.open = ds18b20_open,
.read = ds18b20_read,
.release= ds18b20_release,
};
struct miscdevice ds18_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ds18b20",
.fops = &ds18b20_fops,
};
static void __exit
ds18b20_drv_exit(void)
{
misc_deregister(&ds18_misc);
}
static int __init
ds18b20_drv_init(void)
{
// 注册杂项设备,因为比直接的普通字符设备少写好多代码
int ret = misc_register(&ds18_misc);
if(ret < 0){
printk("misc register failed !\n");
return -EINVAL;
}
return 0;
}
module_init(ds18b20_drv_init);
module_exit(ds18b20_drv_exit);
MODULE_LICENSE("GPL");
然后是测试代码;
#include
#include
#include
#include
#include
#include
int main(void)
{
int fd = -1,
temp= 0,
a = 0;
fd = open("/dev/ds18b20", O_RDWR);
if(fd < 0){
perror("open failed");
exit(1);
}
for(;;)
{
read(fd, &temp, sizeof(temp));
a = (float)temp * 0.625;
if(a > -551 && a < 1251)
printf(" temp: %d.%d C\n", a / 10, a % 10);
sleep(2);
}
if(close(fd) < 0){
perror("close failed");
exit(3);
}
return 0;
}
然后是Makefile:
#指定内核源码路径
KERNEL_DIR = /home/george/1702/exynos/linux-3.5
#指定当前路径
CUR_DIR = $(shell pwd)
MYAPP = w1_ds18b20_app
MODULE = w1_ds18b20
all:
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c
clean:
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
$(RM) $(MYAPP)
install:
cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702
#指定编译当前目录下那个源文件
obj-m = $(MODULE).o
附一张测试效果图:
第1次显示的85℃是正常的,是初始化信息,其它两次58.3℃和90.8℃是不正常的,但是大多数数据都是OK的,大概的原因可能是我这个传感器是外接的,友善之臂真扣,连个DS18B20都不带,外接的接触不良或者有干扰可能会造成这种情况.好了先写到这里吧.