ringbufferts.h
/**
* @file ringbufferts.h
* @brief 线程安全环形缓冲区实现头文件
* @author [作者名]
* @date [日期]
* @version 1.0
*
* 该文件定义了线程安全的环形缓冲区数据结构及相关操作函数。
* 环形缓冲区是一种固定大小的缓冲区,当数据写入到达缓冲区末尾时会从头开始继续写入,
* 实现了先进先出(FIFO)的数据存储方式。本实现通过互斥锁保证线程安全。
*/
#ifndef RING_BUFFER_TS_H
#define RING_BUFFER_TS_H
#include <stdint.h>
#include <stdbool.h>
/**
* @brief 线程安全环形缓冲区结构体
*/
typedef struct RingBufferTS {
uint8_t *buffer; /**< 缓冲区内存指针 */
uint16_t size; /**< 缓冲区总大小 */
volatile uint16_t writeIndex; /**< 写索引,指向下一个可写入位置 */
volatile uint16_t readIndex; /**< 读索引,指向下一个可读取位置 */
void *mutex; /**< 互斥锁句柄,用于线程同步 */
} RingBufferTS;
/**
* @brief 初始化环形缓冲区
* @param rb 环形缓冲区结构体指针
* @param buffer 用于存储数据的缓冲区内存
* @param size 缓冲区大小
*/
void RingBufferTS_Init(RingBufferTS *rb, uint8_t *buffer, uint16_t size);
/**
* @brief 销毁环形缓冲区
* @param rb 环形缓冲区结构体指针
*/
void RingBufferTS_Deinit(RingBufferTS *rb);
/**
* @brief 获取缓冲区中可用数据的字节数
* @param rb 环形缓冲区结构体指针
* @return 可读取的字节数
*/
uint16_t RingBufferTS_AvailableData(RingBufferTS *rb);
/**
* @brief 获取缓冲区中可用空间的字节数
* @param rb 环形缓冲区结构体指针
* @return 可写入的字节数
*/
uint16_t RingBufferTS_AvailableSpace(RingBufferTS *rb);
/**
* @brief 向缓冲区写入数据
* @param rb 环形缓冲区结构体指针
* @param data 待写入的数据指针
* @param len 待写入的数据长度
* @return 实际写入的字节数
*/
uint16_t RingBufferTS_Write(RingBufferTS *rb, const uint8_t *data, uint16_t len);
/**
* @brief 从缓冲区窥视数据(不移动读指针)
* @param rb 环形缓冲区结构体指针
* @param dest 目标存储区域指针
* @param len 希望读取的数据长度
* @return 实际读取的字节数
*/
uint16_t RingBufferTS_Peek(RingBufferTS *rb, uint8_t *dest, uint16_t len);
/**
* @brief 消费缓冲区中的数据(移动读指针)
* @param rb 环形缓冲区结构体指针
* @param len 希望消费的数据长度
*/
void RingBufferTS_Consume(RingBufferTS *rb, uint16_t len);
#endif
ringbufferts.c
/**
* @file ringbufferts.c
* @brief 线程安全环形缓冲区实现源文件
* @author [作者名]
* @date [日期]
* @version 1.0
*
* 该文件实现了线程安全的环形缓冲区相关操作函数,包括初始化、销毁、读写等操作。
* 通过互斥锁保证在多线程环境下的数据一致性。
*/
#include "ringbufferts.h"
#include "cm_os.h"
#include <string.h>
/**
* @brief 初始化环形缓冲区
* @param rb 环形缓冲区结构体指针
* @param buffer 用于存储数据的缓冲区内存
* @param size 缓冲区大小
*
* 此函数初始化环形缓冲区的各个字段,并创建互斥锁用于线程同步。
* 缓冲区的读写索引都被初始化为0,表示缓冲区为空。
*/
void RingBufferTS_Init(RingBufferTS *rb, uint8_t *buffer, uint16_t size) {
rb->buffer = buffer;
rb->size = size;
rb->writeIndex = 0;
rb->readIndex = 0;
rb->mutex = osMutexNew(NULL);
}
/**
* @brief 销毁环形缓冲区
* @param rb 环形缓冲区结构体指针
*
* 此函数释放环形缓冲区使用的互斥锁资源,并将互斥锁句柄置为NULL。
*/
void RingBufferTS_Deinit(RingBufferTS *rb) {
if (rb->mutex) {
osMutexDelete((osMutexId_t )rb->mutex);
// vSemaphoreDelete((SemaphoreHandle_t)rb->mutex);
rb->mutex = NULL;
}
}
/**
* @brief 加锁操作
* @param rb 环形缓冲区结构体指针
*
* 内部辅助函数,用于获取互斥锁,确保对缓冲区的访问是线程安全的。
*/
static void lock(RingBufferTS *rb) {
if (rb->mutex) {
// xSemaphoreTake((SemaphoreHandle_t)rb->mutex, portMAX_DELAY);
osMutexAcquire((osMutexId_t )rb->mutex,osWaitForever);
}
}
/**
* @brief 解锁操作
* @param rb 环形缓冲区结构体指针
*
* 内部辅助函数,用于释放互斥锁,允许其他线程访问缓冲区。
*/
static void unlock(RingBufferTS *rb) {
if (rb->mutex) {
// xSemaphoreGive((SemaphoreHandle_t)rb->mutex);
osMutexRelease((osMutexId_t )rb->mutex);
}
}
/**
* @brief 获取缓冲区中可用数据的字节数
* @param rb 环形缓冲区结构体指针
* @return 可读取的字节数
*
* 此函数计算缓冲区中当前可读取的数据量:
* - 当写索引大于等于读索引时,数据量为 writeIndex - readIndex
* - 当写索引小于读索引时,说明数据环绕到了缓冲区开头,数据量为 size - (readIndex - writeIndex)
*/
uint16_t RingBufferTS_AvailableData(RingBufferTS *rb) {
uint16_t ret;
// lock(rb);
if (rb->writeIndex >= rb->readIndex)
ret = rb->writeIndex - rb->readIndex;
else
ret = rb->size - (rb->readIndex - rb->writeIndex);
// unlock(rb);
return ret;
}
/**
* @brief 获取缓冲区中可用空间的字节数
* @param rb 环形缓冲区结构体指针
* @return 可写入的字节数
*
* 为什么要减1?
* 假设缓冲区大小是 N,索引范围是 0∼N−1。
* 当 writeIndex == readIndex 时,缓冲区为空。
* 如果允许写满整个缓冲区,即写入 N 个元素后,writeIndex 会追上 readIndex,此时会出现空和满状态无法区分的问题。
* 为了避免这种歧义,通常设计成:
* 缓冲区最多只能存储 N−1 个元素。
* 保留一个空位作为"满"和"空"的分界。
*/
uint16_t RingBufferTS_AvailableSpace(RingBufferTS *rb) {
uint16_t space;
// lock(rb);
space = rb->size - RingBufferTS_AvailableData(rb) - 1;
// unlock(rb);
return space;
}
/**
* @brief 向缓冲区写入数据
* @param rb 环形缓冲区结构体指针
* @param data 待写入的数据指针
* @param len 待写入的数据长度
* @return 实际写入的字节数
*
* 此函数将数据写入环形缓冲区,在写入前会检查参数有效性及缓冲区空间是否足够:
* 1. 如果请求写入的数据量大于可用空间,则只写入可用空间大小的数据
* 2. 如果写入位置到达缓冲区末尾,则将剩余数据写入缓冲区开头(处理环绕情况)
*/
uint16_t RingBufferTS_Write(RingBufferTS *rb, const uint8_t *data, uint16_t len) {
uint16_t written = 0;
if (!rb || !data || len == 0) return 0;
lock(rb);
uint16_t space = RingBufferTS_AvailableSpace(rb);
if (len > space) {
len = space;
}
uint16_t firstPart = rb->size - rb->writeIndex;
if (firstPart > len)
firstPart = len;
memcpy(&rb->buffer[rb->writeIndex], data, firstPart);
rb->writeIndex = (rb->writeIndex + firstPart) % rb->size;
uint16_t secondPart = len - firstPart;
if (secondPart > 0) {
memcpy(&rb->buffer[rb->writeIndex], data + firstPart, secondPart);
rb->writeIndex = (rb->writeIndex + secondPart) % rb->size;
}
written = len;
unlock(rb);
return written;
}
/**
* @brief 从缓冲区窥视数据(不移动读指针)
* @param rb 环形缓冲区结构体指针
* @param dest 目标存储区域指针
* @param len 希望读取的数据长度
* @return 实际读取的字节数
*
* RingBufferTS_Peek 函数的作用是 从环形缓冲区中"窥视"一定长度的数据,但不移动读指针,
* 也就是说,它允许你查看缓冲区中当前可读的数据内容,而不会改变缓冲区的状态(不会标记数据为已消费)。
*
* 实现细节:
* 1. 如果请求读取的数据量大于可用数据量,则只读取可用数据量大小的数据
* 2. 如果读取位置到达缓冲区末尾,则从缓冲区开头继续读取(处理环绕情况)
*/
uint16_t RingBufferTS_Peek(RingBufferTS *rb, uint8_t *dest, uint16_t len) {
uint16_t ret = 0;
if (!rb || !dest || len == 0) return 0;
lock(rb);
uint16_t available = RingBufferTS_AvailableData(rb);
if (len > available)
len = available;
uint16_t firstPart = rb->size - rb->readIndex;
if (firstPart > len)
firstPart = len;
memcpy(dest, &rb->buffer[rb->readIndex], firstPart);
uint16_t secondPart = len - firstPart;
if (secondPart > 0) {
memcpy(dest + firstPart, &rb->buffer[0], secondPart);
}
ret = len;
unlock(rb);
return ret;
}
/**
* @brief 消费缓冲区中的数据(移动读指针)
* @param rb 环形缓冲区结构体指针
* @param len 希望消费的数据长度
*
* RingBufferTS_Consume 函数的作用是 从环形缓冲区中"消费"一定长度的数据,也就是说,它会移动读指针(readIndex),
* 表示已经读取或处理了这些数据,缓冲区中这部分数据可以被覆盖或重新写入。
*
* 实现细节:
* 1. 如果请求消费的数据量大于可用数据量,则只消费可用数据量大小的数据
* 2. 读索引会根据消费的数据量进行相应移动,支持环绕操作
*/
void RingBufferTS_Consume(RingBufferTS *rb, uint16_t len) {
if (!rb || len == 0) return;
lock(rb);
uint16_t available = RingBufferTS_AvailableData(rb);
if (len > available)
len = available;
rb->readIndex = (rb->readIndex + len) % rb->size;
unlock(rb);
}
使用示例
监听modbus报文,或者类crc校验报文,并解析。
主函数周期性将数据从串口中读取,并放入环形缓冲区,同时周期调用parseModbusFrames解析缓冲区中的报文。
uint8_t rxBuffer[RX_BUFFER_SIZE];
RingBufferTS rxRingBuffer;
RingBufferTS_Init(&rxRingBuffer,rxBuffer,sizeof(rxBuffer));
uint8_t buf[20];
while (1)
{
sleep_ms(10);
int len = readDataByLenFromUartWait(UART_CAP, buf, sizeof(buf), 0);
if (len > 0) {
rxBufferWrite(&rxRingBuffer,buf, len);
}
// 解析缓冲区中的Modbus报文
parseModbusFrames(&rxRingBuffer);
}
static void parseModbusFrames(RingBufferTS *rb) {
uint8_t tempFrame[RX_BUFFER_SIZE];
while (RingBufferTS_AvailableData(rb) >= 5) {
uint16_t available = RingBufferTS_AvailableData(rb);
uint16_t peekLen = RingBufferTS_Peek(rb, tempFrame, available);
if (peekLen < 5) break;
uint16_t frameLen = getModbusFrameLength(tempFrame, peekLen);
if (frameLen == 0 || frameLen > peekLen) {
if (frameLen == 0) {
// 跳过一个字节,避免死循环
RingBufferTS_Consume(rb, 1);
}
break;
}
uint16_t crcCalc = modbusCRC16(tempFrame, frameLen - 2);
uint16_t crcRecv = (uint16_t)tempFrame[frameLen - 2] | ((uint16_t)tempFrame[frameLen - 1] << 8);
if (crcCalc == crcRecv) {
processModbusFrame(tempFrame, frameLen);
RingBufferTS_Consume(rb, frameLen);
} else {
RingBufferTS_Consume(rb, 1);
}
}
}
#define MIN_MODBUS_FRAME_SIZE 5 // 地址(1) + 功能码(1) + CRC(2) + 最少1字
uint16_t getModbusFrameLength(const uint8_t *frame, uint16_t len) {
if (len < MIN_MODBUS_FRAME_SIZE) {
return 0; // 数据长度不足,无法判断
}
uint8_t funcCode = frame[1];
switch (funcCode) {
case 0x01: // 读线圈状态
case 0x02: // 读离散输入
case 0x03: // 读保持寄存器
case 0x04: // 读输入寄存器
// 响应帧格式:地址(1) + 功能码(1) + 字节计数(1) + 数据(N) + CRC(2)
if (len < 5) return 0;
{
uint8_t byteCount = frame[2];
uint16_t frameLen = 3 + byteCount + 2; // 头3字节 + 数据 + CRC
if (frameLen <= len) {
return frameLen;
} else {
return 0; // 数据不完整
}
}
case 0x05: // 写单个线圈
case 0x06: // 写单个寄存器
case 0x0F: // 写多个线圈(请求)
case 0x10: // 写多个寄存器(请求)
// 请求帧格式固定长度
// 0x05和0x06请求帧长度固定为8字节
if (funcCode == 0x05 || funcCode == 0x06) {
if (len >= 8) return 8; else return 0;
}
// 0x0F和0x10请求帧长度可变
if (funcCode == 0x0F || funcCode == 0x10) {
if (len < 7) return 0; // 至少7字节才能读取字节计数
uint8_t byteCount = frame[6];
uint16_t frameLen = 7 + byteCount + 2; // 7字节头 + 数据 + CRC
if (frameLen <= len) {
return frameLen;
} else {
return 0;
}
}
break;
case 0x81: // 异常响应(功能码 + 0x80)
case 0x82:
case 0x83:
case 0x84:
case 0x85:
case 0x86:
case 0x8F:
case 0x90:
// 异常响应帧长度固定为5字节
if (len >= 5) return 5; else return 0;
default:
// 其他功能码暂时无法判断长度,返回0
return 0;
}
return 0;
}
void processModbusFrame(const uint8_t *frame, uint16_t length) {
// 这里写你的报文处理逻辑
// 例如打印、响应等
}