最近在玩DJI M100,调用API获取GPS位置时发现高程定位完全是错的(负的几百多米),查了一下文档说高程数据是由气压计得到的,而飞行控制时又需要比较可靠的高度信息,于是乎决定用上我们实验室的搭载Ublox芯片的板子搞事情,在子线程通过串口接收板子的定位结果,在主线程调用,开发环境为Ubuntu16.04/14.04,前者为虚拟机,后者为manifold。
Linux串口编程的步骤很简单,打开串口---串口初始化---读写串口---关闭串口
int open_port(int fd)
{
fd = open( "/dev/ttyUSB0", O_RDWR);
if (-1 == fd)
{
perror("Can't Open Serial Port ttyUSB0");
fd = open( "/dev/ttyUSB1", O_RDWR);
}
else
printf("open ttyUSB0 .....\n");
if(fcntl(fd, F_SETFL, 0)<0)
printf("fcntl failed!\n");
else
printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
if(isatty(STDIN_FILENO)==0)
printf("standard input is not a terminal device\n");
else
printf("isatty success!\n");
printf("fd-open=%d\n",fd);
return fd;
}
主要函数:
Open()
pathname:文件路径名,串口在Linux中被看做是一个文件
oflag:一些文件模式选择,有如下几个参数可以设置
· O_RDONLY只读模式
· O_WRONLY只写模式
· O_RDWR读写模式
上面三种模式在传参时必须传入其中一个,另外还有几个可供选择
· O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端。
· O_NONBLOCK设置为非阻塞模式(nonblocking mode)
· 等等。
Fcntl的使用参考http://www.cnblogs.com/lonelycatcher/archive/2011/12/22/2297349.html
Isatty,若为终端设备则返回1(真),否则返回0(假)。
设置串口,包括波特率,校验方式,停止位,数据位等等。详见代码。
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
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;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 5;
newtio.c_cc[VMIN] = 50;
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
printf("set done!\n");
return 0;
}
注意:newtio.c_cc[VTIME]= 5;newtio.c_cc[VMIN] = 50;貌似将串口读改成了阻塞模式。
参考http://blog.csdn.net/specialshoot/article/details/50707965
线程的操作主要步骤是创建线程,互斥锁和信号量来保证线程同步并保护数据。
线程创建函数
int temp = 100;
pthread_t thread;//定义线程
temp = pthread_create(&thread,NULL,Serialthread,NULL);
if(temp != 0)
{
printf("Create thread failed!");
return 0;
}
Serialthread为线程函数,线程在此处创建时即执行该函数。然后同时地执行该语句后的主线程部分,若主线程结束,则子线程也会跟着结束。因此子线程中需要一个死循环一直从串口读取GPS数据,同样的主线程中也应该通过循环调用串口读出来的数据。
同时如果在其他的源文件中也需要这个子线程中获取到的数据,只需要在源文件对应的头文件中声明extern(类型) (变量名)即可使用。
为保证数据在串口读取更新和在主函数中调用时不乱套,能够同步安全地进行,因此需要引入互斥锁和信号量。何为互斥锁,简单来说,就是在一个线程中访问公共数据(如这里的GPS位置)之前将数据锁住,使得其他线程不能再访问,访问操作结束之后再解锁是的其他线程可以访问或者操作。
主要函数包括pthread_mutex_init,初始化线程锁;pthread_mutex_lock进入线程锁;pthread_mutex_unlock,解锁。
何为信号量,在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。
主要函数包括sem_init,信号量初始化;sem_post,发送信号量;sem_wait,等待信号量。
这样的话,在子线程和主线程里对数据进行操作前先锁住,操作完再解锁,子线程读取数据之后发送信号量,主线程中对数据进行操作之前先等待信号量。
我这里用了DJI写好的两个类,用起来很方便。
DJI_lock::DJI_lock()
{
pthread_mutex_init( &m_lock, NULL );
}
DJI_lock::~DJI_lock()
{
}
void DJI_lock::enter()
{
pthread_mutex_lock( &m_lock );
}
void DJI_lock::leave()
{
pthread_mutex_unlock( &m_lock );
}
DJI_event::DJI_event()
{
sem_init( &m_sem, 0, 0 );
}
DJI_event::~DJI_event()
{
}
int DJI_event::set_event()
{
int ret = sem_post( &m_sem );
return ret;
}
int DJI_event::wait_event()
{
int ret = sem_wait( &m_sem );
return ret;
}
具体实现(包括DJI的两个类)可见demo(下载地址http://download.csdn.net/download/innocent_sheld/10226352),有问题欢迎讨论。
另外还要提的一个是线程操作的pthread_join函数,用来等待一个线程的结束。描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :thread:线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。失败,返回的则是错误号。下面是一个小demo。
thread_para son1 = {'k', 10};
thread_para son2 = {'o', 20};
void *sonthread(void *argv)
{
thread_para *son;
son = (thread_para *)argv;
for (int k = 0; k < son->count; k++)
{
fputc(son->character, stderr);
usleep(1000);
}
printf("\n");
return (void *)23333;
}
int main(int argc, char **argv)
{
pthread_t thread1;
pthread_create(&thread1, NULL, sonthread, (void *)&son1);
pthread_t thread2;
pthread_create(&thread2, NULL, sonthread, (void *)&son2);
int res1, res2;
pthread_join(thread1, (void *)&res1);
pthread_join(thread2, (void *)&res2);
printf("res1 = %d\n", res1);
printf("res2 = %d\n", res2);
return 0;
}
这个demo的下载地址http://download.csdn.net/download/innocent_sheld/10226323
在主线程中创建了两个线程,但调用的是同一个线程函数,不同的线程有不同的参数,线程创建时即运行线程函数,因此会依此打印o和k,在主线程中又调用了两次pthread_join函数,因此主线程要等待两个子线程结束才能执行后面的语句。注意:如果这里(void *)&提示错误的话error: invalid conversionfrom ‘void*’ to ‘void**’应该使用gcc编译,不能使用g++。大概是因为C++不会将这里的(void*)&解析为void**。
如果您觉得我的博客对您有所帮助,欢迎对我进行小额资助,以帮助我写出更多博文。:)