论坛

循环缓冲区

开始于 swmcl 2年前15回复最新回复2年前500浏览

你好

我在C编程方面没有那么先进,但是我需要为我的串行数据构建一个循环缓冲区。串行数据在一个9字节至48字节长度的帧中。我当时在考虑可以处理多达16帧的缓冲区。在定义缓冲区的任何帮助将不胜感激。我正在使用PIC处理一些串行数据。

干杯,

史蒂夫

MPLABX 5.10和XC8 2.00

[-]
回覆者 iv2019年2月5日
[-]
回覆者 dnj2019年2月4日

我的想法是,我可以写出所有内容,而您不会真正了解正在发生的事情,或者我可以引导您朝着正确的方向发展,并让您找出详细的细节。除非您在寻求正确的解决方案时犯了很多错误,否则您将不会学习。您将了解无效的内容。如果您在第一次尝试时就可以使用它,那么您将不知道自己做对了什么,也不知道在哪里可以灵活,哪里必须精确。

也就是说,您需要从线性缓冲区中实现循环缓冲区,在每次循环中都必须检查索引,以便在超出范围时将其重置为零。 

这是一个把戏。我喜欢使用基于2的幂的长度。假设长度为8。与其实际检查8的值并将其重置为零,而是在每个增量处执行7的逻辑AND。尝试一下,看看会发生什么。位模式是关键。

对于串行循环缓冲区,您需要两个索引。一个是前索引,另一个是后索引。 

从串行端口获取字符时,将其放置在前索引的位置并递增索引。

在程序循环中,检查是否有可用字符。您从后索引的位置开始获取字符,直到没有可用的字符为止。研究前后指标的关系以找出该情况。

您还将需要弄清楚如何确定缓冲区是否已满以及如何处理。由于您必须考虑线性缓冲区末端发生的情况,因此这可能会很棘手。 2长度的幂将在其中起作用。

如果您有几个简单的函数,那一点也不难。得到,放置,可用,满

[-]
回覆者 swmcl2019年2月5日

谢谢DNJ,

我看到的示例仅适用于具有单个字节的缓冲区。我的将有一个字节数组或48字节长的东西。对我来说,另一个问题是如何增加48.我知道您还谈到其他一些功能。  

我期待“一点都不困难”的一部分……!

干杯,

史蒂夫

[-]
回覆者 焊点2019年2月5日

因此,您使用的是一种消息协议,每个消息最多包含48个字节。

当然,在该消息内有一些长度信息。因此,您可以将所有消息视为线性流并将任何增量数据写入线性环形缓冲区。之后,您逐字节从缓冲区中读取消息并检查长度信息,以确保仅此消息的数据被移出环形缓冲区。

如果没有长度信息,而是一种消息结束指示,则可以用相同的方式进行。

如果长度信息不是结构的一部分,而是一种边带信息,则只需将该信息写入环形缓冲区即可将其添加到环形缓冲区中。当然,在阅读消息时,需要将其提取出来。

另一种选择是具有16个48字节的缓冲区。每个缓冲区用于存储单个消息。您可以为此使用二维数组,因此每个缓冲区将通过0到15之间的索引来作为地址。现在,您有两个计数器:一个写计数器和一个读计数器。您写入当前写计数器指示的缓冲区中的任何传入消息,然后写计数器将递增。从读取计数器指示的缓冲区中读取消息,然后读取计数器递增。当然,您需要处理计数器的换行,还需要弄清楚如何处理缓冲区溢出和空条件。当然,以某种方式,您需要能够确定一条消息实际上消耗了缓冲区中的多少个字节。因此,您可能希望拥有“ is_msg_present”,“ get_msg”和“ get_next_msg_length”之类的服务。


[-]
回覆者 dnj2019年2月5日

对。我的解释是针对一般的。我无法计算为串行通信和其他机制实现循环缓冲区的次数。我的项目涉及读取汽车CAN网络,因此我使用具有足够大容量可容纳CAN消息的元素的循环缓冲区。当每秒显示成千上万的消息时,建立链接列表花费的时间太长。

但是,它保持不变。我的需求总是要求我在可用时获取数据包或字节。您对“可用”的定义是唯一更改的内容。对您来说,“可用”意味着有48个或更多字节,而您仅提取48个字节。索引的增量没有变化。

对于已知的字节长度,我将允许(如前所述)几个消息长度的缓冲区空间-仍然是2的幂。众所周知,当数据量为高,我的处理能力低。给自己一些空间。坚持2的幂次。

[-]
回覆者 BVRamesh2019年2月5日

除了dnj所建议的内容外,循环缓冲区应大于平均数据包长度,这样,即使处理器陷入某个循环,退出整个数据包后也应可用于处理。



[-]
回覆者 Atomq2019年2月5日

在@dnj之后,优良作法是在执行此操作之前检查可用字节数以填充缓冲区。

最好的解决方案是设计一个具有uint_8 frame_start和相同大小frame_stop数据标记的256字节循环缓冲区(如果吞吐量和数据量要求都允许)。在这种情况下,您无需检查缓冲区的溢出。

干杯,

亚当。

[-]
回覆者 swmcl2019年2月5日

谢谢你们,

帧大小由外部硬件固定。我对此无能为力。帧从9到48个字节不等。它确实具有开始和结束字节数组。这意味着该帧的2幂的大小将是64个字节。因此,缓冲区的大小将为8或16帧。对于更高的活动,我需要转到16帧大小,即1024字节,其中一些未使用。

C的编写方式让我感到难过。我说英语 !

[-]
回覆者 mr_bandit2019年2月5日

考虑对一条消息使用结构&&使用结构的循环缓冲区。该结构实际​​上可以是几种消息类型的并集。

结构只是到内存的映射。联合只是到相同内存的一组映射。考虑:

类型定义结构

{

uint8_t buf_u8 [8];

uint16_t buf_u16 [4];

} FOO;

uint8_t * ptr;

FOO foo = {{0x12、0x23、0x34、0x45、0x56、0x67、0x78、0x89

            {0x1223,0x3445,0x5667,0x7889}};

ptr8 =(uint8_t *)&foo.buf_u8;

ptr16 =(uint8_t *)&foo.buf_u16;

for(int i = 0;我< 8; i++ )

{

    printf(“ u8 [%u:%02x] ́” i,ptr8 [i]);

    printf(“ u16 [%u:%02x] \ n”,i,ptr16 [i]);

}

// ==============

请注意,这会使用uint8_t指针在结构中遍历两个数组。将发生以下两种情况之一:两个数组值将彼此==(0x12 0x12),或者它们将被翻转(0x12 0x23)-为什么? (提示:字节性别)


// ================================================ =

//尝试:

类型定义联合

{

uint8_t buf_u8 [8];

uint16_t buf_u16 [4];

} BAR;

uint8_t * ptr;

BAR bar == {{0x12,0x23,0x34,0x45,0x56,0x67,0x78,0x89}};

//尝试同时打印两个缓冲区,看看会发生什么


[-]
回覆者 swmcl2019年2月6日
行。因此,我尝试了一些在线博客类型的文章。他们都避开了实际的例子,因此使用伪代码说话。。。这就是我的早期尝试。这确实可以编译。


在.h文件中,

+++++++++++


#define Circular_Buffer_size 768 // 16 * 48 = 768


typedef结构Circular_pointer_buf_t
{
    uint8_t *缓冲区;
// size_t头;
// size_t tail;
// size_t max; //缓冲区的
    uint16_t头;
    uint16_t尾巴;
    uint16_t max;
    满满
};

typedef结构circular_buf_t
{
    uint8_t buffer [circular_buffer_size];
// size_t头;
// size_t tail;
// size_t max; //缓冲区的
    uint16_t头;
    uint16_t尾巴;
    uint16_t max;    
    满满
};

extern struct Circular_buf_t buf_a;
extern struct Circular_buf_t buf_b;

extern struct Circular_pointer_buf_t buf_c;
extern struct Circular_pointer_buf_t buf_d;

+++++++++++++

在.c文件中,

++++++++++++

struct Circular_buf_t buf_a;
struct Circular_buf_t buf_b;

struct Circular_pointer_buf_t buf_c;
struct Circular_pointer_buf_t buf_d;

++++++++++++

我可以看到buf_a和buf_b的大小如何制作,但是我不明白如何实例化buf_c和buf_d类型。

我什至购买了一本书,叫做《制造嵌入式系统》,它还跳过了如何实例化缓冲区的步骤(在我看来,这是..)

我就是没有指针!



[-]
回覆者 mr_bandit2019年2月6日

#define NUM_MSG 16 // //循环q中的条目

#define MSG_SIZE 48 //一条消息

类型定义结构

{

    uint8_t msg_buf [MSG_SIZE];

    uint16_t头;

    uint16_t尾巴;

    uint16_t max;

    满满

} BUF_t;

类型定义结构

{

    BUF_t buf [NUM_MSG];

    uint16_t头;

    uint16_t尾巴;

    uint16_t max;    

    满满

} CIRC_Q_t;

//除非.h文件被其他文件使用,否则不需要这些

//更好的方法是定义访问队列信息的函数

外部CIRC_Q_t circ_q;

+++++++++++++

在.c文件中,

++++++++++++

//“实例化”是C ++术语,而不是C术语

//这定义/分配了数组

//它不填写数组

CIRC_Q_t circ_q;

//设置circ_q

circ_q.head = 0;

circ_q.tail = 0;

circ_q.max = NUM​​_MSG;

circ_q.full =假;

//现在在循环缓冲区数组中设置每个BUF_t结构

for(i = 0;我< NUM_MSG; i++ )

{

    circ_q [i] .head = 0;

    circ_q [i] .tail = 0;

    circ_q [i] .max = MSG_SIZE;

    circ_q [i] .full =否;

}

//使用

BUF_t * buf_ptr;

uint8_t * msg_buf;

buf_ptr = circ_q [circ_q.head] .buf; //指向结构BUF_t

msg_buf = buf_ptr->msg_buf;

//这应该可以帮助您入门

//您应该将其绘制出来,以便可以看到结构如何相互作用

//基本上,您有BUF_t用于特定的消息缓冲区

//然后有一个定义循环队列的结构数组

// BUF_t结构

[-]
回覆者 swmcl2019年2月6日

感谢Bandit,

我想我已经通读了您的文章,并且一口气就了解了几乎所有内容。我想知道如何在一夜之间将计数器增加48,但是现在我看到您已将其拆分为框架垃圾。

我会去的。谢谢您对小蚱hopper的耐心!

[-]
回覆者 swmcl2019年2月6日

我是否正确认为uint16_t变量现在可以是uint8_t,因为它们再也看不到256以上的数字了?

[-]
回覆者 swmcl2019年2月6日
行。我有一些基于Bandit的回复的代码,但我还有一个问题...为什么结构中存在结构?我是否仅需要结构中的数组?我不明白为什么我会使用BUF_t.head,BUF_t.tail,BUF_t.max或BUF_t.full。至少我应该肯定在下部结构中包含数组msg_buf吗?


我的.h文件是:

++++++++++++++++++++++++++++++++++++++++++++

/ *
 * File:   
 * Author:
 * Comments:
 * Revision history:
 */

#ifndef FRAMES_AND_BUFFERS_H
#define``FRAMES_AND_BUFFERS_H

/ **
  部分:包含的文件
* /




#包括<xc.h>
#包括<stdint.h>
#包括<stdbool.h>
#包括<stddef.h>
#包括<float.h>
#包括<math.h>
#包括<stdio.h>
#包括<stdlib.h>
#包括<ctype.h>
#包括<string.h>

#包括"mcc.h"



/ **
  部分:宏声明
* /


#定义NUM_MSGS_IN_FIFO_RING_BUFFER 16
#define SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER 48

/ **
  部分:数据类型定义
* /

类型定义结构
{
    uint8_t msg_buf [SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER];
    uint8_t头;
    uint8_t tail;
    uint8_t max;
    满满
} BUF_t;

类型定义结构
{
    BUF_t buf [NUM_MSGS_IN_FIFO_RING_BUFFER];
    uint8_t头;
    uint8_t tail;
    uint8_t max;    
    满满
} CIRC_Q_t;


/ **
 部分:全局变量
* /     

外部CIRC_Q_t入库_UART1;
外部CIRC_Q_t支出_UART1;


/ **
  部分:帧和缓冲区API
* /

void Initialise_Incoming_UART1_FIFO_Ring_Buffer(void);

void Initialise_Outgoing_UART1_FIFO_Ring_Buffer(void);




#ifdef __cplusplus
extern“ C” {
#endif / * __cplusplus * /

#ifdef __cplusplus
}
#endif / * __cplusplus * /

#endif // * FRAMES_AND_BUFFERS_H * /

++++++++++++++++++++++++++++++++++++++++++++++++


我的.c文件是:

++++++++++++++++++++++++++++++++++++++++++++++++


/ **
  部分:包含的文件
* /


#包括<xc.h>
#包括"mcc.h"

#include“ frames_and_buffers.h”

/ **
  部分:宏声明
* /



/ **
  部分:全局变量
* /

CIRC_Q_t entry_UART1; //将传入声明为CIRC_Q_t类型的全局变量
CIRC_Q_t支出_UART1; //声明传出为CIRC_Q_t类型的全局变量


/ **
  部分:帧和缓冲区API
* /

void Initialise_Incoming_UART1_FIFO_Ring_Buffer(无效)
{
    uint8_t i,j = 0x00;
    
    entry_UART1.head = 0x00;
    entry_UART1.tail = 0x00;
    entry_UART1.max = NUM​​_MSGS_IN_FIFO_RING_BUFFER;
    entry_UART1.full =否;
    
    对于(i = 0; i<NUM_MSGS_IN_FIFO_RING_BUFFER; i ++)
    {
        for(j = 0; j<SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER; j ++)
        {
            entry_UART1.buf [i] .msg_buf [j] = 0x00;
        }
        
        entry_UART1.buf [i] .head = 0x00;
        entry_UART1.buf [i] .tail = 0x00;
        entry_UART1.buf [i] .max = SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER;
        entry_UART1.buf [i] .full = false;
    }
    
}

void Initialise_Outgoing_UART1_FIFO_Ring_Buffer(无效)
{
    uint8_t i,j = 0x00;
    
    outward_UART1.head = 0x00;
    outward_UART1.tail = 0x00;
    outward_UART1.max = NUM​​_MSGS_IN_FIFO_RING_BUFFER;
    outward_UART1.full =假;
    
    对于(i = 0; i<NUM_MSGS_IN_FIFO_RING_BUFFER; i ++)
    {
        for(j = 0; j<SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER; j ++)
        {
            entry_UART1.buf [i] .msg_buf [j] = 0x00;
        }
        
        outward_UART1.buf [i] .head = 0x00;
        outward_UART1.buf [i] .tail = 0x00;
        outward_UART1.buf [i] .max = SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER;
        outward_UART1.buf [i] .full = false;
    }
    
}

/ **
  文件结束
* /

+++++++++++++++++++++++++++++++++++++++++++++++++++


[-]
回覆者 mr_bandit2019年2月6日

循环缓冲区有很多不同的方法。在我的示例中,我定义了一个BUF_t,它假设有一个循环缓冲区-我将它快速地放在一起。实际上,您只需要缓冲区及其最大长度(良好做法)和实际计数。

第二个结构CIRC_Q_t具有BUF_t结构集和循环队列的head / tail / max / full。这是实际的循环队列。

为什么要在结构中使用结构?我为您做了一个例子。在某些情况下,您想/需要这样做。例如,Linux内核状态结构具有嵌套结构,以便跟踪各种事物,其中最好通过一个结构来处理每个事物。

看你的情况。您需要处理循环缓冲区。如果可以保证消息永远不会为0x00,并且可以保证缓冲区长度是固定的,则可以使用

uint8_t my_buffers [NUM_BUFFERS] [BUF_LEN];

但是-您未指定消息格式。而且-即使消息是文本字符串,您也需要在“将其从队列中取出”后重新初始化该缓冲区。 好的做法 -表示您将缓冲区设置为全0。就个人而言,设置为“ msg.msg_len = 0;”。更容易

您是否尝试过工会的例子?

关于样式的主题:随着您在该领域的进步,您将遇到各种编码样式和编码标准。造成这种情况的原因有很多(没有跌宕起伏,一组开发人员的代码一致性等等)。其中两个是关于名字的&它们的长度,另一个关于空白。

首先,您有很好的majic和变量名。对我来说有点罗but,但是您有许多数字名称-比许多新程序员要好得多!

您在空白处有了良好的开端&&将“ {”放在正确的位置(que火焰大战)..并且操作员周围有空格。 (在C中常出现大括号的地方是花括号-我将{和}放在相同的缩进级别上,这样我就可以一眼看到代码块了。

考虑

for(j = 0; j<SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER; j ++)

for(j = 0; j<SIZE_OF_EACH_MSG_IN_FIFO_RING_BUFFER; j ++)

瞥一眼这些。您更容易挑选出单个操作,例如j ++?

读取代码。阅读很多代码。确定您想要的样式。良好的风格会节省您的精力并避免错误。