今天来设计和实现一个叫uart2tcp的透传小工具,名字里的"2"是"to"的意思。
uart2tcp是什么
此工具把esp32c6开发板上的uart传输都透传到tcp server上,板子会接入环境中的wifi AP,其它设备如PC机通过网络连接这个tcp server,读写socket就相当于读写esp32c6的uart。
为什么做uart2tcp
或者说应用场景是什么。现阶段串口是极佳的调试工具比如看log、输入命令等等,所以几乎每块板子都有串口针脚或者可以焊接针脚,然后和电脑的usb转串口连接起来。但有的场景接电脑并不方便,比如目标设备是移动机器人、扫地机,或者处于封闭空间或服务器机房等等,这时候这个工具就派上用场了,只要测试场所有wifi服务,工程师可以安安静静地坐在自己座位上调试,和把目标板放在身边一样方便。
怎么做uart2tcp
esp idf例子这么全,自己看懂后手搓一个,有句话怎么说来着自己动手丰衣足食。
先看看我们需要做哪些
第一,wifi连接AP;第二,实现一个tcp server;第三,uart读写。巧的是这三块esp idf都有例子代码,分别是examples/common_components/protocol_examples_common, examples/protocols/sockets/tcp_server/与examples/peripherals/uart/uart_events/。注意uart示例目录examples/peripherals/uart中有好几个,个人翻来覆去挑了挑还是觉得uart_events合适。
选谁为主模板
上面这三个各自独立,有自己的task和main函数,但以谁为主模板修改呢?笔者选择examples/protocols/sockets/tcp_server/,它和wifi示例examples/common_components/protocol_examples_common联系比较紧密,uart更独立一些,以tcp_server为主模板修改会更少。那么拷贝一份吧做工程模板吧:
cp -a examples/protocols/sockets/tcp_server/ examples/uart2tcp
mv examples/uart2tcp/main/tcp_server.c examples/uart2tcp/main/uart2tcp.c
并修改下examples/uart2tcp/main/CMakeLists.txt为
idf_component_register(SRCS "uart2tcp.c"
INCLUDE_DIRS ".")
代码设计和实现
模板里的uart2tcp.c只创建了一个任务,就是监听并接收tcp连接,连接建立以后然后从客户端收数据再写回。我们要把它改成从客户端收数据再往uart写入,这里展示下核心代码:
static void do_retransmit(const int tsock)
{
int len;
char rx_buffer[128];
do {
len = recv(tsock, rx_buffer, sizeof(rx_buffer), 0);
if (len < 0) {
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
} else if (len == 0) {
ESP_LOGW(TAG, "Connection closed");
} else {
//ESP_LOGI(TAG, "Received %d bytes", len);
uart_write_bytes(EX_UART_NUM, rx_buffer, len);
}
} while (len > 0);
}
到此已经打通tcp socket收数据后写入uart的通路,还缺另一个方向的通路即从uart读并写入socket。创建一个FreeRTOS任务来完成这个通路。此任务主要就是监听uart什么时候有数据,监听uart通过uart event做到,examples/peripherals/uart/uart_events示例可以搬过来。一旦uart有数据调用uart_read_bytes()函数读取后再调用send()函数写入socket,关键代码片段如下:
if(xQueueReceive(uart0_queue, (void *)&event, (TickType_t)portMAX_DELAY))
{
//ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
switch(event.type) {
//Event of UART receving data
case UART_DATA:
//ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
uart_read_bytes(EX_UART_NUM, data, event.size, portMAX_DELAY);
to_write = event.size;
while (to_write > 0) {
int written = send(sock, data + (event.size - to_write), to_write, 0);
if (written < 0) {
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
}
to_write -= written;
}
break;
//Event of HW FIFO overflow detected
其余uart event比如UART_FIFO_OVF, UART_BUFFER_FULL等等可以沿用examples/peripherals/uart/uart_events中的代码。
处理好初始化uart/wifi/tcp_server等小事
到此coding任务基本完成了,剩下即使uart、wifi和tcp servert初始化。因为沿用examples/protocols/sockets/tcp_server/为主模板,wifi和tcp server相应初始化代码可以沿用,我们需要把examples/peripherals/uart/uart_events中uart初始化代码搬运过来:
static void uart_init(void)
{
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,
};
//Install UART driver, and get the queue.
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0);
uart_param_config(EX_UART_NUM, &uart_config);
uart_set_pin(EX_UART_NUM, UART_TXD, UART_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}
对照原理图选合适的针脚作为uart的RX和TX
FireBeetle 2 ESP32C6开发板可用IO16与IO17两根引脚,并选用UART0。综合以上信息写入宏定义 :
#define EX_UART_NUM UART_NUM_0
#define UART_TXD GPIO_NUM_16
#define UART_RXD GPIO_NUM_17
编译、烧录和测试
cd examples/uart2tcp
idf.py set-target esp32c6
idf.py menuconfig
menuconfig进如下界面
设置一下SSID和密码:
当然也可以从stdin动态输入,对于esp32c6就是由usb那个cdc串口输入
哪种方便自己选择
使用示例
连接FireBeetle 2 ESP32C6开发板的IO16、IO17和目标板串口,别忘记GND也要接上。假设esp32c6的ip地址为192.168.1.88 (获得方式:usb cdc串口会打印,也可通过wifi ap查询), 那么如下命令就可以和目标板串口交互了,和用串口助手、minicom、putty基本一样
nc 192.168.1.88 3333
当然也可以用socat把套接字转成pty,然后用串口终端工具如putty、minicom等打开pty用,最方便的还是瑞士军刀nc。
一些参数调优
个人使用经验,uart的rx和tx buffer可能要按需调一调,就是uart初始化中下面这行代码的参数可以调一调,其它没什么需要调的。目前代码RX和TX都设成了2KB buffer,经测试基本上高波特率都可以满足并无丢数据现象
uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0);
未来可以提升的地方
1.串口的参数设定如波特率目前hardcoding的,将来能否再开一个tcp端口动态接收命令并处理
2.esp32c6板子的ip地址通知: wifi连上后usb cdc那个串口打印了AP分配的地址,将来能否利用mDNS服务免除寻找ip地址的过程
总结
本文先提出uart2tcp这个小工具的由来,然后把整个编程任务分成三个小任务,再寻找esp idf的对应示例,学习这三部分示例代码后,选择esp idf的tcp_server例子为主模板,然后通过类似搭乐高积木一样把它们综合起来手搓完成了工具开发任务,怎么样你学会了吗?