配色: 字号:
串口编程
2020-08-14 | 阅:  转:  |  分享 
  
JZ2440开发板的3个串口,对应设备文件(字符设备):

/dev/s3c2410_serial0c204,64

/dev/s3c2410_serial1c204,65

/dev/s3c2410_serial2c204,66



一、打开串口

在Linux系统下,打开串口是通过使用标准的文件打开函数操作的。

#include

/以读写的方式打开/

intfd=open("/dev/s3c2410_serial1",O_RDWR);



二、设置串口

所有对串口的操作都是通过结构体structtermios和几个函数实现的。



串口配置相关的函数:

tcgetattr//获取属性

tcsetattr//设置属性

cfgetispeed//得到输入速度

cfsetispeed//设置输入速度

cfgetospeed//得到输出速度

cfsetospedd//设置输出速度

tcdrain//等待所有输出都被传输

tcflow//挂起传输或接收

tcflush//刷清未决输入和输出

tcsendbreak//送break字符

tcgetpgrp//得到前台进程组ID

tcsetpgrp//设置前台进程组ID





tcgetattr(0,&oldstdio);//获取默认的配置选项存储到oldstdio结构体中

tcgetattr(fd,&oldstdio);//获取当前配置选项存储到oldstdio结构体中

tcsetattr(fd,TCSANOW,&oldstdio);//TCSANOW修改立即生效

cfgetispeed(&oldstdio);//得到波特率

cfsetispeed(&oldstdio,B115200)//设置波特率为115200



structtermios结构体:

structtermios

{

tcflag_tc_iflag;//输入选项

tcflag_tc_oflag;//输出选项

tcflag_tc_cflag;//控制选项

tcflag_tc_lflag;//行选项

cc_tc_cc[NCCS];//控制字符

};



其中我们更关注的是c_cflag控制选项。其中包含了波特率、数据位、校验位、停止位的设置。

它可以支持很多常量名称其中设置数据传输率为相应的数据传输率前要加上“B”。

c_cflag成员不能直接对其初始化,而要将其通过与、或操作使用其中的某些选项。

设置串口属性主要是配置termios结构体中的各个变量,大致流程如下:



1.使用函数tcgetattr保存原串口属性

structtermiosnewtio,oldtio;

tcgetattr(fd,&oldtio);



2.通过位掩码的方式激活本地连接和接受使能选项:CLOCAL和CREAD

newtio.c_cflag|=CLOCAL|CREAD;



3.使用函数cfsetispeed和cfsetospeed设置数据传输率

cfsetispeed(&newtio,B115200);

cfsetospeed(&newtio,B115200);



4.通过位掩码设置字符大小。

newtio.c_cflag&=~CSIZE;

newtio.c_cflag|=CS8;



5.设置奇偶效验位需要用到两个termios中的成员:c_cflag和c_iflag。首先要激活c_cflag中的校验位使能标志PARENB和是否进行奇偶效验,同时还要激活c_iflag中的奇偶效验使能。

设置奇校验:

newtio.c_cflag|=PARENB;

newtio.c_cflag|=PARODD;

newtio.c_iflag|=(INPCK|ISTRIP);

设置偶校验:

newtio.c_iflag|=(INPCK|ISTRIP);

newtio.c_cflag|=PARENB;

newtio.c_cflag|=~PARODD;



6.激活c_cflag中的CSTOPB设置停止位。若停止位为1,则清除CSTOPB;若停止位为0,则激活CSTOPB。

newtio.c_cflag&=~CSTOPB;



7.设置最少字符和等待时间。在对接收字符和等待时间没有特别要求的情况下,可以将其设置为0。

newtio.c_cc[VTIME]=0;

newtio.c_cc[VMIN]=0;



8.调用函数”tcflush(fd,queue_selector)”来处理要写入引用的对象,queue_selector可能的取值有以下几种。

TCIFLUSH:刷新收到的数据但是不读

TCOFLUSH:刷新写入的数据但是不传送

TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送。



9.激活配置。在完成配置后,需要激活配置使其生效。使用tcsetattr()函数。

inttcsetattr(intfiledes,intopt,conststructtermiostermptr);

配置完毕之后,即可使用read或open来操作串口的发送与接收。





三、串口的读写

与普通文件一样,使用read,write函数进行串口的读写。

示例:read(fd,buff,8);

write(fd,buff,8);



需要注意的是,在串口没有收到数据的时候,执行read函数会发生阻塞。

借助于POLL/SELECT机制,可以实现非阻塞的串口读取操作。

select函数说明:

intselect(intmaxfdp,fd_setreadset,fd_setwriteset,fd_setexceptset,structtimevaltimeout);

参数说明:

maxfdp:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的大值大1,因为文件描述符是从0开始计数的;

readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的文件描述符集合。

timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout==NULL表示等待无限长的时间

select使用范例:

当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:

fd_setrset;

intfd;

FD_ZERO(&rset);

FD_SET(fd,&rset);

FD_SET(stdin,&rset);

然后调用select函数,拥塞等待文件描述符事件的到来;如果超过设定的时间,则不再等待,继续往下执行。

select(fd+1,&rset,NULL,NULL,NULL);

select返回后,用FD_ISSET测试给定位是否置位:

if(FD_ISSET(fd,&rset)

{

...

//dosomething

}



四、串口的关闭

使用tcsetattr()函数恢复原来的串口配置。

tcsetattr(fd,TCSANOW,&oldtio); //恢复原来的串口配置

然后关闭串口。

close(fd);



五、JZ2440串口编程实例

有了上面的基础知识,下面开始编写一个jz2440开发板读取GPS模块的程序,

硬件连接:

JZ2440的串口0--------终端

JZ2440的串口1--------GPS(用宿主机上的AccessPort来模拟GPS接收机模块,NMEA协议)

JZ2440网线-----------PC宿主机Ubuntu,采用NFS





软件部分,包括:gps.h,gps.c,serial.c和Makefile四个文件。





GPS.h头文件:

#ifndef_GPS_H_

#define_GPS_H_



#defineBAUDRATE B9600

#defineCOM2 "/dev/s3c2410_serial1"

#defineENDMINITERM 27 /ESCtoquitminiterm/

#defineFALSE 0

#defineTRUE 1



typedefstruct{

intyear; //年

intmonth; //月

intday; //日

inthour; //时

intminute; //分

intsecond; //秒

}date_time; //日期和时间结构体



typedefstruct{

date_timeD; //日期和时间

charstatus; //接收状态

doublelatitude; //纬度

doublelongitude; //经度

charNS; //南北半球

charEW; //东西半球

doublespeed; //速度

doublehigh; //海拔高度

}GPS_INFO; //GPS信息结构体





//全局变量声明

externvolatileint STOP;

externvolatileint fd;

externGPS_INFO gps_info;

extern char GPS_BUF[1024];





//函数声明

voidreceive(voiddata);

voidkeyboard(voiddata);



voidgps_parse(charline,GPS_INFOGPS);

voidshow_gps(GPS_INFOGPS);



#endif





GPS.C源文件:

//gps.c文件主要实现2个线程,

//1个用来处理用户的按键输入,当发现ESC键输入时,STOP全局变量生效,退出程序

//另1个用来处理串口的数据接收,解析指定格式的GPS数据,解析成功的话,同时将其显示出来



#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include"gps.h"



//接收到GPS数据中第num个信息的开始位置

staticintGetComma(intnum,charstr)

{

inti,j=0;

intlen=strlen(str);

for(i=0;i
{

if(str[i]=='','')j++;

if(j==num)returni+1;

}

return0;

}





//把字符串的参数转换成浮点数进行返回

staticdoubleget_double_number(chars)

{

charbuf[128];

inti;

doublerev;

i=GetComma(1,s);

strncpy(buf,s,i);

buf[i]=0;

rev=atof(buf);

returnrev;

}



//时间格式转换,UTC的时间需要+北京东八区(8小时)

staticvoidUTC2BTC(date_timeGPS)

{

GPS->second++;

if(GPS->second>59)

{

GPS->second=0;

GPS->minute++;

if(GPS->minute>59)

{

GPS->minute=0;

GPS->hour++;

}

}

GPS->hour+=8;

if(GPS->hour>23)

{

GPS->hour-=24;

GPS->day+=1;

if(GPS->month==2||GPS->month==4||GPS->month==6||GPS->month==9||GPS->month==11)

{

if(GPS->day>30)

{

GPS->day=1;

GPS->month++;

}

}else

{

if(GPS->day>31)

{

GPS->day=1;

GPS->month++;

}

}

if(GPS->year%4==0) //闰年

{

if(GPS->day>29&&GPS->month==2)

{

GPS->day=1;

GPS->month++;

}

}else //非闰年

{

if(GPS->day>28&&GPS->month==2)

{

GPS->day=1;

GPS->month++;

}

}

if(GPS->month>12)

{

GPS->month-=12;

GPS->year++;

}

}

}



//解析GPS_BUF中的GPS数据信息,如果解析指定格式的数据成功,同时将其显示出来

voidgps_parse(charline,GPS_INFOGPS)

{

inttmp;

charc;

charbuf=line;

c=buf[5];buf[5]=0;

if((strcmp(buf,"$GPRM")==0||strcmp(buf,"$GPGG")==0)) //只解析GPRMC或者GPGGA格式的数据

{

buf[5]=c;

if(c==''C'') //GPRMC格式

{

printf("GPRMCfounded!\r\n");

GPS->D.hour=(buf[7]-''0'')10+(buf[8]-''0'');

GPS->D.minute=(buf[9]-''0'')10+(buf[10]-''0'');

GPS->D.second=(buf[11]-''0'')10+(buf[12]-''0'');

tmp=GetComma(9,buf);

GPS->D.day=(buf[tmp+0]-''0'')10+(buf[tmp+1]-''0'');

GPS->D.month=(buf[tmp+2]-''0'')10+(buf[tmp+3]-''0'');

GPS->D.year=(buf[tmp+4]-''0'')10+(buf[tmp+5]-''0'')+2000;

GPS->status=buf[GetComma(2,buf)];

GPS->latitude=get_double_number(&buf[GetComma(3,buf)]);

GPS->NS=buf[GetComma(4,buf)];

GPS->longitude=get_double_number(&buf[GetComma(5,buf)]);

GPS->EW=buf[GetComma(6,buf)];

}

if(c==''A'') //GPGGA格式

{

printf("GPGGAfounded!\r\n");

GPS->D.hour=(buf[7]-''0'')10+(buf[8]-''0'');

GPS->D.minute=(buf[9]-''0'')10+(buf[10]-''0'');

GPS->D.second=(buf[11]-''0'')10+(buf[12]-''0'');

tmp=GetComma(9,buf);

GPS->latitude=get_double_number(&buf[GetComma(2,buf)]);

GPS->NS=buf[GetComma(3,buf)];

GPS->longitude=get_double_number(&buf[GetComma(4,buf)]);

GPS->EW=buf[GetComma(5,buf)];

GPS->high=get_double_number(&buf[GetComma(9,buf)]);

}

UTC2BTC(&GPS->D);

if(c==''C''||c==''A'')show_gps(&gps_info); //显示解析指定格式成功的GPS数据信息

}

}



//显示出来解析成功的GPS数据

voidshow_gps(GPS_INFOGPS)

{

printf("DATE:\033[0;32m%d%02d%02d\033[0m\n",GPS->D.year,GPS->D.month,GPS->D.day);

printf("TIME:\033[0;32m%02d\033[5m:\033[0m\033[0;32m%02d\033[0m\033[0;32m%02d\033[0m\n",GPS->D.hour,GPS->D.minute,GPS->D.second);

printf("HIGH:\033[0;35m%10.4f\033[0m\n",GPS->high);

printf("LATITUDE-NS:\033[0;35m%10.4f%c\033[0m\n",GPS->latitude,GPS->NS);

printf("LONGTITUDE-EW:\033[0;35m%10.4f%c\033[0m\n",GPS->longitude,GPS->EW);

printf("STATUS:\033[1;33m%c\033[0m\n\n",GPS->status);

}



//串口接收处理线程

voidreceive(voiddata)

{

inti=0,retval;

charc;

charbuf[1024];

fd_setrfds;



/

structtimevaltv;

tv.tv_sec=5;

tv.tv_usec=0;

/



printf("readmodem\r\n");

while(STOP==FALSE)

{

//通过select机制,获取串口数据,read的时候不阻塞

FD_ZERO(&rfds);

FD_SET(fd,&rfds);

retval=select(fd+1,&rfds,NULL,NULL,NULL/&tv/); //最后一个参数为NULL的时候,进程的CPU占用率最低

if(retval==-1)

{

printf("selecterror\r\n");

break;

}

elseif(retval) //retval>0,表明串口接收有数据

{

read(fd,&c,1); //读取串口接收的数据,每次读取1个字节

buf[i++]=c;

if(c==''\n'')

{

strncpy(GPS_BUF,buf,i); //将串口中收到的一行信息copy到GPS_BUF中,等待下一步的处理

i=0;

gps_parse(GPS_BUF,&gps_info); //解析GPS_BUF中的GPS数据信息,如果解析指定格式的数据成功,同时将其显示出来

}

}

if(STOP)break;

}

printf("exitfromreadingmodem\r\n");

returnNULL;

}



//按键处理线程

voidkeyboard(voiddata)

{

intc;

for(;;)

{

if((c=getchar())==ENDMINITERM) //用户输入ESC键,退出

{

STOP=TRUE;

printf("ESC-KeyPressed!\r\n");

break;

}

usleep(100000);

}

returnNULL;

}





serial.c源文件(主函数):

//serial.c主文件,主要完成串口的打开,配置,创建2个线程,关闭串口的工作

#include

#include

#include

#include

#include

#include

#include

#include

#include"gps.h"



volatileintSTOP=FALSE;

volatileintfd;

GPS_INFO gps_info;

char GPS_BUF[1024];

staticint baud=BAUDRATE;



intmain(intargc,charargv)

{

structtermiosoldtio,newtio,oldstdtio,newstdtio;

pthread_tth_a,th_b,th_show;

fd=open(COM2,O_RDWR); //open函数里面并没有声明非阻塞的方式

if(fd<0)

{

perror(COM2);

exit(-1);

}

if(argc<2)

printf("Defaultbaudrateis9600bps.\r\n");

else

{

if(argc!=2)

{

printf("Error,Pleaseusecommand:serialB4800[B9600...].\r\n");

close(fd);

exit(0);

}

//通过命令行参数,设置串口波特率

if(strcmp(argv[1],"B4800")==0)baud=B4800;

elseif(strcmp(argv[1],"B9600")==0)baud=B9600;

elseif(strcmp(argv[1],"B19200")==0)baud=B19200;

elseif(strcmp(argv[1],"B38400")==0)baud=B38400;

elseif(strcmp(argv[1],"B57600")==0)baud=B57600;

elseif(strcmp(argv[1],"B115200")==0)baud=B115200;

printf("serialbaudrateis%sbps.\r\n",&argv[1][1]);

}

tcgetattr(0,&oldstdtio);

tcgetattr(fd,&oldtio); //获取原串口配置

tcgetattr(fd,&newstdtio); //获得正在工作的stdtio

newtio.c_cflag=baud|CRTSCTS|CS8|CLOCAL|CREAD; //配置控制flag,使用CTS/RTS流控

newtio.c_iflag=IGNPAR; //设置输入flag

newtio.c_oflag=0;

newtio.c_lflag=0;

newtio.c_cc[VMIN]=1;

newtio.c_cc[VTIME]=0;

//处理未接受的数据并激活其配置

tcflush(fd,TCIFLUSH);

tcsetattr(fd,TCSANOW,&newtio);

//创建线程:

pthread_create(&th_a,NULL,keyboard,0); //按键处理线程

pthread_create(&th_b,NULL,receive,0); //串口接收处理线程



while(!STOP)

usleep(100000);

tcsetattr(fd,TCSANOW,&oldtio); //恢复原来的串口配置

tcsetattr(0,TCSANOW,&oldstdtio); //恢复原tty的设置

close(fd);

exit(0);

}







Makefile文件:

serial:serial.ogps.o

arm-linux-gcc-oserialserial.ogps.o-pthread



serial.o:serial.c

arm-linux-gcc-oserial.o-cserial.c-pthread



gps.o:gps.c

arm-linux-gcc-ogps.o-cgps.c



clean:

rm.oserial



献花(0)
+1
(本文系摘摘摘丿丿...首藏)