我们在《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