我正在编写一个需要
的数据采集程序使用select()
读取串行数据(RS232为115200波特),
时间戳(clock_gettime()),
在SPI上读取ADC,
解释它,
通过另一个tty设备发送新数据
循环并重复
ADC目前无关紧要。
在循环结束时,我再次使用select()进行0超时轮询并查看数据是否已经可用,如果是,则意味着我已超限,即I.e。我希望循环在更多数据之前结束,并且在循环开始时的select()阻止并在它到达时立即获取它。
数据应该每5ms到达一次,我的第一个select()超时计算为(5.5ms - 循环时间) - 应该是大约4ms。
我没有超时但很多超支。
检查时间戳显示select()块的持续时间超过超时(但仍然返回> 0)。 看起来select()在超时之前获取数据后会返回。
这可能在1000次重复中发生20次。 可能是什么原因?我该如何解决?
编辑: 这是代码的缩减版本(我做的错误比这更多!)
#include <bcm2835.h> /* for bcm2835_init(), bcm2835_close() */
int main(int argc, char **argv){
int err = 0;
/* Set real time priority SCHED_FIFO */
struct sched_param sp;
sp.sched_priority = 30;
if ( pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) ){
perror("pthread_setschedparam():");
err = 1;
}
/* 5ms between samples on /dev/ttyUSB0 */
int interval = 5;
/* Setup tty devices with termios, both totally uncooked, 8 bit, odd parity, 1 stop bit, 115200baud */
int fd_wc=setup_serial("/dev/ttyAMA0");
int fd_sc=setup_serial("/dev/ttyUSB0");
/* Setup GPIO for SPI, SPI mode, clock is ~1MHz which equates to more than 50ksps */
bcm2835_init();
setup_mcp3201spi();
int collecting = 1;
struct timespec starttime;
struct timespec time;
struct timespec ftime;
ftime.tv_nsec = 0;
fd_set readfds;
int countfd;
struct timeval interval_timeout;
struct timeval notime;
uint16_t p1;
float w1;
uint8_t *datap = malloc(8);
int data_size;
char output[25];
clock_gettime(CLOCK_MONOTONIC, &starttime);
while ( !err && collecting ){
/* Set timeout to (5*1.2)ms - (looptime)ms, or 0 if looptime was longer than (5*1.2)ms */
interval_timeout.tv_sec = 0;
interval_timeout.tv_usec = interval * 1200 - ftime.tv_nsec / 1000;
interval_timeout.tv_usec = (interval_timeout.tv_usec < 0)? 0 : interval_timeout.tv_usec;
FD_ZERO(&readfds);
FD_SET(fd_wc, &readfds);
FD_SET(0, &readfds); /* so that we can quit, code not included */
if ( (countfd=select(fd_wc+1, &readfds, NULL, NULL, &interval_timeout))<0 ){
perror("select()");
err = 1;
} else if (countfd == 0){
printf("Timeout on select()\n");
fflush(stdout);
err = 1;
} else if (FD_ISSET(fd_wc, &readfds)){
/* timestamp for when data is just available */
clock_gettime(CLOCK_MONOTONIC, &time)
if (starttime.tv_nsec > time.tv_nsec){
time.tv_nsec = 1000000000 + time.tv_nsec - starttime.tv_nsec;
time.tv_sec = time.tv_sec - starttime.tv_sec - 1;
} else {
time.tv_nsec = time.tv_nsec - starttime.tv_nsec;
time.tv_sec = time.tv_sec - starttime.tv_sec;
}
/* get ADC value, which is sampled fast so corresponds to timestamp */
p1 = getADCvalue();
/* receive_frame, receiving is slower so do it after getting ADC value. It is timestamped anyway */
/* This function consists of a loop that gets data from serial 1 byte at a time until a 'frame' is collected. */
/* it uses select() with a very short timeout (enough for 1 byte at baudrate) just to check comms are still going */
/* It never times out and behaves well */
/* The interval_timeout is passed because it is used as a timeout for responding an ACK to the device */
/* That select also never times out */
ireceive_frame(&datap, fd_wc, &data_size, interval_timeout.tv_sec, interval_timeout.tv_usec);
/* do stuff with it */
/* This takes most of the time in the loop, about 1.3ms at 115200 baud */
snprintf(output, 24, "%d.%04d,%d,%.2f\n", time.tv_sec, time.tv_nsec/100000, pressure, w1);
write(fd_sc, output, strnlen(output, 23));
/* Check how long the loop took (minus the polling select() that follows */
clock_gettime(CLOCK_MONOTONIC, &ftime);
if ((time.tv_nsec+starttime.tv_nsec) > ftime.tv_nsec){
ftime.tv_nsec = 1000000000 + ftime.tv_nsec - time.tv_nsec - starttime.tv_nsec;
ftime.tv_sec = ftime.tv_sec - time.tv_sec - starttime.tv_sec - 1;
} else {
ftime.tv_nsec = ftime.tv_nsec - time.tv_nsec - starttime.tv_nsec;
ftime.tv_sec = ftime.tv_sec - time.tv_sec - starttime.tv_sec;
}
/* Poll with 0 timeout to check that data hasn't arrived before we're ready yet */
FD_ZERO(&readfds);
FD_SET(fd_wc, &readfds);
notime.tv_sec = 0;
notime.tv_usec = 0;
if ( !err && ( (countfd=select(fd_wc+1, &readfds, NULL, NULL, ¬ime)) < 0 )){
perror("select()");
err = 1;
} else if (countfd > 0){
printf("OVERRUN!\n");
snprintf(output, 25, ",,,%d.%04d\n\n", ftime.tv_sec, ftime.tv_nsec/100000);
write(fd_sc, output, strnlen(output, 24));
}
}
}
return 0;
}
我在串行流上看到的输出的时间戳是相当规则的(通常下一个循环会捕获偏差)。输出片段:
6.1810,0,225.25
6.1867,0,225.25
6.1922,0,225.25
6,2063,0,225.25
,,,0.0010
在这里,高达6.1922s一切正常。下一个样本是6.2063 - 最后一个14.1ms,但它没有超时,也没有从6.1922-6.2063的前一个循环捕获到轮询select()的超限。我的结论是最后一个循环是在接受采样时间,并选择在没有超时的情况下花费太长时间返回-10ms。
,,, 0.0010表示循环之后的循环时间(ftime) - 我应该真正检查出错时的循环时间。我明天会试试。
答案 0 :(得分:3)
传递给select
的超时是一个粗略的下限 - select
被允许延迟你的过程略多于此。特别是,如果进程被不同的进程(上下文切换)或内核中的中断处理抢占,则会延迟您的进程。
以下是Linux手册页对此主题的评论:
请注意,超时间隔将向上舍入为系统时钟 粒度和内核调度延迟意味着阻塞 间隔可能会少量超支。
以下是POSIX标准:
实施可能 还限制了超时间隔的粒度。如果 请求的超时间隔要求比细粒度更精细 实现支持,实际的超时间隔应为 四舍五入到下一个支持的值。
在通用系统上避免这种情况很困难。您将获得合理的结果,特别是在多核系统上,将您的流程锁定在内存中(mlockall
)并将流程设置为实时优先级(使用sched_setscheduler
SCHED_FIFO
,并记得经常睡觉,让其他进程有机会运行)。
更难的方法是使用专用于运行实时代码的实时微控制器。有些人声称reliably sample at 20MHz on fairly cheap hardware使用了这种技术。
答案 1 :(得分:2)
如果struct timeval
的值设置为零,那么select
将不会阻塞,但如果超时参数是NULL指针,它将...
如果timeout参数不是NULL指针,则它指向struct timeval类型的对象,该对象指定最大间隔 等待选择完成。如果超时参数指向 struct timeval类型的对象,其成员为0,select() 不阻止。如果timeout参数是NULL指针,则select() 阻止,直到事件导致其中一个掩码返回 有效(非零)值或直到需要的信号发生 交付。如果时间限制在任何事件发生之前到期 会导致其中一个掩码被设置为非零值,select() 成功完成并返回0.
了解更多 here
编辑来处理评论,并添加新信息:
有几个值得注意的要点。
首先 - 在评论中,有一个建议是将sleep()添加到你的工作循环中。这是一个很好的建议。虽然处理线程入口点,但 stated here 的原因仍然适用,因为您正在实例化一个连续循环。
第二个 - Linux select() 是一个具有有趣的实现历史的系统调用,因此从实现到实现有一系列不同的行为,有些这可能会导致您所看到的意外行为。我不确定Linux Arch Linux 的哪些主要血系来自,但 man7.org page for select() 包括以下两个部分,描述似乎描述了可能导致您遇到的延迟的条件。
校验和错误:
Under Linux, select() may report a socket file descriptor as "ready
for reading", while nevertheless a subsequent read blocks. This could
for example happen when data has arrived but upon examination has wrong
checksum and is discarded.
竞争条件:(介绍并讨论了pselect())
...Suppose the signal handler sets a global flag and returns. Then a test
of this global flag followed by a call of select() could hang indefinitely
if the signal arrived just after the test but just before the call...
鉴于您的观察结果的描述,并且取决于您的Linux版本的实现方式,这些实现功能中的任何一个都可能是一个贡献者。