打印
[经验分享]

串口数据处理-循环数组缓存

[复制链接]
1804|49
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
uptown|  楼主 | 2024-12-19 13:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
既然串口中断能收数据了。那么第一步就是要及时保存中断接收到的数据,这里就要用利用到”循环缓存“的方法进行数据保存。
什么是循环缓存?
它是代码定义了一段数组缓存Buff,并定义两个下标变量(In = 0, Out = 0),分别指向数组的写和读两端。
通常使用unsigned char类型定义读写下标变量In和Out,因为该类型变量最大值为256,计数到最大值时会自动变成0,这样就方便代码处理,所有设定的缓存数组大小也是256字节。
代码实现逻辑是什么?
当有数据时,数据将逐个放在写(In)端所指向的数组位置,同时写(In)端+1,当写(In)端一直增加指到数组边界时自动复位到数组起始位重新开始写,以此循环。
当需要读取数据时,判断读(Out)端与写(In)端不重合即不相等,则说明数组缓存中有数据未读。此时可逐个读取读端(Out)所指向的数组内容,同时读(Out)端将+1,直到读(Out)与写(In)端重合为止,确保数据全部读完。并且,当读(Out)端也到数组边界时同样会自动复位到数组起始位。
下面几幅图详细说明的循环缓存数组的工作流程



读端(Out)返回到起始位置
接下来,再用代码实现以上逻辑(代码开发环境为STM32CubeIDE)uint8_t ucUart3Ch = 0;uint8_t aucUsart3_Rev_Buf[256] = {0};// 循环缓存数组,大小256字节volatile uint8_t In = 0, Out = 0; // 写端下标In, 读端下标Out,当计数累计到 256 时自动变成 0// 串口中断接收函数void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle){  if(USART3 == UartHandle->Instance) {     // 将收到的1个字节写到缓存数组,写端(In) +1                aucUsart3_Rev_Buf[In++] = ucUart3Ch;                HAL_UART_Receive_IT(&huart3, &ucUart3Ch, 1);        }}// 获取缓存中未读的所有字节,并返回读取的长度int UART_Get_Data(uint8_t *pData, int iSize){        int pos = 0;        if(In != Out){//读写端不相等,即缓存中有数据可读      do{                        pData[pos++] = aucUsart3_Rev_Buf[Out++];// 读取1个字节,读端(Out)+1        if(iSize == pos) break;//读到的数据长度已经达到最大值      }while(In != Out);        }        return pos;}int main(void){  uint8_t aucData[512] = {0};  int iDataLen = 0;    while(1){            //发送AT命令,命令发送后最好延时一会,确保模块能返回响应数据            //读取模块响应数据                  iDataLen = UART_Get_Data(aucData, sizeof(aucData));                  if(0 < iDataLen){                        printf("UART Recv:%s\r\n", aucData);         /* 响应数据读到后,就可以对其进行处理了        */      }  }}另外,这里再举例一个解析AT命令响应的函数,仅供参考。本函数仅仅是解析AT+CSQ命令的响应,即获取信号值强度。(代码开发环境为STM32CubeIDE)typedef enum{        NB_OK = 0,        NB_TIMEOUT = 1,        NB_ERROR = 2,        NB_DATA_IN = 3,        NB_NET_CLOSED = 4,        NB_REG_CHANGED = 5}NB_STA;HAL_StatusTypeDef UART_Send_AT(char *pcAt){        return = HAL_UART_Transmit_IT(&huart3, (uint8_t *)pcAt, strlen(pcAt));}int UART_Get_Ch(void){        int ch = -1;        if(In != Out){                ch = aucUsart3_Rev_Buf[Out++];        }        return ch;}NB_STA AT_Get_CSQ_Val(char *pcSigVal){        //+CSQ:19,xx        NB_STA atRet = NB_TIMEOUT;        char *AT_CSQ = "AT+CSQ\r\n";        int iTimeout = 3, ch = 0;        char csqFlag = 0, csqOk = 0, atOK = 0, atErr = 0;        char *acCSQ = "+CSQ:";//定义解析关键字        char acOK[] = {'O','K',0x0D,0x0A}, acErr[] = {'E','R','R','O','R',0x0D,0x0A};//AT命令响应成功和失败关键字        pcSigVal[0] = 0;pcSigVal[1] = 0;//初始化参数        UART_Send_AT(AT_CSQ);HAL_Delay(500);//发送AT+CSQ命令,并延时500ms等待模块响应        do{                ch = UART_Get_Ch();//读取循环缓存数组中的一个字节数据                if(-1 != ch){                        //KE1_Put_Console_Ch((uint8_t)ch);//将字节输出到调试串口,方便查看                        if(acOK[atOK] == ch){// 检查响应是否包含OK                                atOK++;                        }else{                                atOK = 0;                        }                        if(4 == atOK && 1 == csqOk){                                atRet = NB_OK; break;                        }                        if(acErr[atErr] == ch){// 检查响应是否包含ERROR                                atErr++;                        }else{                                atErr = 0;                        }                        if(7 == atErr){                                atRet = NB_ERROR; break;                        }                        if(1 == csqOk){// 响应包含关键字"+CSQ:",并开始获取需要的值                                if(',' == ch) {                                        csqFlag = 0x0F;                                }                                if(3 > csqFlag){                                        pcSigVal[csqFlag++] = ch;                                }                                continue;                        }                        if(acCSQ[csqFlag] == ch){// 检查响应是否包含关键字"+CSQ:"                                csqFlag++;                        }else{                                csqFlag = 0;                        }                        if(5 == csqFlag){csqOk = 1; csqFlag = 0;}// 响应包含关键字"+CSQ:"                }else{                        if(0 != iTimeout) {                                iTimeout--;                                if(0 != iTimeout){                                        HAL_Delay(500);                                }                        }                }        }while(iTimeout);//判断模块是否响应超时        return atRet;}

使用特权

评论回复
沙发
10299823| | 2025-1-3 11:16 | 只看该作者
在串口通信中,数据通常以字节流的形式传输,而且是异步到达的。为了有效地处理这些数据,通常需要一个缓冲区来暂存接收到的数据,直到有足够的数据可以进行处理。循环数组缓存(也称为环形缓冲区)是一种常用的数据结构,它可以高效地管理这种类型的输入流。

使用特权

评论回复
板凳
qiufengsd| | 2025-1-3 11:44 | 只看该作者
当缓冲区满时,新的数据可能会覆盖旧的数据,因此需要合理设计数据处理逻辑,避免数据丢失。

使用特权

评论回复
地板
louliana| | 2025-1-3 11:56 | 只看该作者
通过使用循环数组缓存,可以将串口数据的接收和处理分离开来,使得数据处理逻辑更加清晰和简单

使用特权

评论回复
5
mattlincoln| | 2025-1-3 12:10 | 只看该作者
当有新数据到达时,将其写入写指针所指向的位置,并将写指针向前移动一位。如果写指针到达数组末尾,则自动复位到数组起始位置。

使用特权

评论回复
6
youtome| | 2025-1-3 12:33 | 只看该作者
串口数据处理中的循环数组缓存是一种高效、可靠的数据存储和管理方法。

使用特权

评论回复
7
uytyu| | 2025-1-3 14:07 | 只看该作者
环形缓冲区,是一种用于存储数据的特殊数据结构。在串口数据处理中,它可以有效地存储从串口接收来的数据。其结构类似于一个环形,有一个固定的大小,

使用特权

评论回复
8
fengm| | 2025-1-3 14:33 | 只看该作者
循环数组缓存可以快速处理连续的数据流,适用于实时系统。

使用特权

评论回复
9
gygp| | 2025-1-3 16:22 | 只看该作者
使用两个指针(通常称为“读指针”和“写指针”)来跟踪数据在缓冲区中的位置。
写指针用于指示下一个数据写入的位置。
读指针用于指示下一个数据读取的位置。

使用特权

评论回复
10
hilahope| | 2025-1-3 17:10 | 只看该作者
当写指针到达缓冲区的末尾时,它会回绕到缓冲区的起始位置,继续写入数据。
同样,当读指针到达缓冲区的末尾时,它也会回绕到起始位置,继续读取数据。

使用特权

评论回复
11
timfordlare| | 2025-1-3 18:57 | 只看该作者
由于数据是连续写入的,处理器可以即时访问最新数据,减少了等待时间,从而提高了数据处理的效率。

使用特权

评论回复
12
eefas| | 2025-1-3 19:09 | 只看该作者
使用循环数组缓存 是一种非常有效的策略。

使用特权

评论回复
13
benjaminka| | 2025-1-3 19:22 | 只看该作者
#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 10

typedef struct {
    char buffer[BUFFER_SIZE];
    int head;
    int tail;
    int count;
    int full;
} RingBuffer;

void init_ring_buffer(RingBuffer *rb) {
    rb->head = 0;
    rb->tail = 0;
    rb->count = 0;
    rb->full = 0;
}

int is_empty(RingBuffer *rb) {
    return rb->count == 0;
}

int is_full(RingBuffer *rb) {
    return rb->count == BUFFER_SIZE;
}

void insert(RingBuffer *rb, char data) {
    if (!is_full(rb)) {
        rb->buffer[rb->tail] = data;
        rb->tail = (rb->tail + 1) % BUFFER_SIZE;
        rb->count++;
        if (rb->tail == rb->head) {
            rb->full = 1;
        }
    }
}

char remove(RingBuffer *rb) {
    if (!is_empty(rb)) {
        char data = rb->buffer[rb->head];
        rb->head = (rb->head + 1) % BUFFER_SIZE;
        rb->count--;
        rb->full = 0;
        return data;
    }
    return -1; // Error: Buffer is empty
}

int main() {
    RingBuffer rb;
    init_ring_buffer(&rb);

    // Simulate receiving data
    for (int i = 0; i < 10; i++) {
        insert(&rb, 'a' + i);
    }

    // Process received data
    while (!is_empty(&rb)) {
        char data = remove(&rb);
        printf("%c\n", data);
    }

    return 0;
}

使用特权

评论回复
14
loutin| | 2025-1-3 19:35 | 只看该作者
在某些情况下,可能需要确保发送和接收操作不会相互干扰,这时可以考虑使用互斥锁或其他同步机制。

使用特权

评论回复
15
jkl21| | 2025-1-3 19:47 | 只看该作者
当缓冲区满时,如果继续写入数据,需要采取适当的溢出处理策略,如丢弃旧数据、覆盖旧数据或发出溢出警告等。

使用特权

评论回复
16
cemaj| | 2025-1-3 20:00 | 只看该作者
在串口数据连续接收的情况下,不需要频繁地分配和释放内存。相比传统的线性数组,当线性数组存满后,如果要继续存储数据,可能需要重新分配更大的内存空间,这会消耗更多的时间和资源。而循环数组缓存只要还有剩余空间,就可以持续接收数据。

使用特权

评论回复
17
iyoum| | 2025-1-3 20:12 | 只看该作者
循环数组缓存广泛应用于串口通信中的数据接收和处理。特别是在实时性要求较高的场景中,如嵌入式系统、工业自动化设备等,循环数组缓存能够提供高效、可靠的数据存储和管理机制。

使用特权

评论回复
18
burgessmaggie| | 2025-1-3 20:24 | 只看该作者
数组大小的选择应基于应用的具体需求和可用内存资源。过小的数组可能导致数据丢失,而过大的数组则会增加内存占用和搜索成本。

使用特权

评论回复
19
primojones| | 2025-1-3 20:36 | 只看该作者
当新的数据到达时,它会被添加到缓冲区的尾部,并且尾部指针会向前移动。如果尾部指针到达数组的末尾,它会自动回到数组的起始位置,形成一个循环

使用特权

评论回复
20
juliestephen| | 2025-1-3 20:49 | 只看该作者
当数据从串口接收后,通过写指针将数据存入循环数组中。写指针会随着数据的写入而移动,如果写指针到达数组的末尾,它会自动回到数组的开头(这就是 “循环” 的特性),只要数组还有剩余空间,就可以继续写入数据。读指针则用于从缓存中读取数据,同样,当读指针到达数组的末尾,它也会回到开头。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

45

主题

3558

帖子

2

粉丝