【技术专辑】UART双缓冲技术:中断友好

UART是一个很好的传输协议,适用于业余爱好和专业项目,但在时间要求严格的系统中,UART可能很棘手。

 

UART(通用异步接收传输)是一种流行的协议,用于微控制器与其他微控制器和计算机之间的接口。使用高速微控制器的低波特率设计通常不会出现UART问题。但是,在更高的速度下或者如果微执行器执行许多任务(例如在ZBM项目中),可能会出现严重的问题,包括错过的字节和这些字节的顺序。即使在中断驱动的系统中,订单也很难保留。本文将解释最近为此类问题开发的一种称为UART双缓冲的技术。

 

注:技术术语(推动和弹出)

 

对于那些不熟悉堆栈的人来说,推送数据意味着将数据放到缓冲区上,弹出是指从缓冲区中删除数据。

 

问题解释

 

想象一下执行一项任务的中断例程:在通过UART接收字节时,它将字节存储到缓冲区数组中并递增totalBytes计数器。

 

                    isr_routine()

{

if(UART_RECEIVE)

{

buffer[totalBytes] = UART_GET;

totalBytes ++;

}

}

 

因此,当这个数组填满数据时,我们的主程序将从该缓冲区中取出字节,然后从totalBytes计数器中减去。

 

                    program()

{

do{

if(totalBytes > 0)

{

cout << buffer[totalBytes];

totalBytes --;

}

 

}while(1);

}

    

只要主程序从缓冲区中取出字节比发送字节的速度快,就会保留字节的顺序。但是,如果程序不能足够快地取消字节并且在这个循环的中途添加字节(记住,中断优先于主循环),那么字节的顺序将会丢失。但是什么是“顺序”字节“?

 

字节顺序

 

字节的顺序可以被认为是时间轴,其中字节按时间顺序排序。收到的第一个字节必须是要处理的第一个数据,接收的最后一个字节必须是要处理的最后一个字节。所以在这个例子中,如果一个设备通过UART发送“Hello”并且我们的主程序足够快,cout的输出(假设我们有一个显示)也应该是“Hello”而不是“elHlo”或其他组合。

 

【技术专辑】UART双缓冲技术:中断友好

 

因此,考虑到字节的顺序,让我们现在看看如果主程序不能比ISR正在打开数据更快地从缓冲区中弹出数据,那么这个“顺序”将如何丢失。为了举例,我们假设在我们的程序从缓冲区获取一个字节所花费的时间内,ISR将推动通过UART发送的两个字节。cout输出会是什么样的?输出将拼写为“elolH”。这是怎么发生的?

 

•UART快速发送前两个字节,“他”

 

•主程序占用位于末尾“e”的一个字节

 

•此时UART又发送了两个字节“ll”

 

•主程序再次取最后一个字节,“l”

 

•UART发送最后一个字节“o”

 

•然后主程序将数据从结束开始从开始“lH”开始。

 

•结果是“elolH”

 

不仅数据丢失了订单,而且甚至没有反过来!向后读取字节不能解决问题。即使您从第一个元素读到最后一个元素,也无法调整totalBytes的值,因为ISR可以在值更改之前停止程序,将字节放在数组的末尾然后在返回时返回主程序可以重置totalBytes的值(从而丢失该发送的字节)。如果由于干扰ISR的潜在问题,程序不会改变totalBytes的值,则缓冲区可能会溢出。

 

有一些解决方法,例如使用循环缓冲区,多个计数器和数组排序 - 但最简单的选项(也是最好的选项之一)是使用双缓冲区。

 

双缓冲区

 

双缓冲区可以被认为是两个完全独立的单元,其中中断例程与一个单元一起工作,程序与另一个单元一起工作。为了建模,中断例程将被称为“内核”,而不构成中断例程的函数+程序将被称为“用户”(它们使用数据,内核处理硬件) )。

 

【技术专辑】UART双缓冲技术:中断友好

 

每个单元都有两个变量:一个名为buffer []的数组和一个名为bufferCount的计数器。缓冲区在流入时保存UART数据,bufferCount保存已发送的数据量。此计数器可以以两种方式使用:

 

1.查找缓冲区中存在多少数据

 

2.确定将数据推送到缓冲区/从缓冲区弹出数据的位置

 

注意:对单元和多路复用器进行编程的最简单方法是使用多维缓冲区和多维计数器缓冲区。

 

                    // Each multi-array element is a unit

buffer[2][32]

bufferCounter[2][1]

 

// Unit A Variables

buffer[0][x]

bufferCounter[0][x]

 

// Unit B Variables

buffer[1][x]

bufferCounter[1][x]

    

用户和内核单元选择器使用两个变量完成:uartKernel和uartUser。这些值中的每一个都是相反的。以下是这些值的真值表:

 

【技术专辑】UART双缓冲技术:中断友好

 

多路复用器决定将哪个单元路由到内核和用户(多路复用器只能处于两种状态)。

 

•状态A使用单元A导致内核,使用单元B导致用户

 

•状态B使用单元B和使用单元A的用户导致内核

 

可以通过调用函数switchBuffers()来切换多路复用器。每次从缓冲区读取一个字节时,只有在处理完该缓冲区中的所有数据并且程序准备好从UART获取更多信息时,才会调用此方法。

 

当用户使用以下代码读取数组时,字节将按正确的顺序排列。

 

                    for(int i = 0; i <= bufferCounter[uartUser]; i++)

{

cout << buffer[uartUser][i];

}

                

内核使用以下代码将数据放入缓冲区:

 

                    isr_routine()

{

if(UART_RECEIVE)

{

buffer[uartKernel][bufferCounter[uartKernel]] = UART_GET;

bufferCounter[uartKernel] ++;

}

}

                  

由于uartUser和uartKernel值总是不同(1和0),这意味着用户和内核将始终访问不同的缓冲区和计数器。那么我们如何从内核获取信息给用户呢?我们所要做的就是切换uartUser和uartKernel的值,使它们指向彼此的缓冲区和计数器。因此,当用户正在读取新数据时,内核可以继续写入未使用的缓冲区。要进行此切换,所有用户必须执行(在处理新数据之前),调用switchBuffers()。

 

                    SwitchBuffers()

{

uartUser = (!uartUser) & 0x01;

uartKernel = (!uartKernel) & 0x01;

// Need to reset the counter for the ISR

bufferCounter[uartKernel] = 0;

}

 

因此,让我们看看这种双缓冲技术在微控制器负载很重的情况下,UART以两倍于程序处理速度的速度传输数据。像以前一样,UART将流式传输“Hello”,用户程序将打印字符。

 

•UART将“He”串流到内核中 - 放入A单元

 

•程序调用switchBuffers。程序从单元A打印“H”

 

•UART以“ll”流入内核 - 放入B单元

 

•程序仍在处理数组并从单元A打印“e”

 

•UART以“o”形式流入内核 - 放入B单元

 

•程序已处理单元A并切换缓冲区 - 程序打印“l”

 

•程序仍在处理数组并从单元B打印“l”

 

•程序仍在处理数组并从单元B打印“o”

 

双缓冲技术使ISR和主程序完全分离,使我们能够保持顺序,并且生成了非常简单的代码,并且有大缓冲区的机会。不需要数组排序,ISR不需要移动元素来保持顺序,也不需要复杂的循环缓冲。

  • 【技术专辑】UART双缓冲技术:中断友好已关闭评论
    A+
发布日期:2019年03月04日  所属分类:参考设计