手把手教如何用Linux下IIO设备(附代码)

今天分享一下如何在用户空间操作IIO设备。IIO设备能实现很多有价值的应用,有兴趣的一起来看看~

什么是IIO设备

IIO是 Industrial I/O 的缩写,是Linux下为工业输入输出所设计的子系统。其主要目的是为模数转换 (ADC) 或数模转换 (DAC) 或两者兼而有之的设备提供设备驱动支持。Linux下原来有Hwmon以及输入子系统,但这两个子系统不能很好的涵盖上面需求。而IIO子系统就是为了填补这一空白而设计的。

什么是Hwmon?针对用于监测和控制系统本身的低采样率传感器,如风扇速度控制或温度测量。

而输入子系统(Input subsystem), 顾名思义,主要抽象人机交互输入设备(键盘、鼠标、触摸屏)。当然,从这些描述来看,这两个需求与 IIO 之间存在相当大的重叠。

而IIO子系统则主要管理抽象这些类别的设备:

  • 模数转换器(ADC)
  • 加速度计(Accelerometers)
  • 陀螺仪(Gyros)
  • 惯性测量单元 IMUs)
  • 电容数字转换器 (CDC)
  • 压力传感器
  • 颜色、光线和接近传感器
  • 温度传感器
  • 磁力计(Magnetometers)
  • 数模转换器(DAC)
  • 直接数字合成器(DDS)
  • 锁相环(PLL)
  • 可变/可编程增益放大器(VGA、PGA)
  • ....

此类设备,一般会通过I2C/SPI总线连接到处理器,SPI/I2C通常用来传输控制信息,而这些设备的输入输出应用数据则是通过其他的接口与处理器进行通信,比如采用LVDS。以AD9467为例:

三线制SPI则是控制信息,用于配置ADC的寄存器,而采样数据则是通过LVDS上传给处理器的。

IIO设备长什么样?

IIO设备一般也会在/dev下创建一个设备文件,比如:

然后在sys下也会创建文件,比如在/sys/bus下就会创建一条名为iio的总线:

进入到iio之后:

在devices下,则有:

如果有多个iio设备,则会这样编号:

iio:device0  iio:device1  iio:device2  ......

进入到iio:device0后

则可以看到有mode,scale,of_node,frequeceny等属性。进到scan_elements下:

scan_elements用于描述扫描样本的相关属性:

  • in_voltage0_en:使能控制,设置为1为使能,为0则关闭
  • in_voltage0_index :索引
  • in_voltage0_type:采样类型,本设备该值为le:S16/16>>0,表示是小端有符号整型

buffer中的属性为:

  • data_available :有多少数量的数据准备好了
  • enable :激活缓冲区捕获,设置为1则开始缓冲区捕获
  • length:缓冲区可以存储的数据样本总数
  • length_align_bytes: 对齐要求,用于设置DMA的对齐要求。
  • watermark:此属性从内核版本 4.2 开始可用。它是一个正数,指定阻塞读取应等待多少个扫描元素。如果使用轮询,它将阻塞直到达到watermark设定值。只有当watermark大于请求的读取量时才有意义。它不会影响非阻塞读取。

在用户空间读取iio设备的数据,有扫描方法或者触发方法,这里主要分享一下连续扫描读取。

  • 首先将scan_elements中in_voltage0_en设置为1
root@idaq:/sys/bus/iio/devices/iio:device0/scan_elements# echo 1 > in_voltage0_en
root@idaq:/sys/bus/iio/devices/iio:device0/scan_elements# cat in_voltage0_en
1
  • 然后将buffer中的enable,也设置为1。驱动就开始工作了。marked complete 表示一次DMA传输结束。
root@idaq:/sys/bus/iio/devices/iio:device0/buffer# echo 1 > enable
iio iio:device0: iio_dma_buffer_enable
dma-axi-dmac 43c20000.axi_dmac: vchan (ptrval): txd (ptrval)[2]: submitted
dma-axi-dmac 43c20000.axi_dmac: txd (ptrval)[2]: marked complete
  • 然后就可以标准文件操作直接读取/dev/iio:device0了

代码实现

有了前面的介绍,就有具体实现的思路了。这里以QT为例,设计一个类先进行文件操作,然后不停读取文件即可。

下面是头文件:

#ifndef _IIO_DEVICE_H_
#define _IIO_DEVICE_H_
#include <QObject>
#include <QMutex>
#include <QThread>
//继承自QThread
class IIODevice : public QThread
{
    Q_OBJECT
public:
    explicit IIODevice(QObject *parent = 0,
                       QString fName="iio:device0",
                       QByteArray *pBuffer=nullptr,
                       QMutex *pMutex=nullptr);
    ~IIODevice();

    int  openDevice(void);
    int  closeDevice(void);
    void startSample();
    void stopSample(void);
    int  readDevice(unsigned char * buffer,int size);
    bool isStarted(void);

public slots:

protected:
    void run();

private:
    int fd;
    QString fileName;
    bool m_abort;
    //用于缓存数据
    QByteArray *buffer;
    QMutex *mutex;
    bool m_started;
};

#endif

实现文件如下:

IIODevice::IIODevice(QObject *parent,QString fName,QByteArray *pBuffer,QMutex *pMutex) :
    QThread(parent),
    fileName(fName),
    buffer(pBuffer),
    mutex(pMutex)
{  
    m_abort = false;
    m_started = false;  
}

int IIODevice::openDevice(void)
{
    //使能in_voltage0_en
    QString cmdStr = "echo 1 > /sys/bus/iio/devices/"+fileName+"/scan_elements/in_voltage0_en";
    QByteArray cmdBuffer = cmdStr.toLocal8Bit();
    char * cmd = cmdBuffer.data();
    system(cmd);

    //使能采集
    cmdStr = "echo 1 > /sys/bus/iio/devices/"+fileName+"/buffer/enable";
    cmdBuffer = cmdStr.toLocal8Bit();
    cmd = cmdBuffer.data();
    system(cmd);

    QString path = "/dev/"+fileName;
    QFile * file = new QFile();
    file->setFileName(path);
    if( !file->exists() ){
        qDebug() << "file does not exist";
        return -1;
    }

    //O_NONBLOCK方式打开文件
    fd = open(path.toUtf8().data(), O_RDONLY|O_NONBLOCK);
    if( fd==-1 ){
        qDebug() << "can not open file";
        return -2;
    }

    return 0;
}

int IIODevice::closeDevice(void)
{
    QString cmdStr;
    QByteArray cmdBuffer;
    //1.禁止采样
    cmdStr = "echo 0 > /sys/bus/iio/devices/"+fileName+"/buffer/enable";
    cmdBuffer = cmdStr.toLocal8Bit();
    char * cmd = cmdBuffer.data();
    system(cmd);

    //2.禁止in_voltage0_en
    cmdStr = "echo 0 > /sys/bus/iio/devices/"+fileName+"/scan_elements/in_voltage0_en";
    cmdBuffer = cmdStr.toLocal8Bit();
    cmd = cmdBuffer.data();
    
    system(cmd);
    //3.关闭文件
    if(fd) {
     close(fd);
     fd = -1;    
    }   
  
    return 0;
}

int IIODevice::readDevice(unsigned char * buffer,int size)
{
    int length = 0;
    length = read(fd,buffer,size);
    return length;
}

IIODevice::~IIODevice(void)
{
  if(fd)
    close(fd);
}

void IIODevice::startSample()
{
  mutex->lock();
  m_abort = false;
  m_started = true;
  if(!buffer->isEmpty())
     buffer->remove(0,buffer->size());
  mutex->unlock();
  openDevice();
  start();
}

bool IIODevice::isStarted()
{
  return m_started;
}

void IIODevice::stopSample()
{
  mutex->lock();
  m_abort = true;
  m_started = false;
  closeDevice();
  mutex->unlock();
    
  //完全关闭线程的写法
  wait();
}

#define TEMP_BUFFER_SIZE 4096
//开辟线程用于读取
void IIODevice::run()
{
  int bytes = 0;
  unsigned char devBuf[TEMP_BUFFER_SIZE];
  while(1) {
      bytes = 4096;//假定内部buffer为4096
      while(bytes==4096)
      {
        bytes = readDevice(devBuf,TEMP_BUFFER_SIZE);
        mutex->lock();
        buffer->append((const char *)devBuf,bytes);
        mutex->unlock();
      }

      if (m_abort)
        return;
      //周期性扫描
      usleep(600);
  }
}

QMutex用于线程间数据保护,由于该类是一个线程类,实际使用的时候可能是另一个线程读取IIO设备的采集数据,用于传输或者后续应用处理,两个线程操作同一个QByteArray对象,存在并发竞态,所以需要做互斥访问保护。

如此一来,外界的物理信号就经由ADC芯片,进入Linux内核设备iio:device0,再由用户空间的IIODevice类所例化的对象,传递到用户空间,从而可以在Linux下实现采样应用了,整个信号传递可以用下面这个图来描述。

总结一下

IIO设备在很多工业仪器类非常有用,如前所说,不仅仅能实现AD采样,还可以实现DA,DDS等很多非常有应用价值的设备。要用好IIO设备,可能涉及到设备驱动、信号链设计、用户空间应用程序编写等等技术。本文以一个AD采样IIO设备为例,分享一下如何从用户空间访问控制IIO设备,希望对有兴趣的朋友们有所帮助。

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 1
收藏 3
关注 77
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧