一.简介
串口( UART)是一种非常常见的外设, 串口在嵌入式开发领域当中一般作为一种调试手段,通过串口将调试信息打印出来,或者通过串口发送指令给主机端进行处理;当然除了作为基本的调试手段之外,还可以通过串口与其他设备或传感器进行通信, 譬如有些 sensor 就使用了串口通信的方式与主机端进行数据交互。
根据电平标准的不同,串口可以分为 TTL, RS232,RS485, RS422等这些,虽然它们的电平标准不同,但是却都遵循相同的通信时序协议,所以呢,不管它是什么样的电平标准,对于我们软件而言其驱动程序都是一样的;一般主机端直接出来的电平信号就是 TTL
对于 ZYNQ 来说,其 PS 端只提供了两个串口外设 UART0 和 UART1,很明显对于大一点的工程而言是不够用的,此时可以使用 PL 端串口软核外设, xilinx 同样提供了相应的 IP核 可供调用如这次使用的UART16550
二.基础知识准备
串口在嵌入式 Linux 系统当中经常作为系统的标准输入、输出设备,而提到串口,那么就不得不引出另外两个概念:终端与控制台。关于这两个概念,很多人估计是对此傻傻分不清!
这里参考了原子文档里对终端的理解
1、 什么是终端 Terminal
终端就是处理主机输入、 输出的一套设备,它用来显示主机运算的输出,并且接受主机要求的输入。 典型的终端包括显示器键盘套件,打印机打字机套件等。 其实本质上也就一句话,能接受输入、能显示输出,这就够了,不管到了什么时代,终端始终扮演着人机接口的角色, 所谓 Terminal,即机器的边缘!
只要能提供给计算机输入和输出功能,它就是终端,而与其所在的位置无关。
2、终端的分类
⚫ 本地终端:例如对于我们的个人 PC 机来说, PC 机连接了显示器、键盘以及鼠标等设备, 这样的一个显示器/键盘组合就是一个本地终端;同样对于开发板来说也是如此,开发板也可以连接一个LCD 显示器、键盘和鼠标等,同样可以构成本地终端。
⚫ 用串口连接的远程终端:对于嵌入式 Linux 开发来说,这是最常见的终端—串口终端。 譬如我们的开发板通过串口线连接到一个带有显示器和键盘的 PC 机, 在 PC 机通过运行一个终端模拟程序,譬如 Windows 超级终端、 putty、 MobaXterm、 SecureCRT 等来获取并显示开发板通过串口发出的数据、同样还可以通过这些终端模拟程序将用户数据通过串口线发送给开发板。
⚫ 基于网络的远程终端:譬如我们可以通过 ssh、 Telnet 这些协议登录到一个远程主机。以上列举的这些都是终端,前两类称之为物理终端; 最后一个称之为伪终端。 前两类都是在本地就直接关联了物理设备的, 譬如显示器、鼠标键盘、 串口等之类的,这种终端叫做物理终端,而第三类在本地则没有关联任何物理设备,注意,不要把物理网卡当成终端关联的物理设备,它们与终端并不直接相关,所以这类不直接关联物理设备的终端叫做伪终端。
3、什么是控制台 Console
能够显示系统信息的终端就叫控制台。 控制台的概念与终端含义非常相近,其实现在我们经常用它们表示相同的东西, linux 中基本也已经淡化了控制台和终端的区别。虽然说它们之间的含义非常相近,但还是有一些区别:
⚫ 能够显示系统信息的终端就叫控制台,这说明它们之间是一个包含关系,控制台一定是终端,而终端则不一定是控制台,也就是说控制台是终端的真子集。
⚫ 控制台只有一个。看到这里大家可能就有疑问了,我们使用的开发板可以通过串口终端打印信息,同样我们也可以通过 ssh 协议登录连接到开发板,同样也会打开一个伪终端,并且也可以在伪终端显示打印信息,那么它俩不都可以认为是控制台吗?其实并非如此,上面说到的显示系统信息指的是开发板启动的时候,所有的打印系统信息都会显示到这个终端上,那么这个终端才叫做控制台,所有由此可以知道,譬如我们的开发板在启动时,所有的打印信息都会通过串口输出,所以我们的串口终端就是控制台, 而通过 ssh 远程登录开发板打开的伪终端并不是控制台,因为启动时的打印信息是不可能输出到这个伪终端上的。
⚫ 控制台是计算机本身的设备,一个计算机只有一个控制台。譬如开发板的串口这就是开发板本身的设备。
讲到这里相信大家都应该清楚了,它们之间的共同点和小小的区别,其实我们也不用去刻意区分它们之间的异同,因为 Linux 中它们之间的区别基本完全淡化了。
4、 Linux 下终端对应的设备文件
在 Linux 当中,一切皆是文件。当然,终端也不例外,每一个终端设备在/dev 目录下都
有一个对应的设备文件。
⚫ /dev/ttyX 设备文件: tty( teletype 的简称) 是最令人熟悉的了,在 Linux 中, /dev/ttyX 代表的都是上述的物理终端,其中, /dev/tty1~/dev/tty63 代表的是本地终端,也就是接到本机的键盘显示器可以操作的终端。事实上 Linux 内核在初始化时会生成 63 个本地终端。
⚫ /dev/console 设备文件:这个设备文件表示当前系统的控制台,如果我们往这个设备文件输入信息,它一定会显示在我们的控制台终端中,譬如“echo Hello > /dev/console”。
⚫ 串口终端 ttyPSX:对于我们的调试版来说,有一个ps的串口,12个PL的串口所以也对应了13串口终端设备文件,如下所示:
【图片】
当然这里的名字 ttyPS0,ttyS0不是固定的,这个具体的命名跟串口的驱动程序有关,名字不是统一的,但是名字前缀一般都以“ tty”开头,以表明它是一个终端设备
三.Linux下的串口调用
3.1介绍
串口的工作原理肯定是跟之前裸机中介绍到的谁一样的,重点在编程时linux对串口不同的调用方式
Linux 系统中 UART 串口驱动框架结构图如下所示
简单地说可以分为两层: UART 驱动层和 tty 驱动层。 从图中可以看到,下层 UART 驱动层直接与硬件相接触,也就是说它才是真正的 UART驱动程序,它提供了 UART 硬件操作相关函数集 uart_ops;而上层 tty 驱动层则会将 UART 设备描述成一个 tty(终端)设备, 并向内核注册 tty 字符设备,提供字符设备操作函数集ops,其实 ops 函数集中经过层层跳转最终执行的就是 uart 驱动层的 uart_ops。
首先对于驱动开发工程师来说, tty 驱动层并不需要我们去实现,它会在我们注册 UART
驱动的过程中自动构建出来,对于我们所使用的内核源码来说, xilinx 官方已经提供了串口驱动程序,所以只需要在应用层封装一层驱动了(真实令人感到偷懒和开心的消息)
(如果需要自己写的话,自行查找原子的免费开发文档吧,有很详细的讲解)
3.2 命令行调用测试
老规矩,还是先使用命令行对串口进行一个简单的测试吧
第一步先要看我们有没有生成的tty串口设备,不确定是xilinx的默认设置还是怎么的,不论是交叉编译menuconfig还是官方编译的config kernel都对最大支持设备数有限制,我这边独到的最大值为4,所以需要改,配置内核时找到如下路径,对下面的最大支持设备改为自己的设备数,(没用官方推荐模式的可能默认连8250的设备树都没有支持,往下翻,找到并勾选)
.config - Linux/arm 4.14.0 Kernel Configuration →Device Drivers →Character devices →Serial drivers - Serial drivers Arrow keys navigate the menu. <Enter> selects submenus ---> (or empty submenus----). Highlighted letters are hotkeys. Pressing <Y> includes.<N> excludes. <M> modularizes features. press <Esc><Esc> to exit,<?> for Help,</> for Search. Legend: [*] built-in [ ] <*>8250/16550 and compatible serial support 「* Support 8250 core.* kernel options (DEPRECATED) Support for Fintek F81216A LPC t0 4 UART RS485 API Console on 8250/16550 and compatible serial port [*] DMA support for 16550 compatible UART controllers <*> 8250/16550 PCI device support <*> 8250/16550 Exar/Commtech PcI/PCIe device support (12) Maximum number of 8250 16550 serial ports (12) Number of 8250/16550 serial ports to register at runtime Extended 8250/16550 serial driver options((+) <Select> Exit > < Help > Save > < Load > Hx--1024
stty查看串口参数
stty -F /dev/ttyS0 -a
查看串口1(/dev/ttyS0)当前的参数,包括波特率、数据位等。
stty设置串口参数
stty -F /dev/ttyS0 ispeed 115200 ospeed 115200 cs8
该命令将串口1(/dev/ttyS0)设置成115200波特率,8位数据模式。一般情况下设置这两个参数就可以了,如果显示数据乱码,可能还需要设置其它参数,使用man 查看stty其它设置选项
cat打印串口数据
cat /dev/ttyS0
串口数据就可以在终端上显示了。
发送数据
echo “HelloWorld” >/dev/ttyS0
3.3测试代码
然后就是编写代码了,下面是增改可用的串口驱动头文件:
#ifndef _SERIAL_H_ #define _SERIAL_H_ #include<stdio.h> /*Standard input/output definitions*/ #include<stdlib.h> /*Standard function library definitions*/ #include<unistd.h> /*Unix Standard function definition*/ #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> /*File control definition*/ #include<termios.h> /*PPSIX Terminal control definition*/ #include<errno.h> /*Error number definition*/ #include<string.h> #include<sys/time.h> #define FALSE -1 #define TRUE 0 #define B76800 0010020 #define B153600 0010021 #define B307200 0010022 #define B614400 0010023 // /******************************************************************* * name: serial_open * function: Opens the serial port and returns the serial device file description * @param[in]: port :serial number(ttyS0,ttyS1) * @return: Correct returns fd, error returns -1 *******************************************************************/ int serial_open(char* port); /******************************************************************* * name: serial_init() * function: serial_init * @param[in]: fd : File descriptor * @param[in] speed : Serial speed * @param[in] flow_ctrl Data flow control * @param[in] databits The data bits , either 7 or 8 * @param[in] stopbits The stop bit , either 1 or 2 * @param[in] parity The value of effect type ,choose N,E,O, S * * return: Correct returns 0, error returns -1 *******************************************************************/ int serial_init(int fd, int speed,int flow_ctrl,int databits,int stopbits,int parity); /******************************************************************* * name: serial_recv * function: Receive serial data * @param[in]: fd: File descriptor * rcv_buf : The data from the receiving serial port is stored in the rcv_buf buffer * data_len : The length of the data to read * * return: The actual length of the data read *******************************************************************/ int serial_recv(int fd, char *rcv_buf,int data_len); /******************************************************************** * name: serial_send * function: To send data * @param[in]: fd : File descriptor * send_buf :Stores serial port to send data * data_len :The length of the data to send * return: Correct returns 0, error returns -1 *******************************************************************/ int serial_send(int fd, char *send_buf,int data_len); /******************************************************************* * name: serial_set * function: Set serial port data bit, stop bit and effect check bit * @param[in]: fd File descriptor * speed serial speed * flow_ctrl Data flow control * databits The data bits , either 7 or 8 * topbits The stop bit , either 1 or 2 * parity The value of effect type ,choose N,E,O, S *return: Correct returns 0, error returns -1 *******************************************************************/ int serial_set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity); /******************************************************************* * name: serial_close * function: Closes the serial port and returns the serial device file description * @param[in]: fd :File descriptor * @param[in]: port :serial number(ttyS0,ttyS1) * @return: void *******************************************************************/ void serial_close(int fd); #endif
再然后是包含的库文件
#ifndef _PACKET_H #define _PACKET_H #include <stdint.h> // #define DEBUG #ifdef DEBUG #define PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define PRINT(fmt, ...) #endif typedef enum { UART_SND_REQ = 0x11, UART_UPLOAD = 0x22, UART_ATTR_SET_REQ = 0x33, UART_ATTR_GET_REQ = 0x44, UART_ATTR_GET_ACK = 0x44, JTAG_CONNECT_SET_REQ = 0x55, JTAG_CONNECT_GET_REQ = 0x66, JTAG_CONNECT_GET_ACK = 0x66, IP_ADDR_SET_REQ = 0x77, IP_ADDR_GET_REQ = 0x71, IP_ADDR_GET_ACK = 0x71, SYSTEM_RESET = 0x72 } MANAGE_TYPE; typedef struct { uint32_t PackageLen; uint8_t ManageType; } __attribute__((__packed__)) HEADER; typedef struct { HEADER header; uint8_t PortID; uint8_t Data[]; } __attribute__((__packed__)) UART_SND; typedef struct { HEADER header; uint8_t PortID; uint8_t Baudrate; uint8_t DataBit; uint8_t StopBit; uint8_t Pirity; uint8_t FlowCtrl; uint8_t CheckSum; } __attribute__((__packed__)) UART_SET_REQ; typedef struct { HEADER header; uint8_t PortID; uint8_t CheckSum; } __attribute__((__packed__)) UART_GET_REQ; #define UART_GET_ACK UART_SET_REQ #define JTAG_SET_REQ UART_GET_REQ #define JTAG_INQ_REQ UART_GET_REQ //Jtag connect status inquire request #define JTAG_INQ_ACK UART_GET_REQ //Jtag connect status inquire answer #define IP_INQ_REQ UART_GET_REQ #define COMMON_ACK UART_GET_REQ #define IP_INQ_ACK IP_SET_REQ typedef struct { HEADER header; uint8_t IpAddr[4]; uint8_t NetMask[4]; uint8_t GateWay[4]; uint8_t CheckSum; } __attribute__((__packed__)) IP_SET_REQ; typedef struct { int sock; char status; uint8_t ip[20]; } TCP_CLIENT; #endif 之后就是驱动c文件了 #include "serial.h" #include "packet.h" /******************************************************************* * 名称: serial_open * 功能: 打开串口并返回串口设备文件描述 * 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1) * 出口参数: 正确返回为fd,错误返回为-1 *******************************************************************/ int serial_open(char* port) { int fd; fd = open( port, O_RDWR|O_NOCTTY); // fd = open( port, O_RDWR|O_NOCTTY|O_NDELAY); if (FALSE == fd) { perror("Can't Open Serial Port"); return(FALSE); } //恢复串口为阻塞状态 // if(fcntl(fd, F_SETFL, 0) < 0) // { // PRINT("fcntl failed!\n"); // return(FALSE); // } // else // { // // PRINT("fcntl=%d\n",fcntl(fd, F_SETFL,0)); // } // // //测试是否为终端设备 // // if(0 == isatty(STDIN_FILENO)) // // { // // PRINT("standard input is not a terminal device\n"); // // return(FALSE); // // } // // else // // { // // // PRINT("%s is a terminal device!\n", port); // // } return fd; } /******************************************************************* * 名称: serial_close * 功能: 关闭串口并返回串口设备文件描述 * 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1) * 出口参数: void *******************************************************************/ void serial_close(int fd) { close(fd); } /******************************************************************* * 名称: serial_set * 功能: 设置串口数据位,停止位和效验位 * 入口参数: fd 串口文件描述符 * speed 串口速度 * flow_ctrl 数据流控制 * databits 数据位 取值为 7 或者8 * topbits 停止位 取值为 1 或者2 * parity 效验类型 取值为N,E,O,,S *出口参数: 正确返回为1,错误返回为0 *******************************************************************/ int serial_set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity) { int i; //int status; int speed_arr[] = { B115200, B38400, B19200, B9600, B4800, B2400, B1200, B614400,B76800}; //int name_arr[] = {115200, 38400, 19200, 9600, 4800, 2400, 1200, 300}; // int name_arr[] = {4, 3, 2, 1, 0, 2400, 1200, 300}; int name_arr[] = {115200, 38400, 19200, 9600, 4800, 2400, 1200, 614400,76800}; struct termios options; /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1. */ if( tcgetattr( fd,&options) != 0) { perror("SetupSerial 1"); return(FALSE); } //设置串口输入波特率和输出波特率 for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) { if (speed == name_arr[i]) { cfsetispeed(&options, speed_arr[i]); // printf("ispeed is %d----------------------------\n",speed_arr[i]); cfsetospeed(&options, speed_arr[i]); // printf("ospeed is %d----------------------------\n",speed_arr[i]); } } //修改控制模式,保证程序不会占用串口 options.c_cflag |= CLOCAL; //修改控制模式,使得能够从串口中读取输入数据 options.c_cflag |= CREAD; //清bit位 关闭字符映射 0x0a 0x0d options.c_iflag &= ~(INLCR|ICRNL); //清bit位 关闭流控字符 0x11 0x13 options.c_iflag &= ~(IXON); options.c_cflag |= CBAUDEX; //设置特定波特率的标志位. //设置数据流控制 switch(flow_ctrl) { case 0 ://不使用流控制 options.c_cflag &= ~CRTSCTS; break; case 1 ://使用硬件流控制 options.c_cflag |= CRTSCTS; break; case 2 ://使用硬件流控制DTR/DSR options.c_cflag &= ~CRTSCTS; break; case 3 ://使用软件流控制 options.c_cflag |= IXON | IXOFF | IXANY; break; } //设置数据位 //屏蔽其他标志位 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 (FALSE); } //设置校验位 switch (parity) { case 0: case 'N': //无奇偶校验位。 options.c_cflag &= ~PARENB; options.c_iflag &= ~INPCK; break; case 1: case 'O'://设置为奇校验 options.c_cflag |= (PARODD | PARENB); options.c_iflag |= INPCK; break; case 2: 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 (FALSE); } // 设置停止位 switch (stopbits) { case 1: options.c_cflag &= ~CSTOPB; break; case 2: options.c_cflag |= CSTOPB; break; default: fprintf(stderr,"Unsupported stop bits\n"); return (FALSE); } //修改输出模式,原始数据输出 options.c_oflag &= ~OPOST; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //options.c_lflag &= ~(ISIG | ICANON); //设置等待时间和最小接收字符 options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */ options.c_cc[VMIN] = 32; /* 读取字符的最少个数为1 */ //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读 tcflush(fd,TCIFLUSH); //激活配置 (将修改后的termios数据设置到串口中) if (tcsetattr(fd,TCSANOW,&options) != 0) { perror("com set error!\n"); return (FALSE); } return (TRUE); } /******************************************************************* * 名称: serial_init() * 功能: 串口初始化 * 入口参数: fd : 文件描述符 * speed : 串口速度 * flow_ctrl 数据流控制 * databits 数据位 取值为 7 或者8 * stopbits 停止位 取值为 1 或者2 * parity 效验类型 取值为N,E,O,,S * * 出口参数: 正确返回为0,错误返回为-1 *******************************************************************/ int serial_init(int fd, int speed, int flow_ctrl, int databits, int stopbits, int parity) { //设置串口数据帧格式 //if (serial_set(fd,19200,0,8,1,'N') == FALSE) if((speed < 0) || (speed > 10000000)) return 1; if (serial_set(fd,speed,flow_ctrl,databits,stopbits,parity) == FALSE) { return FALSE; } else { return TRUE; } } /******************************************************************* * 名称: serial_recv * 功能: 接收串口数据 * 入口参数: fd : 文件描述符 * rcv_buf :接收串口中数据存入rcv_buf缓冲区中 * data_len :数据的长度 * * 出口参数: 实际读到的长度 *******************************************************************/ int 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 = 10; time.tv_usec = 0; len = read(fd,rcv_buf,data_len); // printf("I am right!(version1.2) len = %d fs_sel = %d\n",len,fs_sel); return len; //使用select实现串口的多路通信 /* 入口参数: ①:ndfs:select() 中监视的文件句柄,一般设为要监视的文件中的最大文件号加一。 ②:rdfds:select()监视的可读文件句柄集合,当rdfds映象的文件句柄状态变成可读时系统告诉select函数返回。这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的读变化; ③:wtfds: select()监视的可写文件句柄集合,当wtfds映象的文件句柄状态变成可写时系统告诉select函数返回。 如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写, 如果没有可写的文件,则根据timeout参数再判断是否超时, 若超出timeout的时间,select返回0,若发生错误返回负值, 可以传入NULL值,表示不关心任何文件的写变化。 ④:exfds:select()监视的异常文件句柄集合,当exfds映象的文件句柄上有特殊情况发生时系统会告诉select函数返回。 ⑤:timeout:select()的超时结束时间 */ /* FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。 FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。 FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。 FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。 */ // fs_sel = select(fd+1,&fs_read,NULL,NULL,&time); // // printf("fs_sel = %d\n",fs_sel); // if(fs_sel) // { // len = read(fd,rcv_buf,data_len); // // printf("I am right!(version1.2) len = %d fs_sel = %d\n",len,fs_sel); // return len; // } // else // { // // printf("Sorry,I am wrong!"); // return FALSE; // } } /******************************************************************** * 名称: serial_send * 功能: 发送数据 * 入口参数: fd :文件描述符 * send_buf :存放串口发送数据 * data_len :一帧数据的个数 * 出口参数: 正确返回为0,错误返回为-1 *******************************************************************/ int serial_send(int fd, char *send_buf,int data_len) { int len = 0; len = write(fd,send_buf,data_len); if (len == data_len ) { PRINT("send data is %s\n",send_buf); return TRUE; } else { tcflush(fd,TCOFLUSH); return FALSE; } }
测试demo
仅仅是修改中,read是阻塞型的,所以测试的话直接while(1)吧,先写了一路,没开多线程
#include "packet.h" #include "serial.h" #include<stdio.h> /*标准输入输出定义*/ #include<stdlib.h> /*标准函数库定义*/ #include<unistd.h> /*Unix 标准函数定义*/ #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> /*文件控制定义*/ #define __USE_MISC #include<termios.h> /*PPSIX 终端控制定义*/ #include<errno.h> /*错误号定义*/ #include<string.h> #include <sys/mman.h> #include <poll.h> #define FALSE -1 #define TRUE 0 #define UART_NUM 12 void Uart_Out32(unsigned int * Addr, unsigned int Value) { volatile unsigned int *LocalAddr = (volatile unsigned int *)Addr; *LocalAddr = Value; } unsigned int * Uart_In32(unsigned int * Addr) { return *(volatile unsigned int *) Addr; } uint8_t *uartdev[UART_NUM] = { "/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2", "/dev/ttyS3", "/dev/ttyS4", "/dev/ttyS5", "/dev/ttyS6", "/dev/ttyS7", "/dev/ttyS8", "/dev/ttyS9", "/dev/ttyS10", "/dev/ttyS11", }; int main(int argc, char **argv) { int fd; //文件描述符 int err; //返回调用函数的状态 int len; int i; char rcv_buf[0x100]; char send_buf[20]="HelloWorld"; fd = serial_open(uartdev[0]); //打开串口,返回文件描述符 err = serial_init(fd,614400,0,8,1,'N'); printf("Set Port Exactly!\n"); for(i = 0;i < 3;i++) { len = serial_send(fd,send_buf,10); if(len > 0) printf(" %d time send %d data successful\n",i,len); else printf("send data failed!\n"); sleep(2); } // serial_close(fd); while (1) //循环读取数据 { memset(rcv_buf,0,256); len = serial_recv(fd, rcv_buf,100); if(len > 0) { // rcv_buf[len] = '\0'; for(i = 0;i < len;i++) printf("receive data is 0x%x\n",rcv_buf[i]); printf("len = %d\n",len); } else { printf("cannot receive data\n"); } // sleep(2); } // serial_close(fd); }
简单好用,对于波特率的修改和进阶在补充篇讲吧