Linux下的ds18b20驱动
扫描二维码
随时随地手机看文章
运行环境 Fedora9.0
交叉编译 arm-linux-gcc-4.3.2
今天在各位前辈已有成就的基础上花了两天时间终于把这个驱动给搞定了,从开始编译成模块看效果,进行调试,再到编译进内核,最后又编译了一个界面出来,虽说大多数的程序代码是用各位前辈的成果,但坐下来自己收获也不小,现在写下来,以供以后参考,也和各位爱好者交流一下,呵呵!
一.编译成模块
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <asm/uaccess.h>
#define DEVICE_NAME "DS18B20"
#define DS18B20_MAJOR 250
#define DS_PIN S3C2410_GPB1
#define OUT S3C2410_GPB1_OUTP
#define IN S3C2410_GPB1_INP
#define DIS_UP 1
#define EN_UP 0
//#define Search 0x00F0
#define Read_ROM 0x0033 //just for one
#define Skip_ROM 0x00CC //ds18b20的固定时序, 跳过读序号列号的操作
#define Convert 0x0044 //ds18b20的固定时序,管脚识别到该信号是开始对测到的温度进行转换
#define Write 0x004E //TH---TL---Config
#define Read 0x00BE //读取温度寄存器等(共可读9个寄存器) 前两个就是温度
//#define bit_9 0x001F
//#define bit_10 0x003F
//#define bit_11 0x005F
#define bit_12 0x007F
#define uint16 unsigned int
//unsigned int ROM_DATA[8];
void usdelay(unsigned int i)
{
unsigned int j;
for(i=i;i>0;i--)
for(j=90;j>0;j--);
}
void msdelay(unsigned int i) //延时 i ms
{
for(i=i;i>0;i--)
usdelay(1000);
}
void SetL(void)
{
s3c2410_gpio_cfgpin(DS_PIN,OUT);
s3c2410_gpio_setpin(DS_PIN,0);
}
void SetH(void)
{
s3c2410_gpio_cfgpin(DS_PIN,OUT);
s3c2410_gpio_setpin(DS_PIN,1);
}
unsigned int Read_DS(void)
{
unsigned int i;
s3c2410_gpio_cfgpin(DS_PIN,IN);
s3c2410_gpio_pullup(DS_PIN,EN_UP);
__asm("nop");
__asm("nop");
__asm("nop");
i=s3c2410_gpio_getpin(DS_PIN);
if(i!=0)
i=1;
return i;
}
unsigned int ds_start(void) //初始化ds18b20
{
unsigned int flag=1;
int err=0;
SetH();
udelay(2);
SetL();
udelay(600); //560延时要大于480u
SetH();
udelay(60); //稍作延时
while(Read_DS()!=0) //ds18B20初始化成功会返回一个低电平,此时跳出循环,执行下面的操作
{
printk(DEVICE_NAME "Wait....\n");
udelay(5);
err++; //初始化的最多次数吧,超过规定的次数,说明初始化失败
if(err==20)
{
printk(DEVICE_NAME "start fail\n");
return -1;
}
}
//printk(DEVICE_NAME "start sucess\n");
flag=0;
SetH();//初始化成功后赋为高电平准备从外界读入温度
udelay(400);
return flag;
}
void ds_send(unsigned int uidata) //向18b20写入一个字节的数据
{
//printk("the send data is %d\n",uidata);
int i;
for(i=0;i<8;i++)
{
SetL();
udelay(1);
if((uidata&1)!=0)
{
SetH();
udelay(80); //等待18b20进行数据采集
}
else
{
udelay(80); //等待18b20进行数据采集
SetH();
}
uidata>>=1;
}
}
unsigned int ds_read(void) //从18b20读一个字节的数据
{
unsigned int uidata=0;unsigned int i;
for(i=0;i<8;i++)
{
uidata>>=1;
SetL();
udelay(1);
s3c2410_gpio_setpin(DS_PIN,1);
s3c2410_gpio_cfgpin(DS_PIN,IN);
udelay(10);
if(s3c2410_gpio_getpin(DS_PIN))
uidata=(uidata|0x80);
udelay(65);
SetH();
}
// printk("ds_read success\n");
return uidata;
}
unsigned int read_tem(void)
{
unsigned int th,tl;
//int err=0;
//ds_init(100,0,bit_12);
th=tl=0;
ds_start();
ds_send(Skip_ROM); //跳过读序号列号的操作
ds_send(Convert); //启动温度转换
mdelay(50);
ds_start();
ds_send(Skip_ROM); //跳过读序号列号的操作
ds_send(Read); //准备读温度
tl=ds_read();
th=ds_read();
th<<=8; //温度在低两个字节中
//printk("the tl data is %d\n",tl);
tl|=th; //获取温度
//printk("the th data is %d\n",th);
//printk("the tl2 data is %d\n",tl);
//printk("read_tmp success\n");
return tl;
}
static int ds18b20_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,unsigned long arg)
{
return 0;
}
static ssize_t ds18b20_read(struct file *pFile, uint16 __user *pData, size_t count, loff_t *off )
{
uint16 tmp,ret;
tmp =read_tem();
// printk("the tmpk data is %d\n",tmp);
ret=copy_to_user(pData, &tmp, sizeof(tmp)); //将读取得的DS18B20数值复制到用户区
if(ret>0)
{
printk("copy data failed\n");
return -1;
}
//else
// printk("copy data succese\n");
return 0;
}
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULE,
.ioctl = ds18b20_ioctl,
.read = ds18b20_read,
};
static int __init ds18b20_init(void)
{
int ret;
ret = register_chrdev(DS18B20_MAJOR, DEVICE_NAME, &ds18b20_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
s3c2410_gpio_cfgpin(DS_PIN, OUT);
s3c2410_gpio_setpin(DS_PIN, 1);
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit ds18b20_exit(void)
{
unregister_chrdev(DS18B20_MAJOR, DEVICE_NAME);
printk(DEVICE_NAME " rmmodule\n");
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_AUTHOR("benjamin_xc@163.com"); // 驱动程序的作者
MODULE_DESCRIPTION("DS18B20 Driver"); // 一些描述信息
MODULE_LICENSE("GPL");
下面是将驱动编译成模块的过程:
1.将编写好的驱动代码copy或move到你的内核驱动代码中,比如我的是FriendlyARM/mini2440/linux2.6.29/drivers/char
2.编辑char目录下的Makefile文件,添加如下obj-m += 18b20.o 注意此处红色的部分要和你自己建的程序代码的文件名保持一致。
3.退到Linux根目录下,我的就是linux2.6.29,输入命令make modules。
如果没有错误,在char目录下就会看到编译后的18b20.ko的模块文件。
使用ftp或是串口将编译好的模块文件下载到开发板上
接下来就是加载设备驱动程序模块了
1.创建设备驱动程序的进入点mknod /dev/DS18B20 c 250 0 此处的主设备号要和你在程序中设定的一样,创建的设备接口文件要和测试程序中的保持一致啊!
2.加载驱动程序 insmod 18b20.ko
如果上面的步骤没出差错,就说明驱动方法这块是搞定的了。
此处说下和本驱动没直接联系的话,各位别嫌我啰嗦啊,因为我做的时候碰到了,还郁闷了好几天的。想必有些朋友在将内核编译成模块后,在加载是没有什么错误,用lsmod命令来看,自己的模块也确实是加载进去了,但是在用rmmod进行卸载时,却出现提示说,找不到需要卸载的文件,因此在这个问题解决之前,每次加载后,想要卸载都要重新启动开发板,麻烦呀,后来终于在一个论坛里看到了解决方法,在/lib/modules/下建立Linux2.6.29.4-FriendlyARM文件夹,然后把想加载的模块放到这个文件中,这个问题就解决了,具体原因自己也不知道,能用就OK了吧,呵呵,感谢各位前辈的努力啊!
下面是测试代码(这是完全从网上搜的哦,再次感谢各位大哥)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#define K 0.0625
int main(void)
{
int fd = -1;
char count = 5;
unsigned int tmp = 0;float res=0;
fd = open("/dev/DS18B20", 0);
if(fd < 0)
{
perror("Can't open /dev/DS18B20 \n");
exit(1);
}
printf("open ds18b20 success \n");
while(1){
read(fd, &tmp , sizeof(tmp));
res=tmp*K;
//printf("the currently data is %d\n",tmp);
printf("the currently temperature is %f\n",res);
sleep(5);
}
close(fd);
return 0;
}
下面简单介绍一下ds18b20的东西吧:
具体可参考
大致有以下几种形状的
用的最多的当然是最左边的三针的,管脚少,操作也简单嘛。另外有一点注意,就是用的时候DQ(数据管脚)和VD之间要加一个4k—10k的电阻。
心情好,事情才能做得好!!
交叉编译过之后或是用NFS或者下载到板子上都可以,没什么差错的话就可以实现测温了。
下面是运行情况
以上是手离开传感器后温度的变化情况。
下面简单介绍一下ds18b20的东西吧
1.DS18B20是Dallas公司生产的数字温度传感器,具有体积小、适用电压宽、经济灵活的特点。它内部使用了onboard专利技术,全部传感元件及转换电路集成在一个形如三极管的集成电路内。DS18B20有电源线、地线及数据线3根引脚线,工作电压范围为3~5.5 V,支持单总线接口。
DS18B20的结构和工作原理
2.1DS18B20的内外结构
DS18B20的外部结构如图1所示。其中,VDD为电源输入端,DQ为数字信号输入/输出端,GND为电源地。
DS18B20内部结构主要包括4部分:64位光刻ROM、温度传感器、非易失的温度报警触发器TH和TL、配置寄存器,如图2所示。
64位ROM中,在产品出厂前就被厂家通过光刻刻录好了64位序列号。该序列号可以看作是DS18B20的地址序列码,用来区分每一个DS18B20,从而更好地实现对现场温度的多点测量。
图2中的暂存器是DS18B20中最重要的寄存器。暂存器由9个字节组成,各字节定义如表1所列。
配置寄存器用于用户设置温度传感器的转换精度,其各位定义如下:
TM位是测试模式位,用于设置DS18B20是工作模式(0)还是测试模式(1),其出厂值为0。R1、R0用于设置温度传感器的转换精度:00,分辨率为9位,转换时间为93.75ms;01,分辨率为10位,转换时间为187.5 ms;10,分辨率为11位,转换时间为375 ms;11,分辨为12位,转换时间为750 ms。R1、R0的出厂值为11。其余5位值始终为1。
第0和第1字节为16位转换后的温度二进制值,其中前4位为符号位,其余12位为转换后的数据位(分辨率为12位)。如果温度大于0,则前4位值为0,只要将测到的数值乘上0.062 5即可得到实际温度值;如果温度小于0,则前4位为1,需将测得的数值取反加1后,再乘上0.062 5。第0和第1字节各位的二进制值如下:
3. DS18B20的应用电路结构
按DS18B20的供电方式,其应用电路结构可分为如下3种:寄生电源供电方式;寄生电源强上拉供电方式;外部电源供电方式。实际应用中,以外部电源供电方式为主。其应用原理图如图3所示。
4. DS18B20的工作原理
根据DS18B20的通信协议,MCU对其操作主要有如下3个步骤:读写之前,对DS18B20发送约500 μs的低电平进行复位;复位成功,发送ROM指令;发送RAM指令。MCU对DS18B20的具体操作流程如图4所示。
5. 实物图
6.具体电路
其中p1.7是指你自己选的管脚
二.编译进内核
程序代码和上面的没有本质的差别,就是多了一些,创建设备节点的函数
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <asm/uaccess.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <asm/io.h>
#include <plat/regs-timer.h>
#define DS_PIN S3C2410_GPB1
#define OUT S3C2410_GPB1_OUTP
#define IN S3C2410_GPB1_INP
#define DIS_UP 1
#define EN_UP 0
//#define Search 0x00F0
#define Read_ROM 0x0033 //just for one
#define Skip_ROM 0x00CC //ds18b20的固定时序,管脚识别到该信号时开始从外界获取温度
#define Convert 0x0044 //ds18b20的固定时序,管脚识别到该信号是开始对测到的温度进行转换
#define Write 0x004E //TH---TL---Config
#define Read 0x00BE
//#define bit_9 0x001F
//#define bit_10 0x003F
//#define bit_11 0x005F
#define bit_12 0x007F
#define uint16 unsigned int
//unsigned int ROM_DATA[8];
#define DEVICE_NAME "DS18B20"
#define DS18B20_MAJOR 0
static int device_major = DS18B20_MAJOR; //系统动态生成的主设备号
void usdelay(unsigned int i) //延时 i us 对于不同系统可能会有所差别,请适当修改
{
unsigned int j;
for(i=i;i>0;i--)
for(j=90;j>0;j--);
}
void msdelay(unsigned int i) //延时 i ms
{
for(i=i;i>0;i--)
usdelay(1000);
}
void SetL(void)
{
s3c2410_gpio_cfgpin(DS_PIN,OUT);
s3c2410_gpio_setpin(DS_PIN,0);
}
void SetH(void)
{
s3c2410_gpio_cfgpin(DS_PIN,OUT);
s3c2410_gpio_setpin(DS_PIN,1);
}
unsigned int Read_DS(void)
{
unsigned int i;
s3c2410_gpio_cfgpin(DS_PIN,IN);
s3c2410_gpio_pullup(DS_PIN,EN_UP);
__asm("nop");
__asm("nop");
__asm("nop");
i=s3c2410_gpio_getpin(DS_PIN);
if(i!=0)
i=1;
return i;
}
unsigned int ds_start(void) //初始化ds18b20
{
unsigned int flag=1;
int err=0;
SetH();
udelay(2);
SetL();
udelay(600); //560延时要大于480u
SetH();
udelay(60); //稍作延时
while(Read_DS()!=0) //ds18B20初始化成功会返回一个低电平,此时跳出循环,执行下面的操作
{
printk(DEVICE_NAME "Wait....\n");
udelay(5);
err++; //??应该是初始化的最多次数吧??
if(err==20)
{
printk(DEVICE_NAME "start fail\n");
return -1;
}
}
//printk(DEVICE_NAME "start sucess\n");
flag=0;
SetH();//初始化成功后赋为高电平准备从外界读入温度
udelay(400);
return flag;
}
void ds_send(unsigned int uidata)
{
//printk("the send data is %d\n",uidata);
int i;
for(i=0;i<8;i++)
{
SetL();
udelay(1);
if((uidata&1)!=0)
{
SetH();
udelay(80);
}
else
{
udelay(80);
SetH();
}
uidata>>=1;
}
}
unsigned int ds_read(void)
{
unsigned int uidata=0;unsigned int i;
for(i=0;i<8;i++)
{
uidata>>=1;
SetL();
udelay(1); //2 3
s3c2410_gpio_setpin(DS_PIN,1);
s3c2410_gpio_cfgpin(DS_PIN,IN);
udelay(10); //1 2 3 4 5(e)
if(s3c2410_gpio_getpin(DS_PIN))
uidata=(uidata|0x80);
udelay(65);
SetH();
}
// printk("ds_read success\n");
return uidata;
}
unsigned int read_tem(void)
{
unsigned int th,tl;
//int err=0;
//ds_init(100,0,bit_12);
th=tl=0;
ds_start();
ds_send(Skip_ROM);
ds_send(Convert);
mdelay(50);
ds_start();
ds_send(Skip_ROM);
ds_send(Read);
tl=ds_read();
th=ds_read();
th<<=8;
//printk("the tl data is %d\n",tl);
tl|=th;
//printk("the th data is %d\n",th);
//printk("the tl2 data is %d\n",tl);
//printk("read_tmp success\n");
return tl;
}
static int ds18b20_ioctl(
struct inode *inode,
struct file *file,
unsigned int cmd,unsigned long arg)
{
return 0;
}
static ssize_t ds18b20_read(struct file *pFile, uint16 __user *pData, size_t count, loff_t *off )
{
uint16 tmp,ret;
tmp =read_tem();
// printk("the tmpk data is %d\n",tmp);
ret=copy_to_user(pData, &tmp, sizeof(tmp)); //将读取得的DS18B20数值复制到用户区
if(ret>0)
{
printk("copy data failed\n");
return -1;
}
//else
// printk("copy data succese\n");
return 0;
}
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULE,
.ioctl = ds18b20_ioctl,
.read = ds18b20_read,
};
static struct class *tmp_class;//从此处开始和上面的代码有区别了啊
static int __init ds18b20_init(void)
{
device_major = register_chrdev(DS18B20_MAJOR, DEVICE_NAME, &ds18b20_fops);
if (device_major < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return -1;
}
tmp_class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(tmp_class))
{
printk(DEVICE_NAME " register class falid!\n");
return -1;
}
//创建一个设备节点,设备名为tmp_NAME,即:my2440_tmp
device_create(tmp_class, NULL, MKDEV(device_major, 0), NULL, DEVICE_NAME);
s3c2410_gpio_cfgpin(DS_PIN, OUT);
s3c2410_gpio_setpin(DS_PIN, 1);
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit ds18b20_exit(void)
{
unregister_chrdev(DS18B20_MAJOR, DEVICE_NAME);
//删除设备节点
device_destroy(tmp_class, MKDEV(device_major, 0));
//注销设备类
class_destroy(tmp_class);
printk(DEVICE_NAME " rmmodule\n");
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_AUTHOR("BINBIN"); // 驱动程序的作者
MODULE_DESCRIPTION("DS18B20 Driver"); // 一些描述信息
MODULE_LICENSE("GPL");
将上面的驱动程序保存为DS18B20.c,下面有解释,为什么要保存成该名称
编译方法
1. 将驱动程序放到FriendlyARM/mini2440/linux2.6.29/drivers/char目录下
2. 编译char目录里的Kconfig文件添加如下内容
config MY2440_TMP //在Makefile中用
tristate "My2440 TMP Device"
depends on ARCH_S3C2440
default y
---help---
My2440 TMP
3. 编译char目录下的Makefile文件 添加如下内容
obj-$(CONFIG_MY2440_TMP) += DS18B20.o //这里就体现出了上面将驱动保存成DS18B20.c的原因了吧
4. 编译内核,在Linux2.6.29目录下执行命令:make zImage
然后将内核烧录到板子里,直接运行测试程序就可以看到和上面用加载模块方法一样的效果了。
三.编写界面
1.最终效果
运行时,会将测到的温度显示在上面的框中,转换的次数会显示在下面的框中。有程序可知按下Start按钮,可以转换20次,再按下会继续测。
运行环境 按照友善之臂的手册,安装好Qtopia-2.2.0
1.编写.ui文件
打开Qtopia2.2.0编译出入上界面,窗体名字是:TMPBaseForm,因为下面的程序要用到这个基类,如果要改名字请保证后面程序中用相同的名字
2.编写tmp.h文件
#ifndef MYHELLOFORM_H
#define MYHELLOFORM_H
#include "tmp_base.h"
#include <qpixmap.h>
class MyTMPForm : public TMPBaseForm
{
Q_OBJECT
public:
MyTMPForm( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 );
virtual ~MyTMPForm();
//const QPixmap *eddy;
int fd;
private slots:
void startTMP();
void stopTMP();
};
#endif // MYHELLOFORM_H
3.编写tmp.cpp文件
#include "tmp.h"
#include <qlabel.h>
#include <qpushbutton.h>
#include <qspinbox.h>
#include <qlcdnumber.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <errno.h>
#include <string.h>
#define K 0.0625;
MyTMPForm::MyTMPForm( QWidget* parent, const char* name, WFlags fl)
:TMPBaseForm(parent, name, fl)
{
fd=0;
connect(PushButton1,SIGNAL(clicked()),this,SLOT(startTMP()));
connect(PushButton2,SIGNAL(clicked()),this,SLOT(stopTMP()));
}
MyTMPForm::~MyTMPForm()
{
::close(fd);
}
void MyTMPForm::startTMP()
{
//TextLabel1->setText("System Led is Started");
fd = ::open("/dev/DS18B20", 0);
if (fd < 0)
{
::exit(1);
}
for(int num=0;num<20;num++)
{
unsigned int tmp = 0;
float res=0;
read(fd, &tmp , sizeof(tmp));
res=tmp*K;
LCDNumber1->display(res);
sleep(3);
LCDNumber2->display(num);
}
::close(fd);
}
void MyTMPForm::stopTMP()
{
::close(fd);
}
4.编写main.cpp
#include "tmp.h"
#include <qtopia/qpeapplication.h>
QTOPIA_ADD_APPLICATION("tmp",MyTMPForm)
QTOPIA_MAIN
5.编写tmp.desktop
[Desktop Entry]
Comment=An Example Program
Exec=tmp
Icon=alarmbell
Type=gaof
Name=TMP
6.编写tmp.pro文件
CONFIG += qtopiaapp
CONFIG -= buildQuicklaunch
HEADERS = tmp.h
SOURCES = tmp.cpp
SOURCES+=main.cpp
INTERFACES = tmp_base.ui
desktop.files = tmp.desktop
desktop.path = /apps/gaof
INSTALLS += desktop
TARGET = tmp
7.编写build
#!/bin/bash
source /opt/FriendlyARM/mini2440/arm-qtopia/qtopia-2.2.0-FriendlyARM/setQpeEnv
qmake -spec /opt/FriendlyARM/mini2440/arm-qtopia/qtopia-2.2.0-FriendlyARM/qtopia/mkspecs/qws/linux-arm-g++ -o Makefile *.pro
make clean
make
以上都做好后执行./bulid
将生成的tmp可执行文件下载到板子上,并将其移到/opt/Qtopia/bin/目录下
将tmp.desktop下载到板子上,并移动到/opt/Qtopia/apps/gaof/目录下 此处的gaof是我建立的各人可能不同,自己更改
重启板子就会看到和开始介绍的一样的界面效果了。
这个程序就写到这了,呵呵!
努力,就有进步!!