您尚未登录。

楼主 #1 2018-02-07 22:53:55

zhen8838
会员
注册时间: 2018-01-19
已发帖子: 32
积分: 32

v3s串口接收,只能一次接收32个字节,这是什么问题?

我想用v3s解析gps数据。本来想得是可以读取到换行符停止。但是目前的现象是一串数据过来,比如45个字节,我串口接收到就变成长度32和13的两个数据。
例如我发送$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
接受到的信息如下

# ./uartRec
fcntl=0
isatty success!
fd->open=3
Receive 32
Receice 11

我的串口接收线程如下

static void *threadRead(void *arg)//串口读取线程
{
    while(1)
    {

        bufflen=UART_Recv(uartfd,RecvBuff,150);
        if(bufflen>0)
        {
            SendBuff=(char *)malloc(bufflen);
            memcpy(SendBuff,RecvBuff,bufflen);
            printf("Receive %d\n",bufflen);
            free(SendBuff);
        }
        usleep(1000);
    }
    return NULL;
}

串口接受程序如下

int UART_Recv(int fd, char *rcv_buf,int data_len)
{
    int len,fs_sel;
    fd_set fs_read;

    struct timeval time;

    FD_ZERO(&fs_read);
    FD_SET(fd,&fs_read);

    time.tv_sec = 10;//等待时间 秒
    time.tv_usec = 0;//等待时间 微妙

    //使用select实现串口的多路通信
    fs_sel = select(fd+1,&fs_read,NULL,NULL,&time);
    //printf("fs_sel = %d\n",fs_sel);
    if(fs_sel)
    {
        len = read(fd,rcv_buf,data_len);
        //printf("len = %d fs_sel = %d\n",len,fs_sel);
        return len;
    }
    else
    {
        //printf("Sorry,I am wrong!");
        return FALSE;
    }
}

请大家给些指点,或者给个思路怎样在一次只接收32字节的情况下解析gps。。。

离线

#2 2018-02-08 00:40:24

晕哥
管理员
注册时间: 2017-09-06
已发帖子: 9,348
积分: 9202

Re: v3s串口接收,只能一次接收32个字节,这是什么问题?

你用一个环形缓冲区实现,一个生产者,一个消费者,
消费者只分析缓冲区就可以。





离线

#3 2018-02-08 08:09:41

晕哥
管理员
注册时间: 2017-09-06
已发帖子: 9,348
积分: 9202

Re: v3s串口接收,只能一次接收32个字节,这是什么问题?

参考链接: 在嵌入式C中实现循环/环形缓冲区

嵌入式软件通常涉及状态机,循环缓冲区和队列。在这篇文章中,将给你一个数据结构的概述,并引导你完成在低内存设备中实现循环/环形缓冲区所涉及的步骤。

对于那些不知道循环缓冲区是什么的人来说,这是一个数组结构,在数组结构中,数组被视为循环,并且在达到数组长度后,循环返回到0。这是通过有两个指针来完成的。一个指向“头”,另一个指向缓冲区的“尾部”。随着数据被添加到缓冲区,头指针向上移动,并且随着数据被移除(读取),尾指针向上移动。这与实施有关,因观点而异。所以,为了争辩,我们会同意的,你写在头上,从尾巴读。

这是一个很好的维基百科的 GIF ,
circular-buffer-animation.gif


图片说明了一切。动画是非常快的,可能需要一些时间观察动画的迭代,然后才会注意到所有涉及的情况,但是花时间给出了对内存和指针的直观表示。

完整与空白
关于循环缓冲区的下一个重要的事情是没有“干净的方式”来区分缓冲区满空的情况。这是因为在这两种情况下,头等于尾巴。有很多方法/解决方法来处理这个问题,但其中大多数是不可读的。我提出了一个相当可读的方法。

在这种方法中,有两个关键的情况(边界条件)在实施循环缓冲区时必须考虑,

头等于尾巴 - >缓冲区是空的
(头+ 1)等于尾 - >缓冲区已满
其实质是,每当你试图推动,你检查is_buffer_full条件,每当有流行,你检查is_buffer_empty。

有了这些知识,我将着手定义数据类型!

typedef struct {
    uint8_t * const buffer;
    int head;
    int tail;
    const int maxLen;
} circBuf_t;

我们的主要结构是处理缓冲区及其指针。注意缓冲区是uint8_t * const buffer。const uint8_t *是一个指向常量元素的字节数组的指针,即被指向的值不能被改变,但指针本身可以被改变。另一方面uint8_t * const是指向一个字节数组的常量指针,其中被指向的值可以被改变,但指针不能被改变。

这样做是为了能够对缓冲区进行更改,但是不会意外地孤立指针。这是一个非常好的安全措施,我强烈建议你不要跳过那部分。

在push和pop例程中,我们将计算“next”偏移点到当前写入/读取将发生的位置。如前所述,如果下一个位置指向尾部指向的位置,那么我们知道缓冲区已满,并且不会将数据写入缓冲区(返回错误)。同样,当头等于尾时,我们知道缓冲区是空的,没有任何东西可以被读取。

将数据推入循环缓冲区
在大多数用例场景中,您将在ISR中调用此方法。因此,一个推送应该尽可能小,整个例程应该包含关键部分,以使其在多线程环境中同步。

在头指针递增之前,必须加载数据,以确保只有有效的数据被消费者线程(一个调用pop的函数)读取,见下文。

另外,如果您注意到,我们在缓冲区中保留一个字节作为保留空间。乍一看,它可能会被看作是一个一个,但如果你有这个事情,你会发现它的工程交易。如果我要使用这一个额外的字节,检测完整和空的情况就会变得稍微复杂一些,编写能够处理所有情况的代码是非常耗时的,而且很难调试。

因此,总而言之,对于小型和固定大小的数据单元,只要保留一个字节,而您仍然可以保持清醒。

int circBufPush(circBuf_t *c, uint8_t data)
{
    // next is where head will point to after this write.
    int next = c->head + 1;
    if (next >= c->maxLen)
        next = 0;

    if (next == c->tail) // check if circular buffer is full
        return -1;       // and return with an error.

    c->buffer[c->head] = data; // Load data and then move
    c->head = next;            // head to next data offset.
    return 0;  // return success to indicate successful push.
}

从循环缓冲区中弹出数据
Pop例程被应用程序调用来从缓冲区中取出数据。如果有多个线程正在读取这个缓冲区,这也必须包含在关键部分(虽然这不是通常的做法)

在这里,由于每个数据单元都是一个字节,所以在数据读取之前,尾部可以移动到下一个偏移量,当我们完全加载时,我们在缓冲区中保留一个字节。但是,在更先进的循环缓冲器实现方式中,数据单元并不需要是相同的大小。在这种情况下,我们不知道在读取数据之前需要移动多少尾部。

为了保持与这些实现的一致性,我将读取数据,然后移动尾指针。

int circBufPop(circBuf_t *c, uint8_t *data)
{
    // if the head isn't ahead of the tail, we don't have any characters
    if (c->head == c->tail) // check if circular buffer is empty
        return -1;          // and return with an error

    // next is where tail will point to after this read.
    int next = c->tail + 1;
    if(next >= c->maxLen)
        next = 0;

    *data = c->buffer[c->tail]; // Read data and then move
    c->tail = next;             // tail to next data offset.
    return 0;  // return success to indicate successful push.
}

用法
我觉得很明显,你必须定义一个特定长度的缓冲区,然后创建一个实例,circBuf_t并将指针分配给缓冲区及其指针maxLen。

不言而喻,缓冲区必须是全局的,或者只要需要使用缓冲区就必须处于堆栈状态。

为了使维护变得容易一点,你可以使用这个宏,但会损害新用户的代码可读性。

#define CIRCBUF_DEF(x,y)          \
    uint8_t x##_dataSpace[y];     \
    circBuf_t x = {               \
        .buffer = x##_dataSpace,      \
        .head = 0,                \
        .tail = 0,                \
        .maxLen = y               \
    }
例如,如果你需要一个长度为32字节的循环缓冲区,你可以在你的应用程序中这样做,

CIRCBUF_DEF(myDatBuf, 32);

void thisIsYourAppCode()
{
    uint8_t outData, inData = 0x55;

    if (circBufPush(&myDatBuf, inData)) {
        DBG("Out of space in CB");
        return;
    }

    if (circBufPop(&myDatBuf, &outData)) {
        DBG("CB is empty");
        return;
    }

    // here outData = inData = 0x55;

    return data;
}

我希望这篇文章有助于理解循环缓冲区。我们会看到更多这样的数据结构,并且在将来的帖子中对这个循环缓冲区类型进行了高级扩展.





离线

楼主 #4 2018-02-08 10:41:43

zhen8838
会员
注册时间: 2018-01-19
已发帖子: 32
积分: 32

Re: v3s串口接收,只能一次接收32个字节,这是什么问题?

晕哥 说:

你用一个环形缓冲区实现,一个生产者,一个消费者,
消费者只分析缓冲区就可以。

ok!谢谢指点!

离线

#5 2018-02-08 10:59:46

晕哥
管理员
注册时间: 2017-09-06
已发帖子: 9,348
积分: 9202

Re: v3s串口接收,只能一次接收32个字节,这是什么问题?

zhen8838 说:
晕哥 说:

你用一个环形缓冲区实现,一个生产者,一个消费者,
消费者只分析缓冲区就可以。

ok!谢谢指点!

哈,不用谢。
想想还是C++轮子多,
直接new 出对象直接用就可以了。

http://www.boost.org/doc/libs/1_61_0/doc/html/circular_buffer.html

http://www.boost.org/doc/libs/1_61_0/doc/html/circular_buffer/example.html

// Create a circular buffer with a capacity for 3 integers.
boost::circular_buffer<int> cb(3);





离线

#6 2019-10-11 17:57:29

yuliang_8
会员
所在地: 番禺 广州 中国
注册时间: 2019-08-28
已发帖子: 20
积分: 20

Re: v3s串口接收,只能一次接收32个字节,这是什么问题?

不错,写的非常好。

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn