目录
[UART DATA 事件:](#UART DATA 事件:)
前言:
系统环境: Windows 10
开发环境:VS-Code + ESP-IDF(V5.2.2)
开发版 : ESP32 WROOM32
官方资料: 通用异步接收器/发送器 (UART) - ESP32 - --- ESP-IDF 编程指南 v5.2.2 文档
例 程 :esp-idf/examples/peripherals/uart/uart_events at v5.2.2 · espressif/esp-idf · GitHub
遇到问题点:
1.刚开始接触ESP32不知道如何使用中断像之前STM32F103一样使用中断接收一帧数据
2.需要使用句柄和队列实现接收数据
3.一次性接收到的数据长度只有120字节
4.发生uart事件报错 assert failed: xQueueSemaphoreTake queue.c:1713 (pxQueue->uxItemSize == 0
5.接收的数据长度超过RX缓冲区
配置串口
配置参数
这里使用结构体来配置 通过函数uart_param_config90;
参数:
uart_port_t uart_num 串口号 如UART_NUM_0 串口0
uart_config_t *uart_config uart配置结构体
具体代码:
cpp
const uart_config_t uart_config = {
.baud_rate = 115200, //波特率
.data_bits = UART_DATA_8_BITS, //数据位
.parity = UART_PARITY_DISABLE, //奇偶校验位
.stop_bits = UART_STOP_BITS_1, //停止位
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, //流控制 这里禁用硬件控制
.source_clk = UART_SCLK_DEFAULT, //uart时钟源
}; //使用vscode的快速查看->速览定义 功能可以快速查看这些成员如何设置
uart_param_config(UART_NUM_0, &uart_config);
安装UART驱动
主要是设置接收缓冲区大小和事件队列
uart安装函数 uart_driver_install();
参数:
参数类型 参数名
uart_port_t uart_num
串口号 如 UART_NUM_0
int rx_buffer_size
RX缓冲区大小 一次可以缓冲数据长度 单位字节 RX缓冲区满了的情况下还有数据过来会造成RX FIFO缓存区溢出 触发FIFO溢出事件 根据需求分配大小加上预留空间
int tx_buffer_size
TX缓冲区大小,如果此参数为0 UART发送函数uart_write_bytes()会等待数据全部发送完再返回(任务阻塞),此参数不为0 则会将数据拷贝到缓冲区然后立即返回,系统会在后台发送数据(任务不阻塞i)
int event_queue_size
UART数据i事件队列大小,最多可以存储的事件项目数
QueueHandle_t *uart_queue
UART事件句柄 通过这个句柄其他函数可以访问UART事件队列的项目
int intr_alloc_flags
中断标志 这个不是UART中断
具体代码:
这里把上面的代码一起放过来,例程里是有更改UART管脚号的函数,我这里没有
cpp
static const int RX_BUF_SIZE = 512; //接收缓冲区大小
static const int TX_BUF_SIZE = 512; //发生缓冲区大小
static QueueHandle_t uart0_queue; //UART0的事件句柄
//UART配置结构体
const uart_config_t uart_config = {
.baud_rate = 115200, //波特率
.data_bits = UART_DATA_8_BITS, //数据位
.parity = UART_PARITY_DISABLE, //奇偶校验位
.stop_bits = UART_STOP_BITS_1, //停止位
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, //流控制 这里禁用硬件控制
.source_clk = UART_SCLK_DEFAULT, //uart时钟源
};
// We won't use a buffer for sending data.
// uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, TX_BUF_SIZE * 2, 10, &uart1_queue, 0);
// uart_param_config(UART_NUM_1, &uart_config);
// uart_set_pin(UART_NUM_1, TXD1_PIN, RXD1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//init uart0
//安装uart驱动 这里启用事件检测 最大队列项目20个 句柄结构体uart0_queue
//参数1.uart端口号 2.接收缓冲区 3.发送缓冲区 4.事件队列大小 5.事件句柄 6.分配中断标志(不分配)
uart_driver_install(UART_NUM_0, RX_BUF_SIZE * 1, TX_BUF_SIZE * 1, 20, &uart0_queue, 0);
//配置uart0
uart_param_config(UART_NUM_0, &uart_config);
//修改UART管脚号
uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
到这里UART配置完成
这里只写用到的函数,因为太多还在摸索针对于通过UART事件接收数据够用 还往大佬不要笑话
事件处理
完整代码
cpp
/* UART asynchronous example, that uses separate RX and TX tasks
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"
static const char* TAG0 = "uart0_event"; //UART0日志标签
static const int RX_BUF_SIZE = 512; //接收缓冲区大小
static const int TX_BUF_SIZE = 512; //发生缓冲区大小
#define TXD1_PIN (GPIO_NUM_4)
#define RXD1_PIN (GPIO_NUM_5)
static QueueHandle_t uart0_queue; //UART0的事件句柄
//static QueueHandle_t uart1_queue;
void init(void)
{
//UART配置结构体
const uart_config_t uart_config = {
.baud_rate = 115200, //波特率
.data_bits = UART_DATA_8_BITS, //数据位
.parity = UART_PARITY_DISABLE, //奇偶校验位
.stop_bits = UART_STOP_BITS_1, //停止位
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, //流控制 这里禁用硬件控制
.source_clk = UART_SCLK_DEFAULT, //uart时钟源
};
// We won't use a buffer for sending data.
// uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, TX_BUF_SIZE * 2, 10, &uart1_queue, 0);
// uart_param_config(UART_NUM_1, &uart_config);
// uart_set_pin(UART_NUM_1, TXD1_PIN, RXD1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//init uart0
//安装uart驱动 这里启用事件检测 最大队列项目20个 句柄结构体uart0_queue
//参数1.uart端口号 2.接收缓冲区 3.发送缓冲区 4.事件队列大小 5.事件句柄 6.分配中断标志(不分配)
uart_driver_install(UART_NUM_0, RX_BUF_SIZE * 1, TX_BUF_SIZE * 1, 20, &uart0_queue, 0);
//配置uart0
uart_param_config(UART_NUM_0, &uart_config);
}
/*--------------------------------------------------------------------------------*/
static void UART0_EVENT(void *pvParameters){
uart_event_t event; //uart事件结构体
size_t buffered_size; //存放RX缓冲区里的数据长度 单位:字节
uint8_t* dtmp = (uint8_t*) malloc(RX_BUF_SIZE); //开辟一段内存存放接收到的数据 无符号字节型(动态分配的内存dtmp的地址不可以发生改变)
for (;;)
{
//等得UART事件
//从uart事件队列中接收一个项目并拷贝到事件结构体中 成功接收到项目将从队列中删除
if (xQueueReceive(uart0_queue, (void *)&event, (TickType_t)portMAX_DELAY))
{
//C语言函数 将指定长度的内存清零 这里如果有事件发生清零接收数据存放区
bzero(dtmp,RX_BUF_SIZE);
switch (event.type)
{
case UART_FIFO_OVF: //RX FIFO缓存区溢出
ESP_LOGI(TAG0,"[UART_FIFO_OVF]:The upper buffer is full causing the FIFO cache to overflow\n");
if (ESP_OK == uart_flush(UART_NUM_0)) //这里直接清除FIFO
ESP_LOGI(TAG0,"[UART_FIFO_OVF]:FIFO clearing is complete!\n");
else
ESP_LOGI(TAG0,"[UART_FIFO_OVF]:Failed to clear FIFO败!!\n");
xQueueReset(uart0_queue); //将队列初始化
break;
case UART_DATA: //接收到数据
//这里需要注意!!!!
//ESP32默认的RX FIFO缓存区是128字节 当接收到120字节时就会触发此事件。如果需要接收大于120字节的数据,可以使用uart_event_t结构体中的timeout_flag成员来实现
//timeout_flag UART读取超时标志,官方翻译过来:
/*UART数据读取超时标志UART数据事件(在配置的RX输出期间没有接收到新数据)如果事件是由FIFO-full中断引起的,那么在下一个字节到来之前将没有带有超时标志的事件。
UART_DATA事件的UART数据读取超时标志(在配置的RX TOUT期间没有接收到新数据)如果该事件是由FIFO-full中断引起的,那么在下一个字节到来之前将没有带有超时标志的事件。*/
if (event.timeout_flag) //发生超时。说明数据全部接收完成,可以开始处理了
{
uart_get_buffered_data_len(UART_NUM_0,&buffered_size); //读取RX缓冲区里的数据长度
ESP_LOGI(TAG0,"[UART_DATA] [%d] [%d]:",buffered_size,event.timeout_flag);
uart_read_bytes(UART_NUM_0,dtmp,buffered_size,portMAX_DELAY); //这里的数据长度就不能按照例程里的使用event.size,因为这个值最大是120 也就是FIFO的最大接收长度
uart_write_bytes(UART_NUM_0,(const uint8_t *)dtmp,buffered_size);
}
break;
case UART_BREAK :
ESP_LOGI(TAG0,"[UART_event] [%d]:",event.size);
break;
case UART_BUFFER_FULL:
break;
case UART_FRAME_ERR:
break;
case UART_PARITY_ERR:
break;
case UART_DATA_BREAK:
break;
case UART_PATTERN_DET:
break;
default:
break;
}
}
}
free(dtmp); //释放内存
dtmp=NULL; //将指针地址指向NULL 防止误调用造成非法访问,
vTaskDelete(NULL); //删除任务,这个函数是通过传递句柄来删除任务,如果传递NULL将删除调用这个函数的任务(本任务)
}
/*
int sendData(const char* logName, const char* data)
{
const int len = strlen(data);
const int txBytes = uart_write_bytes(UART_NUM_0, data, len);
ESP_LOGI(logName, "Wrote %d bytes", txBytes);
return txBytes;
}
static void tx_task(void *arg)
{
static const char *TX_TASK_TAG = "TX_TASK";
esp_log_level_set(TX_TASK_TAG, ESP_LOG_INFO);
while (1) {
sendData(TX_TASK_TAG, "Hello world");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
static void rx_task(void *arg)
{
static const char *RX_TASK_TAG = "RX_TASK";
esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE + 1);
while (1) {
const int rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 1000 / portTICK_PERIOD_MS);
if (rxBytes > 0) {
data[rxBytes] = 0;
ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);
}
}
free(data);
}
*/
void app_main(void)
{
esp_log_level_set(TAG0, ESP_LOG_INFO);
init();
//创建uart0任务
xTaskCreate(UART0_EVENT, "uart0_event", 2048, NULL, 12, NULL);
}
1.创建一个事件处理任务
cpp
xTaskCreate(UART0_EVENT, "uart0_event", 2048, NULL, 12, NULL);
}
参数:
参数类型 参数名
参数1: TaskFunction_t pxTaskCode 函数地址这里直接填函数名即可 如UART0_EVENT 这个参数的类型实际就是函数指针
参数2: const char * const pcName 任务名,方便调试,字符串型最大长度由 configMAX_TASK_NAME_LEN 定义 - 默认值为 16
参数3:const configSTACK_DEPTH_TYPE usStackDepth 指定任务的堆栈大小 单位字节型,这里分配了2048字节 注意:这里需要分配足够大的堆栈,我一开始分配的1024字节,触发事件后监视器报错:assert failed: xQueueSemaphoreTake queue.c:1713 (pxQueue->uxItemSize == 0
参数4:void * const pvParameters 将用作正在创建的任务的参数的指针 这里不使用,NULL 指向地址0
参数5: UBaseType_t uxPriority 任务优先级 这里设置的和例程一样 12
参数6: TaskHandle_t * const pxCreatedTask 用于传回可引用所创建任务的句柄,通过句柄可以与其他任务通讯 这里不使用 NULL
事件处理函数
cpp
static void UART0_EVENT(void *pvParameters){
任务函数需要写一个死循环 我这里用到 for(; ;) 所有的执行操作都写在这个死循环里
定义变量
cpp
uart_event_t event; //uart事件结构体
size_t buffered_size; //存放RX缓冲区里的数据长度 单位:字节
uint8_t* dtmp = (uint8_t*) malloc(RX_BUF_SIZE); //开辟一段内存存放接收到的数据 无符号字节型(动态分配的内存dtmp的地址不可以发生改变)
uint8_t* dtmp = (uint8_t*) malloc(RX_BUF_SIZE); 这是一种可变长度数组的写法 用动态内存分配函数malloc()申请一块内存 将malloc()返回的内存首地址作为指针数组的地址,这个数组是用来存储接收到的数据,大小和RX缓冲区一样即可。接收完一帧数据要立即从RX缓冲区拷贝出来。让RX缓冲区接收下一帧数据
事件触发
cpp
if (xQueueReceive(uart0_queue, (void *)&event, (TickType_t)portMAX_DELAY))
{
//C语言函数 将指定长度的内存清零 这里如果有事件发生清零接收数据存放区
bzero(dtmp,RX_BUF_SIZE);
函数 xQueueReceive() 从队列接收项目,成功接收的项目将从队列中删除 成功接收返回pdTRUE (= 1) 失败返回pdFALSE (=0)
参数1: QueueHandle_t xQueue 队列句柄
参数2: void * const pvBuffer 存放接收项目的变量地址,接收的项目就放在在里面
参数3: TickType_t xTicksToWait 如果队列为空需要等待的周期数(等待瞬间) 这里的(TickType_t)portMAX_DELAY) = (unsigned long)4294967295UL 等待时间较长 可以使用:
cpp
#define portCRITICAL_NESTING_IN_TCB 0
#define portSTACK_GROWTH ( -1 )
#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ )
#define portBYTE_ALIGNMENT 16 // Xtensa Windowed ABI requires the stack pointer to always be 16-byte aligned. See "isa_rm.pdf 8.1.1 Windowed Register Usage and Stack Layout"
#define portTICK_TYPE_IS_ATOMIC 1
#define portNOP() XT_NOP()
获取事件类型
cpp
uart_event_t event; //uart事件结构体
通过上面的函数接收到的队列项目是存储在这个结构体中的,展开这个结构体类型:
cpp
//这是官方的定义
typedef struct {
uart_event_type_t type; /*!< UART event type */
size_t size; /*!< UART data size for UART_DATA event*/
bool timeout_flag; /*!< UART data read timeout flag for UART_DATA event (no new data received during configured RX TOUT)*/
/*!< If the event is caused by FIFO-full interrupt, then there will be no event with the timeout flag before the next byte coming.*/
} uart_event_t;
type 事件类型 这个成员是枚举类型,
size 数据长度 这里指UART触发事件时接收的数据长度 单位字节,
timeout_flag超时标志,UART data 事件,接收完一帧数据之前=0 接收完一帧数据之后=1(个人理解 )可以参考官方文档)
所有事件类型
cpp
ypedef enum {
UART_DATA, /*!< 数据事件 接收到数据*/
UART_BREAK, /*!< 中断事件 发生接收中断*/
UART_BUFFER_FULL, /*!< RX缓冲区已满*/
UART_FIFO_OVF, /*!< RX FIFO缓存区溢出*/
UART_FRAME_ERR, /*!< 接收数据错误*/
UART_PARITY_ERR, /*!< RX奇偶校验错误*/
UART_DATA_BREAK, /*!< 发送数据中断*/
UART_PATTERN_DET, /*!< 检测到uart模式 */ //这个没开始学习
#if SOC_UART_SUPPORT_WAKEUP_INT
UART_WAKEUP, /*!< UART wakeup event */
#endif
UART_EVENT_MAX, /*!< UART event max index*/
} uart_event_type_t;
事件类型处理
cpp
//处理事件
switch (event.type)
这里使用switch语句来判断事件类型
cpp
ESP_LOGI(TAG0,"[UART_DATA] [%d] [%d]:",buffered_size,event.timeout_flag);
使用ESP_LOGI()宏可以很好的监视和调试, 用法和printf()函数一样
UART DATA 事件:
cpp
if (event.timeout_flag) //发生超时。说明数据全部接收完成,可以开始处理了
{
uart_get_buffered_data_len(UART_NUM_0,&buffered_size); //读取RX缓冲区里的数据长度
ESP_LOGI(TAG0,"[UART_DATA] [%d] [%d]:",buffered_size,event.timeout_flag);
uart_read_bytes(UART_NUM_0,dtmp,buffered_size,portMAX_DELAY); //这里的数据长度就不能按照例程里的使用event.size,因为这个值最大是120 也就是FIFO的最大接收长度
uart_write_bytes(UART_NUM_0,(const uint8_t *)dtmp,buffered_size);
}
(个人理解)ESP32的uart接收到数据之后会缓存到RX FIFO,(官方底层代码里RXFIFO分配的是128字节默认接收120字节) 当RXFIFO满了之后系统会将数据拷贝到RX缓冲区(不覆盖)并触发事件,RXFIFO继续接收剩下的数据(一帧数据大于120字节),或者RXFIFO 没有满,但还没没有数据过来了也会把数据拷贝到缓冲区并触发事件(一帧数据小于等于120字节),等待接收下一帧数据
这里需要注意!!!因为RX FIFO的大小是128字节,如果一帧的数据是<=120字节,接收完这帧数据会触发一次事件 size=120 如果一帧数据>120字节,比如300字节 接收完会触发三次事件 :
如果每次事件都调用uart_read_bytes()函数
cpp
case UART_DATA: //接收到数据
uart_read_bytes(UART_NUM_0,dtmp,buffered_size,portMAX_DELAY);
break
第一次 size=120
第二次 size=120
第三次 size= 60
如果不调用,也就是不处理
cpp
case UART_DATA: //接收到数据
//放任不管
//uart_read_bytes(UART_NUM_0,dtmp,buffered_size,portMAX_DELAY);
break
第一次 size=120
第二次 size=240
第三次 size= 300
所以利用timeout_flag 超时标志位我们就可以接收完整的一帧数据
cpp
case UART_DATA: //接收到数据
if (event.timeout_flag) //发生超时。说明数据全部接收完成,可以开始处理了
{
uart_read_bytes(UART_NUM_0,dtmp,buffered_size,portMAX_DELAY);
}
---------------------------------------------------------------------------以上是我的学习笔记