赞
踩
实验目标:在Linux下通过串口屏显示并控制功能模块的状态和参数
操作系统:Ubuntu 20.04.6 LTS
串口屏:迪文串口屏 DMG48270C043_03W
代码功能就是在Linux下使用串口和TCP,重点在于如何处理好串口和网口接收的数据。 通过本实验可以基本掌握如何在Linux下使用串口和TCP。网上很多教程都会介绍Liunx下如何实现串口和TCP这两种通信方式,很少有结合实际应用进行介绍的文章,本文就将结合一个实际应用例子进行分析记录,也是对自己学习这些东西的一个简单总结记录。
这里的通信协议不是说解释串口或是TCP这样的通信协议,本文指的是在传输数据过程自己定义的数据帧格式,不同的数据需要设置功能模块的不同功能通信协议的有助于我们管理不同的控制信号。发送数据时按以下格式设置好数据后再通过到其他模块。
主控模块<----->功能模块 网口通信TCP
字段 | 指令头 | 指令码 | 数据长度 | 数据 |
---|---|---|---|---|
长度(字节) | 1 | 1 | 4 | N |
值 | 0xAA | 不同指令 | N | 具体数据 |
主控模块<----->串口屏 串口通信
Linux的串口表现为设备文件。实验使用的串口屏是USB扩展的,实验前需要安装CH341驱动串口设备文件命名为dev/ttyCH341USB*,不同的硬件平台对串口设备文件的命名有所区别。
在Linux下,不管是设备、文档、可执行程序,对于内核来说都是读写文件,会涉及到open、read、write的操作,对于文件操作就有了“阻塞”和“非阻塞”的概念。不同模式对文件的处理方式会有不同,使用的场景也不一样。
“阻塞”的定义
对于 read,当串口的接收缓冲区没有数据的时候,read函数会阻塞在那里,不返回,程序也无法下一步执行,一直到串口的接收缓冲区中有数据可读时,read读到了想要长度的字节数后才会返回,返回值为读到的字节数。
对于write,当串口发送缓冲区满时,或者剩下的空间小鱼将要写入的字节数时,则write阻塞,一直到串口的发送缓冲区中剩下的空间大于等于将要写入的字节数,再执行写操作,返回写入的字节数。
“非阻塞”的定义
对于read,当串口的接收缓冲区中没有数据时,read操作立即返回,返回值为0.
对于write,当串口发送缓冲区满,或者剩下的空间小鱼将要写入的字节数时,write仍然会被执行,写入当前串口发送缓冲区剩下的空间字节数,然后返回写入的字节数。
serial init
serial = serial_new();
if (serial_open(serial,"/dev/ttyCH341USB1",115200) <0) //打开并设置设备文件
{
serial_free(serial);
printf("serial open failed!\n");
}
else
{
printf("serial opened! \n");
}
serial_open 函数
网上很多都是直接用的open函数,实验使用的函数同样基于open函数通过该函数设置好串口,默认使用的阻塞模式。
int serial_open(serial_t *serial, const char *path, uint32_t baudrate) { return serial_open_advanced(serial, path, baudrate, 8, PARITY_NONE, 1, false, false);//参数设置 } int serial_open_advanced(serial_t *serial, const char *path, uint32_t baudrate, unsigned int databits, serial_parity_t parity, unsigned int stopbits, bool xonxoff, bool rtscts) { struct termios termios_settings; /* Validate args */ if (databits != 5 && databits != 6 && databits != 7 && databits != 8) return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid data bits (can be 5,6,7,8)"); if (parity != PARITY_NONE && parity != PARITY_ODD && parity != PARITY_EVEN) return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid parity (can be PARITY_NONE,PARITY_ODD,PARITY_EVEN)"); if (stopbits != 1 && stopbits != 2) return _serial_error(serial, SERIAL_ERROR_ARG, 0, "Invalid stop bits (can be 1,2)"); memset(serial, 0, sizeof(serial_t)); /* Open serial port */ if ((serial->fd = open(path, O_RDWR | O_NOCTTY)) < 0) return _serial_error(serial, SERIAL_ERROR_OPEN, errno, "Opening serial port \"%s\"", path); fcntl(serial->fd,F_SETFL,0); memset(&termios_settings, 0, sizeof(termios_settings)); /* c_iflag */ /* Ignore break characters */ termios_settings.c_iflag = IGNBRK; if (parity != PARITY_NONE) termios_settings.c_iflag |= INPCK; /* Only use ISTRIP when less than 8 bits as it strips the 8th bit */ if (parity != PARITY_NONE && databits != 8) termios_settings.c_iflag |= ISTRIP; if (xonxoff) termios_settings.c_iflag |= (IXON | IXOFF); /* c_oflag */ termios_settings.c_oflag = 0; /* c_lflag */ termios_settings.c_lflag = 0; /* c_cflag */ /* Enable receiver, ignore modem control lines */ termios_settings.c_cflag = CREAD | CLOCAL; /* Databits */ if (databits == 5) termios_settings.c_cflag |= CS5; else if (databits == 6) termios_settings.c_cflag |= CS6; else if (databits == 7) termios_settings.c_cflag |= CS7; else if (databits == 8) termios_settings.c_cflag |= CS8; /* Parity */ if (parity == PARITY_EVEN) termios_settings.c_cflag |= PARENB; else if (parity == PARITY_ODD) termios_settings.c_cflag |= (PARENB | PARODD); /* Stopbits */ if (stopbits == 2) termios_settings.c_cflag |= CSTOPB; /* RTS/CTS */ if (rtscts) termios_settings.c_cflag |= CRTSCTS; /* Baudrate */ cfsetispeed(&termios_settings, _serial_baudrate_to_bits(baudrate)); cfsetospeed(&termios_settings, _serial_baudrate_to_bits(baudrate)); /* Set termios attributes */ if (tcsetattr(serial->fd, TCSANOW, &termios_settings) < 0) { int errsv = errno; close(serial->fd); serial->fd = -1; return _serial_error(serial, SERIAL_ERROR_CONFIGURE, errsv, "Setting serial port attributes"); } serial->use_termios_timeout = false; return 0; }
fd为文件识别号,buf为收发数组
接收数据
int len;unsigned char buf[11];
len = read(fd, buf, 11);
if (len < 0){
printf("reading data faile \n");
}
发送数据
int len;
char buf[] = "hello world!";
len = write(fd, buf, sizeof(buf));
if (len< 0) {
printf("write data to serial failed! \n");
}
Socket编程是一种网络编程的方式,用于实现不同计算机之间的数据通信。在网络应用中,Socket是端到端通信的一个抽象概念,它建立在网络模型的传输层之上,提供了一种方式来允许程序跨网络发送和接收数据。最常见的是TCP(传输控制协议)和UDP(用户数据报协议)。
Socket编程通常涉及以下几个基本步骤:
#include"TCPserver.h" int TCPserverinit(void) { //服务器监听套接字和连接套接字 listen_fd=-1; connect_fd=-1; struct sockaddr_in servaddr;//定义服务器对应的套接字地址 //服务器接收和发送缓冲区 uint8_t sendbuf[MAXLINE], recbuf[MAXLINE]; //初始化套接字地址结构体 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET;//IPv4 servaddr.sin_port = htons(PORT);//设置监听端口 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//表示接收任意IP的连接请求 //创建套接字 if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ //如果创建套接字失败,返回错误信息 //strerror(int errnum)获取错误的描述字符串 printf("create socket error: %s(error: %d)\n", strerror(errno), errno); exit(0); } //绑定套接字和本地IP地址和端口 if(bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ //绑定出现错误 printf("bind socket error: %s(error: %d)\n", strerror(errno), errno); exit(0); } //使得listen_fd变成监听描述符 if(listen(listen_fd, 10) == -1){ printf("listen socket error: %s(error: %d)\n", strerror(errno), errno); exit(0); } //accept阻塞等待客户端请求 printf("等待客户端发起连接\n"); if((connect_fd = accept(listen_fd, (struct sockaddr*)NULL, NULL)) == -1){ printf("accept socket error: %s(error: %d)\n", strerror(errno), errno); } }
在Linux下,都是按读写文件的方式去管理设备。
接收数据
int len;unsigned char buf[11];
len = read(fd, buf, 11);
if (len < 0){
printf("reading data faile \n");
}
发送数据
int len;
char buf[] = "hello world!";
len = write(fd, buf, sizeof(buf));
if (len< 0) {
printf("write data to serial failed! \n");
}
void* ScreenTranslateMessage(void* arg) { uint8_t i,j=0; uint8_t hedebyte[3]; uint8_t onebyte; while (1) { serial_read(serial,&onebyte, 1); usleep(5000); if(onebyte==0X5A) //识别帧头标识 { hedebyte[0]=onebyte; onebyte=0; serial_read(serial, hedebyte+1, 2); usleep(5000); } if (hedebyte[0]==0X5A && hedebyte[1]==0XA5) { sdatalen=hedebyte[2]; serial_read(serial,sdata,sdatalen);//接受串口屏反馈数据 usleep(2500); for (i = 0; i<3; i++) { printf("%02x",hedebyte[i]); } for (j = 0; j<sdatalen; j++)//数据 { printf("%02x",sdata[j]); } printf("\n"); serialPolling(sdata,sdatalen); //按照通信协议依次处理数据,通过TCP反馈到功能模块 memset(sdata,0,sizeof(sdata)); sdatalen=0; } usleep(5000); memset(hedebyte,0,sizeof(hedebyte)); } return NULL; }
uint8_t serialPolling(uint8_t *data,uint8_t datalen) { int i=0,j=0; double power=2; uint8_t chartonum=0; if(datalen==0) { return 0;} else { LCDaddr=((uint16_t)data[1]<<8)|data[2];//识别串口屏部件 switch (LCDaddr) { case State_check: sendbuf[0]=0xAA; sendbuf[1]=0x01; sendbuf[2]=0x00; sendbuf[3]=0x00; sendbuf[4]=0x00; sendbuf[5]=0x00; //设置数据帧 write(connect_fd, sendbuf, 6);//通过TCP反馈到功能模块 break; case IP_address : //获得网络网址,将bits数据转化为网址 for(i=4;i < 19;i ++) { if(data[i]!= 0xFF) { IPaddress[i] = data[i]; if(data[i]!=0x2e) { chartonum=(IPaddress[i]-0x30)*pow(10,power); IP_buffer[j]=IP_buffer[j]+chartonum; power--; } else { j++; power=2; } } } memset(IPaddress,0,15); break; default: break; } } }
void* TCPtranslateMessage(void* arg) { while (1) { //读取客户端发来的信息 ssize_t tdatalen = read(connect_fd,tdata, sizeof(tdata)); if(tdatalen <= 0){ return 0; } for (uint8_t i = 0; i < tdatalen; i++) { printf("%02x",tdata[i]); } printf("\n"); TCPPolling(tdata,tdatalen);//处理TCP接收数据,和串口类似先找帧头然后针对不同数据进行处理 memset(tdata,0,sizeof(tdata)); } return NULL; }
根据自己的需求去设计处理数据的函数
uint8_t TCPPolling(uint8_t *data,uint8_t datalen) { uint8_t position=6; if(datalen==0) { return 0;} else { command=data[1]; switch (command) { case 01: /* code */ //状态显示,设置屏幕显示数据 DW_SetValue(Running_state,data[position]); DW_SetValue(Input_datastate,data[position+1]); break; case 02: DW_SetValue(Output_power,data[position]); DW_SetValue(PAR_coefficient,data[position+1]); break; case 03: /* code */ break; default: break; } }
《T5L DGUSII应用开发指南》
串口通信协议和Linux下的串口编程
TCP的socket详解
迪文串口屏教程
迪文串口屏(T5L2 & DGUS II)开发 – 入门
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。