我們在《Linux IIO接口的低成本8通道AD》這篇文章中,已經介紹了如何通過程序對IIO設備進行單次讀取,接下來我們就介紹波形的實現,關于IIO子系統的詳細說明可以參考資料Linux Industrial I/O Subsystem。
本次測試采用的平臺還是ESM7200主板加上ESMARC通用評估底板,波形采集要求AD能夠進行等時的連續采樣,在IIO子系統的部分需要有較多的設置,所以推薦直接使用ADI公司提供的libiio庫,這個庫文件抽象了和硬件相關的底層細節,并提供了簡單而完整的編程接口,可以省去很多和硬件相關的設置,英創公司已經將libiio移植到了ESM7200主板上。
在上一篇文章中已經介紹過,ESM7200主板的AD是CPU內部自帶的資源,在硬件上只能夠支持單通道的連續采集,并且最高的速率能夠達到58kSPS。在ESM7200主板中,提供了8路AD資源,用戶可以指定任意一路AD作為連續采樣的通道,但同一時間只能夠使能一路通道進行連續采集。
管腳 | AD通道 | 對應設備名稱 | 索引號 | 連續采集 |
E2 | AIN_CH1 | /sys/bus/iio/devices/iio:device0/ in_voltage0_raw | 0 | 支持 |
E3 | AIN_CH2 | /sys/bus/iio/devices/iio:device0/ in_voltage1_raw | 1 | 支持 |
E4 | AIN_CH3 | /sys/bus/iio/devices/iio:device0/ in_voltage2_raw | 2 | 支持 |
E5 | AIN_CH4 | /sys/bus/iio/devices/iio:device0/ in_voltage3_raw | 3 | 支持 |
E6 | AIN_CH5 | /sys/bus/iio/devices/iio:device0/ in_voltage4_raw | 4 | 支持 |
E7 | AIN_CH6 | /sys/bus/iio/devices/iio:device0/ in_voltage5_raw | 5 | 支持 |
E8 | AIN_CH7 | /sys/bus/iio/devices/iio:device0/ in_voltage8_raw | 8 | 支持 |
E9 | AIN_CH8 | /sys/bus/iio/devices/iio:device0/ in_voltage9_raw | 9 | 支持 |
表1
同時因為受到CPU內部時鐘分頻的限制,所以ESM7200連續采樣的速率只能夠支持幾個特定的值,可參考下表:
支持頻率(KHz) | 58098 | 29676 | 15000 | 7541 |
表2
關于ESM7200主板和ESMARC通用評估底板的管腳定義介紹,請參考上一篇文章《Linux IIO接口的低成本8通道AD》中的表1和表2。
整個程序的基本流程如下:
下面就是具體的代碼,首先是獲取IIO設備,代碼如下:
static struct iio_context *ctx; struct iio_device *dev; /* 獲取IIO設備 */ ctx = iio_create_context_from_uri("local:"); dev = iio_context_get_device(ctx, 0); if (!dev) { fprintf(stderr, "Device not found\n"); iio_context_destroy(ctx); return EXIT_FAILURE; }
在IIO子系統中,在連續采樣時,會分配一個可自定義長度的環形緩沖區,這個緩沖區需要一個觸發(trigger)才能夠使用。這里我們可以通過軟件模擬創建一個trigger,通過下面的命令就可以創建一個軟件模擬的trigger:
echo 1 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
在ESM7200主板啟動后,輸入一次這個命令即可,可以通過英創公司提供的自啟動腳本來執行。
創建好trigger后,就可以在程序中為IIO設備指定這個trigger了,libiio提供的API可以獲取創建的trigger并設置到對應的IIO設備中:
static const char *trigger_name = "sysfstrig1"; struct iio_device *trigger; /* 獲取trigger并設置 */ if (trigger_name) { struct iio_device *trigger = iio_context_find_device( ctx, trigger_name); if (!trigger) { fprintf(stderr, "Trigger %s not found\n", trigger_name); iio_context_destroy(ctx); return EXIT_FAILURE; } if (!iio_device_is_trigger(trigger)) { fprintf(stderr, "Specified device is not a trigger\n"); iio_context_destroy(ctx); return EXIT_FAILURE; } ret = iio_device_set_trigger(dev, trigger); if (ret < 0) { char buf[256]; iio_strerror(-(int)ret, buf, sizeof(buf)); fprintf(stderr, "set trigger failed : %s\n", buf); } }
然后就需要獲取我們要采集的通道(channel),并使能連續采集。因為ESM7200主板只支持單通道的連續采集,所以這里我們只選擇一路來使能:
struct iio_channel *ch; /* 使能對應的IIO通道,可以根據實際情況修改成需要的通道 */ ch = iio_device_get_channel(dev, 9); if (!iio_channel_is_scan_element(ch) || iio_channel_is_output(ch)) { printf("Can not get channel %d\n", ch); return -1; } iio_channel_enable(ch);
設置采樣的速率,ESM7200主板上只能夠設置幾種固定的頻率,可參考表2:
int sample_freq[4] = {58098, 29676, 15000, 7541}; /* 設置采樣頻率,因為分頻的原因,只能提供幾組固定的采樣頻率 * 可以參考sample_freq變量 * */ freq = malloc(10); sprintf(freq, "%d", sample_freq[0]); ret = iio_channel_attr_write(ch, attr, freq); if (ret <= 0) { printf("ERROR: while writing '%s' with '%s'\n", attr, freq); }
創建一個環形緩沖區用于存儲連續采集的數據,環形緩沖區的大小根據實際情況自行修改,測試代碼中設置為1024:
unsigned int buffer_size = 1024; /* 創建一個和指定IIO設備關聯的數據緩沖區 */ buffer = iio_device_create_buffer(dev, buffer_size, false); if (!buffer) { char buf[256]; iio_strerror(errno, buf, sizeof(buf)); fprintf(stderr, "Unable to allocate buffer: %s\n", buf); iio_context_destroy(ctx); return EXIT_FAILURE; }
在讀取數據之前,我們需要先確定需要讀取的通道的樣本大小,以方便我們來判斷緩沖區內的數據,是否均為需要的樣本數據:
/* 獲取IIO設備中當前樣本長度,因為AD的精度為12位,獲取到的值應該為2(byte) */ sample_size = iio_device_get_sample_size(dev); /* Zero isn't normally an error code, but in this case it is an error */ if (sample_size == 0) { fprintf(stderr, "Unable to get sample size, returned 0\n"); iio_context_destroy(ctx); return EXIT_FAILURE; } else if (sample_size < 0) { char buf[256]; iio_strerror(errno, buf, sizeof(buf)); fprintf(stderr, "Unable to get sample size : %s\n", buf); iio_context_destroy(ctx); return EXIT_FAILURE; }
接下來就可以開始讀取數據了,讀取的時候可以指定需要讀取的數據長度。讀取的數據可以根據實際的需求進行處理,比如保存到文件中,例子中是直接通過fwrite將數據寫到stdout中,其實就是直接打印到串口終端中:
/* 主循環,讀取數據和處理 */ while(app_running) { /* 獲取通道更多樣本 */ ret = iio_buffer_refill(buffer); if (ret < 0) { if (app_running) { char buf[256]; iio_strerror(-(int)ret, buf, sizeof(buf)); fprintf(stderr, "Unable to refill buffer: %s\n", buf); break; } } /* 獲取channel中兩個sample之間的長度,并判斷和IIO設備的sample_size是否相等 * 如果相等,說明全部是需要的樣本,直接讀取 * 如果不相等,說明存在其他數據,需要單獨解析處理 * */ if (iio_buffer_step(buffer) == sample_size) { /* 獲取buffer中的起始地址和結束地址,并計算數據長度 */ void *start = iio_buffer_start(buffer); size_t read_len, len = (intptr_t) iio_buffer_end(buffer) - (intptr_t) start; if (num_samples && len > num_samples * sample_size) len = num_samples * sample_size; /* 數據處理,這里通過fwrite函數將數據直接輸出到stdout中 * 用戶可以根據實際的情況進行處理,比如寫入到記錄文件中 * */ for (read_len = len; len; ) { size_t nb = fwrite(start, 1, len, stdout); if (!nb) goto err_destroy_buffer; len -= nb; start = (void *)((intptr_t) start + nb); } /* 讀取了指定長度(num_samples)的數據后,釋放資源并退出 */ if (num_samples) { num_samples -= read_len / sample_size; if (!num_samples) quit_all(EXIT_SUCCESS); } else { /* 如果channel存在其他數據,只能進行單獨處理,一個一個樣本的讀取 * 并通過回調函數print_sample來處理,同樣回調函數中也只是通過fwrite函數 * 將數據輸出到sdtout中,用戶可以根據自己的需求修改 * */ ret = iio_buffer_foreach_sample(buffer, print_sample, NULL); if (ret < 0) { char buf[256]; iio_strerror(-(int)ret, buf, sizeof(buf)); fprintf(stderr, "buffer processing failed : %s\n", buf); } } } }
在實際測試中,我們接入了一個頻率為1KHz,幅度為0-3.3V的正弦波,使用程序采樣了1024個點存入了文件中,然后使用gnuplot軟件繪制波形如下:
多通道和單通道的連續采集在軟件上是類似的,區別只在于使能的通道數量不一樣。英創公司也即將推出支持基于IIO(Industrial I/O)子系統的多通道連續采樣的主板,我們將在后續的文章中繼續介紹。
感興趣的客戶可以聯系英創的工程師索要例程的代碼。
成都英創信息技術有限公司 028-8618 0660