From 2c5fbe219558e6c5e4996dc9081fb4c7edb33a33 Mon Sep 17 00:00:00 2001 From: zhaohe Date: Sun, 3 Sep 2023 18:27:31 +0800 Subject: [PATCH] update --- .clang-format | 5 ++ .gitignore | 0 .vscode/settings.json | 5 ++ README.md | 19 ++++++ build.sh | 1 + uart.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ uart.hpp | 28 ++++++++ zuart.cpp | 153 ++++++++++++++++++++++++++++++++++++++++++++ zuart.out | Bin 0 -> 53648 bytes 9 files changed, 383 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100755 build.sh create mode 100644 uart.cpp create mode 100644 uart.hpp create mode 100644 zuart.cpp create mode 100755 zuart.out diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8587fbe --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +# Defines the Chromium style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +Language: Cpp +BasedOnStyle: Google +ColumnLimit: 300 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a478c1f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "string_view": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aab1d9e --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# README + + +``` + 用于将板子上的串口以网络透传出去。客户端通过tcp协议链接上之后,即可通过socket_fd直接和串口通信。 + +v1: + 1.支持基本功能 + 2.支持多个客户端同时链接服务器。多个客户端同时链接时,串口来消息,是以广播的形势发个各个Tcp客户端。 + +v1.1: + 1.修复占用CPU过高的BUG +``` + +``` +Usage: + net_tty.out /dev/ttyUSB0 115200 port + +``` \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..a51ea81 --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +aarch64-linux-gnu-g++ zuart.cpp uart.cpp -o zuart.out -lpthread \ No newline at end of file diff --git a/uart.cpp b/uart.cpp new file mode 100644 index 0000000..90b16e3 --- /dev/null +++ b/uart.cpp @@ -0,0 +1,172 @@ + +/* + * uart.c + * + * Created on: Aug 5, 2019 + * Author: Cristian Fatu + * Implements basic UART functionality, over Uart Lite linux driver, using termios. + * After booting linux, a device like "/dev/ttyUL1" must be present. + * These functions work in both canonic and not canonic modes. + * In the canonic communication mode, the received chars can be retrieved by read only after \n is detected. + * In the non canonic communication mode, the received chars can be retrieved by read as they are received. + */ +#include "uart.hpp" + +#include +#include +#include +#include +#include +/* +Parameters: + struct UartDevice* dev - pointer to the UartDevice struct + unsigned char canonic - communication mode + 1 - canonic communication (chars are only received after \n is detected). + 0 - non canonic communication (chars are received as they arrive over UART). +Return value: + UART_FAILURE -1 failure + UART_SUCCESS 0 success +Description: + Initializes the UART device. + When calling the function, the device name (usually "/dev/ttyUL1") must be filled in dev->name and the baud rate must be filled in dev->rate. + The canonic function parameter indicates communication mode (canonic or not). + In the canonic communication mode, the received chars can be retrieved by read only after \n is detected. + In the non canonic communication mode, the received chars can be retrieved by read as they are received, as the non canonic mode is configured with no wait. +*/ +int uartStart(struct UartDevice* dev, unsigned char canonic) { + struct termios* tty; + int fd; + int rc; + + fd = open(dev->name, O_RDWR | O_NOCTTY); + if (fd < 0) { + printf("%s: failed to open file descriptor for file %s\r\n", __func__, dev->name); + return UART_FAILURE; + } + + tty = (struct termios*)calloc(1, sizeof(*dev->tty)); + if (!tty) { + printf("%s: failed to allocate tty instance\r\n", __func__); + return UART_FAILURE; + } + // memset(tty, 0, sizeof(struct termios)); + + /* + BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed. + CRTSCTS : output hardware flow control (only used if the cable has + all necessary lines. See sect. 7 of Serial-HOWTO) + CS8 : 8n1 (8bit,no parity,1 stopbit) + CLOCAL : local connection, no modem contol + CREAD : enable receiving characters + */ + // tty->c_cflag = dev->rate | CRTSCTS | CS8 | CLOCAL | CREAD; + tty->c_cflag = dev->rate | CS8 | CLOCAL | CREAD; + if (canonic) { + // canonic + /* + IGNPAR : ignore bytes with parity errors + ICRNL : map CR to NL (otherwise a CR input on the other computer + will not terminate input) + otherwise make device raw (no other input processing) + */ + tty->c_iflag = IGNPAR | ICRNL; + /* + ICANON : enable canonical input + disable all echo functionality, and don't send signals to calling program + */ + tty->c_lflag = ICANON; + } else { + // not canonic + /* + IGNPAR : ignore bytes with parity errorsc_cc[VTIME] + */ + tty->c_iflag = IGNPAR; + /* set input mode (non-canonical, no echo,...) */ + tty->c_lflag = 0; + /* Do not wait for data */ + tty->c_cc[VTIME] = 10; /* inter-character timer unused */ + tty->c_cc[VMIN] = 0; /* blocking read until 5 chars received */ + } + + /* + Raw output. + */ + tty->c_oflag = 0; + + /* Flush port */ + tcflush(fd, TCIFLUSH); + + /* Apply attributes */ + rc = tcsetattr(fd, TCSANOW, tty); + if (rc) { + printf("%s: failed to set TCSANOW attr\r\n", __func__); + return UART_FAILURE; + } + + dev->fd = fd; + dev->tty = tty; + + return UART_SUCCESS; +} + +/* +Parameters: + struct UartDevice* dev - pointer to the UartDevice struct + char *data - pointer to the array of chars to be sent over UART + int size + positive value - number of chars to be sent over UART + -1 - indicates that all the chars until string terminator \0 will be sent +Return value: + number of chars sent over UART +Description: + This function sends a number of chars over UART. + If the size function parameter is -1 then all the characters until string terminator \0 will be sent. +*/ +int uartSend(struct UartDevice* dev, char* data, int size) { + int sent = 0; + if (size == -1) { + size = strlen(data); + } + sent = write(dev->fd, data, size); + +#ifdef DEBUG + printf("%s: sent %d characters\r\n", __func__, sent); +#endif + return sent; +} + +/* +Parameters: + struct UartDevice* dev - pointer to the UartDevice struct + char *data - pointer to the array of chars to hold the cars revceived over UART + int size_max - the maximum number of characters to be received +Return value: + number of chars received over UART +Description: + This function receives characters over UART. + In the canonic communication mode, the received chars will be retrieved by read only after \n is detected. + In the non canonic communication mode, the received chars will be retrieved by read as they are received, as the non canonic mode is configured with no wait. +*/ +int uartReceive(struct UartDevice* dev, char* data, int size_max) { + int received = 0; + +#ifdef DEBUG +// printf("%s: receiving characters %d\r\n", __func__, size_max); +#endif + received = read(dev->fd, data, size_max - 1); + data[received] = '\0'; + +#ifdef DEBUG +// if(received > 0) +// printf("%s: received %d characters\r\n", __func__, received); +// else +// printf("%s: r%d/%d\r\n", __func__, received, size_max); +#endif + return received; +} + +int uartStop(struct UartDevice* dev) { + free(dev->tty); + + return UART_SUCCESS; +} \ No newline at end of file diff --git a/uart.hpp b/uart.hpp new file mode 100644 index 0000000..66de474 --- /dev/null +++ b/uart.hpp @@ -0,0 +1,28 @@ +/* + * uart.h + * + * Created on: Aug 5, 2019 + * Author: cristian + */ + +#include +#include +#ifndef SRC_UART_H_ +#define SRC_UART_H_ +#define UART_FAILURE -1 +#define UART_SUCCESS 0 +// #define DEBUG +struct UartDevice { + char* name; + int rate; + + int fd; + struct termios* tty; +}; + +int uartStart(struct UartDevice* dev, unsigned char canonic); +int uartSend(struct UartDevice* dev, char* data, int size); +int uartReceive(struct UartDevice* dev, char* data, int size_max); +int uartStop(struct UartDevice* dev); + +#endif /* SRC_UART_H_ */ \ No newline at end of file diff --git a/zuart.cpp b/zuart.cpp new file mode 100644 index 0000000..941267f --- /dev/null +++ b/zuart.cpp @@ -0,0 +1,153 @@ + +#include +#include +#include +#include +#include +#include +#include +// +#include +#include +#include +#include +// +#include +#include +#include + +#include "uart.hpp" +using namespace std; +#define BACK_LOG 10 +#define MAX_RECV_SIZE 235 + +typedef struct { + int fd; + struct sockaddr_in client; +} tcpclient_t; +map g_baundmap = { + {"0", 0000000}, {"50", 0000001}, {"75", 0000002}, {"110", 0000003}, // + {"134", 0000004}, {"150", 0000005}, {"200", 0000006}, {"300", 0000007}, // + {"600", 0000010}, {"1200", 0000011}, {"1800", 0000012}, {"2400", 0000013}, // + {"4800", 0000014}, {"9600", 0000015}, {"19200", 0000016}, {"38400", 0000017}, // + {"57600", 0010001}, {"115200", 0010002}, {"230400", 0010003}, {"460800", 0010004}, // + {"500000", 0010005}, {"576000", 0010006}, {"921600", 0010007}, {"1000000", 0010010}, // + {"1152000", 0010011}, {"1500000", 0010012}, {"2000000", 0010013}, {"2500000", 0010014}, // + {"3000000", 0010015}, {"3500000", 0010016}, {"4000000", 0010017}, +}; +UartDevice g_uart_device = {0}; +void printf_buf(char *rx, ssize_t size) { + for (int i = 0; i < size; i++) { + printf("0x%02x,", rx[i]); + } + printf("\n"); +} + +void *uart_rx_thread_func(void *arg) { + char buff[4096]; + while (true) { + int ret = uartReceive(&g_uart_device, buff, 1024); // send the received text over UART + if (ret < 0) { + printf("uartReceive fail\n"); + exit(-1); + } + + // + if (ret > 0) { + printf("net<-uart: %d\n", ret); + printf_buf(buff, ret); + } + // + } + return NULL; +} + +int openuart(struct UartDevice *device, const char *devname, int rate) { + device->name = (char *)devname; + device->rate = rate; + + printf("UART open %s\n", device->name); + return uartStart(device, 0); +} + +static int strtohex(const char *str, uint8_t *hex) { + /** + * @brief + * str: 02 34 78 92 65 AF + */ + int len = strlen(str); + int i = 0; + int j = 0; + while (i < len) { + if (str[i] == ' ') { + i++; + continue; + } + char tmp[3] = {0}; + tmp[0] = str[i]; + tmp[1] = str[i + 1]; + hex[j] = strtol(tmp, NULL, 16); + i += 2; + j++; + } + if(j == 0){ + return 0; + } + return j-1; +} + +int main(int argc, char const *argv[]) { + /* code */ + //"net_uart /dev/ttyUSB0 115200 port" + + if (argc != 4) { + printf("Usage: %s /dev/ttyUSB0 115200 port\n", argv[0]); + return -1; + } + printf("device name:%s\n", argv[1]); + printf("baundrate :%s\n", argv[2]); + printf("port :%s\n", argv[3]); + + auto baundrate_find_result = g_baundmap.find(argv[2]); + if (baundrate_find_result == g_baundmap.end()) { + printf("unsupport baundrate\n"); + return -1; + // baundrate_find_result. + }; + + /** + * 打开串口 + */ + int rc = openuart(&g_uart_device, argv[1], baundrate_find_result->second); + if (rc) { + perror("open uart fail"); + exit(-1); + } + + pthread_t uart_rx_thread; + pthread_create(&uart_rx_thread, NULL, uart_rx_thread_func, NULL); + + while (true) { + char input[1024] = {0}; + uint8_t hex[1024] = {0}; + // scanf("%s", input); + fgets(input, 1024, stdin); + // printf("input:%s\n", input); + // 将输入的字符串转换为16进制 + + int nhex = strtohex(input, hex); + printf("tx:"); + printf_buf((char *)hex, nhex); + + // int ret = uartSend(&g_uart_device, input, strlen(input)); // send the received text over UART + int ret = uartSend(&g_uart_device, (char *)hex, nhex); // send the received text over UART + + if (ret < 0) { + printf("uartSend fail\n"); + } else { + printf("uartSend ok\n"); + } + } + + return 0; +} diff --git a/zuart.out b/zuart.out new file mode 100755 index 0000000000000000000000000000000000000000..61c09972a55e8b9be0396c88a87b1b461615eb7e GIT binary patch literal 53648 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 QdTmS$7 literal 0 HcmV?d00001