ESP32-C3硬件串口资源较少, 有的项目需要较多的串口, 有一个arduino库实现了用GPIO模拟的串口, SoftwareSerial. 这里给出一个基于RMT外设的实现, 接收时避免频繁进入中断, 发送时不用禁止中断以保证发送时序.RMT(Remote Control)外设不仅可以用于红外遥控、LED灯条控制等场景,还可以通过灵活配置实现UART串行通信功能.
1. 使用方法
目前仅支持9600波特率,1位起始位, 1位停止位
#include "ks_software_serial.h"
#include "soc/usb_serial_jtag_reg.h"
#define TEST_UART_RX 2
#define TEST_UART_TX 3
class ks_software_serial_c test_uart;
static void test_uart_callback(void *data, uint32_t data_len)
{
Serial.println("test_uart_callback");
uint8_t *data_buf = (uint8_t *)data;
for (int i = 0; i < data_len; i++) {
Serial.printf("%02x ", data_buf[i]);
}
Serial.println();
test_uart.write(data_buf, data_len); // send back
test_uart.flush();
}
void setup() {
// initialize serial communication at 115200 bits per second:
static const int SEND_BUF_SIZE = 1024;
Serial.setTxBufferSize(SEND_BUF_SIZE); // If set to zero, driver will not use TX buffer, TX function will block task until all data have been sent out.
Serial.begin ( 115200 );
Serial.println("start");
test_uart.begin(9600, -1, TEST_UART_RX, TEST_UART_TX, 64, KS_SWS_BASED_RMT);
//test_uart.begin(9600, -1, TEST_UART_RX, TEST_UART_TX, 64, KS_SWS_BASED_GPIO); // you can use gpio if rmt channel is not enough, not recommended
//CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_DP_PULLUP); // for io19
test_uart.set_timeout_callback(test_uart_callback);
test_uart.start_rx();
}
void loop() {
test_uart.perform_work();
}
测试收发
原理
RMT外设可以捕获到脉冲的持续时间和电平, 存放到如下的结构体中
typedef struct rmt_item32_s {
union {
struct {
uint32_t duration0 :15;
uint32_t level0 :1;
uint32_t duration1 :15;
uint32_t level1 :1;
};
uint32_t val;
};
} rmt_item32_t;
如上图可以得到每个边沿的时刻. 但是最后的几位如果是高电平, 则需要手动补满一帧.
根据获取到的边沿的微秒时刻, 分析出一整包串口数据.
static size_t parse_data(uint32_t *micro, uint8_t *lvl, size_t len, uint8_t *out_byte, size_t out_max)
{
int out_idx = 0;
int lvl_idx = 0;
int micro_idx = 0;
int start_bit_idx = 0;
// at the end of whole pack, level is high, the left bit will be lost, need manual add it back
uint32_t tail = (micro[len - 1] - micro[0]) % (BAUD_9600_US * 10);
// LOG_INFO("tail: %d\n", tail);
uint16_t high_lvl_tail = BAUD_9600_US * 10 - tail;
// LOG_INFO("high_lvl_tail: %d\n", high_lvl_tail);
micro[len] = high_lvl_tail + micro[len - 1];
lvl[len] = 0;
while(micro_idx < (int)len){
uint32_t sample_stamp = micro[micro_idx];
sample_stamp += BAUD_9600_US / 2; // for 9600, mid of the start bit(a clock is 104 us)
micro_idx++;
uint8_t parse_byte = 0;
bool one_frame_lvl[10] = {0};
int one_frame_idx = 0;
while(sample_stamp < micro[start_bit_idx] + (BAUD_9600_US / 2) + 9 * BAUD_9600_US){ // while < stop bit mid
bool cur_lvl = lvl[lvl_idx];
one_frame_lvl[one_frame_idx] = cur_lvl;
one_frame_idx++;
sample_stamp += BAUD_9600_US;
if ( sample_stamp > micro[micro_idx] ) {
micro_idx++;
lvl_idx++;
}
}
for (int i = 1; i < 9; i++) {
parse_byte |= one_frame_lvl[i] << (i - 1);
}
start_bit_idx = micro_idx;
lvl_idx++;
if ( out_idx < out_max ) {
out_byte[out_idx] = parse_byte;
out_idx++;
}
}
return out_idx;
}
代码
https://github.com/kalimdorsummer/rmt_serial