EdgeGateway_FSU/DevicePortGet/Uart_passthrough/uart_passthrough.c

589 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/***************************************************************
Copyright © huijue Network Co., Ltd. 1998-2129. All rights reserved.
Copyright © 上海汇珏网络通信设备股份有限公司 1998-2129. All rights reserved.
文件名 : uart_passthrough.c
作者 : kooloo
版本 : V1.0
描述 : 串口处理文件 支持读 写 透传 方便调试4G模块
硬件平台 : iMX6ULL
内核版本 : linux-imx-4.1.15-2.1.0-g3dc0a4b-v2.7
编译器版本 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf
日志 : 初版V1.0 2023/7/15 kooloo创建
***************************************************************/
#define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE宏
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
typedef struct uart_hardware_cfg {
unsigned int baudrate; /* 波特率 */
unsigned char dbit; /* 数据位 */
char parity; /* 奇偶校验 */
unsigned char sbit; /* 停止位 */
} uart_cfg_t;
static struct termios old_cfg; //用于保存终端的配置参数
static int fd; //串口终端对应的文件描述符
static struct termios passthroughold_cfg; //用于保存终端的配置参数
static int passthroughfd; //串口终端对应的文件描述符
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: uart_init
** 功能描述: 串口初始化 主串口 电脑侧连接的串口 初始化
** 参数描述: 参数device表示串口终端的设备节点
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static int uart_init(const char *device)
{
/* 打开串口终端 */
fd = open(device, O_RDWR |O_NONBLOCK); //O_NOCTTY
if (0 > fd) {
fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));
return -1;
}
/* 获取串口当前的配置参数 */
if (0 > tcgetattr(fd, &old_cfg)) {
fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
return 0;
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: uart_initpassthrough
** 功能描述: 串口初始化 透传目的端口 初始化
** 参数描述: 参数device表示串口终端的设备节点
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static int uart_initpassthrough(const char *device)
{
/* 打开串口终端 */
passthroughfd = open(device, O_RDWR |O_NONBLOCK);
if (0 > passthroughfd) {
fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));
return -1;
}
/* 获取串口当前的配置参数 */
if (0 > tcgetattr(passthroughfd, &passthroughold_cfg)) {
fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
close(passthroughfd);
return -1;
}
return 0;
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: uart_cfg
** 功能描述: 串口配置 主串口 电脑侧连接的串口 配置
** 参数描述: 参数cfg指向一个uart_cfg_t结构体对象
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static int uart_cfg(const uart_cfg_t *cfg)
{
struct termios new_cfg = {0}; //将new_cfg对象清零
speed_t speed;
/* 设置为原始模式 */
cfmakeraw(&new_cfg);
/* 使能接收 */
new_cfg.c_cflag |= CREAD;
/* 设置波特率 */
switch (cfg->baudrate) {
case 1200: speed = B1200;
break;
case 1800: speed = B1800;
break;
case 2400: speed = B2400;
break;
case 4800: speed = B4800;
break;
case 9600: speed = B9600;
break;
case 19200: speed = B19200;
break;
case 38400: speed = B38400;
break;
case 57600: speed = B57600;
break;
case 115200: speed = B115200;
break;
case 230400: speed = B230400;
break;
case 460800: speed = B460800;
break;
case 500000: speed = B500000;
break;
default: //默认配置为115200
speed = B115200;
printf("default baud rate: 115200\n");
break;
}
if (0 > cfsetspeed(&new_cfg, speed)) {
fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
return -1;
}
/* 设置数据位大小 */
new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零
switch (cfg->dbit) {
case 5:
new_cfg.c_cflag |= CS5;
break;
case 6:
new_cfg.c_cflag |= CS6;
break;
case 7:
new_cfg.c_cflag |= CS7;
break;
case 8:
new_cfg.c_cflag |= CS8;
break;
default: //默认数据位大小为8
new_cfg.c_cflag |= CS8;
printf("default data bit size: 8\n");
break;
}
/* 设置奇偶校验 */
switch (cfg->parity) {
case 'N': //无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
break;
case 'O': //奇校验
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;
break;
case 'E': //偶校验
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除PARODD标志配置为偶校验 */
new_cfg.c_iflag |= INPCK;
break;
default: //默认配置为无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
printf("default parity: N\n");
break;
}
/* 设置停止位 */
switch (cfg->sbit) {
case 1: //1个停止位
new_cfg.c_cflag &= ~CSTOPB;
break;
case 2: //2个停止位
new_cfg.c_cflag |= CSTOPB;
break;
default: //默认配置为1个停止位
new_cfg.c_cflag &= ~CSTOPB;
printf("default stop bit size: 1\n");
break;
}
/* 将MIN和TIME设置为0 */
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;
/* 清空缓冲区 */
if (0 > tcflush(fd, TCIOFLUSH)) {
fprintf(stderr, "tcflush error: %s\n", strerror(errno));
return -1;
}
/* 写入配置、使配置生效 */
if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {
fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
return -1;
}
/* 配置OK 退出 */
return 0;
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: uart_cfgpassthrough
** 功能描述: 串口配置 串口配置 透传目的端口 配置
** 参数描述: 参数cfg指向一个uart_cfg_t结构体对象
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static int uart_cfgpassthrough(const uart_cfg_t *cfg)
{
struct termios new_cfg = {0}; //将new_cfg对象清零
speed_t speed;
/* 设置为原始模式 */
cfmakeraw(&new_cfg);
/* 使能接收 */
new_cfg.c_cflag |= CREAD;
/* 设置波特率 */
switch (cfg->baudrate) {
case 1200: speed = B1200;
break;
case 1800: speed = B1800;
break;
case 2400: speed = B2400;
break;
case 4800: speed = B4800;
break;
case 9600: speed = B9600;
break;
case 19200: speed = B19200;
break;
case 38400: speed = B38400;
break;
case 57600: speed = B57600;
break;
case 115200: speed = B115200;
break;
case 230400: speed = B230400;
break;
case 460800: speed = B460800;
break;
case 500000: speed = B500000;
break;
default: //默认配置为115200
speed = B115200;
printf("default baud rate: 115200\n");
break;
}
if (0 > cfsetspeed(&new_cfg, speed)) {
fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
return -1;
}
/* 设置数据位大小 */
new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零
switch (cfg->dbit) {
case 5:
new_cfg.c_cflag |= CS5;
break;
case 6:
new_cfg.c_cflag |= CS6;
break;
case 7:
new_cfg.c_cflag |= CS7;
break;
case 8:
new_cfg.c_cflag |= CS8;
break;
default: //默认数据位大小为8
new_cfg.c_cflag |= CS8;
printf("default data bit size: 8\n");
break;
}
/* 设置奇偶校验 */
switch (cfg->parity) {
case 'N': //无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
break;
case 'O': //奇校验
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;
break;
case 'E': //偶校验
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除PARODD标志配置为偶校验 */
new_cfg.c_iflag |= INPCK;
break;
default: //默认配置为无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
printf("default parity: N\n");
break;
}
/* 设置停止位 */
switch (cfg->sbit) {
case 1: //1个停止位
new_cfg.c_cflag &= ~CSTOPB;
break;
case 2: //2个停止位
new_cfg.c_cflag |= CSTOPB;
break;
default: //默认配置为1个停止位
new_cfg.c_cflag &= ~CSTOPB;
printf("default stop bit size: 1\n");
break;
}
/* 将MIN和TIME设置为0 */
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;
/* 清空缓冲区 */
if (0 > tcflush(passthroughfd, TCIOFLUSH)) {
fprintf(stderr, "tcflush error: %s\n", strerror(errno));
return -1;
}
/* 写入配置、使配置生效 */
if (0 > tcsetattr(passthroughfd, TCSANOW, &new_cfg)) {
fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
return -1;
}
/* 配置OK 退出 */
return 0;
}
/***
--dev=/dev/ttymxc2
--brate=115200
--dbit=8
--parity=N
--sbit=1
--type=read
***/
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: show_help
** 功能描述: 打印帮助信息
** 参数描述:
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static void show_help(const char *app)
{
printf("Usage: %s [选项]\n"
"\n必选选项:\n"
" --dev=DEVICE 指定串口终端设备名称, 譬如--dev=/dev/ttymxc2\n"
" --type=TYPE 指定操作类型, 读串口还是写串口, 譬如--type=read(read表示读、write表示写、passthrough表示透传、其它值无效)\n"
"\n可选选项:\n"
" --brate=SPEED 指定串口波特率, 譬如--brate=115200\n"
" --dbit=SIZE 指定串口数据位个数, 譬如--dbit=8(可取值为: 5/6/7/8)\n"
" --parity=PARITY 指定串口奇偶校验方式, 譬如--parity=N(N表示无校验、O表示奇校验、E表示偶校验)\n"
" --sbit=SIZE 指定串口停止位个数, 譬如--sbit=1(可取值为: 1/2)\n"
" --passthroughdev=DEVICE 指定串口终端目的端口,此模式只有在type模式为透传模式时才有意义)\n"
" --help 查看本程序使用帮助信息\n\n", app);
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: io_handler
** 功能描述: 信号处理函数,当串口有数据可读时,会跳转到该函数执行
** 参数描述:
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static void io_handler(int sig, siginfo_t *info, void *context)
{
unsigned char buf[1024] = {0};
int ret;
int n;
if(SIGRTMIN != sig)
return;
/* 判断串口是否有数据可读 */
if (POLL_IN == info->si_code) {
ret = read(fd, buf, 1024); //一次最多读1024个字节数据
printf("[ ");
for (n = 0; n < ret; n++)
printf("0x%hhx ", buf[n]);
printf("]\n");
}
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: async_io_init
** 功能描述: 异步I/O初始化函数
** 参数描述:
** 作  者: kooloo
** 日  期: 2022年03月21
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
static void async_io_init(void)
{
struct sigaction sigatn;
int flag;
/* 使能异步I/O */
flag = fcntl(fd, F_GETFL); //使能串口的异步I/O功能
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag);
/* 设置异步I/O的所有者 */
fcntl(fd, F_SETOWN, getpid());
/* 指定实时信号SIGRTMIN作为异步I/O通知信号 */
fcntl(fd, F_SETSIG, SIGRTMIN);
/* 为实时信号SIGRTMIN注册信号处理函数 */
sigatn.sa_sigaction = io_handler; //当串口有数据可读时会跳转到io_handler函数
sigatn.sa_flags = SA_SIGINFO;
sigemptyset(&sigatn.sa_mask);
sigaction(SIGRTMIN, &sigatn, NULL);
}
int main(int argc, char *argv[])
{
uart_cfg_t cfg = {0};
uart_cfg_t passthroughcfg = {0};
char *device = NULL;
char *passthroughdevice =NULL; //透传时设备目的端口 kooloo add 202312
int rw_flag = -1;
// unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44,
// 0x55, 0x66, 0x77, 0x88}; //通过串口发送出去的数据
unsigned char w_buf[10] = "ATI\n"; //通过串口发送出去的数据
unsigned char passthroughBuf[100]; //透传buf kooloo add 202312
int passthroughlen; //透传长度 读取到多少数据就发送多少数据 kooloo add 202312
int n;
printf("Uart_passthroughApp \n"); //开机 程序打印信息 做区分使用
/* 解析出参数 */
for (n = 1; n < argc; n++) {
if (!strncmp("--dev=", argv[n], 6))
device = &argv[n][6];
else if (!strncmp("--passthroughdev=", argv[n], 17)) //新增 透传时目的端口 --passthroughdev kooloo add 202312
passthroughdevice = &argv[n][17];
else if (!strncmp("--brate=", argv[n], 8))
cfg.baudrate = atoi(&argv[n][8]);
else if (!strncmp("--dbit=", argv[n], 7))
cfg.dbit = atoi(&argv[n][7]);
else if (!strncmp("--parity=", argv[n], 9))
cfg.parity = argv[n][9];
else if (!strncmp("--sbit=", argv[n], 7))
cfg.sbit = atoi(&argv[n][7]);
else if (!strncmp("--type=", argv[n], 7)) {
if (!strcmp("read", &argv[n][7]))
rw_flag = 0; //读
else if (!strcmp("write", &argv[n][7]))
rw_flag = 1; //写
else if (!strcmp("passthrough", &argv[n][7]))
{
rw_flag = 2; //透传
}
else if (!strcmp("passthroughdbg", &argv[n][7]))
{
rw_flag = 3; //透传 带输出
}
}
else if (!strcmp("--help", argv[n])) {
show_help(argv[0]); //打印帮助信息
exit(EXIT_SUCCESS);
}
}
if (NULL == device || -1 == rw_flag) {
fprintf(stderr, "Error: the device and read|write type must be set!\n");
show_help(argv[0]);
exit(EXIT_FAILURE);
}
if(((rw_flag==2)||(rw_flag==3))&&(NULL==passthroughdevice)) //如果为透传模式 但透传目的端口不指定 则报错 kooloo add 202312
{
fprintf(stderr, "Error: the passthrough mode must be set!\n");
show_help(argv[0]);
exit(EXIT_FAILURE);
}
printf("test: %s %s type=%d\n",device,passthroughdevice,rw_flag); //要带\r\n 否则在缓冲区出不来 kooloo add 202312
/* 串口初始化 */
if (uart_init(device))
exit(EXIT_FAILURE);
/* 串口配置 */
if (uart_cfg(&cfg)) {
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
exit(EXIT_FAILURE);
}
if((rw_flag==2)||(rw_flag==3)) //如果为透传模式 则初始化目的端口
{
/* 串口初始化 */
if (uart_initpassthrough(passthroughdevice))
exit(EXIT_FAILURE);
/* 串口配置 */
if (uart_cfgpassthrough(&passthroughcfg)) {
tcsetattr(passthroughfd, TCSANOW, &passthroughold_cfg); //恢复到之前的配置
close(passthroughfd);
exit(EXIT_FAILURE);
}
printf("test: %s %s type=%d\n",device,passthroughdevice,rw_flag);
}
/* 读|写串口 */
switch (rw_flag) {
case 0: //读串口数据
async_io_init(); //我们使用异步I/O方式读取串口的数据调用该函数去初始化串口的异步I/O
for ( ; ; )
sleep(1); //进入休眠、等待有数据可读有数据可读之后就会跳转到io_handler()函数
break;
case 1: //向串口写入数据
for ( ; ; ) { //循环向串口写入数据
write(fd, w_buf, 5); //一次向串口写入8个字节
sleep(1); //间隔1秒钟
}
break;
case 2: //透传模式 device 为中转口passthroughdevice为目的端口
case 3: //透传模式 device 为中转口passthroughdevice为目的端口
for ( ; ; )
{
passthroughlen = read(fd, passthroughBuf, sizeof(passthroughBuf)); //一次最多读passthroughBuf个字节数据
//printf("test %d\n",passthroughlen);
if(passthroughlen>0) //如果有数据 则转发
{
write(passthroughfd, passthroughBuf, passthroughlen); //一次向串口写入passthroughBuf个字节
if(rw_flag==3)
{
printf("pt %s to %s: %s \n",device,passthroughdevice,passthroughBuf); //将数据打印出来 kooloo add 2023
}
else
{
//printf("test %d\n",passthroughlen);
}
}
passthroughlen = read(passthroughfd, passthroughBuf, sizeof(passthroughBuf)); //一次最多读passthroughBuf个字节数据
if(passthroughlen>0) //如果有数据 则转发
{
write(fd, passthroughBuf, passthroughlen); //一次向串口写入passthroughBuf个字节
if(rw_flag==3)
{
printf("pt %s to %s: %s \n",passthroughdevice,device,passthroughBuf); //将数据打印出来 kooloo add 2023
}
else
{
//printf("test %d\n",passthroughlen);
}
}
usleep(10*1000); //间隔10ms钟
//sleep(1); //间隔1秒钟
}
break;
}
/* 退出 */
tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置
close(fd);
if(rw_flag==2) //如果为透传模式 则初始化目的端口
{
tcsetattr(passthroughfd, TCSANOW, &old_cfg); //恢复到之前的配置
close(passthroughfd);
}
exit(EXIT_SUCCESS);
}