0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

嵌入式环形队列和消息队列的实现

CHANBAEK 来源:南山府嵌入式 作者:编外人员 2023-04-14 11:52 次阅读

说明

嵌入式环形队列和消息队列是实现数据缓存和通信的常见数据结构,广泛应用于嵌入式系统中的通信协议和领域。

环形队列是一种先进先出(FIFO)的数据结构,其中数据被存储在一个环形缓冲区中。 它使用两个指针,分别指向队列的头和尾,以便在读写操作时追踪队列的状态。 当队列满时,新数据将覆盖旧数据,以确保队列的长度保持不变。 环形队列在实现嵌入式通信协议时特别有用,例如UARTCAN等。

消息队列是一种多个发送者和接收者之间共享数据的通信机制。 它允许多个任务或线程向队列发送消息,并允许多个任务或线程从队列中接收消息。 每个消息都有一个固定的大小和格式,并可以根据需要进行排队和检索。 在嵌入式系统中,消息队列广泛用于处理异步事件,例如中断处理和任务之间的通信。

主要应用于:

  1. 网络通信协议(例如TCP/IP,UDP等)中的数据缓存和队列管理。
  2. 嵌入式操作系统(例如FreeRTOS,uC/OS等)中的任务通信和事件处理。
  3. 汽车电子领域中的CAN和LIN通信协议。
  4. 工业自动化领域中的Modbus,Profibus等通信协议。
  5. 无线通信领域中的蓝牙ZigbeeLoRa等通信协议。

大致应用

  1. 串口通信中,可以使用环形队列来接收和发送数据。 当接收到新的数据时,将其存储到环形队列中,并在需要发送数据时从队列中取出数据发送。 这种方式可以减少中断处理的时间,提高系统的响应速度。
  2. 多任务系统中,消息队列用于任务之间的通信。 每个任务都可以向消息队列中发送消息,其他任务可以从队列中获取消息并进行相应的处理。 这种方式可以实现任务之间的解耦,提高系统的可扩展性和可维护性。
  3. 实时控制系统中,环形队列可以用于缓存传感器数据或控制命令。 当传感器或其他设备向系统发送数据时,可以将其存储到环形队列中,然后由控制任务从队列中获取数据并进行相应的处理。 这种方式可以减少系统对硬件的依赖性,提高系统的灵活性和可靠性。
  4. 音频处理中,环形队列可以用于实现音频数据的缓存。 当音频数据输入时,将其存储到环形队列中,然后由音频处理任务从队列中获取数据并进行处理。 这种方式可以实现音频数据的流式处理,提高系统的处理效率和响应速度。

嵌入式环形队列

嵌入式环形队列是一种先进先出(FIFO)的队列,其实现基于环形缓冲区。 队列的头尾指针分别指向队列的第一个元素和最后一个元素,当队列满时,新加入的元素将覆盖队列头的元素。 嵌入式环形队列的实现过程如下:

  1. 队列初始化:初始化头尾指针为0,表示队列为空。
  2. 入队操作:将元素插入队列尾部,尾指针加1,如果队列满了,则尾指针回到队列开头,覆盖头指针所指向的元素。
  3. 出队操作:将队列头部元素出队,并将头指针加1,如果队列已经空了,则头指针回到队列开头。

嵌入式环形队列的实现可以使用数组或链表来实现。 使用数组时,需要考虑队列满时需要覆盖队列头的元素,所以需要额外的逻辑来保证正确性。

嵌入式环形队列操作步骤(大致如此)

1)定义一个固定大小的数组

1#define QUEUE_SIZE 10
2int queue[QUEUE_SIZE];

2)定义两个指针,分别指向队列的起始位置和末尾位置

1int head = 0;  // 队列起始位置
2int tail = 0;  // 队列末尾位置

3)实现入队操作,即将元素添加到队列末尾。 如果队列已满,则不能再添加元素

1void enqueue(int data) {
2    if ((tail + 1) % QUEUE_SIZE == head) {
3        // 队列已满
4        return;
5    }
6    queue[tail] = data;
7    tail = (tail + 1) % QUEUE_SIZE;
8}

4)实现出队操作,即将队列中的元素删除并返回。 如果队列为空,则不能执行出队操作。

1int dequeue() {
2    if (head == tail) {
3        // 队列为空
4        return -1;
5    }
6    int data = queue[head];
7    head = (head + 1) % QUEUE_SIZE;
8    return data;
9}

5)实现查询队列大小的函数

1int queue_size() {
2    return (tail - head + QUEUE_SIZE) % QUEUE_SIZE;
3}

完整代码实现

1#define QUEUE_SIZE 10
 2int queue[QUEUE_SIZE];
 3int head = 0;
 4int tail = 0;
 5
 6void enqueue(int data) {
 7    if ((tail + 1) % QUEUE_SIZE == head) {
 8        // 队列已满
 9        return;
10    }
11    queue[tail] = data;
12    tail = (tail + 1) % QUEUE_SIZE;
13}
14
15int dequeue() {
16    if (head == tail) {
17        // 队列为空
18        return -1;
19    }
20    int data = queue[head];
21    head = (head + 1) % QUEUE_SIZE;
22    return data;
23}
24
25int queue_size() {
26    return (tail - head + QUEUE_SIZE) % QUEUE_SIZE;
27}

嵌入式消息队列

嵌入式消息队列的实现原理

嵌入式消息队列通常采用循环缓冲区实现,即使用一个数组作为缓冲区,通过头指针和尾指针来管理队列。 消息队列的基本操作包括入队和出队

入队操作

入队操作将一个消息写入队列中,实现方法如下:

1void queue_push(queue_t *queue, void *data, size_t size)
 2{
 3    uint32_t next = (queue->tail + 1) % queue->capacity;
 4
 5    if (next == queue->head) {
 6        // 队列已满,不再添加数据
 7        return;
 8    }
 9
10    queue_item_t *item = &queue->items[queue->tail];
11    item->size = size;
12    item->data = malloc(size);
13    memcpy(item->data, data, size);
14
15    queue->tail = next;
16}

在入队操作中,我们首先检查队列是否已满。 如果队列已满,就直接返回,不再添加数据。 如果队列未满,则使用尾指针指向的空间来存储新的数据。 为了避免新的数据覆盖旧的数据,我们需要动态分配一个新的内存空间,将数据复制到该空间中,并将其地址存储到队列的元素中。 最后,我们将尾指针往后移动一个位置。

出队操作

出队操作将一个消息从队列中读取出来,并将其从队列中删除,实现方法如下

1void queue_pop(queue_t *queue, void *data, size_t *size)
 2{
 3    if (queue->head == queue->tail) {
 4        // 队列为空,无法读取数据
 5        return;
 6    }
 7
 8    queue_item_t *item = &queue->items[queue->head];
 9    *size = item->size;
10    memcpy(data, item->data, *size);
11
12    free(item->data);
13    queue->head = (queue->head + 1) % queue->capacity;
14}

在出队操作中,我们首先检查队列是否为空。 如果队列为空,就直接返回,无法读取数据。 如果队列非空,则使用头指针指向的空间来读取数据。 我们将元素中存储的数据复制到指定的地址中,并返回数据的大小。 最后,我们释放元素中分配的内存空间,并将头指针往后移动一个位置。

消息示例:

1#include 
  2#include 
  3#include 
  4
  5// 消息队列结构体
  6typedef struct {
  7    uint8_t *buf;    // 指向队列缓存区的指针
  8    uint32_t head;   // 队头指针
  9    uint32_t tail;   // 队尾指针
 10    uint32_t size;   // 队列容量
 11    uint32_t count;  // 当前队列中的消息数量
 12} message_queue_t;
 13
 14// 创建一个消息队列
 15message_queue_t *message_queue_create(uint32_t size) {
 16    message_queue_t *mq = (message_queue_t *)malloc(sizeof(message_queue_t));
 17    if (mq == NULL) {
 18        return NULL;
 19    }
 20
 21    mq->buf = (uint8_t *)malloc(size);
 22    if (mq->buf == NULL) {
 23        free(mq);
 24        return NULL;
 25    }
 26
 27    mq->head = 0;
 28    mq->tail = 0;
 29    mq->size = size;
 30    mq->count = 0;
 31
 32    return mq;
 33}
 34
 35// 销毁一个消息队列
 36void message_queue_destroy(message_queue_t *mq) {
 37    if (mq == NULL) {
 38        return;
 39    }
 40
 41    if (mq->buf != NULL) {
 42        free(mq->buf);
 43    }
 44
 45    free(mq);
 46}
 47
 48// 向消息队列中发送一条消息
 49uint32_t message_queue_send(message_queue_t *mq, uint8_t *buf, uint32_t len) {
 50    if (mq == NULL || buf == NULL || len == 0) {
 51        return 0;
 52    }
 53
 54    // 如果队列已满,则无法发送消息
 55    if (mq->count >= mq->size) {
 56        return 0;
 57    }
 58
 59    // 将消息写入队列缓存区
 60    uint32_t i;
 61    for (i = 0; i < len; i++) {
 62        mq->buf[mq->tail] = buf[i];
 63        mq->tail = (mq->tail + 1) % mq->size;
 64    }
 65
 66    // 更新队列状态
 67    mq->count += len;
 68
 69    return len;
 70}
 71
 72// 从消息队列中接收一条消息
 73uint32_t message_queue_recv(message_queue_t *mq, uint8_t *buf, uint32_t len) {
 74    if (mq == NULL || buf == NULL || len == 0) {
 75        return 0;
 76    }
 77
 78    // 如果队列为空,则无法接收消息
 79    if (mq->count == 0) {
 80        return 0;
 81    }
 82
 83    // 读取队列缓存区中的消息
 84    uint32_t i;
 85    for (i = 0; i < len && i < mq->count; i++) {
 86        buf[i] = mq->buf[mq->head];
 87        mq->head = (mq->head + 1) % mq->size;
 88    }
 89
 90    // 更新队列状态
 91    mq->count -= i;
 92
 93    return i;
 94}
 95
 96// 获取消息队列中的消息数量
 97uint32_t message_queue_count(message_queue_t *mq) {
 98    if (mq == NULL) {
 99        return 0;
100    }
101
102    return mq->count;
103}

消息队列示例说明

上面的示例是一个基于循环队列实现的简单嵌入式消息队列的代码实现,下面详细说明其实现原理:

消息队列结构体

定义一个消息队列结构体,包含队列缓存区指针、队头指针、队尾指针、队列容量和当前队列中的消息数量等信息

1typedef struct {
2    uint8_t *buf;    // 指向队列缓存区的指针
3    uint32_t head;   // 队头指针
4    uint32_t tail;   // 队尾指针
5    uint32_t size;   // 队列容量
6    uint32_t count;  // 当前队列中的消息数量
7} message_queue_t;

创建消息队列

使用 message_queue_create 函数创建一个消息队列,该函数会动态分配一块内存用于存储消息队列结构体和队列缓存区,初始化消息队列的各个参数,并返回一个指向消息队列结构体的指针。

1message_queue_t *message_queue_create(uint32_t size) {
 2    message_queue_t *mq = (message_queue_t *)malloc(sizeof(message_queue_t));
 3    if (mq == NULL) {
 4        return NULL;
 5    }
 6
 7    mq->buf = (uint8_t *)malloc(size);
 8    if (mq->buf == NULL) {
 9        free(mq);
10        return NULL;
11    }
12
13    mq->head = 0;
14    mq->tail = 0;
15    mq->size = size;
16    mq->count = 0;
17
18    return mq;
19}

销毁消息队列

使用 message_queue_destroy 函数销毁一个消息队列,该函数会释放消息队列结构体和队列缓存区所占用的内存。

1void message_queue_destroy(message_queue_t *mq) {
 2    if (mq == NULL) {
 3        return;
 4    }
 5
 6    if (mq->buf != NULL) {
 7        free(mq->buf);
 8    }
 9
10    free(mq);
11}

发送消息

使用 message_queue_send 函数向消息队列中发送一条消息,该函数会将消息写入队列缓存区,并更新队列的状态,返回实际写入队列缓存区的消息长度。

1uint32_t message_queue_send(message_queue_t *mq, uint8_t *buf, uint32_t len) {
 2    if (mq == NULL || buf == NULL || len == 0) {
 3        return 0;
 4    }
 5
 6    // 如果队列已满,则无法发送消息
 7    if (mq->count >= mq->size) {
 8        return 0;
 9    }
10
11    // 将消息写入队列缓存区
12    uint32_t i;
13    for (i = 0; i < len; i++) {
14        mq->buf[mq->tail] = buf[i];
15        mq->tail = (mq->tail + 1) % mq->size;
16    }
17
18    // 更新队列状态
19    mq->count += len;
20
21    return len;
22}
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4982

    文章

    18281

    浏览量

    288465
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1271

    浏览量

    78297
  • 数据结构
    +关注

    关注

    3

    文章

    564

    浏览量

    39900
  • 队列
    +关注

    关注

    1

    文章

    46

    浏览量

    10849
  • 消息队列
    +关注

    关注

    0

    文章

    31

    浏览量

    2921
收藏 人收藏

    评论

    相关推荐

    嵌入式开发中消息队列实现

    嵌入式开发中经常会看到消息队列的身影,队列的应用是很广泛的,它可以应用的场景有很多,比如缓存通信消息,暂存执行内容,数据顺序转发等的操作。
    发表于 08-22 11:19 528次阅读
    <b class='flag-5'>嵌入式</b>开发中消息<b class='flag-5'>队列</b>的<b class='flag-5'>实现</b>

    基于STM32的串口环形队列IAP调试

    基于STM32的串口环形队列IAP调试心得
    的头像 发表于 09-18 15:33 848次阅读
    基于STM32的串口<b class='flag-5'>环形</b><b class='flag-5'>队列</b>IAP调试

    嵌入式常用数据结构------队列操作简介

    嵌入式常用数据结构------队列操作简介队列嵌入式软件中常用的一种数据结构。什么是队列呢?在生活中,我们都知道,买东西时要排队,比如最近
    发表于 06-17 17:30

    环形队列在串口数据接收中的使用

    前言  书接上回,前文主要介绍了环形队列实现原理以及C语言实现及测试过程,本文将回归到嵌入式平台的应用中,话不多说,淦,上干货!实验目的H
    发表于 12-06 06:27

    如何使用队列实现STM32串口环形缓冲?

    串口环形缓冲的好处是什么?如何使用队列实现STM32串口环形缓冲?
    发表于 12-07 07:13

    嵌入式软件中的队列有何特点

    数据结构之队列篇1 队列之特点及在嵌入式软件中的应用队列(queue)是一个简单线性表,它是一个允许在一端进行插入操作,而在另一端进行删除操作的线性表。
    发表于 12-21 07:28

    为什么要使用消息队列?消息队列的优缺点

    由于项目需要,在嵌入式操作系统的中间件设计中,采用了消息队列来作为各个模块之间的通信方式,同时允许用户层的不同用户来访问中间件的各个模块。 现在列出下面三个问题来加深对消息队列的理解,以便后续的架构
    发表于 12-22 06:28

    实现队列环形缓冲的方法

    串口队列环形缓冲区队列串口环形缓冲的好处代码实现队列  要
    发表于 02-21 07:11

    环形队列的相关资料分享

    前言  当代码,不再是简单的完成需求,对代码进行堆砌,而是开始思考如何写出优美代码的时候,我们的代码水平必然会不断提升,今天,咱们来学习环形队列结构。环形队列的基本概念  相信对数据结
    发表于 02-23 06:10

    环形队列的操作如何去实现

    环形队列结构的定义是什么?环形队列的操作如何去实现呢?
    发表于 02-25 06:35

    深度解析数据结构与算法篇之队列环形队列实现

    对尾的位置。 02 — 环形队列实现 要想将元素放入队列我们必须知道对头和队尾,在队列长度不能无限大的条件下我们还要知道
    的头像 发表于 06-18 10:07 1641次阅读

    STM32串口环形缓冲--使用队列实现(开放源码)

    串口队列环形缓冲区队列串口环形缓冲的好处代码实现队列  要
    发表于 12-24 19:04 24次下载
    STM32串口<b class='flag-5'>环形</b>缓冲--使用<b class='flag-5'>队列</b><b class='flag-5'>实现</b>(开放源码)

    基于STM32的串口环形队列IAP调试心得

    使用环形队列,简单点说就是个环形数组,一边接收上位机数据,一边往flash里面写。
    发表于 02-08 15:22 5次下载
    基于STM32的串口<b class='flag-5'>环形</b><b class='flag-5'>队列</b>IAP调试心得

    嵌入式环形队列和消息队列是如何去实现的?

    嵌入式环形队列和消息队列实现数据缓存和通信的常见数据结构,广泛应用于嵌入式系统中的通信协议和领
    发表于 05-20 14:55 670次阅读

    裸机中环形队列与RTOS中消息队列有何区别呢?

    环形队列”和“消息队列”在嵌入式领域有应用非常广泛,相信有经验的嵌入式软件工程师对它们都不陌生。
    的头像 发表于 01-26 09:38 246次阅读
    裸机中<b class='flag-5'>环形</b><b class='flag-5'>队列</b>与RTOS中消息<b class='flag-5'>队列</b>有何区别呢?