打印
[经验分享]

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

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

使用特权

评论回复
沙发
wilhelmina2| | 2024-11-8 22:02 | 只看该作者
循环数组缓存(也称为环形缓冲区)是一种常用的解决方案,它可以高效地管理串口接收的数据。

使用特权

评论回复
板凳
mikewalpole| | 2024-11-8 22:45 | 只看该作者
在处理缓冲区的函数中,确保代码是中断安全的,避免在ISR中执行复杂的操作。

使用特权

评论回复
地板
vivilyly| | 2024-11-9 07:32 | 只看该作者
选择合适的缓冲区大小,以平衡内存使用和数据处理效率。

使用特权

评论回复
5
minzisc| | 2024-11-9 08:02 | 只看该作者
循环数组缓存在嵌入式系统中有广泛的应用场景

使用特权

评论回复
6
mattlincoln| | 2024-11-9 08:33 | 只看该作者
如果写端指针在复位前已经追上了读端指针,会导致数据溢出。因此,需要在实际应用中添加相应的处理逻辑,如增加数据缓存区的大小、及时读取数据等。

使用特权

评论回复
7
pixhw| | 2024-11-9 10:35 | 只看该作者
循环数组缓存是一种数据结构,用于在串口通信中存储接收到的数据。它通过定义一个固定大小的数组和两个指针(读指针和写指针)来实现数据的循环存储和读取。

使用特权

评论回复
8
rosemoore| | 2024-11-9 12:29 | 只看该作者
定义循环数组和指针

定义一个固定大小的数组作为缓冲区。
定义两个指针(或索引),一个用于写入数据(write_ptr),一个用于读取数据(read_ptr)。
初始化

初始化数组和指针,通常将读写指针都设置为0。
数据写入

在写入数据前,检查缓冲区是否已满。如果未满,则将数据写入当前write_ptr位置,并更新write_ptr。
如果缓冲区已满,则根据具体需求处理(例如,覆盖旧数据或丢弃新数据)。
数据读取

在读取数据前,检查缓冲区是否为空。如果不为空,则将数据从当前read_ptr位置读取,并更新read_ptr。
如果缓冲区为空,则等待直到有数据可读。
循环数组更新

更新读写指针时,需要考虑循环数组的特性,即当指针到达数组末尾时,需要回到数组开头。

使用特权

评论回复
9
nomomy| | 2024-11-9 14:05 | 只看该作者
串口数据处理中的循环数组缓存是一种高效的数据管理方法,特别适用于串口通信中数据的实时接收和处理。

使用特权

评论回复
10
plsbackup| | 2024-11-9 14:38 | 只看该作者
当有数据写入时,数据被放置在写指针指向的位置,然后写指针递增。当写指针达到数组末尾时,它会自动回到数组的起始位置,形成一个循环。读取数据时,从读指针指向的位置开始读取,读指针同样在达到数组末尾时回到起始位置。

使用特权

评论回复
11
robincotton| | 2024-11-9 16:00 | 只看该作者
定义一个固定大小的数组以及两个指针(或索引),分别用于指示读和写的位置。

使用特权

评论回复
12
sdCAD| | 2024-11-9 18:00 | 只看该作者
当串口接收到数据时,将数据逐个放入写端指针所指向的数组位置。
写端指针递增,如果到达数组边界,则复位到数组起始位置。

使用特权

评论回复
13
lihuami| | 2024-11-9 20:21 | 只看该作者
在缓冲区满或空时,需要有适当的错误处理机制。

使用特权

评论回复
14
plsbackup| | 2024-11-9 20:57 | 只看该作者
能够保持数据的顺序和连续性,方便后续对数据的处理和分析。串口数据通常是按顺序依次接收的,使用循环数组缓存可以确保数据按照接收的顺序依次存储,不会出现数据丢失或乱序的情况。

使用特权

评论回复
15
uptown| | 2024-11-9 21:16 | 只看该作者
定义一个足够大的数组作为缓存区,通常使用unsigned char类型,因为该类型变量的最大值为256,方便处理。

使用特权

评论回复
16
wwppd| | 2024-11-10 09:47 | 只看该作者
在实际应用中,可以根据具体的需求对缓存区中的数据进行处理。例如,当缓存区中的数据达到一定长度或满足特定条件时,进行数据解析或处理操作。

使用特权

评论回复
17
mnynt121| | 2024-11-10 13:40 | 只看该作者
通过循环数组缓存,可以将串口数据的接收和处理分离开来,使得数据处理逻辑更加清晰和简单。接收数据时只需将数据放入缓存中,而在处理数据时,可以按照自己的节奏从缓存中取出数据进行处理,无需担心数据的同步和丢失问题。

使用特权

评论回复
18
loutin| | 2024-11-10 14:06 | 只看该作者
#include <stdio.h>

#define BUFFER_SIZE 256

uint8_t buffer[BUFFER_SIZE];
uint16_t write_ptr = 0;
uint16_t read_ptr = 0;

// 检查缓冲区是否为空
int is_buffer_empty(void)
{
    return write_ptr == read_ptr;
}

// 检查缓冲区是否已满
int is_buffer_full(void)
{
    return ((write_ptr + 1) % BUFFER_SIZE) == read_ptr;
}

// 写入数据到缓冲区
void write_to_buffer(uint8_t data)
{
    if (!is_buffer_full()) {
        buffer[write_ptr] = data;
        write_ptr = (write_ptr + 1) % BUFFER_SIZE;
    } else {
        // 缓冲区已满,处理错误
        printf("Buffer is full!\n");
    }
}

// 从缓冲区读取数据
uint8_t read_from_buffer(void)
{
    uint8_t data = 0;

    if (!is_buffer_empty()) {
        data = buffer[read_ptr];
        read_ptr = (read_ptr + 1) % BUFFER_SIZE;
    } else {
        // 缓冲区为空,处理错误
        printf("Buffer is empty!\n");
    }

    return data;
}

int main(void)
{
    // 示例:写入和读取数据
    write_to_buffer('A');
    write_to_buffer('B');
    printf("Read from buffer: %c
", read_from_buffer());
    printf("Read from buffer: %c
", read_from_buffer());

    return 0;
}

使用特权

评论回复
19
febgxu| | 2024-11-10 16:29 | 只看该作者
如果在多线程或多任务环境中使用循环数组缓存,需要考虑并发访问的问题,可能需要使用互斥锁或其他同步机制。

使用特权

评论回复
20
deliahouse887| | 2024-11-10 18:30 | 只看该作者
#define BUFFER_SIZE 1024

// 定义循环缓冲区结构体
typedef struct {
    uint8_t buffer[BUFFER_SIZE]; // 存储数据的数组
    uint16_t read_index;          // 读指针
    uint16_t write_index;         // 写指针
} RingBuffer;

// 初始化循环缓冲区
void RingBuffer_Init(RingBuffer *rb) {
    rb->read_index = 0;
    rb->write_index = 0;
}

// 向循环缓冲区写入数据
void RingBuffer_Write(RingBuffer *rb, uint8_t data) {
    if ((rb->write_index + 1) % BUFFER_SIZE != rb->read_index) {
        rb->buffer[rb->write_index] = data;
        rb->write_index = (rb->write_index + 1) % BUFFER_SIZE;
    }
}

// 从循环缓冲区读取数据
uint8_t RingBuffer_Read(RingBuffer *rb) {
    if (rb->read_index != rb->write_index) {
        uint8_t data = rb->buffer[rb->read_index];
        rb->read_index = (rb->read_index + 1) % BUFFER_SIZE;
        return data;
    }
    return 0; // 缓冲区为空
}

使用特权

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

本版积分规则

194

主题

6632

帖子

3

粉丝