FreeRTOS - 基于ESP32 串口数据收发
这篇是基于FreeRTOS 针对数据收发做了个集成化处理 这样保证引入该文件后 外界只需要调用收发接口 就能做到对数据的收发 。
- 目前是在ESP32的开发环境下做了个简单的串口数据收发
- 1.核心思想
)
目前是在ESP32的开发环境下做了个简单的串口数据收发
1.核心思想
既然是数据收发 避免不了超时、堵塞、流控等这些机制。
既然是基于FreeRTOS 就要好好利用它的一些特性 帮助我们更好的维护数据。
在开始说FreeRTOS的前 不妨先发散下思维 想一想 假如你也不熟悉RTOS特性 你要怎么设计 才能保证数据收发过程中 数据不会丢失呢。
数据收发 不外乎就2种场景 好理解点 以下把收到数据叫做生产者 因为生产数据嘛
把发送数据叫做消费者。
场景1.生产者生产的数量 > 消费者消费的数量 这种可以叫做 “供大于求”
场景2.生产者生产的数量 <= 消费者消费的数量 这种可以叫做 “供不应求”
那场景1呢 我们就想着 要在缓存满了的情况下 让生产者不生产了 这样就不会出现生产者一直生产 但消费者来不及消费 导致那些生产的数据丢失了
场景2呢 就是完全可以接受 在缓冲区足够的情况下 进行收发。
所以我们要针对场景1做处理。
FreeRTOS有锁 信号量 堵塞等机制 我们利用好这俩机制就能解决场景1的问题。
//基于以上 我们可以创建这样一个结构体 用来存放我们的数据
//其中buf用来存放数据
//read write分别是读写指针 由于buf空间有限 我们可以通过读写指针做一个环形缓冲区
//count是当前写入的数据量
//queue 是数据队列
//mutex 是锁
//ready_semaphore 是信号量
typedef struct {uint8_t *buf;size_t read;size_t write;size_t count;QueueHandle_t queue;SemaphoreHandle_t mutex;SemaphoreHandle_t ready_semaphore;
} DATA_T;
目前用EPS32的串口数据收发做举例
#include "driver/uart.h"
#include "driver/gpio.h"#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <esp_log.h>
#include "freertos/semphr.h"
#include "freertos/queue.h"#define UART_NUM UART_NUM_1
#define UART_TX_PIN 18
#define UART_RX_PIN 17
#define UART_BUF_SIZE 1024typedef struct {uint8_t *buf;size_t length;uint32_t timestamp;
} DATA_PACKET_T;typedef struct {uint8_t *buf;size_t read;size_t write;size_t count;QueueHandle_t queue;SemaphoreHandle_t mutex;SemaphoreHandle_t ready_semaphore;
} DATA_T;static DATA_T data_t;
static const char *UART_TAG = "example:uart";int buffer_get_used_space(void)
{return data_t.count;
}int buffer_get_remain_space(void)
{return UART_BUF_SIZE - buffer_get_used_space();
}bool buffer_is_empty(void)
{return data_t.count == 0;
}bool buffer_is_full(void)
{return data_t.count >= UART_BUF_SIZE;
}void uart_init(void)
{memset(&data_t, 0, sizeof(data_t));data_t.ready_semaphore = xSemaphoreCreateBinary();data_t.mutex = xSemaphoreCreateMutex();data_t.buf = malloc(UART_BUF_SIZE);data_t.read = data_t.write = data_t.count = 0;if (data_t.buf == NULL) {ESP_LOGI(UART_TAG, "缓冲区分配失败\n");return;}uart_config_t uart_config = {.baud_rate = 230400, // 设置波特率.data_bits = UART_DATA_8_BITS, // 设置数据位为 8 位.parity = UART_PARITY_DISABLE, // 不使用校验位.stop_bits = UART_STOP_BITS_1, // 使用一个停止位.flow_ctrl = UART_HW_FLOWCTRL_DISABLE // 禁用硬件流控};ESP_ERROR_CHECK(uart_driver_install(UART_NUM, UART_BUF_SIZE, 0, 20, &data_t.queue, 0)); // 串口驱动安装ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config)); // 串口参数配置ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); // 串口引脚配置ESP_ERROR_CHECK(uart_enable_rx_intr(UART_NUM)); //使能串口中断
}void buffer_write_data(uint8_t *data, size_t length)
{if (length == 0) return;if (xSemaphoreTake(data_t.mutex, pdMS_TO_TICKS(100)) == pdTRUE) {for (size_t i = 0; i < length; i++) {data_t.buf[data_t.write] = data[i];data_t.write = (data_t.write + 1) % UART_BUF_SIZE;data_t.count++;xSemaphoreGive(data_t.ready_semaphore);}xSemaphoreGive(data_t.mutex);//ESP_LOGI(UART_TAG, "缓冲区写入 %d 字节,当前数据量: %d\n", length, data_t.count);}
}// 非阻塞读取,立即返回可用数据
size_t buffer_read_nonblocking(uint8_t *output, size_t max_length)
{size_t total_read = 0;if (xSemaphoreTake(data_t.mutex, pdMS_TO_TICKS(10)) == pdTRUE) {while (!buffer_is_empty() && total_read < max_length) {output[total_read] = data_t.buf[data_t.read];data_t.read = (data_t.read + 1) % UART_BUF_SIZE;data_t.count--;total_read++;}//ESP_LOGI(UART_TAG, "RX noblock %d %d/%d\n", total_read, data_t.read, data_t.write);xSemaphoreGive(data_t.mutex);}return total_read;
}// 从缓冲区读取数据(阻塞等待)
size_t buffer_read_blocking(uint8_t *output, size_t required_length, TickType_t timeout)
{size_t total_read = 0;uint32_t start_time = xTaskGetTickCount();while (total_read < required_length) {// 检查超时if ((xTaskGetTickCount() - start_time) > timeout) {ESP_LOGI(UART_TAG, "读取超时,已读取 %d/%d 字节\n", total_read, required_length);break;}// 一次性读取尽可能多的数据size_t available = buffer_get_used_space();if (available > 0) {size_t to_read = (available < (required_length - total_read)) ? available : (required_length - total_read);// 等待数据信号量if (xSemaphoreTake(data_t.ready_semaphore, pdMS_TO_TICKS(100)) == pdTRUE) {if (xSemaphoreTake(data_t.mutex, pdMS_TO_TICKS(50)) == pdTRUE) {// 计算连续可读的数据长度size_t contiguous = (data_t.read <= data_t.write) ? (data_t.write - data_t.read) : (UART_BUF_SIZE - data_t.read);size_t actual_read = (to_read < contiguous) ? to_read : contiguous;memcpy(output + total_read, data_t.buf + data_t.read, actual_read);//ESP_LOGI(UART_TAG, "RX block %d/%d\n", data_t.read, data_t.write);data_t.read = (data_t.read + actual_read) % UART_BUF_SIZE;data_t.count -= actual_read;total_read += actual_read;xSemaphoreGive(data_t.mutex);}}} else {vTaskDelay(1 / portTICK_PERIOD_MS);}}return total_read;
}void data_recv_proc(void *param)
{int len;int temp;uint8_t buf[256];uart_event_t event;uart_init();// 检查队列是否创建成功if (data_t.queue == NULL) {ESP_LOGE(UART_TAG, "UART事件队列创建失败,任务退出");vTaskDelete(NULL);return;}while (1) {if (xQueueReceive(data_t.queue, &event, portMAX_DELAY)) {switch (event.type) {case UART_DATA:// 有数据到达if (!event.size) {break;}temp = buffer_get_remain_space();temp = event.size > temp ? temp : event.size;temp = sizeof(buf) > temp ? temp : sizeof(buf);// 读取数据到缓冲区len = uart_read_bytes(UART_NUM, buf, temp, pdMS_TO_TICKS(100));if (len > 0) {buffer_write_data(buf, len);}break;case UART_FIFO_OVF:case UART_BUFFER_FULL:ESP_LOGI(UART_TAG, "UART %s\n", (event.type == UART_FIFO_OVF) ? "FIFO 溢出" : "缓冲区满");uart_flush_input(UART_NUM);xQueueReset(data_t.queue);break;default:ESP_LOGI(UART_TAG, "UART 事件类型: %d\n", event.type);break;}}}
}void uart_send_data(uint8_t *data, size_t length)
{if (length == 0) return;int sent = uart_write_bytes(UART_NUM, data, length);if (sent == length) {ESP_LOGI("UART", "成功发送 %d 字节", sent);} else {ESP_LOGE("UART", "发送失败,期望: %d, 实际: %d", length, sent);}
}