基于全志A40i的Linux单总线驱动的开发(AM2301温湿度传感器)
本文主要是介绍在Linux系统上驱动AM2301,实现读取到当前环境的温湿度数据。通过这个来记录和分享一下在Linux系统下单总线设备的驱动该怎么写(Linux驱动初学者)。
一、驱动代码实现
最终的实现是采用一个gpio口模拟的单总线,来与AM2301通讯,通过注释来解释说明(本文侧重点在驱动的实现上,关于AM2301相关的介绍和单总线的知识,网上有很多,我这就不罗嗦了,下面上代码)。
/*************************************************************************
> File Name: AM2301.c
> Author:jonker
> address:
> Created Time: 10时42分19秒
************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/rwsem.h>
#include <linux/timer.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/ctype.h>
#include <linux/sysfs.h>
#include <linux/pinctrl/pinconf-sunxi.h>
#include <linux/io.h>
#include <linux/of_gpio.h>
#include <linux/sys_config.h>
#include "../base/base.h"
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <asm/io.h>
#define DEV_NAME "AM2301"
#define AM2301_GPIO GPIOG(4)
volatile unsigned long * GPIO_DAT;
static struct cdev demo_cdev;
struct class *class = NULL;
struct device *device = NULL;
unsigned int major = 0;
int AM2301_GPIO = AM2301_GPIO;
struct mutex res_mutex; // 采用信号量来防止数据竞争的出现
void setL(void) {
printk("into setL !\n");
gpio_set_value(AM2301_GPIO, 0);
}
void setH(void) {
printk("into setH !\n");
gpio_set_value(AM2301_GPIO, 1);
}
void setOutput(void) {
printk("into setOutput !\n");
gpio_direction_output(AM2301_GPIO, 1); // 设为输出模式
}
void setInput(void) {
printk("into setInput !\n");
gpio_direction_input(AM2301_GPIO); // 设为输入模式
}
int getOutput(void) {
if(readl(GPIO_DAT)&0x10) {
return 1;
}
return 0;
}
int ow_start(void) {
char timeOut = 0;
printk("into ow_start !\n");
setOutput();
setH();
udelay(100);
setL();
udelay(1000); // 拉低至少800us
setH();
udelay(20);
setInput();
// printk("setInput######\n");
if(__gpio_get_value(AM2301_GPIO) == 0) {
timeOut = 0;
while(!__gpio_get_value(AM2301_GPIO)) { // 最多80us
timeOut++;
udelay(1);
if(timeOut > 80)
goto END1;
}
timeOut = 0;
while(__gpio_get_value(AM2301_GPIO)) { // 最多80us
timeOut++;
udelay(1);
if(timeOut > 80)
goto END1;
}
return 0;
}
printk("start Error!\n");
return -1;
END1:
printk("start TimeOut!\n");
return -1;
}
void ow_read(unsigned char *sbuf) {
unsigned char i, j, data;
unsigned int timeOut = 0;
for(j = 0; j < 5; j++) {
data = 0;
for(i = 0; i < 8; i++) {
data <<= 1;
timeOut = 1;
while((getOutput()) && timeOut < 65536) {
timeOut++;
}
if(timeOut >= 65535) {
sbuf[j] = data;
goto END;
}
timeOut = 1;
while((!getOutput()) && timeOut < 65535) {// 50us Low
timeOut++;
}
if(timeOut >= 65535) {
sbuf[j] = data;
goto END;
}
timeOut = 1;
udelay(30); // 延时35us 判断low or hig
if(getOutput()) {
data |= 0x01;
} else {
data &= 0xfe;
}
timeOut = 1;
}
sbuf[j] = data;
}
return;
END:
printk("ow_read TimeOut!\n");
return;
}
static int AM2301_open(struct inode *inodeP, struct file *fileP)
{
printk("device open success!\n");
return 0;
}
static ssize_t AM2301_read(struct file *pFile, char __user *buf, size_t count, loff_t *off) {
unsigned char sum = 0xff;
//unsigned char sbuf = 0, check = 0;
unsigned char tdata[5];
mutex_lock(&res_mutex); // 上锁
if(ow_start() == 0) {
ow_read(tdata);
sum = (tdata[0] + tdata[1] + tdata[2] + tdata[3]);
// printk("read: %d,%d,%d,%d,%d,%d\n",(int)tdata[0], (int)tdata[1], (int)tdata[2], (int)tdata[3], (int)tdata[4], (int)sum);
mutex_unlock(&res_mutex);
if(tdata[4] != sum)
return -1;
if(copy_to_user(buf, tdata, 4))
return -1;
return 4;
} else {
printk("AM2301_read ow_start faile!\n");
mutex_unlock(&res_mutex);
}
return -1;
}
static void setup_cdev(struct cdev *dev, int minor, struct file_operations *fops)
{
int err;
int devno;
devno = MKDEV(major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
dev->ops = fops;
err = cdev_add(dev, devno, 1);
if (err) {
printk(KERN_NOTICE" Error %d adding dev %d", err, minor);
}
}
static struct file_operations AW2301_fpos={
.owner = THIS_MODULE,
.open = AM2301_open,
// .close = AM2301_close,
.read = AM2301_read,
};
static int __init AW2301_init(void) {
int re;
dev_t dev = MKDEV(major, 0);
printk("into AW2301_init\n");
mutex_init(&res_mutex);
GPIO_DAT = ioremap(0x01C20800+0x00E8, 8); // 将GPIOG4口的数据寄存器映射过来
//为字符设备分配设备号
if (major) {
re = register_chrdev_region(dev, 1, DEV_NAME);
} else {
re = alloc_chrdev_region(&dev, 0, 1, DEV_NAME); // 未指定主设备号,系统自动分配
}
if (re < 0) {
printk(KERN_WARNING" Demo dev-->unable to get major %d\n", major);
return re;
}
major = MAJOR(dev);
setup_cdev(&demo_cdev, 0, &AW2301_fpos); // 初始化并将注册字符设备
printk("The major of the demo device is %d\n", major);
class = class_create(THIS_MODULE,DEV_NAME); /*在sys下创建类目录/sys/class/AM2301*/
device_create(class, NULL, MKDEV(major,0), NULL, DEV_NAME); // 自动创建设备节点
if (gpio_request(AM2301_GPIO, NULL)) {
pr_err("AM2301_GPIO:%d gpio_request fail\n", AM2301_GPIO);
}
return 0;
}
static void __exit Aw2301_exit(void) {
printk("into Aw2301_exit\n");
gpio_free(AM2301_GPIO);
device_destroy(class, MKDEV(major,0));// 销毁设备
class_unregister(class); // 注销类
cdev_del(&demo_cdev); // 注销设备
unregister_chrdev_region(MKDEV(major, 0), 1); // 释放设备号
printk("Demo device uninstalled\n");
}
module_init(AW2301_init);
module_exit(Aw2301_exit);
MODULE_AUTHOR("jonker 1432603260@qq.com");
MODULE_DESCRIPTION("one wires AW2301");
MODULE_LICENSE("GPL");
二、测试应用层程序
/*************************************************************************
> File Name: test-AM2301.c
> Author:jonker
> address:
> Created Time: 17时07分10秒
************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = -1;
int value = 0;
float tem= 0.0, htm = 0.0;
unsigned char dst[4];
fd = open("/dev/AM2301", O_RDWR);
if(fd < 0) {
printf("open error!\n");
return -1;
}
read(fd, &dst, 4); // 第一次读取到的数据是错误的,需要连连续读两次
sleep(1);
while(1) {
read(fd, &dst, 4);
printf("AM2301 data is %d,%d,%d,%d\n",dst[0],dst[1],dst[2],dst[3]);
value = (int)(dst[2]<< 8 | dst[3]);
tem = value/10.0;
value = (int)(dst[0]<< 8 | dst[1]);
htm = value/10.0;
printf("tem: %f; hum: %f\n",tem,htm);
sleep(3);
}
close(fd);
return 0;
}
三、后记
本文知识点:
1、学习Linux字符驱动框架的开发;
2、在Linux驱动中如何设置普通gpio口工作模式和设置以及读取gpio的值;
3、通过gpio口模拟单总线。