一、串口通讯原理
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息,ESP32 自有一个串口用于程序下载和 log 打印,就是这个道理。
1.串行通讯原理:
串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。
2. RS232通讯协议
RS232通讯协议比较简单,通常遵循 96-N-8-1 格式。
“96”表示的是通信波特率为 9600。串口通信中通常使用的是异步串口通信,即没有时钟线,所以两个设备要通信,必须要保持一致的波特率,当然,波特率常用值还有 4800、115200 等。
“N”表示的是无校验位,由于串口通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、0 校验(space)、1 校验(mark)以及无校验(noparity)。具体的介绍,大家可以百度下串口通信了解。
“8”表示的是数据位数为 8 位,其数据格式在前面介绍异步通信中已讲过。当然数据位数还可以为 5、6、7 位长度。
“1”表示的是 1 位停止位,串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑 0 的数据位表示,而数据包的停止信号可由 0.5、 1、 1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。
二、API函数
- UART 配置函数:uart_param_config();
函数原型 | esp_err_t uart_param_config ( uart_port_t uart_num, const uart_config_t *uart_config ) |
函数功能 | UART 配置函数 |
参数 | [in] uart_num: 串口号,取值
[in] uart_config:串口参数配置 typedef struct { |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
- UART 的 IO 映射设置函数:uart_set_pin();
函数原型 | esp_err_t uart_set_pin ( uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num ) |
函数功能 | UART 的IO映射函数 |
参数 | [in] uart_num:串口号,取值 [in] tx_io_num:发送引脚 [in] rx_io_num:接收引脚 [in] rts_io_num:rts 流控引脚 [in] cts_io_num:cts 流控引脚 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
- UART 功能安装使能函数:uart_driver_install();
函数原型 | esp_err_t uart_driver_install int tx_buffer_size, |
函数功能 | UART 功能安装使能函数 |
参数 | [in] uart_num:串口号 [in] rx_buffer_size:接收缓存大小 [in] tx_buffer_size:发送缓存大小 [in] queue_size:队列大小 [in] uart_queue:串口队列指针 [in] intr_alloc_flags:分配中断标记 |
返回值 | ESP_OK:成功 ESP_ERR_INVALID_ARG : 参数错误 |
- UART 发送函数:uart_write_bytes();
函数原型 | int uart_write_bytes ( uart_port_t uart_num, const char* src, size_t size ) |
函数功能 | UART 发送函数 |
参数 | [in] uart_num:串口号 [in] src:发送数据指针 [in]size:发送数据大小 |
返回值 | (-1) :参数错误 (>=0):数据已放到发送缓存 |
- UART 读取函数:uart_read_bytes();
函数原型 | int uart_read_bytes ( uart_port_t uart_num, uint8_t* buf, uint32_t length, TickType_t ticks_to_wait ) |
函数功能 | UART 读取函数 |
参数 | [in] uart_num:串口号 [in] buf:接收数据指针 [in]length:接收数据最大大小 [in]ticks_to_wait:等待时间 |
返回值 | (-1) :参数错误 (>=0):数据已放到发送缓存 |
三、程序编写
程序基于以上API函数进行编写,对于接收数据的功能,在FreeRTOS实时操作系统的基础之上,创建了一个UART数据接收任务,负责监听读取来自串口的数据。
uart.h文件内容:
#ifndef COMPONENTS_UART_INCLUDE_UART_H_
#define COMPONENTS_UART_INCLUDE_UART_H_
#include <stdio.h>
#include "esp_system.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "string.h"
//UART1
#define RX1_BUF_SIZE (1024) //接收缓冲
#define TX1_BUF_SIZE (512) //发送缓冲
#define TXD1_PIN (GPIO_NUM_5)
#define RXD1_PIN (GPIO_NUM_4)
//UART2
#define RX2_BUF_SIZE (1024)
#define TX2_BUF_SIZE (512)
#define TXD2_PIN (GPIO_NUM_12)
#define RXD2_PIN (GPIO_NUM_13)
void uart_init(void); //UART初始化函数
/**
* 数据接收任务
*/
void uart1_rx_task();
void uart2_rx_task();
#endif /* COMPONENTS_UART_INCLUDE_UART_H_ */
uart.c文件内容:
/*
* uart.c
*
* Created on: 2021年12月18日
* Author: wangy
*/
#include "uart.h"
void uart_init(void){
//串口配置结构体
uart_config_t uart1_config, uart2_config;
/**
* 配置串口1
*/
uart1_config.baud_rate = 9600; //波特率为115200
uart1_config.parity = UART_PARITY_DISABLE; //无校验位
uart1_config.data_bits = UART_DATA_8_BITS; //数据位8
uart1_config.stop_bits = UART_STOP_BITS_1; //停止位1
uart1_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; //硬件流控制
uart_param_config(UART_NUM_1, &uart1_config); //配置串口1
//IO映射-> T:IO4 R:IO5
uart_set_pin(UART_NUM_1, TXD1_PIN, RXD1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//注册串口服务使能并设置缓冲区大小
uart_driver_install(UART_NUM_1,RX1_BUF_SIZE * 2, TX1_BUF_SIZE * 2, 0, NULL, 0); //不使用队列
/**
* 配置串口2
*/
uart2_config.baud_rate = 115200; //波特率
uart2_config.data_bits = UART_DATA_8_BITS; //数据位
uart2_config.parity = UART_PARITY_DISABLE; //校验位
uart2_config.stop_bits = UART_STOP_BITS_1; //停止位
uart2_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; //硬件流控
uart_param_config(UART_NUM_2, &uart2_config); //设置串口
//IO映射-> T:IO12 R:IO13
uart_set_pin(UART_NUM_2, TXD2_PIN, RXD2_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
//注册串口服务即使能+设置缓存区大小
uart_driver_install(UART_NUM_2, RX2_BUF_SIZE * 2, TX2_BUF_SIZE * 2, 0, NULL, 0);
/**
* 创建两个任务,分别负责监听读取来自串口1和串口2的数据。
*/
//创建串口1接收任务
xTaskCreate(uart1_rx_task, "uart1_rx_task", 1024*2, NULL, configMAX_PRIORITIES, NULL);
//创建串口2接收任务
xTaskCreate(uart2_rx_task, "uart2_rx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL);
}
/**
* 串口1数据接收任务
*/
void uart1_rx_task(){
uint8_t* data = (uint8_t*) malloc(RX1_BUF_SIZE+1); //分配接收数据的内存
while (1) {
//获取串口1接收的数据
const int rxBytes = uart_read_bytes(UART_NUM_1, data, RX1_BUF_SIZE, 10 / portTICK_RATE_MS);
if (rxBytes > 0) {
data[rxBytes] = 0;
//将接收到的数据发出去
uart_write_bytes(UART_NUM_1, (char *)data, rxBytes);
}
}
free(data); //释放分配的内存。
}
/**
* 串口2接收任务
*/
void uart2_rx_task()
{
uint8_t* data = (uint8_t*) malloc(RX2_BUF_SIZE+1);
while (1) {
const int rxBytes = uart_read_bytes(UART_NUM_2, data, RX2_BUF_SIZE, 10 / portTICK_RATE_MS);
if (rxBytes > 0) {
data[rxBytes] = 0;
//将接收到的数据发出去
uart_write_bytes(UART_NUM_2, (char *)data, rxBytes);
}
}
free(data);
}
将上述程序下载到开发板,通过串口助手,可以接收到ESP32发送的数据,且ESP32也可以接收到串口助手发送的数据。
四、结束
本文简单介绍了串口通讯原理,并通过ESP32实现与串口助手之间的通讯。