我們?cè)?a href='http://www.huochepiao123.com.cn/article/article20221270.html' target='_blank'>《Linux IIO接口的低成本8通道AD》這篇文章中,已經(jīng)介紹了如何通過程序?qū)IO設(shè)備進(jìn)行單次讀取,接下來我們就介紹波形的實(shí)現(xiàn),關(guān)于IIO子系統(tǒng)的詳細(xì)說明可以參考資料Linux Industrial I/O Subsystem。
本次測(cè)試采用的平臺(tái)還是ESM7200主板加上ESMARC通用評(píng)估底板,波形采集要求AD能夠進(jìn)行等時(shí)的連續(xù)采樣,在IIO子系統(tǒng)的部分需要有較多的設(shè)置,所以推薦直接使用ADI公司提供的libiio庫(kù),這個(gè)庫(kù)文件抽象了和硬件相關(guān)的底層細(xì)節(jié),并提供了簡(jiǎn)單而完整的編程接口,可以省去很多和硬件相關(guān)的設(shè)置,英創(chuàng)公司已經(jīng)將libiio移植到了ESM7200主板上。
在上一篇文章中已經(jīng)介紹過,ESM7200主板的AD是CPU內(nèi)部自帶的資源,在硬件上只能夠支持單通道的連續(xù)采集,并且最高的速率能夠達(dá)到58kSPS。在ESM7200主板中,提供了8路AD資源,用戶可以指定任意一路AD作為連續(xù)采樣的通道,但同一時(shí)間只能夠使能一路通道進(jìn)行連續(xù)采集。
管腳 | AD通道 | 對(duì)應(yīng)設(shè)備名稱 | 索引號(hào) | 連續(xù)采集 |
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
同時(shí)因?yàn)槭艿紺PU內(nèi)部時(shí)鐘分頻的限制,所以ESM7200連續(xù)采樣的速率只能夠支持幾個(gè)特定的值,可參考下表:
支持頻率(KHz) | 58098 | 29676 | 15000 | 7541 |
表2
關(guān)于ESM7200主板和ESMARC通用評(píng)估底板的管腳定義介紹,請(qǐng)參考上一篇文章《Linux IIO接口的低成本8通道AD》中的表1和表2。
整個(gè)程序的基本流程如下:
下面就是具體的代碼,首先是獲取IIO設(shè)備,代碼如下:
static struct iio_context *ctx; struct iio_device *dev; /* 獲取IIO設(shè)備 */ 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子系統(tǒng)中,在連續(xù)采樣時(shí),會(huì)分配一個(gè)可自定義長(zhǎng)度的環(huán)形緩沖區(qū),這個(gè)緩沖區(qū)需要一個(gè)觸發(fā)(trigger)才能夠使用。這里我們可以通過軟件模擬創(chuàng)建一個(gè)trigger,通過下面的命令就可以創(chuàng)建一個(gè)軟件模擬的trigger:
echo 1 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
在ESM7200主板啟動(dòng)后,輸入一次這個(gè)命令即可,可以通過英創(chuàng)公司提供的自啟動(dòng)腳本來執(zhí)行。
創(chuàng)建好trigger后,就可以在程序中為IIO設(shè)備指定這個(gè)trigger了,libiio提供的API可以獲取創(chuàng)建的trigger并設(shè)置到對(duì)應(yīng)的IIO設(shè)備中:
static const char *trigger_name = "sysfstrig1"; struct iio_device *trigger; /* 獲取trigger并設(shè)置 */ 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),并使能連續(xù)采集。因?yàn)镋SM7200主板只支持單通道的連續(xù)采集,所以這里我們只選擇一路來使能:
struct iio_channel *ch; /* 使能對(duì)應(yīng)的IIO通道,可以根據(jù)實(shí)際情況修改成需要的通道 */ 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);
設(shè)置采樣的速率,ESM7200主板上只能夠設(shè)置幾種固定的頻率,可參考表2:
int sample_freq[4] = {58098, 29676, 15000, 7541}; /* 設(shè)置采樣頻率,因?yàn)榉诸l的原因,只能提供幾組固定的采樣頻率 * 可以參考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); }
創(chuàng)建一個(gè)環(huán)形緩沖區(qū)用于存儲(chǔ)連續(xù)采集的數(shù)據(jù),環(huán)形緩沖區(qū)的大小根據(jù)實(shí)際情況自行修改,測(cè)試代碼中設(shè)置為1024:
unsigned int buffer_size = 1024; /* 創(chuàng)建一個(gè)和指定IIO設(shè)備關(guān)聯(lián)的數(shù)據(jù)緩沖區(qū) */ 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; }
在讀取數(shù)據(jù)之前,我們需要先確定需要讀取的通道的樣本大小,以方便我們來判斷緩沖區(qū)內(nèi)的數(shù)據(jù),是否均為需要的樣本數(shù)據(jù):
/* 獲取IIO設(shè)備中當(dāng)前樣本長(zhǎng)度,因?yàn)锳D的精度為12位,獲取到的值應(yīng)該為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; }
接下來就可以開始讀取數(shù)據(jù)了,讀取的時(shí)候可以指定需要讀取的數(shù)據(jù)長(zhǎng)度。讀取的數(shù)據(jù)可以根據(jù)實(shí)際的需求進(jìn)行處理,比如保存到文件中,例子中是直接通過fwrite將數(shù)據(jù)寫到stdout中,其實(shí)就是直接打印到串口終端中:
/* 主循環(huán),讀取數(shù)據(jù)和處理 */ 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中兩個(gè)sample之間的長(zhǎng)度,并判斷和IIO設(shè)備的sample_size是否相等 * 如果相等,說明全部是需要的樣本,直接讀取 * 如果不相等,說明存在其他數(shù)據(jù),需要單獨(dú)解析處理 * */ if (iio_buffer_step(buffer) == sample_size) { /* 獲取buffer中的起始地址和結(jié)束地址,并計(jì)算數(shù)據(jù)長(zhǎng)度 */ 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; /* 數(shù)據(jù)處理,這里通過fwrite函數(shù)將數(shù)據(jù)直接輸出到stdout中 * 用戶可以根據(jù)實(shí)際的情況進(jìn)行處理,比如寫入到記錄文件中 * */ 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); } /* 讀取了指定長(zhǎng)度(num_samples)的數(shù)據(jù)后,釋放資源并退出 */ if (num_samples) { num_samples -= read_len / sample_size; if (!num_samples) quit_all(EXIT_SUCCESS); } else { /* 如果channel存在其他數(shù)據(jù),只能進(jìn)行單獨(dú)處理,一個(gè)一個(gè)樣本的讀取 * 并通過回調(diào)函數(shù)print_sample來處理,同樣回調(diào)函數(shù)中也只是通過fwrite函數(shù) * 將數(shù)據(jù)輸出到sdtout中,用戶可以根據(jù)自己的需求修改 * */ 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); } } } }
在實(shí)際測(cè)試中,我們接入了一個(gè)頻率為1KHz,幅度為0-3.3V的正弦波,使用程序采樣了1024個(gè)點(diǎn)存入了文件中,然后使用gnuplot軟件繪制波形如下:
多通道和單通道的連續(xù)采集在軟件上是類似的,區(qū)別只在于使能的通道數(shù)量不一樣。英創(chuàng)公司也即將推出支持基于IIO(Industrial I/O)子系統(tǒng)的多通道連續(xù)采樣的主板,我們將在后續(xù)的文章中繼續(xù)介紹。
感興趣的客戶可以聯(lián)系英創(chuàng)的工程師索要例程的代碼。
成都英創(chuàng)信息技術(shù)有限公司 028-8618 0660