[Controlador de Linux] El controlador DS18B20 de IMX6ULL es adecuado para la placa 100ask

Prefacio: acabo de aprender a usar el medidor de temperatura y humedad DTH11. Sin embargo, ya sea una comunicación asincrónica escrita por mí mismo o una rutina oficial, encontraré un error de respuesta de error de kernel o tiempo de espera. Puede ser que el tiempo sea a veces correcto ya veces no, este problema queda sin respuesta. Dado que el cableado del módulo de DS18B20 y DTH11 es exactamente el mismo, básicamente no hay diferencia en el establecimiento del árbol de dispositivos, la aplicación de pines y el archivo del dispositivo. Por lo tanto, usaré esto para revisar lo que aprendí sobre el módulo DHT11.
[1] La arquitectura del software
se refiere a las rutinas oficiales de lectura y escritura, pero el orden de ejecución de las rutinas oficiales es:
abrir el archivo → ioctl → kernel → ejecutar la función → ioctl → capa de aplicación → imprimir → finalizar
y no puede hacer una bucle Requisitos de impresión, ejecute una vez para imprimir una vez. Por tanto, si quieres hacer tu propia mejora, la primera es no usar el archivo oficial del dispositivo /dev/ds18b20, y la segunda es poder imprimir en bucle. Hay muchas formas de imprimir circularmente, por ejemplo, la más simple es realizar una lectura circular en el while (1) de la capa de aplicación y leer ds18b20 una vez cada vez que se ejecuta la lectura. Este método es relativamente simple y directo, y no hay ningún problema. Este método es demasiado simple. Para consolidar Linux, aquí se utilizan métodos de comunicación asíncrona y temporizador.
Entonces, ¿cuál es la ejecución del programa? Los detalles son los siguientes:
Establecimiento del programa: establecer singal→abrir→kernel→inicialización→capa de aplicación→esperar señal
Operación del programa: desbordamiento del temporizador→ejecutar trabajo→leer→enviar señal→leer→obtener datos del kernel→procesar y procesar la capa de aplicación Imprimir
[2] código
(1) Árbol de dispositivos
Primero está la definición de pines, que debe configurarse con la herramienta de pines de imx. Lo que configuro aquí es cambiar la fuerza de la unidad a 40 ohmios y luego bajar 100k de manera predeterminada. Las configuraciones de salida y entrada son ninguna, la velocidad es de 100 MHz (2) y las demás son predeterminadas.
definición de pin
Entonces el nodo del dispositivo
nodo de dispositivo
Tenga en cuenta que los pines anteriores usan el último conjunto de gpio del módulo de uso general en la placa 100ask, y la serigrafía que se encuentra arriba es GPIO0, que en realidad es GPIO4_IO19.
Algunos amigos le preguntarán ahora, si agrega este nodo de dispositivo, el dispositivo oficial no usa los mismos pines, entonces generará un archivo de dispositivo cuando se encienda, entonces su árbol de dispositivos obviamente tiene un conflicto IO, cuando el llega el momento de cargar .ko, se debe reportar un error.
De hecho, ya sea DS18B20 o DTH11, en el código del kernel de 100ask, su aplicación de PIN se completa en ioctl, y se requiere el comando XX_IOCINIT para realizar la inicialización del dispositivo y la aplicación de PIN.
Los detalles son los siguientes:
El código del núcleo de ds18b20
Entonces, incluso si genera el archivo del dispositivo, mientras la aplicación no se esté ejecutando, no ocupa IO.
Tiene su conveniencia, por lo que mi programa es similar, pero es solo la inicialización y la aplicación de PIN en la etapa abierta. No sé cómo usar ioctl por el momento. Aprendamos después de la investigación.
Después del cambio, compile y genere dtb y colóquelo en el directorio de inicio de la placa de desarrollo, y tendrá efecto después de reiniciar.
(2) Módulo (.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");

Algunos puntos a tener en cuenta:
1. Hay funciones relacionadas con local_irq_save, local_irq_restore y mutex en las funciones de lectura y escritura. El primero es desactivar todas las interrupciones y guardar la situación de interrupción actual, y el segundo es restaurar la situación de interrupción, es decir, abrir la interrupción. Mutex es un bloqueo del kernel La combinación de los dos tiene un solo propósito: no ser interrumpido a nivel de hardware/sistema de Linux al leer y escribir. Esto también es fácil de entender, porque hay muchos dispositivos en Linux, incluida su situación de depuración actual, la tarjeta de red y el puerto serie son dos existencias inevitables. ¿Por qué no hay interrupción de apagado en la función RST? No lo agregué porque creo que su propio tiempo de retraso es muy largo y el apagado a largo plazo puede causar problemas para otros dispositivos. Según mi experiencia en el desarrollo de STM32, ya sea que se esté ingresando a un área crítica o cerrando una interrupción, lo mejor es tomar una decisión rápida.
2. El tiempo de lectura y escritura, intenté escribir de acuerdo con las rutinas en STM32 en Internet, pero descubrí que los números leídos no son correctos. Al final, no tuve más remedio que referirme al código del kernel. en Linux para inicialización, lectura y escritura, y finalmente leer el valor correcto. Dios puede modificar y volver a intentarlo, tal vez sea por la escritura incorrecta de mi código.
3. Originalmente, quería hacer un cálculo aproximado del kernel, pasar directamente un número de tipo corto y luego simplemente dividirlo por 10 en la capa de la aplicación para obtener el resultado, pero apareció una advertencia de Advertencia durante el proceso de compilación, y yo personalmente Tuve que forzarlo Síntoma, así que rastreé la fuente y encontré el problema.
ADVERTENCIA: “__aeabi_d2uiz” [xxx.ko] indefinido!
ADVERTENCIA: “__aeabi_dmul” [xxx.ko] indefinido!
ADVERTENCIA: “__aeabi_ddiv” [xxx.ko] indefinido!
ADVERTENCIA: “__aeabi_ui2d” [xxx.ko] indefinido!
Este tipo de error se debe al uso de la aritmética de punto flotante en el kernel, donde el número resultante se multiplica por 0.625, por lo que tengo que modificar la forma de enviar el número.Actualmente, el big-endian y little-endian que se leen directamente se envían. (El bit alto viene primero, puede ser diferente de lo que escribe la mayoría de la gente)
(3) Capa de aplicación

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

Aparte de la comunicación asíncrona, básicamente no hay nada que decir.
(4) archivo MAKE

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

A lo que Makefile debe prestar atención es que se debe completar su propio directorio SDK, y se debe configurar el compilador, y nada más.
[3] Use para
cargar el módulo y ejecutar

insmod ds18b20_my_drv.ko
./ds18b20_app /dev/myds18b20

Sal y desinstala el mod.

正常退出,kill掉它
rmmod ds18b20_my_drv.ko

[4] Resumen
Esta es una aplicación de controlador DS18B20 basada en la comunicación asíncrona de Linux.La comunicación asíncrona generalmente coopera con las interrupciones y es adecuada para aplicaciones con altos requisitos de tiempo real. Aquí se adopta el método de temporizador + comunicación asíncrona, y se revisan principalmente la arquitectura del controlador de la plataforma, gpio y pinctrl, el árbol de dispositivos, la comunicación asíncrona y el temporizador.

Supongo que te gusta

Origin blog.csdn.net/qq_32006213/article/details/131130796
Recomendado
Clasificación