- #ifndef QUEUE_H
- #define QUEUE_H
- #include <stdint.h>
- #include <stdbool.h>
- #include <stddef.h>
- // 队列控制块结构体
- typedef struct {
- uint8_t *buf; // 数据存储区首地址
- uint16_t elem_size; // 单个元素大小(字节)
- uint16_t max_elem; // 最大元素数量
- uint16_t head; // 头指针(出队位置)
- uint16_t tail; // 尾指针(入队位置)
- uint16_t count; // 当前元素数量
- } queue_t;
- /**
- * [url=/u/brief]@brief[/url] 初始化队列
- * @param q 队列控制块指针
- * @param buf 预分配的数据存储区地址
- * @param elem_size 单个元素的字节大小
- * @param max_elem 最大可容纳元素数量
- * [url=/u/return]@return[/url] true成功,false参数非法
- */
- bool queue_init(queue_t *q, void *buf, uint16_t elem_size, uint16_t max_elem);
- /**
- * @brief 元素入队
- * @param q 队列控制块指针
- * @param elem 待入队元素指针
- * @return true成功,false队列已满
- */
- bool queue_enqueue(queue_t *q, const void *elem);
- /**
- * @brief 元素出队
- * @param q 队列控制块指针
- * @param elem_out 接收出队元素的缓冲区指针
- * @return true成功,false队列为空
- */
- bool queue_dequeue(queue_t *q, void *elem_out);
- /**
- * @brief 查看队头元素(不取出)
- * @param q 队列控制块指针
- * @param elem_out 接收队头元素的缓冲区指针
- * @return true成功,false队列为空
- */
- bool queue_peek(const queue_t *q, void *elem_out);
- /**
- * @brief 判断队列是否为空
- */
- bool queue_is_empty(const queue_t *q);
- /**
- * @brief 判断队列是否已满
- */
- bool queue_is_full(const queue_t *q);
- /**
- * @brief 获取当前队列元素数量
- */
- uint16_t queue_get_count(const queue_t *q);
- /**
- * @brief 清空队列
- */
- void queue_clear(queue_t *q);
- #endif
实现文件(queue.c)
- #include "queue.h"
- #include <string.h>
- bool queue_init(queue_t *q, void *buf, uint16_t elem_size, uint16_t max_elem)
- {
- if (q == NULL || buf == NULL || elem_size == 0 || max_elem == 0) {
- return false;
- }
- q->buf = (uint8_t *)buf;
- q->elem_size = elem_size;
- q->max_elem = max_elem;
- q->head = 0;
- q->tail = 0;
- q->count = 0;
- return true;
- }
- bool queue_enqueue(queue_t *q, const void *elem)
- {
- if (q == NULL || elem == NULL) return false;
- if (q->count >= q->max_elem) return false; // 队列满
- // 计算尾指针位置,复制元素
- uint8_t *dst = q->buf + q->tail * q->elem_size;
- memcpy(dst, elem, q->elem_size);
- // 尾指针循环前进
- q->tail = (q->tail + 1) % q->max_elem;
- q->count++;
- return true;
- }
- bool queue_dequeue(queue_t *q, void *elem_out)
- {
- if (q == NULL || elem_out == NULL) return false;
- if (q->count == 0) return false; // 队列空
- // 计算头指针位置,复制元素
- const uint8_t *src = q->buf + q->head * q->elem_size;
- memcpy(elem_out, src, q->elem_size);
- // 头指针循环前进
- q->head = (q->head + 1) % q->max_elem;
- q->count--;
- return true;
- }
- bool queue_peek(const queue_t *q, void *elem_out)
- {
- if (q == NULL || elem_out == NULL) return false;
- if (q->count == 0) return false;
- const uint8_t *src = q->buf + q->head * q->elem_size;
- memcpy(elem_out, src, q->elem_size);
- return true;
- }
- bool queue_is_empty(const queue_t *q)
- {
- return (q == NULL) ? true : (q->count == 0);
- }
- bool queue_is_full(const queue_t *q)
- {
- return (q == NULL) ? true : (q->count >= q->max_elem);
- }
- uint16_t queue_get_count(const queue_t *q)
- {
- return (q == NULL) ? 0 : q->count;
- }
- void queue_clear(queue_t *q)
- {
- if (q == NULL) return;
- q->head = 0;
- q->tail = 0;
- q->count = 0;
- }
二、移植到瑞萨 e²s IDE 的方法- 将queue.c和queue.h复制到工程src/utils/目录;
- 在 e²s 工程上右键 → Add Files,将queue.c加入编译路径;
- 根据业务需求定义对应类型的存储缓冲区与队列控制块;
- 在生产者(中断、任务)中调用queue_enqueue放入数据;
- 在消费者(主循环、任务)中轮询判空,调用queue_dequeue取出数据处理。
三、使用方法- 定义存储区与控制块:声明一个对应类型的数组作为数据存储区,同时定义一个queue_t控制块;
- // 示例:定义一个最多存8个事件结构体的队列
- typedef struct {
- uint8_t event_id;
- uint16_t param;
- } event_t;
- event_t event_buf[8]; // 数据存储区
- queue_t event_queue; // 队列控制块
- 初始化队列:调用queue_init,传入控制块、存储区地址、单个元素大小、最大元素数;
- queue_init(&event_queue, event_buf, sizeof(event_t), 8);
- 生产者入队:在中断或任务中,构造好元素后调用queue_enqueue入队;
- 消费者出队:在主循环或任务中,调用queue_dequeue取出元素处理;
- 状态查询:可随时调用queue_is_empty/queue_get_count判断是否有数据待处理。
四、调用代码与实测验证测试场景设计覆盖数值型队列、结构体队列、边界条件、清空操作四大场景,验证通用性与鲁棒性:
- 基础数值队列:uint16_t 类型,入队 5 个元素,分批出队
- 满队列边界:队列深度 4,入队 5 个,验证第 5 个入队失败
- 结构体队列:自定义事件结构体,验证复杂类型存取正确性
- 清空与判空:入队后清空,验证状态复位
验证代码(基于瑞萨 RA6M5 + e²s IDE)- #include "hal_data.h"
- #include "queue.h"
- #include <stdio.h>
- #include <string.h>
- void uart_print(const char *str) {
- R_SCI_UART_Write(&g_uart0_ctrl, (uint8_t *)str, strlen(str));
- }
- // 测试用事件结构体
- typedef struct {
- uint8_t id;
- uint16_t data;
- } test_event_t;
- void queue_test(void)
- {
- char buf[128];
- uint16_t val;
- test_event_t evt;
- uart_print("=== 通用队列功能验证 ===\r\n\r\n");
- // ========== 测试1:基础数值队列 ==========
- uart_print("--- 测试1:uint16数值队列 ---\r\n");
- uint16_t num_buf[8];
- queue_t num_q;
- queue_init(&num_q, num_buf, sizeof(uint16_t), 8);
- // 入队5个元素
- for (int i = 1; i <= 5; i++) {
- uint16_t x = i * 10;
- queue_enqueue(&num_q, &x);
- }
- snprintf(buf, sizeof(buf), "入队5个,当前队列长度:%d\r\n", queue_get_count(&num_q));
- uart_print(buf);
- // 分2次出队
- uart_print("出队元素:");
- for (int i = 0; i < 3; i++) {
- queue_dequeue(&num_q, &val);
- snprintf(buf, sizeof(buf), "%d ", val);
- uart_print(buf);
- }
- snprintf(buf, sizeof(buf), "\r\n剩余队列长度:%d\r\n\r\n", queue_get_count(&num_q));
- uart_print(buf);
- // ========== 测试2:满队列边界 ==========
- uart_print("--- 测试2:满队列边界测试(深度4) ---\r\n");
- uint16_t small_buf[4];
- queue_t small_q;
- queue_init(&small_q, small_buf, sizeof(uint16_t), 4);
- bool ret;
- for (int i = 0; i < 5; i++) {
- uint16_t x = i;
- ret = queue_enqueue(&small_q, &x);
- snprintf(buf, sizeof(buf), "入队%d:%s\r\n", i, ret ? "成功" : "失败");
- uart_print(buf);
- }
- uart_print("\r\n");
- // ========== 测试3:结构体队列 ==========
- uart_print("--- 测试3:事件结构体队列 ---\r\n");
- test_event_t evt_buf[4];
- queue_t evt_q;
- queue_init(&evt_q, evt_buf, sizeof(test_event_t), 4);
- test_event_t e1 = {1, 100};
- test_event_t e2 = {2, 200};
- test_event_t e3 = {3, 300};
- queue_enqueue(&evt_q, &e1);
- queue_enqueue(&evt_q, &e2);
- queue_enqueue(&evt_q, &e3);
- uart_print("出队事件:\r\n");
- while (!queue_is_empty(&evt_q)) {
- queue_dequeue(&evt_q, &evt);
- snprintf(buf, sizeof(buf), " ID:%d Data:%d\r\n", evt.id, evt.data);
- uart_print(buf);
- }
- uart_print("\r\n");
- // ========== 测试4:清空与判空 ==========
- uart_print("--- 测试4:清空与判空 ---\r\n");
- queue_enqueue(&num_q, &val);
- snprintf(buf, sizeof(buf), "入队1个后,是否为空:%s\r\n", queue_is_empty(&num_q) ? "是" : "否");
- uart_print(buf);
- queue_clear(&num_q);
- snprintf(buf, sizeof(buf), "清空后,是否为空:%s,长度:%d\r\n",
- queue_is_empty(&num_q) ? "是" : "否", queue_get_count(&num_q));
- uart_print(buf);
- }
- void hal_entry(void) {
- R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
- queue_test();
- while(1);
- }
五、结果对比
我们通过串口助手验证数据与其是否相符:
六、注意事项- 线程安全说明:
- 单生产者 + 单消费者场景(如中断入队、主循环出队)是天然线程安全的,无需加锁;
- 多生产者或多消费者场景,需在操作队列前关中断 / 加互斥锁,防止指针错乱。
- 元素类型约束:
- 单个队列的元素大小固定,不支持变长元素;如需变长可存储指针 + 长度,或使用环形缓冲区;
- 元素中若包含指针,入队仅复制指针值,不会复制指针指向的内容,需注意生命周期。
- 缓冲区对齐:
- 若元素为 32 位整型、结构体等,建议存储区按 4 字节对齐,避免非对齐访问导致的性能下降或 HardFault。
- 与环形缓冲区的区别:
- 通用队列:面向定长元素,适合事件、指令、结构体等结构化数据,使用简单;
- 环形缓冲区:面向字节流,适合串口 / CAN 等连续数据流,支持批量读写、覆盖模式。
- 适用场景:
- 事件驱动架构的事件队列、指令队列;
- 多任务间的数据传递、消息邮箱;
- 传感器数据缓存、按键事件缓存等。