使用串口的基本流程
- 使用结构体
uart_config_t
来为串口设置参数(波特率,停止位,是否有奇偶校验等) - 利用函数
uart_set_pin
来设置串口的引脚映射(没有映射的话就默认为原来的)ESP32的串口管脚可以任意映射到其他管脚上,就是说任意两个PIN脚都可以作为串口的输出输入脚来使用 - 利用函数
uart_driver_install
来为ESP32安装串口驱动 - 串口应用函数的使用,即按照用户要求在任务中调用串口的收发函数
- 使用串口的中断(可选)
- 串口卸载(基本不常用)
设置串口参数
typedef struct {
int baud_rate; /*串口波特率*/
uart_word_length_t data_bits; /*串口字长*/
uart_parity_t parity; /*是否启动串口的奇偶校验*/
uart_stop_bits_t stop_bits; /*串口的停止位*/
uart_hw_flowcontrol_t flow_ctrl; /*是否启动串口的流控位*/
uint8_t rx_flow_ctrl_thresh; /*UART HW RTS阈值*/不常用
union {
uart_sclk_t source_clk; /*UART源时钟选择*/
bool use_ref_tick __attribute__((deprecated)); /*!< Deprecated method to select ref tick clock source, set source_clk field instead */
};
} uart_config_t;
利用该结构体为串口配置基本参数,如设置波特率、数据位、停止位等
串口配置引脚
esp_err_t uart_set_pin(uart_port_tuart_num,
int tx_io_num,
int rx_io_num,
int rts_io_num,
int cts_io_num)
参数
- uart_num: 是哪个串口
- tx_io_num: UART TX pin GPIO number.
- rx_io_num: UART RX pin GPIO number.
- rts_io_num: UART RTS pin GPIO number.
- cts_io_num: UART CTS pin GPIO number.
如果不需要映射的话,将管脚参数设置为UART_PIN_NO_CHANGE
就好
串口驱动的安装
函数uart_driver_install()
esp_err_t uart_driver_install(uart_port_t uart_num,
int rx_buffer_size,
int tx_buffer_size,
int queue_size,
QueueHandle_t *uart_queue,
int intr_alloc_flags)
- 参数
- uart_num: 串口号(用的哪个串口就选哪个)
- rx_buffer_size: 串口接收环缓冲区大小
- tx_buffer_size: 串口发送环缓冲区大小(如果设置为0,驱动程序将不使用TX缓冲区,TX函数将阻塞任务,直到所有数据发送完毕)。
- queue_size: 队列长度。
- uart_queue: 串口事件队列的句柄
- intr_alloc_flags: 分配中断的标志
运行UART通讯
串行通信由每个UART控制器的有限状态机(FSM)控制。
发送数据的过程涉及以下步骤:
将数据写入Tx FIFO缓冲区
FSM序列化数据
FSM将数据发送出去
接收数据的过程类似,但是步骤相反:
FSM处理传入的串行流并将其并行化
FSM将数据写入Rx FIFO缓冲区
从Rx FIFO缓冲区读取数据
因此,应用程序将被限制为分别使用uart_write_bytes()
和从相应的缓冲区写入和读取数据uart_read_bytes()
,而FSM将完成其余的工作。
函数uart_read_bytes()
int uart_read_bytes(uart_port_t uart_num,
void *buf,
uint32_t length,
TickType_t ticks_to_wait)
- 参数
- uart_num: 串口号
- buf: 用于存放读取到数据的缓冲区
- length: 需要读取的长度
- ticks_to_wait: 阻塞时间(在这个时间内任务会阻塞,直到读满长度为length的数据后,或者超时)
- 返回值
- (-1): 读取失败
- 其他: 返回从串口缓冲区读取到的字节数
函数uart_write_bytes()
int uart_write_bytes(uart_port_t uart_num,
const void *src,
size_t size)
- 参数
- uart_num: 串口号
- src: 需要发送数据的地址
- size: 需要发送的长度
- 返回值
- (-1): 发送失败
- 其他: 发送到TX FIFO的字节数
串口队列接收
/* UART Events Example
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 <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"
static const char *TAG = "uart_events";
/**
* This example shows how to use the UART driver to handle special UART events.
*
* It also reads data from UART0 directly, and echoes it to console.
*
* - Port: UART0
* - Receive (Rx) buffer: on
* - Transmit (Tx) buffer: off
* - Flow control: off
* - Event queue: on
* - Pin assignment: TxD (default), RxD (default)
*/
#define EX_UART_NUM UART_NUM_0
#define PATTERN_CHR_NUM (3) /*!< Set the number of consecutive and identical characters received by receiver which defines a UART pattern*/
#define BUF_SIZE (1024)
#define RD_BUF_SIZE (BUF_SIZE)
static QueueHandle_t uart0_queue;
static void uart_event_task(void *pvParameters)
{
uart_event_t event;
size_t buffered_size;
uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE);
for(;;) {
//Waiting for UART event.
if(xQueueReceive(uart0_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
bzero(dtmp, RD_BUF_SIZE);
ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
switch(event.type) {
//Event of UART receving data
/*We'd better handler data event fast, there would be much more data events than
other types of events. If we take too much time on data event, the queue might
be full.*/
case UART_DATA:
ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY);
ESP_LOGI(TAG, "[DATA EVT]:");
uart_write_bytes(EX_UART_NUM, (const char*) dtmp, event.size);
break;
//Event of HW FIFO overflow detected
case UART_FIFO_OVF:
ESP_LOGI(TAG, "hw fifo overflow");
// If fifo overflow happened, you should consider adding flow control for your application.
// The ISR has already reset the rx FIFO,
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart0_queue);
break;
//Event of UART ring buffer full
case UART_BUFFER_FULL:
ESP_LOGI(TAG, "ring buffer full");
// If buffer full happened, you should consider encreasing your buffer size
// As an example, we directly flush the rx buffer here in order to read more data.
uart_flush_input(EX_UART_NUM);
xQueueReset(uart0_queue);
break;
//Event of UART RX break detected
case UART_BREAK:
ESP_LOGI(TAG, "uart rx break");
break;
//Event of UART parity check error
case UART_PARITY_ERR:
ESP_LOGI(TAG, "uart parity error");
break;
//Event of UART frame error
case UART_FRAME_ERR:
ESP_LOGI(TAG, "uart frame error");
break;
//UART_PATTERN_DET
case UART_PATTERN_DET:
uart_get_buffered_data_len(EX_UART_NUM, &buffered_size);
int pos = uart_pattern_pop_pos(EX_UART_NUM);
ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size);
if (pos == -1) {
// There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not
// record the position. We should set a larger queue size.
// As an example, we directly flush the rx buffer here.
uart_flush_input(EX_UART_NUM);
} else {
uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS);
uint8_t pat[PATTERN_CHR_NUM + 1];
memset(pat, 0, sizeof(pat));
uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "read data: %s", dtmp);
ESP_LOGI(TAG, "read pat : %s", pat);
}
break;
//Others
default:
ESP_LOGI(TAG, "uart event type: %d", event.type);
break;
}
}
}
free(dtmp);
dtmp = NULL;
vTaskDelete(NULL);
}
void app_main(void)
{
esp_log_level_set(TAG, ESP_LOG_INFO);
/* Configure parameters of an UART driver,
* communication pins and install the driver */
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_APB,
};
//Install UART driver, and get the queue.
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 30, &uart0_queue, 0);
uart_param_config(EX_UART_NUM, &uart_config);
//Set UART log level
esp_log_level_set(TAG, ESP_LOG_INFO);
//Set UART pins (using UART0 default pins ie no changes.)
uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//Set uart pattern detect function.
uart_enable_pattern_det_baud_intr(EX_UART_NUM, '+', PATTERN_CHR_NUM, 9, 0, 0);
//Reset the pattern queue length to record at most 20 pattern positions.
uart_pattern_queue_reset(EX_UART_NUM, 30);
//Create a task to handler UART event from ISR
xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, 12, NULL);
}
思路
与普通串口初始化一样,不过串口的队列接收过程中多了队列这个缓冲。
串口的队列接收可以实现串口的不定长接收。并且对于数据处理的方式上更灵活(具体情况具体讨论)
创建一个串口队列,利用串口安装函数将其注册,之后只需要设置一个任务优先级较高的任务利用阻塞检测串口事件的方式就可以知道串口FIFO中是否有消息。当只有消息的时候才读取。这样就相当于一个中断了。
并且可以通过配置串口接收消息数目来达到接收固定数目消息中断的效果
之后利用该函数为串口注册中断函数也是可以的
小结一下
串口的使用也很简单
- 设置串口参数
- 设置串口引脚
- 安装串口驱动
- 使用串口函数
- 使用中断(可选)
- 删除串口驱动(可选)
测试下串口的中断使用
串口的队列接收消息的方式还需要配合事件标志组来进行,这里ESPIDF配合着FreeRTOS在串口方面创建了一个串口事件 uart_event_t
创建了一个串口队列(相当于之前的STM32的串口缓冲区),用于缓冲接收到的消息。
这里有个疑问,使用uart_read_bytes
这个函数之后是否串口的FIFO缓冲区就被清除了呢?–>应该是的
对于ESP32来说,利用队列来接收串口发送的消息也是一种很好的形式。不需要在额外的开中断了。只需要把队列接收的任务的优先级调成最高级的优先级,之后使用xQueueReceive
这个队列接收函数来长时间的阻塞接收任务(只有队列中又消息的时候才执行任务)。
之后根据串口事件类型uart_event_t
event.type
就可以根据不同的队列接收类型来对队列中接收到的消息来进行处理了。
其实这个串口的消息队列也不需要有多长,只需要满足接收消息的最大的值就可以了,并且可以通过event.size
来知道此时队列中接收了多少消息
懂了,串口协议还要再看下,ESP32是怎么知道串口接收完成的,这里利用了识别串口的结束位的方式才完成这个任务,并且也根据这个原理知道这个串口接收数据的长度。
ESP32利用这种方式配合着FreeRTOS队列的方式完成了
但是为什么这个串口只能接收120个字符?虽然也能完成接下来的接收并且能串起来,但为什么是120个字符?还是说限定时间内
罪魁祸首找到了,再uart_driver_install
这个函数中,设置了最大接收数量,但是这个UART_FULL_THRESH_DEFAULT
被默认为了120。考虑到这是自带的ESPIDF的库文件,还是不要修改的比较好。但是其实这也无伤大雅。数据小了写,但是仍然可以接收完成一次完成的数据帧。如果之后有需求再想办法就是了… 暂时就先这样吧

解决办法
通过在结构uart_intr_config_t
中输入缓冲区长度和超时各自的阈值并调用uart_intr_config()
来配置它们
后记
暂时就先这样子吧…
利用ESPIDF编程指南配合着乐鑫官方提供的例程来学习也是蛮不错的….
对于ESP32的串口而言,还可以加入uart_enable_pattern_det_baud_intr
这类函数(作用是接收到固定字符进进入中断),这样就能实现类似于AT这样的指令操作了,如果单将ESP32作为一个蓝牙/WIFI模块来配合着其他单片机使用的话这样子是不错的一个选择。