Skip to main content
欢迎来到PAWPAW技术文档网站了解更多信息

缓冲接收器

这个例子展示了一个接收器任务,它从外部I/O引脚读取数据并对数据进行缓冲。客户任务可以从缓冲区中读取数据。

image-20231211234600072

通过这种方式对数据进行缓冲,可以使客户任务与接收器任务解耦,从而使得客户任务不受I/O的实时约束。需要注意的是,并非总是需要在输入和客户之间进行缓冲。在实时流应用程序中,最好直接输出给客户端,并设计客户端以跟上数据速率。然而,在以下情况下有时需要进行缓冲:

  • 当输入速率突发时,最大数据速率高于客户端处理能力,但平均数据速率不高时
  • 当客户端的行为是突发性的(这可能是客户端在一个逻辑核心上处理多个事件的情况),因此它会在不规则的间隔中消耗数据。

在这两种情况下,重要的是要确定缓冲区是否可能溢出,以及在这种情况下应用程序将采取什么措施。

接收器通过一个简单的时钟端口接收数据,每次接收32位数据。当它在端口上接收到数据时,它将数据放入FIFO缓冲区并通知客户端。然后客户端可以从这个FIFO中拉取32位的数据。

程序中首先需要定义的是接收器任务与其客户端之间的接口。

interface receiver_if {
// 这个*通知*函数在缓冲区中有数据时通知客户端。由于它是一个通知函数,它是特殊的 - 它不是由客户端调用的,而是由客户端用于在数据准备好时触发事件。
[[ notification ]] slave void data_ready () ;
// 客户端可以使用这个函数从FIFO中拉取数据。它清除了由data_ready()函数引发的通知。
[[ clears_notification ]] unsigned get_data () ;
// 这个函数可以被调用来检查接收器是否在缓冲区中有数据。
// 通常情况下,你不需要使用这个函数来轮询接收器,因为你可以使用data_ready()通知来代替。
unsigned has_data () ;
};

接收器任务接受参数进行设置。它接受一个使用receiver_if接口的接口连接的服务器端,这将与客户端连接。它还接受所需的缓冲区大小以及用于使用外部I/O引脚的端口和时钟块变量。I/O接口需要一个数据引脚、一个时钟块和一个提供readyIn信号的引脚。

I/O协议是一种简单的协议,直接由XMOS硬件响应端口块支持。时钟块提供数据的时钟。当外部驱动的p_ready_in信号被拉高时,它表示数据的开始。之后,数据在时钟的上升沿被驱动到p_data端口。由于数据端口是一个缓冲端口,数据被反序列化为32位块,因此程序将一次从端口接收一个32位字。

image-20231211234817080

这是接收器任务的原型:

void receiver (server interface receiver_if i,
static const unsigned bufsize,
in buffered port:32 p_data,
clock clk,
in port p_ready_in)
{
// ... (任务定义)
}

在任务定义中,首先需要定义本地状态。缓冲区数组为FIFO提供了内存空间。为了实现FIFO,fifo_first_elemfifo_last_elem保存了FIFO中第一个和最后一个元素的索引。在这些索引之间的所有数组元素都保存着数据(FIFO可能会绕过数组的末尾回到开头)。

unsigned buffer[bufsize];
unsigned fifo_first_elem = 0, fifo_last_elem = 0;

任务的初始部分设置了端口块。引脚上的协议是硬件响应端口块支持的协议,因此可以使用库函数对端口进行配置,将其设置为脉冲从属模式(即数据输入由readyIn信号控制)。端口配置函数可以在xs1.h中找到。

configure_in_port_strobed_slave(p_data, p_ready_in, clk);

任务的主体是一个带有select语句的循环。这个select语句将根据端口提供的输入或来自接口连接的客户端的请求来进行响应:

while (1) {
select {
case p :> unsigned data:
// 处理端口输入
// ...
case i.get_data() -> unsigned result:
// 客户端请求获取数据,从FIFO中弹出元素并将其放入返回值'result'中
// ...
case i.has_data() -> unsigned result:
// 客户端请求确定缓冲区中是否有数据,将1或0放入返回值'result'中
// ...
}
}

当端口信号有数据时,任务将其读入data变量。

case p_data :> unsigned data:

为了处理端口输入,程序通过将最后一个元素索引加一(如果需要的话,在缓冲区中绕回)来确定数据应该被添加到FIFO中的位置。

unsigned new_last_elem = fifo_last_elem + 1;
if (new_last_elem == bufsize)
new_last_elem = 0;

如果最后一个元素索引绕回到FIFO的开头,就会发生缓冲区溢出。在这种情况下,任务只是丢弃数据,但这里可以添加不同的溢出处理代码。

if (new_last_elem == fifo_first_elem) {
// 处理缓冲区溢出
break;
}

如果缓冲区中有空间,数据将被插入数组中,并更新最后一个元素索引。

buffer[fifo_last_elem] = data;
fifo_last_elem = new_last_elem;

最后,服务器调用data_ready通知。这向客户端表示缓冲区中有一些数据。

i.data_ready();

以下情况响应了客户端对数据的请求。返回给客户端的返回值被声明为一个变量result。这个情况的主体可以设置这个变量,以将返回值传递给客户端。

case i.get_data() -> unsigned result:

需要从FIFO中提取的数据在数组中,位置由第一个元素索引变量标记。然而,如果这与最后一个元素相同,那么缓冲区就是空的。在这种情况下,任务将值0返回给客户端,但这里可以添加不同的缓冲区下溢处理代码。

if (fifo_first_elem == fifo_last_elem) {
// 处理缓冲区下溢
result = 0;
break;
}

为了从FIFO中弹出一个元素,需要设置result变量,并递增第一个元素索引变量(可能绕回缓冲区数组)。

result = buffer[fifo_first_elem];
fifo_first_elem++;
if (fifo_first_elem == bufsize)
fifo_first_elem = 0;

最后,如果FIFO不为空,任务会重新通知客户端有数据可用。

if (fifo_first_elem != fifo_last_elem)
i.data_ready();

接收器任务处理的最后一个请求是来自客户端的请求,询问数据是否可用。这种情况非常简单,只需要根据FIFO的索引变量返回当前状态。

case i.has_data() -> unsigned result:
// 客户端请求确定缓冲区中是否有数据,将1或0放入返回值'result'中
result = (fifo_first_elem != fifo_last_elem);
break;

任务可以连接到这个接收器任务,并通过接口连接访问数据。例如,下面的消费者任务获取了与接收器的客户端端口连接:

void consumer(client interface receiver_if i) {
// 这个消费者任务可以等待来自接收器任务的数据
while (1) {
select {
case i.data_ready():
unsigned x = i.get_data();
// 在这里处理数据
break;
}
}
}

任务可以使用par语句并通过接口连接在并行运行:

int main() {
interface receiver_if i;
par {
consumer(i);
receiver(i, 1024, p_data, clk, p_ready_in);
}
return 0;
}

完整的例程

#include <xs1.h>
interface receiver_if
{
// 当缓冲区中有数据时,此*通知*函数向客户端发出信号。由于它是一个通知函数,所以它是特殊的 - 它不是由客户端调用的,而是由客户端用于在数据准备就绪时触发事件。
[[notification]] slave void data_ready();
// 客户端可以使用此函数从FIFO中取出数据。它清除了由data_ready()函数引发的通知。
[[clears_notification]] unsigned get_data();
// 可以调用此函数来检查接收器是否在缓冲区中有任何数据。
// 通常情况下,您不需要使用此函数轮询接收器,因为您可以使用data_ready()通知来代替。
unsigned has_data();
};

void receiver(server interface receiver_if i,
static const unsigned bufsize,
in buffered port : 32 p_data,
clock clk,
in port p_ready_in)
{
unsigned buffer[bufsize];
unsigned fifo_first_elem = 0, fifo_last_elem = 0;
configure_in_port_strobed_slave(p_data, p_ready_in, clk);
while (1)
{
select
{
case p_data:
unsigned data :> unsigned new_last_elem = fifo_last_elem + 1;
if (new_last_elem == bufsize)
new_last_elem = 0;
if (new_last_elem == fifo_first_elem)
{
// 处理缓冲区溢出
break;
}
buffer[fifo_last_elem] = data;
fifo_last_elem = new_last_elem;
i.data_ready();
break;
case i.get_data()->unsigned result:
if (fifo_first_elem == fifo_last_elem)
{
// 处理缓冲区下溢
result = 0;
break;
}
result = buffer[fifo_first_elem];
fifo_first_elem++;
if (fifo_first_elem == bufsize)
fifo_first_elem = 0;
if (fifo_first_elem != fifo_last_elem)
i.data_ready();
break;
case i.has_data()->unsigned result:
// 客户端请求确定缓冲区中是否有数据,将1或0放入返回值'result'
result = (fifo_first_elem != fifo_last_elem);
break;
}
}
}

void consumer(client interface receiver_if i)
{
// 这个消费者任务可以等待来自接收器任务的数据
while (1)
{
select
{
case i.data_ready():
unsigned x = i.get_data();
// 在这里处理数据
break;
}
}
}

in buffered port : 32 p_data = XS1_PORT_1A;
clock clk = XS1_CLKBLK_1;
in port p_ready_in = XS1_PORT_1B;

int main()
{
interface receiver_if i;
par
{
consumer(i);
receiver(i, 1024, p_data, clk, p_ready_in);
}
return 0;
}