前言:刚刚学会了DTH11温湿度计的使用,然而无论是自己写的异步通信还是官方给的例程,都会遇到内核错误打印response error或者timeout。可能是时序有时候对的上,有时候对不上导致的,这个问题先按下不表。由于DS18B20和DTH11的模块接线完全一致,所以在设备树、引脚申请、设备文件的建立基本没有任何区别,因此借此来复习复习DHT11模块上学到的东西。
【一】软件的架构
在读写上参考了官方的例程,但官方的例程的执行顺序是:
打开文件→ioctl→内核→执行函数→ioctl→应用层→打印→结束
并不能做到循环打印的要求,运行一次就至打印一次。因此想做出自己的改进,首先是不使用官方的设备文件/dev/ds18b20,其次是能做到循环打印。循环打印的方式有很多种,比如说最简单的就是在应用层的while(1)中进行循环read,每执行一次read就读一次ds18b20。这种方法比较简单直接,没有问题。这种方法过于简单了,为了巩固Linux,这里采用了异步通信以及定时器的方式。
那么程序的执行是怎样的呢?具体如下:
程序建立:建立singal→open→内核→初始化→应用层→等待信号
程序运行:定时器溢出→执行work→进行读取→发出信号→进行read→从内核中获取数据→应用层处理并打印
【二】代码
(1)设备树
首先是引脚定义,这个就是需要使用imx的引脚工具来进行配置,我这里配置的就是改了下驱动强度,改至40ohm,然后默认下拉100k。输出和输入设置的是无,速度是100MHz(2),其他都是默认。
然后是设备结点
注意以上的引脚使用的都是100ask板子中通用模块的最后一组gpio,它上方丝印是GPIO0实际上是GPIO4_IO19。
有的小盆友现在就会问了,你添加了这个设备节点,那官方的设备不是也用的一样的引脚吗,那它开机就会生成设备文件,那你这个设备树明显IO冲突了,到时候加载.ko,一定报错。
实际上,无论是DS18B20还是DTH11,在100ask的内核代码中,它们的引脚申请都是在ioctl中完成的,并且需要XX_IOCINIT的命令才会进行引脚的申请与设备初始化。
详情如下:
所以,即便它生成了设备文件,但只要app不运行,就没有占用IO。
它有它的便利性,所以本人的程序也是差不多,只不过是在open阶段进行初始化和引脚申请,暂时还不怎么会用ioctl,研究研究之后再学习吧。
改了之后请编译生成dtb并放置于开发板的boot目录下,重启后生效。
(2)模组(.ko)
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#define ds18b20_NAME "myds18b20"
#define DELAY_TIME 1000
struct ds18b20_device{
/*file*/
int major;
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
/*gpio*/
int gpio;
struct gpio_desc *gpiod;
/*task*/
int read_flag;
struct timer_list timer;
struct work_struct work;
struct mutex lock;
/*data*/
u8 temperature[2];
};
struct ds18b20_device ds18b20_dev;
static struct fasync_struct *ds18b20_fasync;
static DECLARE_WAIT_QUEUE_HEAD(ds18b20_wait);
static int DS18B20_Rst(void)
{
int ret = 1;
mutex_lock(&ds18b20_dev.lock);
gpiod_direction_output(ds18b20_dev.gpiod, 1);
gpiod_set_value(ds18b20_dev.gpiod, 1);
udelay(2);
gpiod_set_value(ds18b20_dev.gpiod, 0);
udelay(480);
gpiod_set_value(ds18b20_dev.gpiod, 1);
gpiod_direction_input(ds18b20_dev.gpiod);
udelay(35);
ret = gpiod_get_value(ds18b20_dev.gpiod);
udelay(250);
mutex_unlock(&ds18b20_dev.lock);
return ret;
}
static u8 DS18B20_Read_Byte(void)
{
int i = 0;
u8 data = 0;
unsigned long flags;
mutex_lock(&ds18b20_dev.lock);
local_irq_save(flags);
for(i = 0;i<8;i++)
{
gpiod_direction_output(ds18b20_dev.gpiod,1);
udelay(2);
gpiod_set_value(ds18b20_dev.gpiod,0);
udelay(2);
data >>=1;
gpiod_direction_input(ds18b20_dev.gpiod);
if(gpiod_get_value(ds18b20_dev.gpiod)==1)
data |= 0x80;
udelay(60);
}
local_irq_restore(flags);
mutex_unlock(&ds18b20_dev.lock);
return data;
}
void DS18B20_Write_Byte(u8 data)
{
int i = 0;
unsigned long flags;
mutex_lock(&ds18b20_dev.lock);
local_irq_save(flags);
gpiod_direction_output(ds18b20_dev.gpiod, 1);
for(i = 0;i<8;i++)
{
gpiod_set_value(ds18b20_dev.gpiod,1);
udelay(2);
gpiod_set_value(ds18b20_dev.gpiod,0);
udelay(5);
gpiod_set_value(ds18b20_dev.gpiod,data&0x01);
udelay(60);
data >>= 1;
}
local_irq_restore(flags);
mutex_unlock(&ds18b20_dev.lock);
}
static int DS18B20_Start(void)
{
int ret = 0;
ret = DS18B20_Rst();
if(ret != 0)
{
printk("DS18B20 RST fail\n");
return -1;
}
DS18B20_Write_Byte(0xcc);
DS18B20_Write_Byte(0x44);
ret = DS18B20_Rst();
if(ret != 0)
{
return -1;
}
DS18B20_Write_Byte(0xcc);
DS18B20_Write_Byte(0xbe);
return ret;
}
static int DS18B20_Init(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int ret = 0;
mutex_init(&ds18b20_dev.lock);
ds18b20_dev.gpio = of_get_gpio(node,0);
if(ds18b20_dev.gpio <0)
{
printk("of_get_gpio fail!\n");
return -1;
}else
{
printk("of_get_gpio successful!\n");
}
ret = devm_gpio_request_one(&pdev->dev,ds18b20_dev.gpio,0,NULL);
if(ret == 0)
{
printk("devm_gpio_request_one successful!\n");
}else
{
printk("devm_gpio_request_one fail!\n");
return -1;
}
ds18b20_dev.gpiod = gpio_to_desc(ds18b20_dev.gpio);
ret = DS18B20_Rst();
return ret;
}
int DS18B20_Get_Temp(void)
{
int ret = 0;
u8 LSB,MSB;
ret = DS18B20_Start();
if(ret != 0)
{
printk("DS18B20 Start fail\n");
return -1;
}
LSB = DS18B20_Read_Byte();
MSB = DS18B20_Read_Byte();
ds18b20_dev.temperature[0] = MSB;
ds18b20_dev.temperature[1] = LSB;
return 0;
}
static void ds18b20_work_callback(struct work_struct *work)
{
int ret = 0;
ret = DS18B20_Get_Temp();
if(ret == 0)
{
ds18b20_dev.read_flag = 1;
wake_up_interruptible(&ds18b20_wait);//触发
kill_fasync(&ds18b20_fasync,SIGIO,POLL_IN);//FASYNC tranmit signal
}else
{
printk("Ds18b20 read fail!\n");
}
return;
}
static void ds18b20_timer_expire(unsigned long data)
{
schedule_work(&ds18b20_dev.work);//run work
mod_timer(&ds18b20_dev.timer,jiffies + msecs_to_jiffies(DELAY_TIME));//delay 1.5 s
}
static ssize_t ds18b20_drv_read(struct file *file,char __user *buf,size_t size,loff_t *offset)
{
int err = 0;
printk("File:%s Function:%s line: %d\n",__FILE__,__FUNCTION__,__LINE__);
wait_event_interruptible(ds18b20_wait,ds18b20_dev.read_flag);//阻塞等待直到g_key>0
err = copy_to_user(buf,&ds18b20_dev.temperature,sizeof(ds18b20_dev.temperature));
ds18b20_dev.read_flag = 0;
return 0;
}
static int ds18b20_drv_open(struct inode *node,struct file *file)
{
printk("File:%s Function:%s line: %d\n",__FILE__,__FUNCTION__,__LINE__);
ds18b20_dev.read_flag = 0;
mutex_lock(&ds18b20_dev.lock);
init_timer(&ds18b20_dev.timer);
ds18b20_dev.timer.function = ds18b20_timer_expire;
ds18b20_dev.timer.expires = jiffies + msecs_to_jiffies(DELAY_TIME);
ds18b20_dev.timer.data = 0;
add_timer(&ds18b20_dev.timer);
INIT_WORK(&ds18b20_dev.work,ds18b20_work_callback);
mutex_unlock(&ds18b20_dev.lock);
return 0;
}
static int ds18b20_drv_close(struct inode *node,struct file *file)
{
printk("File:%s Function:%s line: %d\n",__FILE__,__FUNCTION__,__LINE__);
mutex_lock(&ds18b20_dev.lock);
if(ds18b20_dev.gpio)
gpiod_put(ds18b20_dev.gpiod);//drop gpio
del_timer(&ds18b20_dev.timer);
cancel_work_sync(&ds18b20_dev.work);
mutex_unlock(&ds18b20_dev.lock);
return 0;
}
static int ds18b20_drv_fasync(int fd,struct file *file,int on)
{
printk("File:%s Function:%s line: %d\n",__FILE__,__FUNCTION__,__LINE__);
if(fasync_helper(fd,file,on,&ds18b20_fasync)>=0)
{
return 0;
}else
{
return -EIO;
}
}
static struct file_operations ds18b20_drv = {
.owner = THIS_MODULE,
.open = ds18b20_drv_open,
.read = ds18b20_drv_read,
.release = ds18b20_drv_close,
.fasync = ds18b20_drv_fasync,
};
static int ds18b20_probe(struct platform_device *pdev)
{
int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
ret = DS18B20_Init(pdev);
if(ret < 0)
{
printk("ds18b20 init fail\r\n");
goto ERROR;
}else
{
printk("ds18b20 init successful\r\n");
}
if(ds18b20_dev.major)
{
ds18b20_dev.devid = MKDEV(ds18b20_dev.major,0);
ret = register_chrdev_region(ds18b20_dev.devid,1,ds18b20_NAME);
printk("register successful\r\n");
}else
{
ret = alloc_chrdev_region(&ds18b20_dev.devid,0,1,ds18b20_NAME);
ds18b20_dev.major = MAJOR(ds18b20_dev.devid);
printk("alloc_chrdev successful\r\n");
}
if(ret<0){
printk("%s Couldn't alloc_chrdev_region,ret = %d\r\n",ds18b20_NAME,ret);
goto ERROR;
}
cdev_init(&ds18b20_dev.cdev,&ds18b20_drv);
ret = cdev_add(&ds18b20_dev.cdev,ds18b20_dev.devid,1);
if(ret<0)
{
printk("Cannot add cdev\n");
goto ERROR;
}
ds18b20_dev.class = class_create(THIS_MODULE,ds18b20_NAME);
if(IS_ERR(ds18b20_dev.class))
{
printk("Cannot create class\n");
goto ERROR;
}else
{
printk("class_create successful\r\n");
}
ds18b20_dev.device = device_create(ds18b20_dev.class,NULL,ds18b20_dev.devid,NULL,ds18b20_NAME);
if(IS_ERR(ds18b20_dev.device))
{
printk("Cannot create device\n");
goto ERROR;
}else
{
printk("device_create successful\r\n");
}
return 0;
ERROR:
gpiod_put(ds18b20_dev.gpiod);//drop gpio
return -1;
}
static int ds18b20_remove(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
cdev_del(&ds18b20_dev.cdev);
unregister_chrdev_region(ds18b20_dev.devid,1);
device_destroy(ds18b20_dev.class,ds18b20_dev.devid);
class_destroy(ds18b20_dev.class);
return 0;
}
static const struct of_device_id my_ds18b20[]=
{
{ .compatible = "myds18b20,ds18b20drv"},
{ },
};
static struct platform_driver ds18b20_driver = {
.probe = ds18b20_probe,
.remove = ds18b20_remove,
.driver = {
.name = "myds18b20",
.of_match_table = my_ds18b20,
},
};
static int __init ds18b20_init(void)
{
int err;
printk("File:%s Function:%s line: %d\n",__FILE__,__FUNCTION__,__LINE__);
/* 1.register led_drv*/
err = platform_driver_register(&ds18b20_driver);
return err;
}
/* 5. Exit Function */
static void __exit ds18b20_exit(void)
{
printk("File:%s Function:%s line: %d\n",__FILE__,__FUNCTION__,__LINE__);
platform_driver_unregister(&ds18b20_driver);
}
/* 6. complete dev information*/
module_init(ds18b20_init);//init
module_exit(ds18b20_exit);//exit
MODULE_LICENSE("GPL");
需要注意的几个点:
1.在读取和写入的函数中都有local_irq_save和local_irq_restore以及mutex相关的函数。前者是关掉所有的中断并保存目前的中断情况,后者是恢复中断情况,也就是开中断。Mutex则是内核锁,两者相结合的目的只有一个:不希望在读写时受到linux硬件/系统层面上的打断。这也很好理解,因为linux上的设备很多,包括你目前在调试的情况下,网卡和串口就是两个绕不开的存在。为什么RST函数中没有加入关中断呢?我没加,是因为我觉得它本身delay的时间很长,长时间关中断可能对其他的设备运行造成问题。以我开发STM32的经验来看,无论是进入临界区还是关中断,最好都是速战速决。
2.读写的时序,我曾尝试按照网上STM32中的例程去写,但是发现怎样读出来的数字都不对,最后无奈还是参考了Linux中内核代码进行初始化和读写才最终读出正确的数值。大神可以再修改修改去尝试尝试,也许是我代码写的不对导致的。
3.原本是想从内核里算个大概,直接传出一个short 类型的数,再在应用层中进行简单的除10得出结果,但在编译过程中出现了Warning的警告,我个人有强迫症,于是追根溯源发现了问题所在。
WARNING: “__aeabi_d2uiz” [xxx.ko] undefined!
WARNING: “__aeabi_dmul” [xxx.ko] undefined!
WARNING: “__aeabi_ddiv” [xxx.ko] undefined!
WARNING: “__aeabi_ui2d” [xxx.ko] undefined!
这种报错是因为内核中使用了浮点数运算,就在得出数乘以0.625那里,因此我不得不修改了传出数的方式,目前是传出直接读到的大端、小端。(高位在前,可能与大多数人写的不一样)
(3)应用层
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
int fd;
struct ds18b20_value_msg {
float temperature;
};
struct ds18b20_value_msg ds18b20_msg;
static void sig_func(int sig)
{
int ret = 0;
int PorN = 0;
unsigned char temperature_get[2];
ret = read(fd,&temperature_get,sizeof(temperature_get));
if(ret == 0)
{
if(temperature_get[0]>7)
{
temperature_get[0] = ~temperature_get[0];
temperature_get[1] = ~temperature_get[1];
PorN = 0;
}else
{
PorN = 1;
}
ds18b20_msg.temperature = (float)((temperature_get[0]<<8)|temperature_get[1])*0.0625f;
if(PorN == 0)ds18b20_msg.temperature = -ds18b20_msg.temperature;
printf("temperature:%f'C\r\n",ds18b20_msg.temperature);
}
}
int main(int argc,char **argv)
{
int ret;
struct pollfd fds[1];
int flags;
char *filename;
if(argc !=2)
{
printf("Usage:%s <dev>\n",argv[0]);
return -1;
}
filename = argv[1];
signal(SIGIO,sig_func);
fd = open(filename,O_RDWR);//WR enbale
if(fd == -1)
{
printf("can not open file %s\n",filename);
return -1;
}
fcntl(fd,F_SETOWN,getpid());
flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC);
while(1)
{
sleep(2);
}
close(fd);
return 0;
}
除了异步通信以外,基本上没什么好说的点。
(4)Makefile
KERN_DIR = //home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ds18b20_app ds18b20_app.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ds18b20_app
obj-m +=ds18b20_my_drv.o
Makefile需要注意的就是你自己的SDK目录要填入,同时你的编译器需要配置好,其他都没什么。
【三】使用
加载模组并运行
insmod ds18b20_my_drv.ko
./ds18b20_app /dev/myds18b20
退出并卸载模组
正常退出,kill掉它
rmmod ds18b20_my_drv.ko
【四】总结
这是一个基于Linux异步通信的DS18B20的驱动应用程序,异步通信通常和中断相配合,适用于实时性要求高的应用。这里采用的是定时器+异步通信的方式,主要复习了platform驱动架构、gpio和pinctrl、设备树、异步通信、定时器。