前言
平常的工作使用中,总是免不了要和串口打交道,协议的收发也经常通过串口来实现,海思3559下的串口和标准的linux下串口大同小异,可以参考之前zynq的串口编程,也可以直接阅读本文
使能串口
最直接的方式就是将设备树中对应uart的status修改为 status = “okay”。海思实际加载的串口驱动是PL011,menuconfig查看配置Device Drivers > Character devices > Serial drivers中的ARM AMBA PL011 serial port support 和 Support for console on AMBA serial port是否有选择上。重新编译内核烧入,在/dev 下可以查看是否有串口设备ttyAMA0~4。sdk2.0.3.1版本默认是有的
查看串口配置
可以使用linux的stty命令查看串口的配置参数。比如:stty -F /dev/ttyAMA0 -a
/dev#cd /proc/tty/driver//proc/tty/driver # ls ttyAMA /proc/tty/driver # cat ttyAMA serinfo:1.0 driver revision: 0:uart:PLell rev2 mmio:0x12100000 irq:13 tx:916928 rx:867 RTS|DTR|DSR|CD|RI1:uart:PLoll rev2 mm1o:0x12101000 irq:14 tx:0 rx:0 DSR|CD|RI2:uart:PLoll rev2 mm1o:0x12102000 1rq:15 tx:0 rx:0 DSR|CD|RI3:uart:PLell rev2 mm1o:0x12103000 irq:16 tx:0 rx:0 DSR|CD|RI4:uart:PLell rev2 mm1o:0x12104000 irg:17 tx:0 rx:0 DSR|CD|RI
同样可以使用stty命令设置串口参数,比如:stty -F /dev/ttyAMA0 ispeed 115200 ospeed 115200 cs8
查看串口数据收发
一般调试串口,我们可以将TX与RT接在一起,自己发数据给自己接收,看数据是否正常。也可以使用cat 查看串口数据或是echo发送数据。
发送数据:
echo "test 1234567890" > /dev/ttyAMA0
接收数据:
cat /dev/ttyAMA0
查看串口硬件分配:
串口的硬件分配状态,比如IO和中断使用情况可以在/proc/tty/driver下的ttyAMA 种查看:
/dev#cd /proc/tty/driver//proc/tty/driver # ls ttyAMA /proc/tty/driver # cat ttyAMA serinfo:1.0 driver revision: 0:uart:PLell rev2 mmio:0x12100000 irq:13 tx:916928 rx:867 RTS|DTR|DSR|CD|RI1:uart:PLoll rev2 mm1o:0x12101000 irq:14 tx:0 rx:0 DSR|CD|RI2:uart:PLoll rev2 mm1o:0x12102000 1rq:15 tx:0 rx:0 DSR|CD|RI3:uart:PLell rev2 mm1o:0x12103000 irq:16 tx:0 rx:0 DSR|CD|RI4:uart:PLell rev2 mm1o:0x12104000 irg:17 tx:0 rx:0 DSR|CD|RI
注意
如果串口的配置和数据的收发命令都能够正常,但是串口的引脚没有电平变化,这个可能是串口的复用功能没有设置,需要设置一下GPIO复用为串口功能。复用功能可以在设备树dts中设置,也可以使用海思的himm命令直接设置:
himm 0x120f0100 0x01 #UART2_RXD himm 0x120f0104 0x01 #UART2_TXD
这些都是和标注的linux一致的,不一样的是海思的串口经常需要代码初始化后才能操作,或者使用海思自己的microcom
收发测试代码
操作库函数头文件
/************************************************************************************************ *****Describe: This program is writen to operate HI35xx serial devices. ***** *****Author: xin.han ***** *****Date: 2022-09-17 ***** *************************************************************************************************/ #ifndef _UART_H_ #define _UART_H_ #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<termios.h> #include<errno.h> #include<string.h> #include <signal.h> #include "platform.h" #include "parser.h" #include "queue.h" //宏定义 // #define HI_FALSE -1 // #define HI_TRUE 0 #ifdef debugprintf #define debugpri(mesg, args...) fprintf(stderr, "[HI Serial print:%s:%d:] " mesg "\n", __FILE__, __LINE__, ##args) #else #define debugpri(mesg, args...) /* *描述 : 打开串口 *参数 : HiSerDevice串口设备名 串口设备举例: /dev/ttyAMA1 /dev/ttyAMA2 *返回值: 成功返回fd,失败返回-1 *注意 :无 */ int HI_Serial_Open(char* HiSerDevice); /* *描述 : 关闭串口 *参数 : fd文件描述符 *返回值: 成功返回fd,失败返回-1 *注意 :无 */ void HI_Serial_Close(int fd); /* *描述 : 串口参数设置 *参数 : fd: 文件描述符 * speed: 波特率.115200,19200,9600... * flow_ctrl: 流控 * databits: 数据位 取值为5,6,7,8 * stopbits: 停止位 取值为1,2 * parity: 奇偶校验位 取值为N,W,O,S *返回值: 成功返回0,失败返回-1 *注意 :无 */ int HI_Serial_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity) ; /* *描述 : 串口初始化,实际还是串口参数设置 *参数 : fd: 文件描述符 * speed: 波特率.115200,19200,9600... * flow_ctrl: 流控 * databits: 数据位 取值为5,6,7,8 * stopbits: 停止位 取值为1,2 * parity: 奇偶校验位 取值为N,W,O,S *返回值: 成功返回0,失败返回-1 *注意 :无 */ int HI_Serial_Init(int fd, int speed,int flow_ctrl,int databits,int stopbits,int parity); /* *描述 : 串口发送 *参数 : fd:文件描述符 * send_buf:发送buf * data_len:数据长度 *返回值: 成功返回数据长度,失败返回-1 *注意 :无 */ int HI_Serial_Send(int fd, char *send_buf,int data_len) ; /* *描述 : 串口接受 *参数 : fd:文件描述符 * send_buf:接收buf * data_len:数据长度 *返回值: 成功返回数据长度,失败返回-1 *注意 :无 */ int HI_Serial_Recv(int fd, char *rcv_buf,int data_len) ; /* *描述 : 串口接受函数线程 *参数 : 无 *返回值: 无 *注意 :无 */ HI_VOID *uart_recv_task(HI_VOID *arg); /* *描述 : 串口发送函数线程 *参数 : 无 *返回值: 无 *注意 :无 */ HI_VOID *uart_send_task(HI_VOID *arg); #endif #endif
操作库函数
/************************************************************************************************ *****Describe: This program is writen to operate HI35xx serial devices. ***** *****Author: xin.han ***** *****Date: 2022-09-17 ***** *************************************************************************************************/ #include "uart.h" int HiSerfd; void HI_Serial_Close(int fd); void Hi_sigsegv(int dummy) { if(HiSerfd > 0) HI_Serial_Close(HiSerfd); fprintf(stderr, "Hi Serial Caught SIGSEGV, Abort!\n"); fclose(stderr); abort(); } void Hi_sigterm(int dummy) { if(HiSerfd > 0) HI_Serial_Close(HiSerfd); fprintf(stderr, "Hi Serial Caught SIGTERM, Abort!\n"); fclose(stderr); exit(0); } void Hi_init_signals(void) { struct sigaction sa; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGSEGV); sigaddset(&sa.sa_mask, SIGTERM); sigaddset(&sa.sa_mask, SIGPIPE); sa.sa_handler = Hi_sigsegv; sigaction(SIGSEGV, &sa, NULL); sa.sa_handler = Hi_sigterm; sigaction(SIGTERM, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); } void HI_Serial_Usage(void) { printf("Usage:\n"); printf("\tmyhicom [-d] <HiSerialDevice> [-s] get netdeviece info [-rw] read or wite select\n"); printf("\tmyhicom [-h] for more usage\n"); printf("\tmyhicom [-v] the verson of the sofware\n"); printf("\tExample:\n\tmyhicom -d /dev/ttyAMA1 -s 115200 -w HiSerial:HelloWorld\n"); } int HI_Serial_Open(char* HiSerDevice) { int fd; fd = open( HiSerDevice, O_RDWR|O_NOCTTY);// O_RDWR : 可读可写 // O_NOCTTY :该参数不会使打开的文件成为该进程的控制终端。如果没有指定这个标志,那么任何一个 输入都将会影响用户的进程。 // O_NDELAY :这个程序不关心DCD信号线所处的状态,端口的另一端是否激活或者停止。如果用户不指定了这个标志,则进程将会一直处在睡眠状态,直到DCD信号线被激活。 // fd = open(HiSerDevice, O_RDWR|O_NOCTTY|O_NDELAY); if (-1 == fd) { perror("HiSerial Can't Open Serial HiSerDevice"); return(-1); } //恢复串口为阻塞状态 if(fcntl(fd, F_SETFL, 0) < 0) { debugpri("fcntl failed!\n"); return(-1); } else { debugpri("fcntl=%d\n",fcntl(fd, F_SETFL,0)); } //测试是否为终端设备 if(0 == isatty(STDIN_FILENO)) { debugpri("standard input is not a terminal device\n"); return(-1); } else { debugpri("isatty success!\n"); } printf("fd->open=%d\n",fd); return fd; } void HI_Serial_Close(int fd) { if(fd > 0) close(fd); return; } int HI_Serial_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity) { int i; // int status; int speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300}; int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300}; struct termios options; if( tcgetattr( fd,&options) != 0) { perror("SetupSerial 1"); return(-1); } //set buater rate for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) { if (speed == name_arr[i]) { cfsetispeed(&options, speed_arr[i]); cfsetospeed(&options, speed_arr[i]); } } //set control model options.c_cflag |= CLOCAL; //清bit位 关闭流控字符 0x11 0x13 options.c_cflag |= CREAD; //清bit位 关闭流控字符 0x11 0x13 options.c_iflag &= ~(INLCR|ICRNL);//清bit位 关闭字符映射 0x0a 0x0d options.c_iflag &= ~(IXON);//清bit位 关闭流控字符 0x11 0x13 //set flow control switch(flow_ctrl) { case 0 ://none options.c_cflag &= ~CRTSCTS; break; case 1 ://use hard ware options.c_cflag |= CRTSCTS; break; case 2 ://use sofware options.c_cflag |= IXON | IXOFF | IXANY; break; } //select data bit options.c_cflag &= ~CSIZE; switch (databits) { case 5 : options.c_cflag |= CS5; break; case 6 : options.c_cflag |= CS6; break; case 7 : options.c_cflag |= CS7; break; case 8: options.c_cflag |= CS8; break; default: fprintf(stderr,"Unsupported data size\n"); return (-1); } //select parity bit switch (parity) { case 'n': case 'N'://无奇偶校验位 options.c_cflag &= ~PARENB; options.c_iflag &= ~INPCK; break; case 'o': case 'O'://设置为奇校验 options.c_cflag |= (PARODD | PARENB); options.c_iflag |= INPCK; break; case 'e': case 'E'://设置为偶校验 options.c_cflag |= PARENB; options.c_cflag &= ~PARODD; options.c_iflag |= INPCK; break; case 's': case 'S'://设置为空格 options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; break; default: fprintf(stderr,"Unsupported parity\n"); return (-1); } // set stopbit switch (stopbits) { case 1: options.c_cflag &= ~CSTOPB; break; case 2: options.c_cflag |= CSTOPB; break; default: fprintf(stderr,"Unsupported stop bits\n"); return (-1); } //修改输出模式,原始数据输出 options.c_oflag &= ~OPOST; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //options.c_lflag &= ~(ISIG | ICANON); //set wait time 主要影响read函数 /* options.c_cc[VTIME] = X; //设置从获取到1个字节后开始计时的超时时间 options.c_cc[VMIN] = Y; //设置要求等待的最小字节数 在原始模式下对read()函数的影响: 1、X=0,Y!=0。函数read()只有在读取了Y个字节的数据或者收到一个信号的时候才返回; 2、X!=0,Y=0。即使没有数据可以读取,read()函数等待X时间量后返回; 3、X!=0,Y!=0。第一个字节数据到时开始,最先满足收到Y个字节或达超时时间X任意一个条件,read()返回; 4、X=0,Y=0。即使读取不到任何数据,函数read也会立即返回。 */ options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */ options.c_cc[VMIN] = 32; /* 读取字符的最少个数为xx,单位是字节 */ tcflush(fd,TCIFLUSH); // //激活配置 (将修改后的termios数据设置到串口中) if (tcsetattr(fd,TCSANOW,&options) != 0) { perror("com set error!\n"); return -1; } return 0; } int HI_Serial_Init(int fd, int speed,int flow_ctrl,int databits,int stopbits,int parity) { // int err; //设置串口数据帧格式 if (HI_Serial_Set(fd,speed,flow_ctrl,databits,stopbits,parity) == -1) { return -1; } else { return 0; } } int HI_Serial_Send(int fd, char *send_buf,int data_len) { int len = 0; len = write(fd,send_buf,data_len); if (len == data_len ) { debugpri("send data is %s\n",send_buf); return len; } else { tcflush(fd,TCOFLUSH); return -1; } } int HI_Serial_Recv(int fd, char *rcv_buf,int data_len) { int len,fs_sel; fd_set fs_read; struct timeval time; FD_ZERO(&fs_read); FD_SET(fd,&fs_read); time.tv_sec = 30; time.tv_usec = 0; // len = read(fd,rcv_buf,data_len); // select fdset fs_sel = select(fd+1,&fs_read,NULL,NULL,&time); if(fs_sel) { len = read(fd,rcv_buf,data_len); debugpri("HiSeral Receive Data = %s len = %d fs_sel = %d\n",rcv_buf,len,fs_sel); return len; } else { debugpri("Hiserial haven't data receive!"); return -1; } } HI_VOID *uart_recv_task(HI_VOID *arg) { int len; char HiSerialDev[32]="/dev/ttyAMA1"; // char sendbuf[1024]={0}; char recvbuf[1024]={0}; int SerialSpeed = 115200; int HiSerfd; int i; // Hi_init_signals(); // HI_Serial_Usage(); HiSerfd = HI_Serial_Open(HiSerialDev); HI_Serial_Init(HiSerfd,SerialSpeed,0,8,1,'N'); while(1) { len = HI_Serial_Recv(HiSerfd, recvbuf,sizeof(recvbuf)); // printf("serial_recv length is %d\n",len); if(len > 0) { printf(" ttyAMA1:recv origin data \n"); // recvbuf[len] = '\0'; for (i = 0;i < len ;i++) { printf("%02x ",recvbuf[i]); } printf("\n"); // memset(recvbuf,0,sizeof(recvbuf)); //break; } else { // printf("Hiserial haven't data receive \n"); } // sleep(2); } // debugpri("myHicom write %s\n",optarg); // HiSerfd = HI_Serial_Open(HiSerialDev); // printf("fd = %d device = %s speed = %d\n",HiSerfd,HiSerialDev,SerialSpeed); // HI_Serial_Init(HiSerfd,SerialSpeed,0,8,1,'N'); // sprintf(sendbuf,"%s\n",optarg); // HI_Serial_Send(HiSerfd, sendbuf, strlen(sendbuf)+1); // if(HiSerfd > 0) // HI_Serial_Close(HiSerfd); // break; return 0; } HI_VOID *uart_send_task(HI_VOID *arg) { char HiSerialDev[32]="/dev/ttyAMA1"; char sendbuf[1024]="HelloWorld1234567890"; // char recvbuf[1024]={0}; // char sendbuf[] = {0}; int SerialSpeed = 115200; int HiSerfd; // Hi_init_signals(); // HI_Serial_Usage(); HiSerfd = HI_Serial_Open(HiSerialDev); HI_Serial_Init(HiSerfd,SerialSpeed,0,8,1,'N'); while(1) { HI_Serial_Send(HiSerfd, sendbuf, strlen(sendbuf)+1); sleep(1); } // debugpri("myHicom write %s\n",optarg); // HiSerfd = HI_Serial_Open(HiSerialDev); // printf("fd = %d device = %s speed = %d\n",HiSerfd,HiSerialDev,SerialSpeed); // HI_Serial_Init(HiSerfd,SerialSpeed,0,8,1,'N'); // sprintf(sendbuf,"%s\n",optarg); // HI_Serial_Send(HiSerfd, sendbuf, strlen(sendbuf)+1); // if(HiSerfd > 0) // HI_Serial_Close(HiSerfd); // break; return 0; }
还是新建两个线程,一个收一个发
HI_VOID *uart_recv_task(HI_VOID *arg) { int len; char HiSerialDev[32]="/dev/ttyAMA1"; // char sendbuf[1024]={0}; char recvbuf[1024]={0}; int SerialSpeed = 115200; int HiSerfd; int i; HiSerfd = HI_Serial_Open(HiSerialDev); HI_Serial_Init(HiSerfd,SerialSpeed,0,8,1,'N'); while(1) { len = HI_Serial_Recv(HiSerfd, recvbuf,sizeof(recvbuf)); // printf("serial_recv length is %d\n",len); if(len > 0) { printf(" ttyAMA1:recv origin data \n"); // recvbuf[len] = '\0'; for (i = 0;i < len ;i++) { printf("%02x ",recvbuf[i]); } printf("\n"); // memset(recvbuf,0,sizeof(recvbuf)); //break; } else { // printf("Hiserial haven't data receive \n"); } } return 0; } HI_VOID *uart_send_task(HI_VOID *arg) { char HiSerialDev[32]="/dev/ttyAMA1"; char sendbuf[1024]="HelloWorld1234567890"; // char recvbuf[1024]={0}; // char sendbuf[] = {0}; int SerialSpeed = 115200; int HiSerfd; HiSerfd = HI_Serial_Open(HiSerialDev); HI_Serial_Init(HiSerfd,SerialSpeed,0,8,1,'N'); while(1) { HI_Serial_Send(HiSerfd, sendbuf, strlen(sendbuf)+1); sleep(1); } return 0; }
还是比较简单的
坑
其实也不是海思的坑,做驱动的时候不影响,做应用的时候会稍微恶心一点
每次接收的时候,如果内容短还好,一旦大于8个字,就会分成几次接收,倒也不会丢,现在自己要写应用了,为了方便肯定不能这么用了呀,一番检查发现是options.c_cc[VMIN] 的原因,默认是1,单位是字节,表示最少收到的字节数,select收到就会返回了
options.c_cc[VTIME] = X; //设置从获取到1个字节后开始计时的超时时间
options.c_cc[VMIN] = Y; //设置要求等待的最小字节数
在原始模式下对read()函数的影响:
1、X=0,Y!=0。函数read()只有在读取了Y个字节的数据或者收到一个信号的时候才返回;
2、X!=0,Y=0。即使没有数据可以读取,read()函数等待X时间量后返回;
3、X!=0,Y!=0。第一个字节数据到时开始,最先满足收到Y个字节或达超时时间X任意一个条件,read()返回;
4、X=0,Y=0。即使读取不到任何数据,函数read也会立即返回。