了解率口通信概念
熟悉Linux下的串口应用开发,
了解线程概念及其应用程序结构
熟悉Linux下的串口程序调试方法
串行通信接口简介
串行接口简称串口,也称串行通信接口(通常指COM接口),是采用串行通信方式的扩展接口。
串口通信的两种最基本的方式:同步串行通信方式和异步串行通信方式。
1.同步串行是指SPI(Serial Peripheral interface)的缩写,顾名思义就是串行外围设备接口。SPI总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。
2.异步串行是指UART (Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART包含TTL电平的串口和RS232电平的串口。TTL电平是3.3V的(单片机),而RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平,通常PC机串口与单片机串口通信需要电平转换芯片如MAX232。
串行通信接口简介
串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。这三个标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
RS-232-C也称标准串口,是目前最常用的一种串行通讯接口。它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。
传统的RS-232-C接口标准有22根线,采用标准25芯D型插头座。自BMPC/AT开始使用简化了的9芯D型插座。至今25芯插头座现代应用中已经很少采用。RS-232最大传输距离为15米,最高通信速率为20kb/s。
串行通信的基本参数
串行端口的通信方式是将字节拆分成一个接一个的位再传输出去,接到此电位信号的一方再将此一个一个的位组合成原来的字符,如此形成一个字节的完整传输,在数据传输时,应在通信端口的初始化时设置几个通信参数。
1)波特率,通俗的讲就是传送数据的速度,不过这里的“数据”是数据位数。波特率的意思就是在一秒中可以传输的数据位数,单位是bps。如果采用波特率4800bps进行传输,那么每秒可以传输480个byte。
2)数据位,当接收设备收到起始位后,紧接着就会收到数据位,数据位的个数可以是5、6、7或者8位。在字符数据传输的过程中,数据位从最低有效位开始传输。
3)起始位,在串口线上,没有数据传输时处于逻辑“1”状态,当发送设备要发送一个字符数据时,首先发出一个逻辑“0”信号,这个逻辑低电平就是起始位。起始位通过通信线传像接收设备,当接收设备检测到这个逻辑低电平后,就开始准备接收数据位,因此起始位所起的作用就是告诉接收方字符传输的开始。
4)停止位,在奇偶校验位或者数据位(无奇偶校验位时)就是停止位,它可以是1位、1.5位或者2位,停止位是一个字符数据的结束标志。
5)奇偶校验位,数据位发送完之后,就可以发送奇偶校验位。奇偶校验用于有限差错校验,通信双方在通信时约定一致的奇偶校验方式。就数据传输而言,奇偶校验位是沉余位,但它表示数据的一种性质,这种性质用于检错,虽然有限,很容易现。
Linux串口
*在Linux中,设备文件一般都位于/dev下,其中串口一、串ロ二对应的设备文件依次为/dev/ttySo, /dev/ttyS1,可以查看/dev下的文件以确认。
一般而言,串口通信的过程如下:
1、打开串口设备文件,获取设备句柄fd。
2、设置串口参数,如波特率、数据位、奇偶校验、停止位等等。
3、利用select函数等待串口设备是否可读写(有数据)。
4、应用read、write函数读写串口。
Linux串口权限
·要注意的是,而普通用户一般不能直接访问设备文件,这就导致一般用户编写的串口程序在执行的时候可能会遭遇访问拒绝而无法运行的情况。所以在需要运行串口程序的时候,用户通常可以通过以下几种方法获取执行权限:
改变设备文件的访问权限设置,如串口程序需要访问串口2,那么可以先用如下命令改变/dev/ttyS1设备文件的执行权限,然后就可以照常执行串口程序了。不过该方法仅单次有效,系统重启就无效了。
sudo chmod 666/dev/ttyS1·
以root超级用户的身份运行程序,例如串口程序名为testserial,输入以下命令以root权限运行程序:
sudo/testserial ·
将当前用户添加到Linux系统的dialout用户组当中,这样当前用户就拥有了操作硬件设备的权限,而且该方法一直有效,不用重复设置。例如当前用户名为fish,在终端中输入以下命令添加用户到dialout用户组中:
sudo gpasswd -a fish dialout 或 sudo usermod-aG dialout fish
Linux线程介绍
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。
POSIX thread 简称为pthread,这是一个POSIX标准线程。Pthreads定义了一套C程序语言类型、函数与常量,它以pthread.h头文件和一个线程库实现。
使用pthread需要设置工程的编译选项中的链接库添加pthread库,方法为:
在codeblocks中,右键点击工程名字,选择Build options项,弹出窗口中,选择Linkersettings页,在link libraries栏下方点击Add按钮,填入pthread,点击确定保存。
linuc串口流程
串口接口简称串口,也称串行通信接口(通常为COM接口),串行接口( Serial Interface)是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线可实现双向通信,根据通信的方向可分为单工、半双工和全双工三种。在Linux 下标准的串口节点名为 /dev/ttyS* ,如果是USB转串口,则为/dev/ttyUSB*:
在串口编程中有一个很重要的结构体——struct termios,通过该结构体我们可对串口的属性(输入输出)进行控制:
struct termios
{
tcflag_t c_iflag; //输入模式标志
tcflag_t c_oflag; //输出模式标志
tcflag_t c_cflag; //控制模式标志
tcflag_t c_lflag; //本地模式标志
cc_t c_cc[NCCS]; //控制字符
}
其中tcflag_t定义为:
typedef unsigned int tcflag_t
在该结构体中c_cflag输入模式标志最为重要,可设置波特率、数据位、校验位、停止位。设置波特率需要在前加上B(B9600,B115200),然后对需要设置的位进行"与","或"操作,其中标志所代表的意义如下:
位成员 | 代表含义 |
---|---|
CBAUD | 波特率掩码位 |
EXTA | 外部时钟 |
EXTB | 外部时钟 |
CSIZE | 数据位掩码位 |
CSTOPB | 2位停止位 |
CREAD | 接收使能 |
PARENB | 奇偶校验位使能 |
PARODD | 使用奇校验 |
CLOCAL | 忽略终端状态行 |
CRTSCTS | 硬件流控制使能位 |
通常情况下CLOCAL和CREAD两个选项总是被打开,这两个选项可保证你的程序不会变成端口的所有者,而端口所有者需要去处理发散性控制和挂断信号,同时还保证串行接口驱动会读取输入的数据字节。
位成员 | 代表含义 |
---|---|
IGNBRK | 忽略BREAK键输入 |
BRKINT | 如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断 |
IGNPAR | 忽略奇偶校验错误 |
PARMRK | 标识奇偶校验错误 |
INPCK | 允许输入奇偶校验 |
ISTRIP | 去除字符的第8个比特 |
INLCR | 将输入的NL(换行)转换成CR(回车) |
IGNCR | 忽略输入的回车 |
ICRNL | 将输入的回车转化成换行(如果IGNCR未设置的情况下) |
IUCLC | 将输入的大写字符转换成小写字符(非POSIX) |
IXON | 允许输入时对XON/XOFF流进行控制 |
IXANY | 输入任何字符将重启停止的输出 |
IXOFF | 允许输入时对XON/XOFF流进行控制 |
IMAXBEL | 当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置 |
位成员 | 代表含义 |
---|---|
OPOST | 处理后输出 |
OLCUC | 将输入的小写字符转换成大写字符(非POSIX) |
ONLCR | 将输入的NL(换行)转换成CR(回车)及NL(换行) |
OCRNL | 将输入的CR(回车)转换成NL(换行) |
ONOCR | 第一行不输出回车符 |
ONLRET | 不输出回车符 |
OFILL | 发送填充字符以延迟终端输出 |
OFDEL | 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX) |
NLDLY | 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s) |
CRDLY | 回车延迟,取值范围为:CR0、CR1、CR2和 CR3 |
TABDLY | 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3 |
BSDLY | 空格输出延迟,可以取BS0或BS1 |
VTDLY | 垂直制表符输出延迟,可以取VT0或VT1 |
FFDLY | 换页延迟,可以取FF0或FF1 |
位成员 | 代表含义 |
---|---|
ISIG | 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号 |
ICANON | 使用标准输入模式 |
ECHO | 显示输入字符 |
ECHOE | 如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词 |
ECHOK | 如果ICANON同时设置,KILL将删除当前行 |
ECHONL | 如果ICANON同时设置,即使ECHO没有设置依然显示换行符 |
ECHOPRT | 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX) |
TOSTOP | 向后台输出发送SIGTTOU信号 |
comport->fd = open(comport->dev_name,O_RDWR|O_NOCTTY|O_NDELAY) ;
openz函数除了普通参数外还有O_NOCTTY|O_NDELAY:
判断是否打开成功、它设置file status flag为零、判断是否为终端机。
tcgetattr()
参数:
int fd:打开串口文件后,获取到的文件描述符;
struct termios &termios_p: termios 类型的结构体,包含在
功能:获取对应文件描述符相应串口的原始属性,保存在第二个参数中,通常获取串口需要对原始信息进行备份,在程序退出前需要修改回来,一边继续使用串口;
返回值:成功返回0,失败返回-1。
tcsetattr()
参数:
int fd: 要设置属性的文件描述符
int optional_actions: 设置属性时,可以控制属性生效的时刻,optional_actions可以取下面几个值:
TCSANOW: 立即生效;
TCADRAIN: 改变在所有写入fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变);
TCSAFLUSH :改变在所有写入fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。
*termios termios_p: 用来设置的串口属性的结构体指针,对串口的termios配置好后,传入函数即可。
功能:设置终端参数的函数;
返回值:成功返回0,失败返回-1。
b、cfsetispeed() 与 cfsetospeed()设置波特率
//函数原型
static void set_baudrate (struct termios *opt, unsigned int baudrate)
{
cfsetispeed(opt, baudrate);
cfsetospeed(opt, baudrate);
}
参数:
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,19200, 9600, 4800, 2400, 1200, 300, };
/* 设置波特率 */
int set_baudrate(struct termios *opt,int baudrate)
{
int i;
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if(baudrate == name_arr[i])
{
if(cfsetispeed(opt,baudrate) < 0)
{
printf("cfsetispeed failure:%s\n", strerror(errno));
return -1;
}
if(cfsetospeed(opt,baudrate) < 0)
{
printf("cfsetospeed failure:%s\n", strerror(errno));
return -2;
}
}
}
return 0;
}
/* 设置数据位 */
void set_databit(struct termios *opt,int databit)
{
opt->c_cflag |= (CLOCAL|CREAD ); //cLOCAL本地连接模式,CREAD开启串行数据接收
opt->c_cflag &= ~CSIZE; //字符长度,取值范围为CS5、CS6、CS7或CS8
switch(databit){
case 5:
opt->c_cflag |= CS5;
break;
case 6:
opt->c_cflag |= CS6;
break;
case 7:
opt->c_cflag |= CS6;
break;
case 8:
opt->c_cflag |= CS8;
break;
default:
opt->c_cflag |= CS8;
break;
}
}
/* 设置校验位 */
void set_parity(struct termios *opt,char parity)
{
switch(parity){
case 'n':
case 'N':
opt->c_cflag &= ~PARENB; //使用奇偶校验
break;
case 'e':
case 'E':
opt->c_cflag |= PARENB;
opt->c_cflag &= ~PARODD; //对输入使用奇偶校验,对输出使用偶校验
opt->c_cflag |= (INPCK | ISTRIP); //允许输入奇偶校验|去除字符第8比特
break;
case 'o':
case 'O':
opt->c_cflag |= (PARENB | PARODD); //使用输出奇偶校验
opt->c_cflag |= (INPCK | ISTRIP); // 允许输入奇偶校验|去除字符第8比特
break;
default:
opt->c_cflag &= ~PARENB; //默认使用奇偶校验
break;
}
}
/* 设置停止位 */
void set_stopbit(struct termios *opt, int stopbit)
{
switch(stopbit)
{
case 2:
opt->c_cflag |= CSTOPB; //设置两个停止位
break;
default:
opt->c_cflag &= ~CSTOPB; //停止位为1,则要清楚CSTOPB
break;
}
}
对于接收字符和等待时间没有特别要求时可设为0:
//串口超时设置
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
写数据使用write系统调用,成功时会返回写入的字节数,失败时会返回-1,例如:
rv = write(fd, buf, buf_size);
if (rv < 0)
printf("write data failed!\n", strerror(errno));
读串口数据我们需要考虑到一个问题就是timeout超时问题,当端口在raw data mode操作模式下,那么read系统调用将返回从串口输入缓冲区中实际得到的字节数,如果没有数据可读,那么该系统调用将会被阻塞(block)直到有数据为止,如果超过一定时间仍然没有数据可读,那么将返回一个错误(读错误)。
rv = read(comport->fd, buf, buf_size);
if(rv < 0)
{
printf("Read data from dev failure:%s\n", strerror(errno));
return rv;
}
我们也可通过fcntl()
函数使read()
没有数据可读的情况下立即返回而不是被阻塞:
fcntl(fd, F_SETFL, FNDELAY);
//FNDELAY选项在read无数据可读立即返回0,实际调用如下:
fcntl(fd, F_SETFL, 0);
关闭串口就是关闭该串口下的文件描述符:
close(comport->fd);
整段源码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void *serial_lsd(void *d) //线程函数,串口输出流水灯
{
int i = 0;
int tfd = *(int *)d;
if (tfd > 0)
{
while (1)
{
char buf[20], str[] = "0000000000";
if (i < 10)
{
str[i] = '*';
}
else
{
str[19 - i] = '*';
i %= 20;
}
sprintf(buf, "%s\r\n", str);
write(tfd, buf, strlen(buf));
usleep(100000); // 延时100ms
}
}
return 0;
}
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
/* 五个参量 fd打开文件 speed设置波特率 bit数据位设置 nevent奇偶校验位 stop停止位 */
struct termios newtio, oldtio; // 串口配置结构体
/*
struct termios
{
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_cc[NCCS];
};
上面五个结构成员名称分别代表:
c_iflag:输入模式
c_oflag:输出模式
c_cflag:控制模式
c_lflag:本地模式
c_cc[NCCS]:特殊控制模式
*/
if (tcgetattr(fd, &oldtio) != 0) //获取当前设置
/*
tcgetattr可以初始化一个终端对应的termios结构,tcgetattr函数原型如下:
#include
int tcgetattr(int fd, struct termios *termios_p);
这个函数调用把低昂前终端接口变量的值写入termios_p参数指向的结构。如果这些值其后被修改了,可以通过调用函数tcsetattr来重新配置。
tcsetattr函数原型如下:
#include
int tcsetattr(int fd , int actions , const struct termios *termios_h);
参数actions控制修改方式,共有三种修改方式,如下所示:
TCSANOW:立刻对值进行修改
TCSADRAIN:等当前的输出完成后再对值进行修改
TCSAFLUSH:等当前的输出完成之后,再对值进行修改,但丢弃还未从read调用返回的当前的可用的任何输入。
在我们的代码中,我们设置为NOW立即对值进行修改。
*/
{
perror("SetupSerial 1");
printf("tcgetattr( fd,&oldtio) -> %d\n", tcgetattr(fd, &oldtio));
return -1;
}
bzero(&newtio, sizeof(newtio));
/*步骤一,设置字符大小*/
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE; //屏蔽数据位
/*
c_cflag代表控制模式
CLOCAL含义为忽略所有调制解调器的状态行,这个目的是为了保证程序不会占用串口。
CREAD代表启用字符接收器,目的是是的能够从串口中读取输入的数据。
CS5/6/7/8表示发送或接收字符时使用5/6/7/8比特。
CSTOPB表示每个字符使用两位停止位。
HUPCL表示关闭时挂断调制解调器。
PARENB:启用奇偶校验码的生成和检测功能。
PARODD:只使用奇校验而不使用偶校验。
*/
/*设置停止位*/
switch (nBits)
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8; // 数据位为 8
break;
}
/*设置奇偶校验位*/
switch (nEvent)
{
case 'O': //奇数
newtio.c_cflag |= PARENB; //有校验
newtio.c_cflag |= PARODD; // 奇校验
newtio.c_iflag |= (INPCK | ISTRIP);
/*
c_iflag代表输入模式
BRKINT:当在输入行中检测到一个终止状态时,产生一个中断。
TGNBRK:忽略输入行中的终止状态。
TCRNL:将接受到的回车符转换为新行符。
TGNCR:忽略接受到的新行符。
INLCR:将接受到的新行符转换为回车符。
IGNPAR:忽略奇偶校检错误的字符。
INPCK:对接收到的字符执行奇偶校检。
PARMRK:对奇偶校检错误作出标记。
ISTRIP:将所有接收的字符裁减为7比特。
IXOFF:对输入启用软件流控。
IXON:对输出启用软件流控。
*/
break;
case 'E': //偶数
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB; //有校验
newtio.c_cflag &= ~PARODD; // 偶校验
/*
c_cc特殊的控制字符
标准模式和非标准模式下,c_cc数组的下标有不同的值:
标准模式:
VEOF:EOF字符
VEOL:EOF字符
VERASE:ERASE字符
VINTR:INTR字符
VKILL:KILL字符
VQUIT:QUIT字符
VSTART:START字符
VSTOP:STOP字符
非标准模式:
VINTR:INTR字符
VMIN:MIN值
VQUIT:QUIT字符
VSUSP:SUSP字符
VTIME:TIME值
VSTART:START字符
VSTOP:STOP字符
*/
break;
case 'N': //无奇偶校验位
newtio.c_cflag &= ~PARENB; // 无校验
break;
}
/*设置波特率*/
switch (nSpeed)
{
case 2400:
cfsetispeed(&newtio, B2400);
/*
cfsetispeed和cfsetospeed用来设置输入输出的波特率,函数模型如下:
int cfsetispeed(struct termios *termptr, speed_t speed);
int cfsetospeed(struct termios *termptr, speed_t speed);
参数说明:
struct termios *termptr:指向termios结构的指针
speed_t speed:需要设置的波特率
返回值:成功返回0,否则返回-1
*/
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
/*设置停止位*/
if (nStop == 1)
newtio.c_cflag &= ~CSTOPB; // 一位停止位, 两位停止位 |= CSTOPB
else if (nStop == 2)
newtio.c_cflag |= CSTOPB; //两位停止位
/*设置等待时间和最小接收字符*/
newtio.c_cc[VTIME] = 0; // 等待时间,单位百毫秒 (读)
newtio.c_cc[VMIN] = 0; // 等待时间,单位百毫秒 (读)
/*处理未接收字符*/
tcflush(fd, TCIFLUSH);
/*
tcflush用于清空终端为完成的输入/输出请求及数据,它的函数原型如下:
int tcflush(int fd, int queue_selector);
其中queue_selector时控制tcflush的操作,取值可以为如下参数中的一个:
TCIFLUSH清除正收到的数据,且不会读出来;TCOFLUSH清除正写入的数据,且不会发送至终端;TCIOFLUSH清除所有正在发送的I/O数据。
/*激活新配置*/
if ((tcsetattr(fd, TCSANOW, &newtio)) != 0)
{
perror("com set error");
return -1;
}
/*
tcsetattr函数原型如下:
#include
int tcsetattr(int fd , int actions , const struct termios *termios_h);
参数actions控制修改方式,共有三种修改方式,如下所示:
TCSANOW:立刻对值进行修改
TCSADRAIN:等当前的输出完成后再对值进行修改
TCSAFLUSH:等当前的输出完成之后,再对值进行修改,但丢弃还未从read调用返回的当前的可用的任何输入。
在我们的代码中,我们设置为NOW立即对值进行修改。
*/
printf("set done!\n");
return 0;
}
int open_port(int fd)
{
/* fd 打开串口 comport表示第几个串口 */
fd = open("/dev/ttyS7", O_RDWR | O_NOCTTY | O_NDELAY);
// O_RDWR 读写方式打开
// O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端。
// O_NDELAY 非阻塞(默认为阻塞,打开后也可以使用fcntl()重新设置)
if (-1 == fd)
{
perror("Can't Open Serial Port");
return (-1);
}
else
printf("open ttyS7 .....\n"); //板子的端口设置为S7
if (fcntl(fd, F_SETFL, 0) < 0)
// fd:文件描述符
//设置:fcntl(fd, F_SETFL, FNDELAY); //非阻塞
// fcntl(fd, F_SETFL, 0); // 阻塞
printf("fcntl failed!\n");
else
printf("fcntl=%d\n", fcntl(fd, F_SETFL, 0));
if (isatty(STDIN_FILENO) == 0)
// isatty()函数:判断文件描述词是否是为终端机,如果为终端机则返回1, 否则返回0。
// STDIN_FILENO:接收键盘的输入
// STDOUT_FILENO:向屏幕输出
printf("standard input is not a terminal device\n");
else
printf("isatty success!\n");
printf("fd-open=%d\n", fd);
return fd;
}
int main(void)
{
int fd;
int nread, i;
char buff[] = "Hello\n";
char *b = buff;
int j;
fd_set rd;
if ((fd = open_port(fd)) < 0)
{
perror("open_port error");
return 1;
}
if ((i = set_opt(fd, 9600, 8, 'N', 1)) < 0) //设置需要与串口调试器保持一致
{
perror("set_opt error");
return 1;
}
pthread_t id;
pthread_create(&id, NULL, serial_lsd, &fd);
FD_ZERO(&rd);
FD_SET(fd, &rd);
while (FD_ISSET(fd, &rd))
{
if (select(fd + 1, &rd, NULL, NULL, NULL) < 0)
// select函数做I/O复用,判断所监测的文件描述符有没有准备好,如果没问题返回0.
{
perror("select");
}
else
{
memset(buff, 0, 128);
while ((nread = read(fd, buff, 128)) > 0)
{
printf("nread=%d,%s\n", nread, buff); //板子设置的打印参数与串口调试器一致
// for (j = 0; j < strlen(buff); j++)
// {
// printf("0x%02x\n", buff[j]);
// }
for (j = 0; j < nread; j++)
{
printf("0x%02x\n", buff[j]);
}
// while (*b != '\0')
// {
// printf("0x%02x\n", *b);
// b++;
// }
//一个线程打印奇数一个线程打印偶数
write(fd, buff, strlen(buff)); //回送接收数据
}
}
}
close(fd);
return 1;
}