今天分享一下如何在用戶空間操作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設備,希望對有興趣的朋友們有所幫助。