基于S3C2440的嵌入式Linux驱动——DS18B20温度传感器(添加使用platform总线机制)


此代码为本人原创。该代码仍有不完善之处,有可能还要再次修改!仅供参考!

若有错误、疑问和意见请留言,非常感谢!

该驱动程序基于TQ2440开发板,内核2.6.30。

驱动程序比较简单,使用字符设备来实现。要注意的是在模拟DS18B20的总线时序时,处理器不能抢占当前线程从而造成时序错乱,因此使用了自旋锁来禁止处理器抢占。

代码比较简单,所以代码注释也比较少。如果有不明白的请参考DS18B20的datasheet以及<<Linux设备驱动程序>>的第三章。

1. 驱动代码一

NOTE:请使用驱动代码二,代码一不支持多进程访问。

//using cdev to create device
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>

#define DEVICE_NAME "ds18b20"
#define SKIP_ROM 0xcc
#define CONVERT_T 0x44
#define READ_SCRATCHPAD 0xbe
#define DQ  S3C2410_GPB5

MODULE_LICENSE("GPL");

static struct ds18b20_info
{
	struct mutex 	ds18b20_mutex;
	spinlock_t	ds18b20_spinlock;		

};

static int ds18b20_init_work(struct ds18b20_info *dsin)
{
	char n;
	unsigned long flag;

	spin_lock_irqsave(&dsin->ds18b20_spinlock,flag);
	s3c2410_gpio_setpin(DQ, 1);
	udelay(70);
	s3c2410_gpio_setpin(DQ, 0);
	udelay(500);
	s3c2410_gpio_setpin(DQ, 1);
	udelay(70);
	n = s3c2410_gpio_getpin(DQ);
	if(n == 1)
		n = -1;
	udelay(80);
	spin_unlock_irqrestore(&dsin->ds18b20_spinlock, flag);
	return n;
}

static void ds18b20_write_byte(unsigned char data, struct ds18b20_info *dsin){
	unsigned char i;
	unsigned long flag;
	
	spin_lock_irqsave(&dsin->ds18b20_spinlock,flag);
	for (i = 0; i < 8; i++){
		s3c2410_gpio_setpin(DQ, 0);
		udelay(10);
		s3c2410_gpio_setpin(DQ, (data&0x01)? 1:0);
		udelay(40);
		s3c2410_gpio_setpin(DQ, 1);
		data >>= 1;
		udelay(1);
	}
	spin_unlock_irqrestore(&dsin->ds18b20_spinlock, flag);
}

static unsigned char ds18b20_read_byte(struct ds18b20_info *dsin){
	unsigned char i, value= 0;
	unsigned long flag;
	
	spin_lock_irqsave(&dsin->ds18b20_spinlock,flag);
	for(i = 0; i < 8; i++){
		value >>= 1;
		s3c2410_gpio_setpin(DQ, 0); 	//pull low
		s3c2410_gpio_setpin(DQ, 1);		//pull high
		if (s3c2410_gpio_getpin(DQ))	//read
		{
			value |= 0x80;
		}
		udelay(40);
	}
	spin_unlock_irqrestore(&dsin->ds18b20_spinlock, flag);
	return value;
}

static int ds18b20_open(struct inode *inode, struct file *file)
{
	struct ds18b20_info *dsin = kmalloc(sizeof(* dsin), GFP_KERNEL);
	
	if(dsin == NULL){
		printk("No memory for ds18b20\n");
		return -ENOENT;
	}
	memset(dsin, 0, sizeof(*dsin));
	file->private_data = dsin;
	spin_lock_init(&dsin->ds18b20_spinlock);
	mutex_init(&dsin->ds18b20_mutex);
	printk("<1>open ds18b20 \n");
	return 0;
}

static int ds18b20_close(struct inode *inode, struct file *file)
{
	struct ds18b20_info *dsin = file->private_data;
	kfree(dsin);	//必需释放
	return 0;
}

static int ds18b20_read (struct file *filp, char *buff, size_t __user count, loff_t *offp)
{
	
	struct ds18b20_info *dsin = filp->private_data;
	unsigned char temp1, temp2;
	unsigned char buffer[2];
	unsigned long err;
	
	mutex_lock(&dsin->ds18b20_mutex);
	if(ds18b20_init_work(dsin) < 0)
		printk("ds18b20 unavailable\n");
	ds18b20_write_byte(SKIP_ROM, dsin);
	ds18b20_write_byte(CONVERT_T, dsin);
	
	ssleep(1);
	
	ds18b20_init_work(dsin);
	ds18b20_write_byte(SKIP_ROM, dsin);
	ds18b20_write_byte(READ_SCRATCHPAD, dsin);
	temp1 = ds18b20_read_byte(dsin);
	temp2 = ds18b20_read_byte(dsin);
	mutex_unlock(&dsin->ds18b20_mutex);
	
	buffer[0] = temp1;
	buffer[1] = temp2;
	
	err = copy_to_user(buff, (void *)buffer, count);
	return err? -EFAULT:count ;
		
}

static struct file_operations ds18b20_fops =
{
	.owner = THIS_MODULE,
	.open  = ds18b20_open,
	.read = ds18b20_read,
	.release = ds18b20_close,
};

struct cdev my_cdev;
dev_t dev_num;
static char __initdata banner[]="ds18b20 sensor forth edition, made by yanjun\n";
static struct class *led_class;
static int __init ds18b20_init(void)
{	
	int ret, err;

	//get the device number in a dynamic way
	if ((ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME)) < 0)   
		printk("alloc_chdrev_region error");
	printk("dev_num = %d\n", MAJOR(dev_num));	
	
	//create and initialize a cdev in a dynamic way
	cdev_init(&my_cdev, &ds18b20_fops);			
	my_cdev.owner = THIS_MODULE;		
//	my_cdev.ops = &ds18b20_fops;		
	err = cdev_add(&my_cdev, dev_num, 1);	
	if (err < 0)
		printk("cdev error");
	printk(banner);
	
	//create a device node
	led_class = class_create(THIS_MODULE, DEVICE_NAME);				
	if (IS_ERR(led_class)){
		printk("Err:failed in embedsky_leds class.\n");
		return -1;
	}
	device_create(led_class, NULL, dev_num, NULL, DEVICE_NAME);
	
	printk(DEVICE_NAME"initialized\n");
	return 0;
}

static void __exit ds18b20_exit(void)
{
	cdev_del(&my_cdev);							
	unregister_chrdev_region(dev_num, 1);		
	device_destroy(led_class, dev_num);		
	class_destroy(led_class);			}

module_init(ds18b20_init);
module_exit(ds18b20_exit);


2. 驱动代码二(使用platform机制)

该代码基于代码一,进行了简单的修改,使用platform机制,修正了不能进程访问的bug。

//using cdev to create device
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
//#include </opt/EmbedSky/linux-2.6.30.4/arch/arm/mach-s3c2410/include/mach/regs-gpio.h>
//#include <asm/arch/regs-gpio.h>
//#include </opt/EmbedSky/linux-2.6.30.4/arch/arm/mach-s3c2410/include/mach/hardware.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>

#define DEVICE_NAME "ds18b20"
#define SKIP_ROM 0xcc
#define CONVERT_T 0x44
#define READ_SCRATCHPAD 0xbe
#define DQ  S3C2410_GPB5

MODULE_LICENSE("GPL");

struct ds18b20_info
{
	struct mutex 	ds18b20_mutex;
	spinlock_t	ds18b20_spinlock;		

};
struct ds18b20_info *dsin;


static int ds18b20_init_work(struct ds18b20_info *dsin)
{
	char n;
	unsigned long flag;

	spin_lock_irqsave(&dsin->ds18b20_spinlock,flag);
	s3c2410_gpio_setpin(DQ, 1);
	udelay(70);
	s3c2410_gpio_setpin(DQ, 0);
	udelay(500);
	s3c2410_gpio_setpin(DQ, 1);
	udelay(70);
	n = s3c2410_gpio_getpin(DQ);
	if(n == 1)
		n = -1;
	udelay(80);
	spin_unlock_irqrestore(&dsin->ds18b20_spinlock, flag);
	return n;
}

static void ds18b20_write_byte(unsigned char data, struct ds18b20_info *dsin){
	unsigned char i;
	unsigned long flag;
	
	spin_lock_irqsave(&dsin->ds18b20_spinlock,flag);
	for (i = 0; i < 8; i++){
		s3c2410_gpio_setpin(DQ, 0);
		udelay(10);
		s3c2410_gpio_setpin(DQ, (data&0x01)? 1:0);
		udelay(40);
		s3c2410_gpio_setpin(DQ, 1);
		data >>= 1;
		udelay(1);
	}
	spin_unlock_irqrestore(&dsin->ds18b20_spinlock, flag);
}

static unsigned char ds18b20_read_byte(struct ds18b20_info *dsin){
	unsigned char i, value= 0;
	unsigned long flag;
	
	spin_lock_irqsave(&dsin->ds18b20_spinlock,flag);
	for(i = 0; i < 8; i++){
		value >>= 1;
		s3c2410_gpio_setpin(DQ, 0); 	//pull low
		s3c2410_gpio_setpin(DQ, 1);	//pull high
		if (s3c2410_gpio_getpin(DQ))	//read
		{
			value |= 0x80;
		}
		udelay(40);
	}
	spin_unlock_irqrestore(&dsin->ds18b20_spinlock, flag);
	return value;
}

static int ds18b20_open(struct inode *inode, struct file *file)
{
	file->private_data = dsin;
	printk("<1>open ds18b20 \n");
	return 0;
}

static int ds18b20_close(struct inode *inode, struct file *file)
{	
	printk("Now kfree has been executed");
	return 0;
}

static int ds18b20_read (struct file *filp, char *buff, size_t __user count, loff_t *offp)
{
	
	struct ds18b20_info *dsin = filp->private_data;
	unsigned char temp1, temp2;
	unsigned char buffer[2];
	unsigned long err;
	
	mutex_lock(&dsin->ds18b20_mutex);
	if(ds18b20_init_work(dsin) < 0)
		printk("ds18b20 unavailable\n");
	ds18b20_write_byte(SKIP_ROM, dsin);
	ds18b20_write_byte(CONVERT_T, dsin);
	
	ssleep(1);
	
	ds18b20_init_work(dsin);
	ds18b20_write_byte(SKIP_ROM, dsin);
	ds18b20_write_byte(READ_SCRATCHPAD, dsin);
	temp1 = ds18b20_read_byte(dsin);
	temp2 = ds18b20_read_byte(dsin);
	mutex_unlock(&dsin->ds18b20_mutex);
	
	buffer[0] = temp1;
	buffer[1] = temp2;
	
        msleep(20);
          
        err = copy_to_user(buff, (void *)buffer, count);
	return err? -EFAULT:count ;
		
}

static struct file_operations ds18b20_fops =
{
	.owner = THIS_MODULE,
	.open  = ds18b20_open,
	.read = ds18b20_read,
	.release = ds18b20_close,
};

struct cdev my_cdev;
dev_t dev_num;
static char __initdata banner[] = "Ds18b20 sensor: fifth edition using platform, made by yanjun\n";
static struct class *led_class;

static int s3c2410_ds18b20_probe(struct platform_device *pdev)
{	
	int ret, err;

	//get the device number in a dynamic way
	if ((ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME)) < 0)   
		printk("alloc_chdrev_region error");
	printk("dev_num = %d\n", MAJOR(dev_num));	
	
	//create and initialize a cdev in a dynamic way
	cdev_init(&my_cdev, &ds18b20_fops);			
	my_cdev.owner = THIS_MODULE;			
	err = cdev_add(&my_cdev, dev_num, 1);	
	if (err < 0)
		printk("cdev error");
	printk(banner);
	
	//create a device node
	led_class = class_create(THIS_MODULE, DEVICE_NAME);				
	if (IS_ERR(led_class)){
		printk("Err:failed in embedsky_leds class.\n");
		return -1;
	}
	device_create(led_class, NULL, dev_num, NULL, DEVICE_NAME);
	
	dsin = kmalloc(sizeof(* dsin), GFP_KERNEL);
	if(dsin == NULL){
		printk("No memory for ds18b20\n");
		return -ENOENT;
	}
	memset(dsin, 0, sizeof(*dsin));
	spin_lock_init(&dsin->ds18b20_spinlock);
	mutex_init(&dsin->ds18b20_mutex);

	printk(DEVICE_NAME"initialized\n");
	return 0;
}

static void __exit s3c2410_ds18b20_remove(void)
{
	kfree(dsin);
	cdev_del(&my_cdev);						
	unregister_chrdev_region(dev_num, 1);		
	device_destroy(led_class, dev_num);		
	class_destroy(led_class);			
}

#ifdef CONFIG_PM
static int s3c2410_ds18b20_suspend(struct platform_device *pdev, pm_message_t msg)
{
	/*nothing to do*/
	return 0;
}

static int s3c2410_ds18b20_resume(struct platform_device *pdev, pm_message_t msg)
{
	/*nothing to do*/
	return 0;
}
#else
#define s3c2410_ds18b20_suspend NULL
#define s3c2410_ds18b20_resume NULL
#endif

static struct platform_driver s3c2410_ds18b20_driver = {
	.probe 		= s3c2410_ds18b20_probe,
	.remove 	= s3c2410_ds18b20_remove,
	.suspend 	= s3c2410_ds18b20_suspend,
	.resume		= s3c2410_ds18b20_resume, 	
	.driver		= {
		.name 	= "s3c2410-ds18b20",
		.owner	= THIS_MODULE,
	},
};

static int __init s3c2410_ds18b20_init(void)
{
	return platform_driver_register(&s3c2410_ds18b20_driver);
}

static void __exit s3c2410_ds18b20_exit(void)
{
	platform_driver_unregister(&s3c2410_ds18b20_driver);
}

module_init(s3c2410_ds18b20_init);
module_exit(s3c2410_ds18b20_exit);

在ds18b20_read函数中,添加了20ms的休眠,该句用于实现多进程的访问。不添加的话,在我的平台上,只有一个进程读取到温度值。

上面是驱动的代码,还需要修改另外3个文件。

第一个:arch/arm/plat-s3c24xx/devs.c

添加内容:

/*DS18b20 temperature sensor, added by yj423*/
static u64 s3c_device_ds18b20_dmamask = 0xffffffffUL;

struct platform_device s3c_device_ds18b20 = {
	.name		= "s3c2410-ds18b20",
	.id		= -1,
	.num_resources 	= 0,
	.resource	= NULL,
	.dev		= {
		.dma_mask = &s3c_device_ds18b20_dmamask,
		.coherent_dma_mask = 0xffffffffUL
	}
};

EXPORT_SYMBOL(s3c_device_ds18b20);

第二个: arch/arm/plat-s3c/include/plat/devs.h

添加内容:

/*added by yj423*/
extern struct platform_device s3c_device_ds18b20;
第三个:arch/arm/mach-s3c2440/mach-smdk2440.c

在smdk2440_devices中添加&s3c_device_ds18b20,

编译,加载模块后,我们去sysfs文件系统去验证一下:

[root@yj423 s3c2410-ds18b20]#pwd
/sys/bus/platform/devices/s3c2410-ds18b20
[root@yj423 s3c2410-ds18b20]#ll
lrwxrwxrwx    1 root     root             0 Jan  1 00:33 bus -> ../../../bus/platform
lrwxrwxrwx    1 root     root             0 Jan  1 00:33 driver -> ../../../bus/platform/drivers/s3c2410-ds18b20
-r--r--r--    1 root     root          4096 Jan  1 00:33 modalias
drwxr-xr-x    2 root     root             0 Jan  1 00:33 power
lrwxrwxrwx    1 root     root             0 Jan  1 00:33 subsystem -> ../../../bus/platform
-rw-r--r--    1 root     root          4096 Jan  1 00:33 uevent

3. 应用程序

#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>

int main(void)
{
	int i, temper_fd;
	unsigned char value[2];
	unsigned char temp;
	float temperature;
	i = 0;
	if((temper_fd = open("/dev/ds18b20", 0)) < 0){
		perror("open device ds18b20 error");
		printf("exit\n");
		exit(1);
	}
//		printf("before syscall read\n");	
	for(; ;){	
		int ret = read(temper_fd, value, sizeof(value));
		if (ret != 2){
			printf("read wrong\n");
			exit(0);
		}
		temp = value[0] & 0x0f;
		value[1] = value[1] << 4 | value[0] >> 4; 
		temperature = (float)value[1] + temp * 6.0 / 100.0;
		printf("temperature = %.2f ", temperature);
		fflush(stdout);
		i ++;
		if (i == 4){
			printf("\n");
			i = 0;
		}
	}		
	close(temper_fd);
	return 0;
}


运行结果:

 [root@yj423 modules]#insmod ds18b20.ko
dev_num = 252
ds18b20 sensor forth edition, made by yanjun
ds18b20initialized
[root@yj423 modules]#./sensor2
open ds18b20
temperature = 32.48 temperature = 32.48 temperature = 32.48 temperature = 32.54
temperature = 32.54 temperature = 32.54 temperature = 32.54 temperature = 32.54
temperature = 32.60 temperature = 32.54 temperature = 32.60 temperature = 32.60
temperature = 32.72 temperature = 32.84 temperature = 33.00 temperature = 33.18
temperature = 33.36 temperature = 33.48 temperature = 33.60 temperature = 33.66
temperature = 33.72 temperature = 33.78 temperature = 33.72 ^CNow kfree has been executed

附注:2012.08.21 添加驱动代码二

你可能感兴趣的:(c,linux,struct,嵌入式,Class,byte)