STM32单片机采用环形缓冲区实现串口中断数据接收管理

一、前言

在嵌入式系统开发中,与上位机进行串口通信是非常常见的场景。上位机可以通过串口发送指令或者数据给嵌入式设备,而嵌入式设备需要可靠地接收并解析这些数据,以执行相应的操作。然而,在串口通信过程中,上位机发送数据的速率往往与嵌入式设备接收和处理数据的速率不一致,这就可能导致数据的丢失或者误解析。

为了解决这个问题,决定设计并实现一个环形缓冲区来进行数据接收管理。环形缓冲区是一种高效的数据结构,适用于数据产生速率快于消费速率的场景。它具有固定大小的缓冲区,并且可以循环利用空间,保证数据的连续存储和有效利用。

在本项目中,选择使用STM32微控制器来实现串口数据接收功能。STM32具有丰富的外设资源和强大的性能,非常适合用于串口通信和数据处理。通过在STM32上实现环形缓冲区,可以实现以下目标:

(1)数据稳定接收:通过使用环形缓冲区,确保即使在接收数据速率慢于发送速率的情况下,数据也能够得到稳定的接收,避免数据丢失。

(2)数据缓存和管理:环形缓冲区可以作为一个数据缓存区,将接收到的数据暂时存储起来,以便后续处理。这样可以降低数据处理的延迟和复杂性。

(3)数据解析和应用:通过从环形缓冲区中读取数据,并进行解析和处理,嵌入式设备可以根据接收到的数据执行相应的操作,如控制外部设备或响应上位机指令。

通过使用环形缓冲区管理串口接收的数据,可以实现可靠的数据接收和处理,并提高系统的稳定性和可靠性。同时,该方案也适用于其他嵌入式系统和通信场景。

二、实现思路

(1)定义环形缓冲区的结构体:首先,需要定义一个表示环形缓冲区的结构体,其中包含以下成员变量:

  • 缓冲区的大小(capacity):表示环形缓冲区的容量,即可以存储的最大元素数量。
  • 写指针(write_ptr):表示当前可写入数据的位置。
  • 读指针(read_ptr):表示当前可读取数据的位置。
  • 数据数组(buffer):用于存储实际的数据。

(2)初始化环形缓冲区:在使用环形缓冲区之前,需要进行初始化。初始化时,将缓冲区的大小、写指针和读指针都设置为初始位置,通常都是0。

(3)写入数据:当有新的数据要写入缓冲区时,需要执行以下操作:

  • 检查缓冲区是否已满,如果已满则无法写入新的数据。
  • 将数据写入当前写指针所指向的位置。
  • 更新写指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算。

(4)读取数据:当需要从缓冲区中读取数据时,需要执行以下操作:

  • 检查缓冲区是否为空,如果为空则无数据可读取。
  • 读取当前读指针所指向的数据。
  • 更新读指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算。

(5)判断缓冲区状态:为了方便使用和管理缓冲区,可以实现一些用于判断缓冲区状态的函数,例如:

  • is_full():判断缓冲区是否已满。
  • is_empty():判断缓冲区是否为空。

实现环形缓冲区时,需要注意:

  • 写指针和读指针的位置计算要考虑到环形特性,即超过缓冲区容量时需要进行取模运算。
  • 缓冲区大小要合理选择,根据实际需求确定,以充分利用内存资源并避免数据丢失。
  • 多线程或中断环境下的并发访问要考虑数据同步和互斥操作,以避免竞争条件和数据不一致的问题。

通过以上思路,可以在C语言中实现一个简单高效的环形缓冲区,用于存储和管理数据,在数据收发过程中提高系统的稳定性和可靠性。

三、 C语言实现验证思路

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
​
#define BUFFER_SIZE 10
​
typedef struct {
    int* buffer;      // 缓冲区数组指针
    int size;         // 缓冲区大小
    int head;         // 头部索引
    int tail;         // 尾部索引
} CircularBuffer;
​
// 创建环形缓冲区
CircularBuffer* createCircularBuffer(int size) {
    CircularBuffer* cb = (CircularBuffer*)malloc(sizeof(CircularBuffer));  // 分配内存空间
    cb->buffer = (int*)malloc(sizeof(int) * size);  // 分配缓冲区数据的内存空间
    cb->size = size;   // 设置缓冲区大小
    cb->head = 0;      // 初始化头部索引为0
    cb->tail = 0;      // 初始化尾部索引为0
    return cb;
}
​
// 销毁环形缓冲区
void destroyCircularBuffer(CircularBuffer* cb) {
    free(cb->buffer);  // 释放缓冲区数据的内存空间
    free(cb);          // 释放缓冲区结构体的内存空间
}
​
// 判断环形缓冲区是否已满
int isCircularBufferFull(CircularBuffer* cb) {
    return ((cb->tail + 1) % cb->size == cb->head);
}
​
// 判断环形缓冲区是否为空
int isCircularBufferEmpty(CircularBuffer* cb) {
    return (cb->head == cb->tail);
}
​
// 写入数据到环形缓冲区
void writeData(CircularBuffer* cb, int data) {
    if (isCircularBufferFull(cb)) {  // 如果缓冲区已满,则无法写入数据
        printf("Circular buffer is full. Data cannot be written.\n");
        return;
    }
    cb->buffer[cb->tail] = data;  // 将数据写入缓冲区的尾部
    cb->tail = (cb->tail + 1) % cb->size;  // 更新尾部索引,循环利用缓冲区空间
}
​
// 从环形缓冲区读取数据
int readData(CircularBuffer* cb) {
    if (isCircularBufferEmpty(cb)) {  // 如果缓冲区为空,则无数据可读取
        printf("Circular buffer is empty. No data to read.\n");
        return -1;  // 返回一个默认值表示读取失败
    }
    int data = cb->buffer[cb->head];  // 从缓冲区的头部读取数据
    cb->head = (cb->head + 1) % cb->size;  // 更新头部索引,循环利用缓冲区空间
    return data;
}
​
int main() {
    CircularBuffer* cb = createCircularBuffer(BUFFER_SIZE);  // 创建大小为BUFFER_SIZE的环形缓冲区
​
    writeData(cb, 1);  // 写入数据1
    writeData(cb, 2);  // 写入数据2
    writeData(cb, 3);  // 写入数据3
​
    printf("Read data: %d\n", readData(cb));  // 读取数据并打印
    printf("Read data: %d\n", readData(cb));
​
    writeData(cb, 4);
    writeData(cb, 5);
​
    printf("Read data: %d\n", readData(cb));
    printf("Read data: %d\n", readData(cb));
    printf("Read data: %d\n", readData(cb));
​
    destroyCircularBuffer(cb);  // 销毁环形缓冲区
​
    return 0;
}
​

四、STM32串口接收

cpp 复制代码
#define BUFFER_SIZE 256
​
typedef struct {
  uint8_t buffer[BUFFER_SIZE];
  uint16_t head;
  uint16_t tail;
} CircularBuffer;
​
// 初始化环形缓冲区
void CircularBuffer_Init(CircularBuffer* cb) {
  cb->head = 0;
  cb->tail = 0;
}
​
// 判断环形缓冲区是否已满
bool CircularBuffer_IsFull(const CircularBuffer* cb) {
  return (cb->head + 1) % BUFFER_SIZE == cb->tail;
}
​
// 判断环形缓冲区是否为空
bool CircularBuffer_IsEmpty(const CircularBuffer* cb) {
  return cb->head == cb->tail;
}
​
// 向环形缓冲区写入数据
bool CircularBuffer_Write(CircularBuffer* cb, uint8_t data) {
  if (CircularBuffer_IsFull(cb)) {  // 缓冲区已满,无法写入
    return false;
  }
  
  cb->buffer[cb->head] = data;
  cb->head = (cb->head + 1) % BUFFER_SIZE;
  return true;
}
​
// 从环形缓冲区读取数据
bool CircularBuffer_Read(CircularBuffer* cb, uint8_t* data) {
  if (CircularBuffer_IsEmpty(cb)) {  // 缓冲区为空,无数据可读取
    return false;
  }
  
  *data = cb->buffer[cb->tail];
  cb->tail = (cb->tail + 1) % BUFFER_SIZE;
  return true;
}
​
// 获取环形缓冲区剩余大小
uint16_t CircularBuffer_GetRemainingSize(const CircularBuffer* cb) {
  if (cb->head >= cb->tail) {
    return BUFFER_SIZE - (cb->head - cb->tail);
  } else {
    return cb->tail - cb->head - 1;
  }
}
​
// 获取环形缓冲区已写入大小
uint16_t CircularBuffer_GetWrittenSize(const CircularBuffer* cb) {
  if (cb->head >= cb->tail) {
    return cb->head - cb->tail;
  } else {
    return BUFFER_SIZE - (cb->tail - cb->head - 1);
  }
}
​
// 从环形缓冲区读取指定长度的数据
bool CircularBuffer_ReadData(CircularBuffer* cb, uint8_t* data, uint16_t length) {
  if (CircularBuffer_GetWrittenSize(cb) < length) {
    return false;  // 缓冲区中的数据不足
  }
  
  for (uint16_t i = 0; i < length; ++i) {
    if (!CircularBuffer_Read(cb, &data[i])) {
      return false;  // 读取数据出错
    }
  }
  
  return true;
}
​
// 向环形缓冲区写入指定长度的数据
bool CircularBuffer_WriteData(CircularBuffer* cb, const uint8_t* data, uint16_t length) {
  if (CircularBuffer_GetRemainingSize(cb) < length) {
    return false;  // 缓冲区剩余空间不足
  }
  
  for (uint16_t i = 0; i < length; ++i) {
    if (!CircularBuffer_Write(cb, data[i])) {
      return false;  // 写入数据出错
    }
  }
  
  return true;
}
​
​
​
// 示例:STM32串口接收中断处理函数
void USART_Receive_IRQHandler(void) {
  uint8_t data = USART_ReceiveData(USART1);  // 获取接收到的数据
  if (!CircularBuffer_Write(&rxBuffer, data)) {
    // 缓冲区已满,处理错误
  }
}
​
​

在代码中,定义了一个名为CircularBuffer的结构体来表示环形缓冲区。包含了一个具有固定大小的数组buffer用于存储数据,以及头部指针head和尾部指针tail用于管理数据的读写位置。

接下来,实现了一些函数来对环形缓冲区进行操作。CircularBuffer_Init函数用于初始化环形缓冲区;CircularBuffer_IsFullCircularBuffer_IsEmpty函数分别判断缓冲区是否已满和是否为空;CircularBuffer_Write函数用于向缓冲区写入数据;CircularBuffer_Read函数用于从缓冲区读取数据。

CircularBuffer_GetRemainingSize函数用于获取环形缓冲区的剩余大小,即还能写入多少个字节的数据;CircularBuffer_GetWrittenSize函数用于获取已经写入到缓冲区的字节数;CircularBuffer_ReadData函数用于从环形缓冲区读取指定长度的数据,将其存储到提供的数据数组中;CircularBuffer_WriteData函数用于向环形缓冲区写入指定长度的数据,从提供的数据数组中复制相应的字节。

使用这些方便函数,可以更方便地管理环形缓冲区,实现数据的读取和写入。

最后,给出了一个示例,展示在STM32串口接收中断处理函数中将接收到的数据写入环形缓冲区。在中断处理函数中,通过USART_ReceiveData函数获取接收到的数据,调用CircularBuffer_Write函数将数据写入缓冲区。

相关推荐
如竟没有火炬9 分钟前
全排列——交换的思想
开发语言·数据结构·python·算法·leetcode·深度优先
间彧10 分钟前
Java泛型详解与项目实战
后端
间彧20 分钟前
PECS原则在Java集合框架中的具体实现有哪些?举例说明
后端
间彧22 分钟前
Java 泛型擦除详解和项目实战
后端
寂静山林22 分钟前
UVa 12526 Cellphone Typing
算法
间彧25 分钟前
在自定义泛型类时,如何正确应用PECS原则来设计API?
后端
间彧26 分钟前
能否详细解释PECS原则及其在项目中的实际应用场景?
后端
武子康1 小时前
大数据-132 Flink SQL 实战入门 | 3 分钟跑通 Table API + SQL 含 toChangelogStream 新写法
大数据·后端·flink
李辰洋1 小时前
go tools安装
开发语言·后端·golang
wanfeng_091 小时前
go lang
开发语言·后端·golang