花了近50小时才完成了DS18B20测温模块的驱动,主要是因为对时序的掌握不是很熟练,耽误了很多时间。
后来实用示波器仔细校准了每一段时序才得到满意的结果,小小的庆祝一下。
下面给大家分享一下心得
Mini2440 with linux
115
将DS18B20的DQ脚(2号脚)与GPF0相连(也可以选择其他GPIO口)
GND(1号脚)接开发板的GND(3号脚)
VDD(3号脚)接开发板的5V(1号脚)
DS18B20支持两种供电模式,一种是寄生供电(parasite power),另一种是外部供电(external power)。寄生供电是将18b20的1,3脚接地 直接“窃取”DQ上的电压,这种方式操作起来似乎不太稳定。所以我使用了外部电源供电(开发板上正好有5V的直流电源比较方便)
这个比较有意思,一般的总线系统都会将时钟线和数据线分开,而单总线系统的DQ线甚至还能供电 - -
所以对DS18B20的所有操作都是通过这一条总线,相对而言还是非常简单的。
下面分析各种时序 - -
对于DS18B20初始化是所有命令的初始操作,它由两个部分组成:主机发送的重启脉冲和从机的应答脉冲 时序图:
操作过程: 注意,对DQ脚的操作需要打开上拉电阻(pull up)
1.由主机拉低DQ脚持续至少480us,然后释放总线(拉高引脚)
2.18b20检测到上升沿后会向主机发送一个应答脉冲,拉低总线60-240us,也就是说驱动程序需要在释放总线后60-240us内进行采样。
3.复位DQ
初始化代码:
/* 初始化DQ18B20,返回0表示初始化成功1表示失败 */ unsigned int init_ds18b20(void) { unsigned int dat = 0;
DQ_OUT;
DQ_H; //复位DQ
udelay(10);
DQ_L; //拉低DQ,发出reset脉冲
udelay(600);
DQ_H;
udelay(65);
DQ_IN; //设置DQ为输入,准备读取presence脉冲
dat = DQ_STA; //读取DQ状态,dat=0表示初始化成功,dat=1 表示失败
udelay(50);
DQ_OUT;
DQ_H; //复位DQ
udelay(50);
return dat;
}
操作过程: 1.不论写1还是写0,写时隙都由主机拉低总线开始。
2.写0,主机拉低总线后,需保持低电平60-120us,然后释放DQ,恢复高电平
3.写1,主机拉低总线后,需在15us内释放总线,18b20会在15-60us内对总线进行取样
写字节代码:
/* Write a char */ void write_char(unsigned char dat) { unsigned int i = 0; DQ_OUT; for(i=8;i>0;i--){ DQ_L; //拉低DQ,开始写时序 udelay(10);
if(dat&0x01)
DQ_H; //拉高DQ,写1
else
DQ_L; //拉低DQ,写0
udelay(60);
DQ_H; //复位DQ
udelay(10);
dat >>=1;
}
}
操作过程: 1.读时隙由主机拉低总线至少1us后释放(拉高总线),完成初始化
2.然后18b20会开始向总线传输数据,保持总线高电平为1,拉低总线为0
3.主机取样(过程需要精确延时,下面细讲)
4.复位DQ
详细取样过程: 上图表示,由ds18b20传回的数据在15us内有效,主机最好在结束前的几微秒内取样,这样才能获得精确的结果。
读字节代码:
/* Read a char */ unsigned char read_char(void) { unsigned int i= 0; unsigned char dat = 0; for(i=8;i>0;i--){ DQ_OUT; DQ_L; udelay(2);
dat >>=1;
DQ_H;
udelay(2);
DQ_IN;
udelay(10);
if(DQ_STA)
dat |=0x80;
udelay(60);
}
return dat;
}
以下是18b20 memory map: 如上图所示,如果仅需读取温度,我们只用在BE指令后读出两个字节,就可以得到所需的温度。
#include #include #include #include #include #include #include #include #include #include
#include #include #include #include #include #include
/* 引脚相关定义 */
#define DQ S3C2410_GPF(0)
#define DQ_OUTP S3C2410_GPIO_OUTPUT
#define DQ_INP S3C2410_GPIO_INPUT
/* 设置DQ引脚为输出 */
#define DQ_OUT s3c2410_gpio_cfgpin(DQ,DQ_OUTP)
/* 设置DQ引脚为输入 */
#define DQ_IN s3c2410_gpio_cfgpin(DQ,DQ_INP)
/* 打开上拉电阻 */
#define DQ_UP s3c2410_gpio_pullup(DQ,1)
/* 读取DQ状态 */
#define DQ_STA s3c2410_gpio_getpin(DQ)
/* 拉低DQ */
#define DQ_L s3c2410_gpio_setpin(DQ,0)
/* 拉高DQ */
#define DQ_H s3c2410_gpio_setpin(DQ,1)
/* 设备名称 */
#define DEV_NEME “ds18b20”
struct ds18b20_dev{
dev_t devno; //设备号
int major; //主设备号
char *data; //缓冲区
struct cdev cdev; //内嵌字符设备结构
};
struct ds18b20_dev *ds18b20_device;
/* 初始化DQ18B20,返回0表示初始化成功1表示失败 */
unsigned int init_ds18b20(void)
{ unsigned int dat = 0;
DQ_OUT;
DQ_H; //复位DQ
udelay(10);
DQ_L; //拉低DQ,发出reset脉冲
udelay(600);
DQ_H;
udelay(65);
DQ_IN; //设置DQ为输入,准备读取presence脉冲
dat = DQ_STA; //读取DQ状态,dat=0表示初始化成功,dat=1 表示失败
udelay(50);
DQ_OUT;
DQ_H; //复位DQ
udelay(50);
return dat;
}
/* Write a char */
void write_char(unsigned char dat)
{ unsigned int i = 0;
DQ_OUT;
for(i=8;i>0;i–){
DQ_L; //拉低DQ,开始写时序
udelay(10);
if(dat&0x01)
DQ_H; //拉高DQ,写1
else
DQ_L; //拉低DQ,写0
udelay(60);
DQ_H; //复位DQ
udelay(10);
dat >>=1;
}
}
/* Read a char */
unsigned char read_char(void)
{ unsigned int i= 0;
unsigned char dat = 0;
for(i=8;i>0;i–){
DQ_OUT;
DQ_L;
udelay(2);
dat >>=1;
DQ_H;
udelay(2);
DQ_IN;
udelay(10);
if(DQ_STA)
dat |=0x80;
udelay(60);
}
return dat;
}
/* Read temperature */
int read_temperature(void)
{ unsigned int msb =0;
unsigned int lsb =0;
if(init_ds18b20()){
printk(KERN_WARNING “DS18B20:init_ds18b20 failed1”);
return -1;
}
write_char(0xCC); //跳过读取ROM
write_char(0x44); //开始温度转换
/* 此处必须有一个较长的延时,等待温度转换完成
* 不然可能读出默认值1360 */
msleep_interruptible(900); //此处延时较长,应该改用非忙等待。 感谢aimybee提醒~
if(init_ds18b20()){
printk(KERN_WARNING “DS18B20:init_ds18b20 failed2”);
return -1;
}
write_char(0xCC); //跳过读取ROM
write_char(0xBE); //读取寄存器
lsb = read_char(); //前两个寄存器的值分别为温度的低位和高位
msb = read_char();
msb <<=8;
return msb+lsb;
}
int ds18b20_open(struct inode *inode,struct file *filp)
{ DQ_UP; //打开上拉电阻
DQ_OUT;
return 0;
}
ssize_t ds18b20_read(struct file *filp,char __user *buf ,size_t count , loff_t *f_pos)
{ int temperature;
temperature = read_temperature();
sprintf(ds18b20_device->data,”%dn”,temperature);
if(temperature < 0){
printk(KERN_WARNING “DS18B20:Read temperature failed!”);
return -EFAULT;
}
if(copy_to_user(buf,ds18b20_device->data,sizeof(ds18b20_device->data))){
return -EFAULT;
}
return sizeof(ds18b20_device->data);
}
struct file_operations ds18b20_fops = {
.owner = THIS_MODULE,
.read = ds18b20_read,
.open = ds18b20_open,
};
int __init init_module(void)
{ int result,err;
/* 初始化ds18b20_device 结构体 */
ds18b20_device = kmalloc(sizeof(struct ds18b20_dev),GFP_KERNEL);
if(!ds18b20_device){
result = -ENOMEM;
goto fail_malloc_device;
}
memset(ds18b20_device,0,sizeof(struct ds18b20_dev));
cdev_init(&ds18b20_device->cdev,&ds18b20_fops);
ds18b20_device->cdev.owner = THIS_MODULE;
/* 分配设备号 */
result = alloc_chrdev_region(&ds18b20_device->devno,0,1,DEV_NEME);
ds18b20_device->major = MAJOR(ds18b20_device->devno);
if(result <0 ){
printk(KERN_WARNING “DS18b20:cat’t get major %d”,ds18b20_device->major);
return result;
}
printk(KERN_INFO “DS18b20:major = %d “,ds18b20_device->major);
/* 分配缓冲区 */
ds18b20_device->data = kmalloc(20,GFP_KERNEL);
if(!ds18b20_device->data){
result = -ENOMEM;
goto fail_malloc_data;
}
memset(ds18b20_device->data,0,20);
err = cdev_add(&ds18b20_device->cdev,ds18b20_device->devno,1);
if(err)
printk(KERN_WARNING “DS18b20:Error %d adding ds18b20”,err);
printk(KERN_INFO “DS18b20:init_module”);
return result;
fail_malloc_data:
kfree(ds18b20_device->data);
fail_malloc_device:
unregister_chrdev_region(ds18b20_device->devno,1);
kfree(ds18b20_device);
return result;
}
void __exit cleanup_module(void)
{ unregister_chrdev_region(ds18b20_device->devno,1);
kfree(ds18b20_device->data);
kfree(ds18b20_device);
printk(KERN_INFO “DS18b20:cleanup_module”);
}
module_init(init_module);
module_exit(cleanup_module);
MODULE_AUTHOR(“Issac”);
MODULE_LICENSE(“GPL”);
然后在Kconfig中添加如下命令
config DS18B20 tristate "Driver for DS18B20" help Say Y here to include support for the DS18B20 Programmable Resolution 1-Wire Digital Thermometer It is also possible to say M here to build it as a module (ds18b20)然后在Makefile 中添加
obj-$(CONFIG_DS18B20) += ds18b20.o回到源码根目录
执行meke menuconfig,选上对ds18b20的支持
然后 cat 18b20
就可以读出温度了。