基于Linux IIO接口的波形采集

 2022-5-16 16:12:29     作者:黄志超    

  我们在《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通道对应设备名称索引号连续采集 
E2AIN_CH1/sys/bus/iio/devices/iio:device0/ in_voltage0_raw0 支持
E3AIN_CH2/sys/bus/iio/devices/iio:device0/ in_voltage1_raw1 支持
E4AIN_CH3/sys/bus/iio/devices/iio:device0/ in_voltage2_raw2 支持
E5AIN_CH4/sys/bus/iio/devices/iio:device0/ in_voltage3_raw3 支持
E6AIN_CH5/sys/bus/iio/devices/iio:device0/ in_voltage4_raw4 支持
E7AIN_CH6/sys/bus/iio/devices/iio:device0/ in_voltage5_raw5 支持
E8AIN_CH7/sys/bus/iio/devices/iio:device0/ in_voltage8_raw8 支持
E9AIN_CH8/sys/bus/iio/devices/iio:device0/ in_voltage9_raw9 支持

表1


  同时因为受到CPU内部时钟分频的限制,所以ESM7200连续采样的速率只能够支持几个特定的值,可参考下表:


支持频率(KHz)5809829676150007541

表2


  关于ESM7200主板和ESMARC通用评估底板的管脚定义介绍,请参考上一篇文章《Linux IIO接口的低成本8通道AD》中的表1和表2。


  整个程序的基本流程如下:


基于Linux IIO接口的波形采集.png


  下面就是具体的代码,首先是获取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软件绘制波形如下:


基于Linux IIO接口的波形采集.png


  多通道和单通道的连续采集在软件上是类似的,区别只在于使能的通道数量不一样。英创公司也即将推出支持基于IIO(Industrial I/O)子系统的多通道连续采样的主板,我们将在后续的文章中继续介绍。


  感兴趣的客户可以联系英创的工程师索要例程的代码。