今天分享一下如何在用戶空間操作IIO設備。IIO設備能實現(xiàn)很多有價值的應用,有興趣的一起來看看~
什么是IIO設備
IIO是 Industrial I/O 的縮寫,是Linux下為工業(yè)輸入輸出所設計的子系統(tǒng)。其主要目的是為模數(shù)轉(zhuǎn)換 (ADC) 或數(shù)模轉(zhuǎn)換 (DAC) 或兩者兼而有之的設備提供設備驅(qū)動支持。Linux下原來有Hwmon以及輸入子系統(tǒng),但這兩個子系統(tǒng)不能很好的涵蓋上面需求。而IIO子系統(tǒng)就是為了填補這一空白而設計的。
什么是Hwmon?針對用于監(jiān)測和控制系統(tǒng)本身的低采樣率傳感器,如風扇速度控制或溫度測量。
而輸入子系統(tǒng)(Input subsystem), 顧名思義,主要抽象人機交互輸入設備(鍵盤、鼠標、觸摸屏)。當然,從這些描述來看,這兩個需求與 IIO 之間存在相當大的重疊。
而IIO子系統(tǒng)則主要管理抽象這些類別的設備:
- 模數(shù)轉(zhuǎn)換器(ADC)
- 加速度計(Accelerometers)
- 陀螺儀(Gyros)
- 慣性測量單元 IMUs)
- 電容數(shù)字轉(zhuǎn)換器 (CDC)
- 壓力傳感器
- 顏色、光線和接近傳感器
- 溫度傳感器
- 磁力計(Magnetometers)
- 數(shù)模轉(zhuǎn)換器(DAC)
- 直接數(shù)字合成器(DDS)
- 鎖相環(huán)(PLL)
- 可變/可編程增益放大器(VGA、PGA)
- ....
此類設備,一般會通過I2C/SPI總線連接到處理器,SPI/I2C通常用來傳輸控制信息,而這些設備的輸入輸出應用數(shù)據(jù)則是通過其他的接口與處理器進行通信,比如采用LVDS。以AD9467為例:
三線制SPI則是控制信息,用于配置ADC的寄存器,而采樣數(shù)據(jù)則是通過LVDS上傳給處理器的。
IIO設備長什么樣?
IIO設備一般也會在/dev下創(chuàng)建一個設備文件,比如:
然后在sys下也會創(chuàng)建文件,比如在/sys/bus下就會創(chuàng)建一條名為iio的總線:
進入到iio之后:
在devices下,則有:
如果有多個iio設備,則會這樣編號:
iio:device0 iio:device1 iio:device2 ......
進入到iio:device0后
則可以看到有mode,scale,of_node,frequeceny等屬性。進到scan_elements下:
scan_elements用于描述掃描樣本的相關(guān)屬性:
- in_voltage0_en:使能控制,設置為1為使能,為0則關(guān)閉
- in_voltage0_index :索引
- in_voltage0_type:采樣類型,本設備該值為le:S16/16>>0,表示是小端有符號整型
buffer中的屬性為:
- data_available :有多少數(shù)量的數(shù)據(jù)準備好了
- enable :激活緩沖區(qū)捕獲,設置為1則開始緩沖區(qū)捕獲
- length:緩沖區(qū)可以存儲的數(shù)據(jù)樣本總數(shù)
- length_align_bytes: 對齊要求,用于設置DMA的對齊要求。
- watermark:此屬性從內(nèi)核版本 4.2 開始可用。它是一個正數(shù),指定阻塞讀取應等待多少個掃描元素。如果使用輪詢,它將阻塞直到達到watermark設定值。只有當watermark大于請求的讀取量時才有意義。它不會影響非阻塞讀取。
在用戶空間讀取iio設備的數(shù)據(jù),有掃描方法或者觸發(fā)方法,這里主要分享一下連續(xù)掃描讀取。
- 首先將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。驅(qū)動就開始工作了。marked complete 表示一次DMA傳輸結(jié)束。
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了
代碼實現(xiàn)
有了前面的介紹,就有具體實現(xiàn)的思路了。這里以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;
//用于緩存數(shù)據(jù)
QByteArray *buffer;
QMutex *mutex;
bool m_started;
};
#endif
實現(xiàn)文件如下:
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.關(guān)閉文件
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();
//完全關(guān)閉線程的寫法
wait();
}
#define TEMP_BUFFER_SIZE 4096
//開辟線程用于讀取
void IIODevice::run()
{
int bytes = 0;
unsigned char devBuf[TEMP_BUFFER_SIZE];
while(1) {
bytes = 4096;//假定內(nèi)部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用于線程間數(shù)據(jù)保護,由于該類是一個線程類,實際使用的時候可能是另一個線程讀取IIO設備的采集數(shù)據(jù),用于傳輸或者后續(xù)應用處理,兩個線程操作同一個QByteArray對象,存在并發(fā)競態(tài),所以需要做互斥訪問保護。
如此一來,外界的物理信號就經(jīng)由ADC芯片,進入Linux內(nèi)核設備iio:device0,再由用戶空間的IIODevice類所例化的對象,傳遞到用戶空間,從而可以在Linux下實現(xiàn)采樣應用了,整個信號傳遞可以用下面這個圖來描述。
總結(jié)一下
IIO設備在很多工業(yè)儀器類非常有用,如前所說,不僅僅能實現(xiàn)AD采樣,還可以實現(xiàn)DA,DDS等很多非常有應用價值的設備。要用好IIO設備,可能涉及到設備驅(qū)動、信號鏈設計、用戶空間應用程序編寫等等技術(shù)。本文以一個AD采樣IIO設備為例,分享一下如何從用戶空間訪問控制IIO設備,希望對有興趣的朋友們有所幫助。