听说你不知道 RT-Thread 有个 ringbuffer

在嵌入式开发中,我们经常需要用到 FIFO 数据结构来存储数据,比如任务间的通信、串口数据收发等场合。很多小伙伴不知道 RT-Thread 为我们提供了一个 ringbuffer 数据结构,代码位于:

  • components/drivers/src/ringbuffer.c
  • components/drivers/include/ipc/ringbuffer.h

RingBuffer 其实就是先进先出(FIFO)的循环缓冲区。把一段线性的存储空间当作一个环形的存储空间使用,可以提高存储空间的利用率。

在这里插入图片描述

数据结构

RT-Thread 定义了 rt_ringbuffer 结构体,包括四组成员:缓冲区指针 buffer_ptr、缓冲区大小 buffer_size、读指针、写指针。

struct rt_ringbuffer
{
    rt_uint8_t *buffer_ptr;
    rt_uint16_t read_mirror : 1;
    rt_uint16_t read_index : 15;
    rt_uint16_t write_mirror : 1;
    rt_uint16_t write_index : 15;
    rt_int16_t buffer_size;
};

对于读、写指针,rt_ringbuffer 结构体使用位域来定义 read 和 write 的索引值和镜像位。更具体来说,使用 MSB(最高有效位)作为 read_index 和 write_index 变量的镜像位。通过这种方式,为缓冲区添加了虚拟镜像,用于指示 read 和 write 指针指向的是普通缓冲区还是镜像缓冲区。

  • 如果 write_index 和 read_index 相等,但在不同镜像,说明缓冲区满了;
  • 如果 write_index 和 read_index 相等,但在相同镜像,说明缓冲区空了。

为了让大家更好地理解,我给大家画了个图:

在这里插入图片描述

接口函数

初始化与重置
void rt_ringbuffer_init(struct rt_ringbuffer *rb, rt_uint8_t *pool, rt_int16_t size);
void rt_ringbuffer_reset(struct rt_ringbuffer *rb);

这两个函数适用于以静态方式初始化或重置 ringbuffer,需要事先准备好 ringbuffer 对象和一段内存空间。

创建和销毁
struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t length);
void rt_ringbuffer_destroy(struct rt_ringbuffer *rb);

这两个函数适用于以动态方式创建和销毁 ringbuffer,将在堆空间申请相关资源,并返回一个 ringbuffer 指针。

写入数据
rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length);
rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length);
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch);
rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch);

往 ringbuffer 写入数据可以使用这组函数,其中 _put 为写入一串字符,_putchar 为写入一个字符,带 _force 的函数则表示如果缓冲区满了,将直接用新数据覆盖旧数据(谨慎使用)。

读出数据
rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb, rt_uint8_t *ptr, rt_uint16_t length);
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch);

从 ringbuffer 读出数据可以使用这组函数,其中 _get 为读出一串字符,_getchar 为读出一个字符。

获取长度

读写操作前可以先判断是否有数据可读或者有位置可写,ringbuffer 提供了三个接口获取长度,包括获取 ringbuffer 的总长度、数据长度、空闲长度。

获取缓冲区数据长度

rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb);

获取缓冲区总长度

rt_uint16_t rt_ringbuffer_get_size(struct rt_ringbuffer *rb);

获取缓冲区空闲长度

#define rt_ringbuffer_space_len(rb) ((rb)->buffer_size - rt_ringbuffer_data_len(rb))

应用示例

下面通过一个简单的示例,来看看在程序中该如何使用 ringbuffer。首先创建一个 ringbuffer 对象,然后 Producer 线程往 ringbuffer 写入数据,Consumer 线程从 ringbuffer 读出数据。这是一个典型生产者-消费者模型。

#include <rtthread.h>
#include <rtdevice.h>
#include <string.h>

#define RING_BUFFER_LEN        8

static struct rt_ringbuffer *rb;
static char  *str = "Hello, World";

static void consumer_thread_entry(void *arg)
{
    char ch;

    while (1)
    {
        if (1 == rt_ringbuffer_getchar(rb, &ch))
        {
            rt_kprintf("[Consumer] <- %c\n", ch);
        }
        rt_thread_mdelay(500);
    }
}

static void ringbuffer_sample(int argc, char** argv)
{
    rt_thread_t tid;
    rt_uint16_t i = 0;

    rb = rt_ringbuffer_create(RING_BUFFER_LEN);
    if (rb == RT_NULL)
    {
        rt_kprintf("Can't create ringbffer");
        return;
    }

    tid = rt_thread_create("consumer", consumer_thread_entry, RT_NULL,
                           1024, RT_THREAD_PRIORITY_MAX/3, 20);
    if (tid == RT_NULL)
    {
        rt_ringbuffer_destroy(rb);
    }
    rt_thread_startup(tid);


    while (str[i] != '\0')
    {
        rt_kprintf("[Producer] -> %c\n", str[i]);
        rt_ringbuffer_putchar(rb, str[i++]);
        rt_thread_mdelay(500);
    }

    rt_thread_delete(tid);
    rt_ringbuffer_destroy(rb);
    
}
#ifdef RT_USING_FINSH
MSH_CMD_EXPORT(ringbuffer_sample, Start a producer and a consumer with a ringbuffer);
#endif

运行结果:

在这里插入图片描述

当然可以,下面是一个基于 RT-Thread 的简单 TCP/IP 服务器项目,希望对您有帮助。 该项目主要实现了基于 TCP 协议的简单服务器,可以接收客户端的连接请求,并向客户端发送欢迎消息。 项目代码如下: ```c #include <rtthread.h> #include <lwip/sockets.h> #define PORT 6666 static void server_thread_entry(void *parameter) { int sockfd, new_sockfd; struct sockaddr_in server_addr, client_addr; char buffer[256] = {0}; int n; /* 创建套接字 */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { rt_kprintf("ERROR opening socket\n"); return; } /* 初始化服务器地址结构 */ memset((char *) &server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); /* 绑定套接字到服务器地址 */ if (bind(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { rt_kprintf("ERROR on binding\n"); return; } /* 开始监听 */ listen(sockfd, 5); rt_kprintf("Server started, listening on port %d\n", PORT); /* 接受客户端连接 */ while (1) { socklen_t client_len = sizeof(client_addr); new_sockfd = accept(sockfd, (struct sockaddr *) &client_addr, &client_len); if (new_sockfd < 0) { rt_kprintf("ERROR on accept\n"); continue; } /* 发送欢迎消息 */ memset(buffer, 0, sizeof(buffer)); sprintf(buffer, "Welcome to RT-Thread TCP/IP server!\n"); n = write(new_sockfd, buffer, strlen(buffer)); if (n < 0) { rt_kprintf("ERROR writing to socket\n"); } /* 关闭套接字 */ close(new_sockfd); } } int main(void) { /* 创建服务器线程 */ rt_thread_t server_thread = rt_thread_create("server", server_thread_entry, RT_NULL, 1024, 25, 10); if (server_thread != RT_NULL) { rt_thread_startup(server_thread); } return 0; } ``` 该程序主要实现了以下功能: 1. 创建了一个 TCP 套接字,并将其绑定到指定的端口。 2. 监听客户端的连接请求,并接受客户端的连接。 3. 向客户端发送欢迎消息。 4. 关闭套接字。 当程序运行时,它会在指定的端口上启动一个 TCP 服务器,并等待客户端的连接请求。当客户端连接成功后,服务器会向客户端发送欢迎消息,并关闭连接。 以上是一个简单的基于 RT-Thread 的 TCP/IP 服务器项目示例,希望对您有帮助。若有任何疑问,欢迎继续提问。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿基米东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值