ARM Linux驅動篇 學習溫度感測器ds18b20的驅動編寫過程 原文地址: "http://www.cnblogs.com/NickQ/p/9026545.html " 一、開發板與ds18b20的入門 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
三、 驅動實現
/*********************************************************************************
* Copyright: (C)2018. Xu Qiang <[email protected]>
* All rights reserved.
*
* Filename:
* Description:
*
* Version: 1.0.0 (2018/05/10)
* Author: Xu Qiang <[email protected]>
* ChangeLog: Release version on "2018/05/10 00:14:26"
*
********************************************************************************/
#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 <[email protected]>"
#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
/*********************************************************************************
* Copyright: (C)2018. Xu Qiang <[email protected]>
* All rights reserved.
*
* Filename:
* Description:
*
* Version: 1.0.0 (2018/05/10)
* Author: Xu Qiang <[email protected]>
* ChangeLog: Release version on "2018/05/10 18:25:02"
*
********************************************************************************/
#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