ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程
ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程
原文地址:http://www.cnblogs.com/NickQ/p/9026545.html
一、开发板与ds18b20的入门
ds18B20是常用的数字温度传感器,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。但楼主在使用过程中发现,ds18b20测量的温度还是需要进行一定的软件校准的。后面我们会谈论到。
除了上面提到的,ds18b20还有很多可圈可点的有点。下面说楼主所关注到的几个。
- 单总线协议,称为总线,必然可以挂载很多设备,但却只占用一个IO口。这对于缺乏IO资源的设备来说,就像是救命稻草。
- 可以由用户自己权衡测量精度和测量时间。根据Datasheet,18B20控制寄存器有两位是用来控制精度和测量时间的。如下图。
下图表明:用户可以在精度9bit-12bit中,自由切换,这也对应着93.75ms,187.5ms,375ms,750ms四个最大测量时间。也就是说9bit精度意味着,最大测量时间最有93.75ms(对于缓慢变化的温度来说,这已经很快了),但只可以精确到0.25摄氏度。12bit精度意味着,虽然最大测量时间有750ms,但精度却能达到0.0625(750ms对于温度测量不能算很慢,但换来的这个精度却是不低的)。
- 可以使用寄生电源供电。这也是这个芯片突出的地方。这意味着可以不连接电源线,也为PCB布板,多设备走线省去了很多方便。
二、开发板的硬件电路和寄存器
楼主这里使用的是飞凌2440开发板,做学习之用。
电路连接图如下
这个板子上是接了电源和外部上拉。事实上这个电源可以由寄生电源,即由信号线DQ上的外部上拉提供。
图中,也可以看出信号线DQ是连接在了GPG0口。
寄存器说明图
寄存器物理地址:
寄存器配置说明
GPGCON-GPG0
GPGDAT AND GPGUP
三、 驱动实现
#include <linux/module.h> /* Every Linux kernel module must include this head */ #include <linux/init.h> /* Every Linux kernel module must include this head */ #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* struct fops */ #include <linux/errno.h> /* error codes */ #include <linux/cdev.h> /* cdev_alloc() */ #include <asm/io.h> /* ioremap() */ #include <linux/ioport.h> /* request_mem_region() */ #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/types.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <asm/uaccess.h> #include <linux/gpio.h> #include <linux/device.h> //定义驱动模块信息 //模块作者和描述 #define DRV_AUTHOR "Nick <nickxq@qq.com>" #define DRV_DESC "S3C24XX 18B20 driver" //模块名 #define DEV_NAME "s3c18b20" //模块版本信息(只作用于安装和卸载的打印信息中) #define DRV_MAJOR_VER 1 #define DRV_MINOR_VER 0 #define DRV_REVER_VER 0 //定义GPG口的寄存器地址,注意偏移地址在程序中的使用 #define S3C_GPG_BASE 0x56000060 //寄存器物理地址基地址 #define GPGCON_OFFSET 0 #define GPGDAT_OFFSET 4 #define GPGUP_OFFSET 8 #define S3C_GPG_LEN 0x10 /* 0x56000060~0x56000070 */ //此处定义的大小包括了四个寄存器地址空间,即4*4字节(包括保留的寄存器地址) //定义ds18b20 DQ线对应的端口的GPIO编号。例如:GPG0 GPIO编号为0 #define GPIO_NUM_18B20 0 //18B20 PORT is PG0 //定义函数操作的参数 //GPIO_Mode #define GPIO_MODE_INPUT 0x00 #define GPIO_MODE_OUTPUT 0x01 #define GPIO_MODE_EINT 0x10 //GPIO_STATUS #define GPIO_STATUS_LOW 0 #define GPIO_STATUS_HIGH 1 //GPIO_PULLUP #define GPIO_PULLUP_ENABLE 0 #define GPIO_PULLUP_DISABLE 1 #define DISABLE 0 #define ENABLE 1 //定义函数宏 #define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpg_membase) #define s3c_gpio_write(val,reg) __raw_writel((val),(reg)+s3c_gpg_membase) //设置GPIO模式 参数:操作寄存器的偏移地址、GPIO编号、设置的状态(取值应为上述的宏) #define s3c_18b20_gpio_mode(gpio_mode) s3c2440_gpio_cfgpin_mode(GPGCON_OFFSET,GPIO_NUM_18B20, gpio_mode) #define s3c_18b20_gpio_setsta(gpio_status) s3c2440_gpio_cfgpin_status(GPGDAT_OFFSET,GPIO_NUM_18B20, gpio_status) #define s3c_18b20_gpio_getsta() s3c2440_gpio_getpin_status(GPGDAT_OFFSET,GPIO_NUM_18B20) #define s3c_18b20_gpio_pullup(gpio_pullup) s3c2440_gpio_cfgpin_pullup(GPGUP_OFFSET,GPIO_NUM_18B20, gpio_pullup) //全局变量的定义 //设备数量、主设备好、次设备号(此处主设备号可由静态给定,只要不为0即可。若主设备号为0,则动态申请) int dev_count = 1; int dev_major = 0; int dev_minor = 0; int debug = DISABLE; //定义存储 映射的虚拟地址空间地址的起始地址 变量 static void __iomem *s3c_gpg_membase; static struct cdev *s3c_18b20_cdev; //设置GPIO模式,此函数由上述的带参数宏调用 static int s3c2440_gpio_cfgpin_mode(unsigned long gpio_addr,unsigned char gpio_num, unsigned char gpio_mode) { volatile unsigned long gpg_con; if((GPIO_MODE_INPUT != gpio_mode) && (GPIO_MODE_OUTPUT != gpio_mode) && (GPIO_MODE_EINT != gpio_mode)) { return -1; } /* Set GPxCON register, set correspond GPIO port as input or output mode */ gpg_con = s3c_gpio_read(gpio_addr); //此处的gpio_addr是偏移地址,调用此宏后会根据s3c_gpg_membase转换为绝对虚拟地址 gpg_con &= ~(0x3<<(2*gpio_num)); /* Clear the currespond GPIO configure register */ gpg_con |= gpio_mode<<(2*gpio_num); /* Set the currespond GPIO as output mode */ //带参数宏,实现将gpgdat写入gpio_addr。 s3c_gpio_write(gpg_con,gpio_addr); //此处的gpio_addr是偏移地址,调用此宏后会根据s3c_gpg_membase转换为绝对虚拟地址 return 0; } //设置GPIO引脚电平状态,此函数由上述的带参数宏调用 static int s3c2440_gpio_cfgpin_status(unsigned long gpio_addr,unsigned char gpio_num, unsigned char gpio_status) { volatile unsigned long gpg_dat; if((GPIO_STATUS_LOW != gpio_status) && (GPIO_STATUS_HIGH != gpio_status)) { return -1; } /* Set GPxDAT register, set correspond GPIO port power level as high level or low level */ gpg_dat = s3c_gpio_read(gpio_addr); //此处的gpio_addr是偏移地址,调用此宏后会根据s3c_gpg_membase转换为绝对虚拟地址 if(GPIO_STATUS_LOW == gpio_status) { gpg_dat &= ~(0x1<<gpio_num); /* This port set to low level */ } else { gpg_dat |= (0x1<<gpio_num); /* This port set to high level*/ } //带参数宏,实现将gpgdat写入gpio_addr。 s3c_gpio_write(gpg_dat,gpio_addr); //此处的gpio_addr是偏移地址,调用此宏后会根据s3c_gpg_membase转换为绝对虚拟地址 return 0; } //设置GPIO上拉状态,此函数由上述的带参数宏调用 static int s3c2440_gpio_cfgpin_pullup(unsigned long gpio_addr,unsigned char gpio_num, unsigned char gpio_pullup) { volatile unsigned long gpg_up; if((GPIO_PULLUP_ENABLE != gpio_pullup) && (GPIO_PULLUP_DISABLE != gpio_pullup)) { return -1; } /* Set GPxUP register, set correspond GPIO port pull up resister as enable or disable */ gpg_up = s3c_gpio_read(gpio_addr); if(GPIO_PULLUP_ENABLE == gpio_pullup) { gpg_up &= ~(0x1<<gpio_num); /* Enable pull up resister */ } else { gpg_up |= (0x1<<gpio_num); /* Disable pull up resister */ } s3c_gpio_write(gpg_up,gpio_addr); return 0; } //读取GPIO引脚电平状态,此函数由上述的带参数宏调用 static int s3c2440_gpio_getpin_status(unsigned long gpio_addr,unsigned char gpio_num) { volatile unsigned long gpg_dat; /* Get GPxDAT register, get correspond GPIO port power level as high level or low level */ gpg_dat = s3c_gpio_read(gpio_addr); gpg_dat &= (0x1<<gpio_num); if(gpg_dat) { return GPIO_STATUS_HIGH; } else { return GPIO_STATUS_LOW; } } //向内核申请4*4个字节的虚拟地址空间,并与寄存器物理地址绑定映射 static int s3c_18b20_addr_init(void) { if(!request_mem_region(S3C_GPG_BASE, S3C_GPG_LEN, "s3c2440 18b20")) { return -EBUSY; } if( !(s3c_gpg_membase=ioremap(S3C_GPG_BASE, S3C_GPG_LEN)) ) { release_mem_region(S3C_GPG_BASE, S3C_GPG_LEN); return -ENOMEM; } return 0; } //释放虚拟地址,解除地址映射 static void s3c_18b20_addr_release(void) { release_mem_region(S3C_GPG_BASE, S3C_GPG_LEN); iounmap(s3c_gpg_membase); } //写ds18b20复位时序 static int s3c_ds18b20_clk_reset(void) { int retval = 0; s3c_18b20_gpio_mode(GPIO_MODE_OUTPUT); s3c_18b20_gpio_pullup(GPIO_PULLUP_ENABLE); s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); udelay(2); s3c_18b20_gpio_setsta(GPIO_STATUS_LOW); // 拉低ds18b20总线,复位ds18b20 udelay(500); // 保持复位电平500us s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); // 释放ds18b20总线 udelay(60); // 若复位成功,ds18b20发出存在脉冲(低电平,持续60~240us) s3c_18b20_gpio_mode(GPIO_MODE_INPUT); retval = s3c_18b20_gpio_getsta(); udelay(500); s3c_18b20_gpio_mode(GPIO_MODE_OUTPUT); s3c_18b20_gpio_pullup(GPIO_PULLUP_ENABLE); s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); // 释放总线 return retval; } //ds18b20写数据时序 static void s3c_ds18b20_clk_write_byte(unsigned char data) { int i = 0,flag = 0; s3c_18b20_gpio_mode(GPIO_MODE_OUTPUT); s3c_18b20_gpio_pullup(GPIO_PULLUP_DISABLE); for (i = 0; i < 8; i++) { // 总线从高拉至低电平时,就产生写时隙 s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); udelay(2); s3c_18b20_gpio_setsta(GPIO_STATUS_LOW); flag = data & 0x01; if(flag) { s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); } else { s3c_18b20_gpio_setsta(GPIO_STATUS_LOW); } udelay(60); data >>= 1; } s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); // 重新释放ds18b20总线 } //ds18b20读数据时序 static unsigned char s3c_ds18b20_clk_read_byte(void) { int i; unsigned char data = 0; for (i = 0; i < 8; i++) { // 总线从高拉至低,只需维持低电平17ts,再把总线拉高,就产生读时隙 s3c_18b20_gpio_mode(GPIO_MODE_OUTPUT); s3c_18b20_gpio_pullup(GPIO_PULLUP_ENABLE); s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); udelay(2); s3c_18b20_gpio_setsta(GPIO_STATUS_LOW); udelay(2); s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); udelay(8); data >>= 1; s3c_18b20_gpio_mode(GPIO_MODE_INPUT); if (s3c_18b20_gpio_getsta()) data |= 0x80; udelay(50); } s3c_18b20_gpio_mode(GPIO_MODE_OUTPUT); s3c_18b20_gpio_pullup(GPIO_PULLUP_ENABLE); s3c_18b20_gpio_setsta(GPIO_STATUS_HIGH); // 释放ds18b20总线 return data; } //内核的调用接口 static int s3c_18b20_open(struct inode *inode, struct file *file) { int flag = 0; printk(KERN_ERR "open start\n"); flag = s3c_ds18b20_clk_reset(); printk(KERN_ERR "ds18b20 reset is %d\n",flag); if (flag & 0x01) { printk(KERN_WARNING "open ds18b20 failed\n"); return -1; } printk(KERN_NOTICE "open ds18b20 successful\n"); return 0; } //内核的调用接口 static int s3c_18b20_release(struct inode *inode, struct file *file) { printk(KERN_DEBUG "/dev/s3c_18b20%d closed.\n", iminor(inode)); return 0; } //内核的调用接口 static ssize_t s3c_18b20_read(struct file *filp, char __user * buf, size_t count, loff_t * f_pos) { int flag; unsigned long err; unsigned char result[2] = { 0x00, 0x00 }; flag = s3c_ds18b20_clk_reset(); if (flag & 0x01) { printk(KERN_WARNING "ds18b20 init failed\n"); return -1; } s3c_ds18b20_clk_write_byte(0xcc); s3c_ds18b20_clk_write_byte(0x44); flag = s3c_ds18b20_clk_reset(); if (flag & 0x01) return -1; s3c_ds18b20_clk_write_byte(0xcc); s3c_ds18b20_clk_write_byte(0xbe); result[0] = s3c_ds18b20_clk_read_byte(); // 温度低八位 result[1] = s3c_ds18b20_clk_read_byte(); // 温度高八位 err = copy_to_user(buf, &result, sizeof(result)); return err ? -EFAULT : min(sizeof(result), count); } //定义的ds18b20文件操作的数据结构 static struct file_operations s3c_18b20_fops = { .owner = THIS_MODULE, .open = s3c_18b20_open, .read = s3c_18b20_read, .release = s3c_18b20_release, }; //模块安装调用的初始化 static int __init s3c_18b20_init(void) { int result; dev_t devno; //申请并映射虚拟地址 if( 0 != s3c_18b20_addr_init() ) { printk(KERN_ERR "s3c2440 18B20 addr initialize failure.\n"); return -ENODEV; } //为设备注册设备号。如果dev_major不为0,则动态申请主设备号。否者使用dev_major为主设备号 if (0 != dev_major) /* Static */ { devno = MKDEV(dev_major, dev_minor); result = register_chrdev_region(devno, dev_count, DEV_NAME); } else { result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME); dev_major = MAJOR(devno); } /* Alloc for device major failure */ if (result < 0) { printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major); return -ENODEV; } printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major); //为s3c_18b20_cdev数据结构申请空间 if(NULL == (s3c_18b20_cdev=cdev_alloc()) ) { printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME); unregister_chrdev_region(devno, dev_count); return -ENOMEM; } //绑定字符设备数据结构 s3c_18b20_cdev->owner = THIS_MODULE; cdev_init(s3c_18b20_cdev, &s3c_18b20_fops); //注册cdev到内核 result = cdev_add(s3c_18b20_cdev, devno, dev_count); if (0 != result) { printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); goto ERROR; } printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n", DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER); return 0; ERROR: printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME); cdev_del(s3c_18b20_cdev); unregister_chrdev_region(devno, dev_count); return result; } static void __exit s3c_18b20_exit(void) { dev_t devno = MKDEV(dev_major, dev_minor); s3c_18b20_addr_release(); cdev_del(s3c_18b20_cdev); unregister_chrdev_region(devno, dev_count); printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER); return ; } /* These two functions defined in <linux/init.h> */ module_init(s3c_18b20_init); module_exit(s3c_18b20_exit); module_param(debug, int, S_IRUGO); module_param(dev_major, int, S_IRUGO); MODULE_AUTHOR(DRV_AUTHOR); MODULE_DESCRIPTION(DRV_DESC); MODULE_LICENSE("GPL");
四、编译的Makefile
LINUX_SRC = ${shell pwd}/../kernel/linux-3.0 CROSS_COMPILE=/opt/xtools/arm920t/bin/arm-linux- INST_PATH=${shell pwd}/ PWD := $(shell pwd) EXTRA_CFLAGS+=-DMODULE obj-m += kernel_18b20.o modules: @make -C $(LINUX_SRC) M=$(PWD) modules @make clear uninstall: rm -f ${INST_PATH}/*.ko install: uninstall cp -af *.ko ${INST_PATH} clear: @rm -f *.o *.cmd *.mod.c @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f @rm -f .*ko.cmd .*.o.cmd .*.o.d clean: clear @rm -f *.ko
解释说明:
LINUX_SRC 指定开发板已编译过得内核路径
CROSS_COMPILE 指定交叉编译器
INST_PATH 安装路径
obj-m += kernel_18b20.o 编译成模块,输出文件名为kernel_18b20.o
编译运行。
修改驱动文件名为kernel_18b20.c
使用make编译后,将kernel_18b20.ko传输至开发板。
使用insmod安装。可以使用dmesg查看安装打印信息,也可以使用lsmod查看设备信息
[root@NickQ_fl2440 driver]# insmod kernel_18b20.ko [root@NickQ_fl2440 driver]# dmesg S3C s3c18b20 driver use major 253 S3C s3c18b20 driver[major=253] version 1.0.0 installed successfully! [root@NickQ_fl2440 driver]# lsmod kernel_18b20 3250 0 - Live 0xbf000000
然后查看主设备号cat /proc/devices,使用moknod创建设备节点
[root@NickQ_fl2440 driver]# cat /proc/devices | grep s3c18b20 253 s3c18b20 [root@NickQ_fl2440 driver]# mknod -m 755 /dev/s3c18b20 c 253 0 [root@NickQ_fl2440 driver]# ls /dev/s3c18b20 /dev/s3c18b20
mknod 用法
[root@NickQ_fl2440 driver]# mknod --help BusyBox v1.27.1 (2017-11-20 21:14:15 CST) multi-call binary. Usage: mknod [-m MODE] NAME TYPE MAJOR MINOR Create a special file (block, character, or pipe) -m MODE Creation mode (default a=rw) TYPE: b Block device c or u Character device p Named pipe (MAJOR and MINOR are ignored)
五、编写测试程序
[nick@XQLY driver]$ vim ~/s3c2440/linux/drivers/test_18b20.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/ioctl.h> int main() { int fd; unsigned char result[2]; unsigned char integer_value = 0; float decimal_value = 0.0; float temperature = 0.0; fd = open("/dev/s3c18b20", 0); if(fd < 0) { perror("open device failed\n"); exit(1); } else printf("Open success!\n"); while(1) { read(fd, &result, sizeof(result)); integer_value = ((result[0] & 0xf0) >> 4) | ((result[1] & 0x08) << 4); decimal_value = (result[0] & 0x0f) * 0.0625; temperature = (float)integer_value + decimal_value; printf("Current Temperature:%6.4f\n", temperature); sleep(1); } }
解析温度的说明:
我们读取出来的数据放置在result[2]里。
result[0]对应从LS Byte里读回来的值
result[1]对应从MS Byte里读回来的值
所以integer_value = ((result[0] & 0xf0) >> 4) | ((result[1] & 0x08) << 4);
是将LS的高四位和MS的低四位取出(其中有一位符号位S),并合成一个数,即为整数部分。decimal_value = (result[0] & 0x0f) * 0.0625;
是提取小数部分
六、测试现象
[root@NickQ_fl2440 driver]# ./start_s3c18b20 Open success! Current Temperature:26.5625 Current Temperature:26.6250 Current Temperature:26.5625 Current Temperature:26.6250 Current Temperature:26.6250 ^C
作者:NickQ
出处:http://www.cnblogs.com/NickQ/
版权声明:
本文版权归原创作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步