【Linux驱动】IMX6ULL的DS18B20驱动程序适用于100ask板

前言:刚刚学会了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的命令才会进行引脚的申请与设备初始化。
详情如下:
ds18b20的内核代码
所以,即便它生成了设备文件,但只要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、设备树、异步通信、定时器。

猜你喜欢

转载自blog.csdn.net/qq_32006213/article/details/131130796