ADC/DAC芯片pcf8591的linux驱动的几种实现方案

电子说

1.3w人已加入

描述

下面是PCF8591的介绍:

PCF8591 是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。PCF8591 具有 4 个模拟输入、1 个模拟输出和 1个串行 I2C 总线接口。PCF8591 的 3 个地址引脚 A0, A1 和 A2 可用于硬件地址编程,允许在同个 I2C 总线上接入 8 个 PCF8591 器件,而无需额外的硬件。在 PCF8591 器件上输入输出的地址、控制和数据信号都是通过双线双向 I2C 总线以串行的方式进行传输。

下图是PCF8591的框图

DAC芯片

本篇讨论其linux驱动的以下几种实现方式

  • Hardware Monitoring framework (hwmon)
  • 杂项字符设备 (misc)
  • 通过memmap, IOmap在用户空间直接操作processor i2c
  • Industrial IO framework (iio)

通过hwmon框架实现

在linux中已经支持通过hwmon框架方式实现pcf8591驱动的代码。

下面只贴出部分代码,具体请参见链接

static int pcf8591_probe(struct i2c_client *client,
       const struct i2c_device_id *id)
{
  struct pcf8591_data *data;
  int err;


  data = devm_kzalloc(&client- >dev, sizeof(struct pcf8591_data),
          GFP_KERNEL);
  if (!data)
    return -ENOMEM;


  i2c_set_clientdata(client, data);
  mutex_init(&data- >update_lock);


  /* Initialize the PCF8591 chip */
  pcf8591_init_client(client);


  /* Register sysfs hooks */
  err = sysfs_create_group(&client- >dev.kobj, &pcf8591_attr_group);
  if (err)
    return err;


  /* Register input2 if not in "two differential inputs" mode */
  if (input_mode != 3) {
    err = device_create_file(&client- >dev, &dev_attr_in2_input);
    if (err)
      goto exit_sysfs_remove;
  }


  /* Register input3 only in "four single ended inputs" mode */
  if (input_mode == 0) {
    err = device_create_file(&client- >dev, &dev_attr_in3_input);
    if (err)
      goto exit_sysfs_remove;
  }


  data- >hwmon_dev = hwmon_device_register(&client- >dev);
  if (IS_ERR(data- >hwmon_dev)) {
    err = PTR_ERR(data- >hwmon_dev);
    goto exit_sysfs_remove;
  }


  return 0;


exit_sysfs_remove:
  sysfs_remove_group(&client- >dev.kobj, &pcf8591_attr_group_opt);
  sysfs_remove_group(&client- >dev.kobj, &pcf8591_attr_group);
  return err;
}








static const struct i2c_device_id pcf8591_id[] = {
  { "pcf8591", 0 },
  { }
};
MODULE_DEVICE_TABLE(i2c, pcf8591_id);


static struct i2c_driver pcf8591_driver = {
  .driver = {
    .name  = "pcf8591",
  },
  .probe    = pcf8591_probe,
  .remove    = pcf8591_remove,
  .id_table  = pcf8591_id,
};


static int __init pcf8591_init(void)
{
  if (input_mode < 0 || input_mode > 3) {
    pr_warn("invalid input_mode (%d)n", input_mode);
    input_mode = 0;
  }
  return i2c_add_driver(&pcf8591_driver);
}


static void __exit pcf8591_exit(void)
{
  i2c_del_driver(&pcf8591_driver);
}


MODULE_AUTHOR("Aurelien Jarno < aurelien@aurel32.net >");
MODULE_DESCRIPTION("PCF8591 driver");
MODULE_LICENSE("GPL");


module_init(pcf8591_init);
module_exit(pcf8591_exit);

DAC芯片

在编译内核modules需要把模块添加进去,或者单独编译好模块再复制到树莓派版中。

下图为通过menuconfig配置模块: 图中“Philips PCF8591 ADC/DAC"选项

DAC芯片

安装模块驱动

sudo insmod pcf8591.ko

安装后可以在sysfs下查看,/sys/bus/i2c/drivers下多了一个pcf8591, 见下图:

DAC芯片

文中提到了hwmon设备常通过i2cbus探测的方式。但是我们却可以看到pcf8591.c中并没有detect函数:

static struct i2c_driver pcf8591_driver = {
  .driver = {
    .name  = "pcf8591",
  },
  .probe    = pcf8591_probe,
  .remove    = pcf8591_remove,
  .id_table  = pcf8591_id,
};

这是因为pcf8591没有“制造商和设备ID寄存器”。

因此我们可以通过方式1,方式2和方式4

  • 方法1:静态声明I2C设备
    • 通过devicetree声明I2C设备
    • 在板级文件中声明I2C设备
  • 方法2:显式实例化设备
  • 方法3:对某些设备进行I2C总线探测
  • 方法4:从用户空间实例化

为了避免麻烦,这里就选择方法4。

先查看地址,然后通过sysfs, new_device的方式实例化(参见上篇)

DAC芯片

DAC芯片

实例化成功后,如上图,便可以通过sysfs来获取adc的值,以及设置dac的值。

cat /sys/class/hwmon/hwmon1/device/in0_input

**misc驱动框架实现 **

misc的意思是混合、杂项的,因此MISC驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用MISC驱动。misc设备也是一个字符设备,在misc的初始化函数中注册了一个字符设备,主设备号为MISC_MAJOR (10)。

驱动分为两部分:i2c设备驱动,i2c设备显式实例化

在i2c设备显示实例化中调用,i2c_new_probed_device函数

i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL);

在某些设备中,比如需要后期插入i2c模块板,而预先不知道I2C总线的编号,则可以显式地实例化I2C设备。这是通过填充结构体i2c_board_info并调用i2c_new_client_device()来完成。

如下图,分别将,i2c设备驱动,i2c设备显式实例化,编译成pcf8591.ko 和pcf8591_dev.ko, 然后通过Insmod加载

DAC芯片

再另外编写应用代码,open misc设备,进行读写操作。

通过memmap, IOmap在用户空间直接操作processor i2c

树莓派的一些库,如bcm2835, wiringpi等

/* Open the master /dev/mem device */
      if ((memfd = open("/dev/mem", O_RDWR | O_SYNC) ) < 0)
    {
      fprintf(stderr, "bcm2835_init: Unable to open /dev/mem: %sn",
          strerror(errno)) ;
      goto exit;
    }


      /* Base of the peripherals block is mapped to VM */
      bcm2835_peripherals = mapmem("gpio", bcm2835_peripherals_size, memfd, (off_t)bcm2835_peripherals_base);
      if (bcm2835_peripherals == MAP_FAILED) goto exit;


      /* Now compute the base addresses of various peripherals,
      // which are at fixed offsets within the mapped peripherals block
      // Caution: bcm2835_peripherals is uint32_t*, so divide offsets by 4
      */
      bcm2835_gpio = bcm2835_peripherals + BCM2835_GPIO_BASE/4;
      bcm2835_pwm  = bcm2835_peripherals + BCM2835_GPIO_PWM/4;
      bcm2835_clk  = bcm2835_peripherals + BCM2835_CLOCK_BASE/4;
      bcm2835_pads = bcm2835_peripherals + BCM2835_GPIO_PADS/4;
      bcm2835_spi0 = bcm2835_peripherals + BCM2835_SPI0_BASE/4;
      bcm2835_bsc0 = bcm2835_peripherals + BCM2835_BSC0_BASE/4; /* I2C */
      bcm2835_bsc1 = bcm2835_peripherals + BCM2835_BSC1_BASE/4; /* I2C */
      bcm2835_st   = bcm2835_peripherals + BCM2835_ST_BASE/4;
      bcm2835_aux  = bcm2835_peripherals + BCM2835_AUX_BASE/4;
      bcm2835_spi1 = bcm2835_peripherals + BCM2835_SPI1_BASE/4;

效率比较

下图为使用misc设备驱动,在应用代码中进行1ms采样时,cpu的占用率5.2%。

DAC芯片

而使用bcm2835库进行10ms周期的采样时,cpu的占用率52.1%。

DAC芯片

IIO

IIO 全称是 Industrial I/O,当你使用的传感器本质是 ADC 或 DAC 器件的时候,可以优先考虑使用 IIO 驱动框架。比如常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个 ADC,内部 ADC 将原始的模拟数据转换为数字量,然后通过其他的通信接口,比如 IIC、SPI 等传输给 SOC。Linux 内核为了管理这些日益增多的 ADC 类传感器,特地推出了 IIO 子系统。

iio 支持多种标准的 Linux 设备访问接口:char device, sysfs, configfs, debugfs。

IIO的4种接口

1). sysfs interface

  • /sys/bus/iio/devices/iio:deviceX;
  • 可用于配置 /dev/iio:deviceX 接口的 events / data
  • 可用于轮循的方式低速地直接读/写 IIO 设备;
  • Documentation/ABI/testing/sysfs-bus-iio;

2). character device

  • /dev/iio:deviceX,该接口在 IIO 子系统里是可选非必要的;
  • 标准的文件 IO API: open(), read(), write(), close().
  • 用于读取 events 和 data;

3). configfs

  • 用于配置额外的 IIO 特性,例如:软件 triggers 或者 hrtimer triggers;
  • 详细说明:
    • Documentation/ABI/testing/configfs-iio;
    • Documentation/iio/iio_configfs.txt;

4). debugfs

  • 一些调试功能,例如 direct_reg_access 节点可用于读写寄存器;

具体的代码实现便不再花时间了,可以参考drivers下iio部分代码。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分