linux 下基于特定通信协议利用多线程同步通信机制实现的串口通信

时间:2021-10-04 16:14:31
</pre><pre name="code" class="cpp">/**
 *@Title:利用多线程同步通信机制实现串口通信
 *@Introduce:主要完成根据特定的通信协议实现串口与PC上特定串口
 *	通信软件的通信。测试版,只是完成主要框架,没有完全将协议的
 *	所有通信方式方法做完。
 *	其中包含的测试功能有:监听主机(PC上的软件)发送的特定请求,
 *	能够识别类型,并解析包含里面的信息,并且自动回复,所写的根
 *	据协议要求的固定消息结构体。
 *	实现原理:多线程,同步,通信。
 *	其中一个线程专门读取串口中从主机传递的信号,并通过识别出来	
 *	的长度(length),将一个完整的包截取下来,利用识别出来的类
 *	型(type)将包以字符串缓存形式发往特定的处理线程。
 *	特定的线程按操作分,有两类,一类主动发出请求信号等待主机回
 *	应;另一类,等待主机发送请求,然后根据指令回应相关信息
 *	说明:在这里,因为只是为了做功能测试,所以所有的解析并根据
 *	相应信息内容做处理的操作统一简化为将内容打印出来显示。
 *@Attention:主要难点代码中也有注释,这边稍微记录我的错误提醒
 *	在这个同步通信机制中,不同线程通过一个全局的指针list_head
 *	m_req_list作为机制,所有需要的线程中传递的结构体都挂在它下
 *	面,然后传递信息的结构体也有通用格式GeneralReqT,根据里面
 *	type参量确定不同类型,buffer存储需要传递的字符串信息。
 *@Explain:包含的文件名有:dev_com_main.c(主函数所在文件)
 *			   dev_com_main.h(主函数的头文件)
 *	list.h(特殊的list_head结构体的说明和相关函数宏的说明文件)
 *	crc16.c,crc16.h(CRC16校验码生成的函数体文件和头文件)
 *	linux下交叉编译试例:
 *  确保上述文件都放在当前文件夹下,输入:
 *  #gcc -o dev dev_com_main.c crc16.c -I ./ -lpthread
 *  交叉编译,更换gcc便好。
 *@Author:wanney	
 *@Date:2014-10-25
 *@e-Mail:wanney216@gmail.com
 *@长江不择细流,泰山不辞抔土。
 */

#include "dev_com_main.h"
#include <pthread.h>
#include <malloc.h> 
#define SHOWCHAR 1
#define SHOWHEX  0
#define HEADLOGO 0xaa55

//对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 
//或者调用pthread_mutex_init
static pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
//形成一个闭环,是当前结构体的前节点和后节点都指向本身。
static struct list_head m_req_list = {&m_req_list, &m_req_list};
//串口打开的初始化
//参数:串口设备的路径名 dev,设置整型的波特率(注意格式匹配)
//设备路径名出错会报错,波特率输出默认为:9600
//其他参数设默认值:数据位:8位,校验位:无,停止位:1位
int  uart_open (char *dev, int baud)
{
	struct termios tio;
    int fd;
	int baud_flags;

	fd = open(dev, O_RDWR);
	if(fd == -1) {
	//	fprintf(stdout, "open %s error!\n", dev);
		return -1;
	}

	if(tcgetattr(fd, &tio) < 0) {
	//	fprintf(stdout, "tcgetattr error!\n");
		close(fd);
		return -1;
	}

	switch (baud) {
	case 115200:
		baud_flags = B115200;
		break;
	case 57600:
		baud_flags = B57600;
		break;
	case 38400:
		baud_flags = B38400;
		break;
	case 19200:
		baud_flags = B19200;
		break;
	case 9600:
		baud_flags = B9600;
		break;
	case 2400:
		baud_flags = B2400;
		break;
	case 4800:
		baud_flags = B4800;
		break;
	default:
		baud_flags = B9600;
		break;
	}

	fcntl(fd, F_SETFL,O_NONBLOCK);    
	tio.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG | ECHOE);
	tio.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON | BRKINT);
	tio.c_iflag &= ~(CSIZE | PARENB | CSTOPB);
	tio.c_oflag &= ~(OPOST);
	tio.c_cflag = CS8 | baud_flags | CLOCAL | CREAD;
	tio.c_cc[VMIN] = 0;
	tio.c_cc[VTIME] = 0;

	tcflush(fd, TCIFLUSH);
	if(tcsetattr(fd, TCSAFLUSH, &tio) < 0) {
	//	fprintf(stdout,"tcsetattr error!\n");
		close(fd);
		return -1;
	}

	printf("open uart:%s, baud:%d", dev, baud);
	return fd;
}

//初始化:从机参数查询消息的应答消息
void initAnsCheckInfoC(unsigned char * buf,pAnsCheckInfoC p_ans_check_info){
		unsigned char buf_[26] ;
		WORD crc16;
		int i;
		memset(buf_,0x00,sizeof(buf_));		
		p_ans_check_info->infohead.logo = HEADLOGO;
    p_ans_check_info->infohead.type = 0x7006;
    p_ans_check_info->infohead.length = 0x0e;
    p_ans_check_info->infohead.flag = 0x00;
    p_ans_check_info->infohead.number = 0x1111;
    p_ans_check_info->infohead.version = 0x0001;
    memset(p_ans_check_info->ter_mac,0x00,sizeof(p_ans_check_info->ter_mac));
    p_ans_check_info->hb_period = 0x09;
    p_ans_check_info->repeat_times = 0x05;
    p_ans_check_info->timeover = 200;
    p_ans_check_info->maintain_info = 0x00;
    p_ans_check_info->reserve_1 = 0x00;
    p_ans_check_info->reserve_2 = 0x00;
    p_ans_check_info->reserve_3 = 0x00;
    //内存拷贝
    memcpy(buf,(unsigned char *)p_ans_check_info,sizeof(AnsCheckInfoC));
    //调整双字节或四字节高低位顺序
    word_buffer(buf,0,p_ans_check_info->infohead.logo);
    word_buffer(buf,2,p_ans_check_info->infohead.length);
    word_buffer(buf,4,p_ans_check_info->infohead.type);
    word_buffer(buf,6,p_ans_check_info->infohead.flag);
    word_buffer(buf,8,p_ans_check_info->infohead.number);
    word_buffer(buf,10,p_ans_check_info->infohead.version);
    word_buffer(buf,20,p_ans_check_info->timeover);
    //如何减去两个字节再排序?    
    for(i = 0;i<sizeof(buf_);i++){
    	buf_[i] = *(buf + i);	
    }
    //用于计算crc16校验码的值
    crc16 = CRC16(0x0000,buf_,sizeof(buf_));
    p_ans_check_info->check_code = crc16;
    word_buffer(buf,26,crc16);
}

//发送主机的从机参数查询消息的应答消息的线程
//参数:串口的文件操作符
void * answer_check_info(void * data){
	
	GeneralReqT checkInfo;
	AnsCheckInfoC answer;
	CheckInfoS request;
	int fd = *(int *)data;
	unsigned char * buf;
	while(1){	
		buf = (unsigned char *)malloc(sizeof(AnsCheckInfoC));
		memset(buf,0x00,sizeof(AnsCheckInfoC));
    if (pthread_cond_init (&checkInfo.cond, NULL) != 0) {
        printf ("set rtc fail");
        return;
    }
        debug_trace;;
#if 0    
    char sendinfo[] = "hello world!\n";
    char * psend = sendinfo;
    length = write (fd, psend, sizeof(sendinfo));
    printf("the length is %d \n",length);
#else
		checkInfo.type = 0x8005;
		//初始化数据request的数据
		initAnsCheckInfoC(buf,&answer);
    pthread_mutex_lock (&m_mutex);
    list_add (&checkInfo.list, &m_req_list);                               
    pthread_cond_wait (&checkInfo.cond, &m_mutex);
    //读线程已经得到主机返回信号,可以测试了。
  	//printf("the checkinfo.request is : \n");
		//print_frame((unsigned char *)&checkInfo.request,sizeof(CheckInfoS));
		checkinfocpy(&request, checkInfo.buffer, sizeof(CheckInfoS));
    printf("the checkinfo.request.infohead.type is %04x\n",request.infohead.type);
    printf("the checkinfo.request.infohead.logo is %04x\n",request.infohead.logo);
    printf("the checkinfo.request.infohead.length is %04x\n",request.infohead.length);
    printf("the checkinfo.request.infohead.flag is %04x\n",request.infohead.flag);
    checkInfo.time = 0;
    printf("the buffer is :\n");
    print_frame(buf,sizeof(AnsCheckInfoC));
    write (fd, buf, sizeof(AnsCheckInfoC));
    free(buf);
    pthread_mutex_unlock (&m_mutex);
    pthread_cond_destroy (&checkInfo.cond);
  }
#endif
}

//打印字符串中指定长度的内容,用二进制显示出来。
static void print_frame (unsigned char *frame,int size)
{
	int i;
	unsigned char *buf = frame;
	for (i=0; i<size;i++) {
		printf ("%02x ", buf[i]);
	}
	printf ("\r\n");
}

//初始化从机心跳信号结构体,并填写传进来的写缓存buf。
void initInfoHeartbeatC(unsigned char * buf,pInfoHeartbeatC p_info_heart_heat){
		unsigned char buf_[20] ;
		WORD crc16;
		int i;
		memset(buf_,0x00,sizeof(buf_));
		p_info_heart_heat->infohead.logo = 0xAA55;
    p_info_heart_heat->infohead.type = 0x7003;
    p_info_heart_heat->infohead.length = 0x0a;
    p_info_heart_heat->infohead.flag = 0x02;
    p_info_heart_heat->infohead.number = 0x1111;
    p_info_heart_heat->infohead.version = 0x0001;
    p_info_heart_heat->status = 0x00;
    p_info_heart_heat->reserve = 0x00;
    memset(p_info_heart_heat->ter_mac,0x00,sizeof(p_info_heart_heat->ter_mac));
		memcpy(buf,(unsigned char *)p_info_heart_heat,sizeof(InfoHeartbeatC));
		word_buffer(buf,0,p_info_heart_heat->infohead.logo);
    word_buffer(buf,2,p_info_heart_heat->infohead.length);
    word_buffer(buf,4,p_info_heart_heat->infohead.type);
    word_buffer(buf,6,p_info_heart_heat->infohead.flag);
    word_buffer(buf,8,p_info_heart_heat->infohead.number);
    word_buffer(buf,10,p_info_heart_heat->infohead.version);
    
    //如何减去两个字节再排序?
    for(i = 0;i<20;i++){
    	buf_[i] = *(buf + i);	
    }
    crc16 = CRC16(0x0000,buf_,20);
		p_info_heart_heat->check_code = crc16;
		word_buffer(buf,20,crc16);
}

//主机回应心跳信号的解析函数,功能:
//将缓存buf从大端传输的存储中转存到小端模式存储
//原理:将机构体中相应的双字节高低位交换一下
void infoHeartbeatcpy(pInfoHeartbeatS p_info_heart_heat, unsigned char *buf, int size)
{
	memcpy((unsigned char *)p_info_heart_heat,buf,size);
	p_info_heart_heat->infohead.logo	= word_to_int(*buf,*(buf+1));
	p_info_heart_heat->infohead.length	= word_to_int(*(buf+2),*(buf+3));
	p_info_heart_heat->infohead.type	= word_to_int(*(buf+4),*(buf+5));
	p_info_heart_heat->infohead.flag	= word_to_int(*(buf+6),*(buf+7));
	p_info_heart_heat->infohead.number	= word_to_int(*(buf+8),*(buf+9));
	p_info_heart_heat->infohead.version	= word_to_int(*(buf+10),*(buf+11));
	p_info_heart_heat->check_code = word_to_int(*(buf+20),*(buf+21));
}

//发送心跳消息的线程
//参数:串口文件操作符
void * request_heart_beat(void *data){
		GeneralReqT hebe;
		InfoHeartbeatC request;							
		InfoHeartbeatS answer;
		int length;
		int fd = *(int *)data;
		unsigned char * buf ;
		debug_trace;
	while(1){
		buf	= (unsigned char *)malloc(sizeof(InfoHeartbeatC));
		memset(buf,0x00,sizeof(InfoHeartbeatC));
    if (pthread_cond_init (&hebe.cond, NULL) != 0) {
        printf ("set rtc fail");
        return;
    }
#if 0    
    char sendinfo[] = "hello world!\n";
    char * psend = sendinfo;
    length = write (fd, psend, sizeof(sendinfo));
    printf("the length is %d \n",length);
#else
		//初始化数据request的数据
    //pthread_mutex_lock (&m_mutex);
    //pic_crack (&hebe.request);
	 	//初始化需要发送的数据
	 	initInfoHeartbeatC(buf,&request);
    write (fd, buf, sizeof(InfoHeartbeatC));
    printf("the buffer of heart beat is \n");
		print_frame (buf,sizeof(InfoHeartbeatC));
		hebe.type = 0x8002;
    list_add (&hebe.list, &m_req_list);
    //因为这个线程还没有放到等待队列上,所以调用pthread_cond_wait前
    //要先锁互斥量,即调用pthread_mutex_lock(),pthread_cond_wait在
    //把线程放进阻塞队列后,自动对mutex进行解锁,使得其它线程可以获
    //得加锁的权利。这样其它线程才能对临界资源进行访问并在适当的时
    //候唤醒这个阻塞的进程。当pthread_cond_wait返回的时候又自动给
    //mutex加锁。
    //实际上加解锁过程如下:
    /***************pthread_cond_wait()的使用方法*******************/
		//pthread_mutex_lock(&qlock); /*lock*/
		//pthread_cond_wait(&qready, &qlock); /*block-->unlock-->wait() return-->lock*/
		//pthread_mutex_unlock(&qlock); /*unlock*/
		/***************************************************************/                                                                               
    //pthread_cond_wait (&hebe.cond, &m_mutex);
    //读线程已经得到主机返回信号,可以测试了。
    printf("the answer from the client:\n");
		print_frame(hebe.buffer,sizeof(InfoHeartbeatS));
		//infoHeartbeatcpy(&answer, hebe.buffer,sizeof(InfoHeartbeatS));
		printf("the heart beat type is %04x",answer.infohead.type);
		printf("the heart beat logo is %04x",answer.infohead.logo);
    //pthread_mutex_unlock (&m_mutex);
    pthread_cond_destroy (&hebe.cond);
    sleep(1);
  }

#endif
}
//主机请求查询从机参数的信号解析函数,功能:
//将缓存buf从大端传输的存储中转存到小端模式存储
//原理:将机构体中相应的双字节高低位交换一下
void checkinfocpy(pCheckInfoS p_check_info, unsigned char *buf, int size)
{
	memcpy((unsigned char *)p_check_info,buf,size);
	p_check_info->infohead.logo	= word_to_int(*buf,*(buf+1));
	p_check_info->infohead.length	= word_to_int(*(buf+2),*(buf+3));
	p_check_info->infohead.type	= word_to_int(*(buf+4),*(buf+5));
	p_check_info->infohead.flag	= word_to_int(*(buf+6),*(buf+7));
	p_check_info->infohead.number	= word_to_int(*(buf+8),*(buf+9));
	p_check_info->infohead.version	= word_to_int(*(buf+10),*(buf+11));
	p_check_info->check_code = word_to_int(*(buf+18),*(buf+19));
	
}




//用来监听串口输入信息的线程
//主要功能:将所读出来的串口字节流识别出来
//并且根据它的类型将它们裁剪成一个个有效
//的消息体。
//参数:无。
//数据传输按照网络字节序,即大端模式传递字和双字
void *thread_read(void *data){
	//一直循环地监听com口是否有数据,是否符合消息头
	int * pfd = (int *)data;
	//printf("the pfd is %d\n",*pfd);
	unsigned char rbuf[64];
  unsigned char frame[64];
	fd_set readfds;
	struct timeval tv;
  int ret, i, wpos = 0;
  int FLAG = 0;//0:最初状态,1:第一次进来
  WORD length,type;
  struct list_head *pos, *n;
  //pheartbeatReqT p_wait;
  //pInfoHeartbeatS p_answer;
  pGeneralReqT p_wait;
  pCheckInfoS p_answer;

	tv.tv_sec = 0;
	tv.tv_usec = 100000; // 100ms
/*****************以上为初始化变量过程*****************/
//
	while (1)
	{
		FD_ZERO(&readfds);
		FD_SET(*pfd,&readfds);
		//select读串口的阻塞等待函数,
		//返回:1、数字0:times is error。
		//2、数字-1:表示连接出现异常返回。
		//3、default(ret > 0):状态则是正常连接执行
		//参数tv为链接超时时间,结构体:秒和微秒两位,NULL表示不限时间
		ret = select((*pfd)+1, &readfds, NULL, NULL, &tv);
		//不用判断其他的情况。如果操作的状态改变,这边就能察觉
		if (ret > 0) {
			//为多串口的操作监听函数操作,这句判断则表明如果是当前这个串口的
			//文件操作符有数据响应,并执行以下步骤,如果不是则退出本次循环!
      if(!FD_ISSET(*pfd,&readfds))
				continue;
			//读取串口数据到缓存区rbuf里面,最多一次读64个字节
	
			ret = read(*pfd, rbuf, sizeof(rbuf));
			if(ret > 0) {									//读取成功,并且ret表示读取到的字节数
        for (i=0; i<ret; i++) {			
#if 0					
					printf(" %x \n ",rbuf[i]);
#else
        //按位取出所有字节来分别进行以下操作
        //该过程为打包过程,将数据流按固定结构体格式打包好
          if (wpos == 0 ) {					
            if(rbuf[i] == 0xAA&&rbuf[i+1] == 0x55){
            		frame[wpos++] = rbuf[i];
            		frame[wpos++] = rbuf[i+1];
            		FLAG = 1;									//赋值一次,作为第一次进来的标志
            }
          }
          else {
          	//判断完标志为之后,将剩余的字节依次往缓存frame
          	if(FLAG == 1){
          		i++;//由于一次性判断了两位,所以,这边ret第一次进来是要加一!
          		FLAG = 0;           //第一次已经进来,消除该标志
          	}
          	//printf("the wpos is %d \n",wpos);
          	//获取长度信息。
            frame[wpos++] = rbuf[i];
            if(wpos > 6){
            	length = word_to_int(frame[2],frame[3]);
            	type = word_to_int(frame[4],frame[5]);
            	//printf("the answer length is %d \n",length);
            }
            if (wpos == length + 12) {	//判断是否长度已到目标结构体长度						
              wpos = 0;//这时因为若循环未结束则继续上述步骤,收取下一个包。
              //判断包类型,这里先假设为info_answer_hss(主机对从机通用应答类消息的回复)
              //将收到的包,强制转换为目标结构体格式
              //p_answer = (pCheckInfoS) frame;
              //printf("the answer is %x \n",(char *) frame);
              // 收到一个ok的包 
              //pic_uncrack (p_answer);			//将收到的包解码
              //遍历已有的列表
              printf("the received buffer is \n");
              print_frame(frame,length + 12);
              list_for_each (pos, &m_req_list) {
                p_wait = list_entry(pos,GeneralReqT,list);
                //判断所得的包名是否符合类型
                //printf("the request.infohead.type is %x\n",p_wait->type);
                //printf("p_answer->infohead.type is %x\n",type);
                if (p_wait->type == type) {
                	//将准备好的
                  memcpy (p_wait->buffer, frame, length + 12);
                  pthread_cond_signal (&p_wait->cond);
									list_del (pos);
                  break;
                }
              }
              pthread_mutex_unlock (&m_mutex);
            }
          }
#endif
        }
      }
		}

#if 0
	  // 超时处理
	  if (tv.tv_usec < 10000) {
	    tv.tv_sec = 0;
	    tv.tv_usec = 100000; // 100ms
	    pthread_mutex_lock (&m_mutex);			//线程同步互斥,锁
	    list_for_each_safe (pos, n, &m_req_list) {
	      p_wait = list_entry(pos,heartbeatReqT,list);
	      p_wait->time += 100;
	      p_wait->answer.cmd = 0;
	      if (p_wait->time > 1000) {
	        list_del (pos);
	        pthread_cond_signal (&p_wait->cond);//唤醒线程
	      }
	    }
	    pthread_mutex_unlock (&m_mutex);    //线程同步互斥,解锁
	  }	
#endif
	}
#if 0
  // 清理资源
  pthread_mutex_lock (&m_mutex);
  list_for_each_safe (pos, n, &m_req_list) {
    p_wait = list_entry(pos,heartbeatReqT,list);
    p_wait->answer.cmd = 0;
    list_del (pos);
    pthread_cond_signal (&p_wait->cond);
  }
  pthread_mutex_unlock (&m_mutex);
	return NULL;
#endif
}


//双字节的字节交换顺序。网络传输默认为大端模式,
//本地存储目前为小端存储和运算。
WORD word_to_int(BYTE one,BYTE two){
	WORD tmp = (WORD)one<<8;    //右移一个字节,需要移动八位
	tmp = tmp + (WORD)two;
	return tmp;
}
//将小端存储的双字节按大端方式填入缓存buf中
//功能:根据需要替换的双字节在缓存中的偏移位置,将其高低位互换
//buf:需要更换顺序的buf缓存,
//offset:双字节在缓存中的偏移位置
//word:该爽字节需要替换成的值
void 	word_buffer(unsigned char * buf,int offset,WORD word){
	BYTE tmp1 = word&0x00ff ;			//(低八位)
	BYTE tmp2 = word >> 8;				//(高八位)
	*(buf + offset) = tmp2;
	*(buf + offset + 1) = tmp1;	
}



int main(){
	int fd;
	pthread_t read_com_t,check_info_com_t,heart_beat_com_t;		
	//初始化串口
	fd = uart_open("/dev/ttyS1",57600);
	if(fd == -1){
		printf("open the com and set it.it's failed!");
		return -1;
	}
	printf("the path = \"/dev/ttyS1\"\n");
	printf("the paud = 57600 bps \n");
	//printf("the fd is %d",fd);
#if 1
	if(pthread_create(&read_com_t,NULL,(void *)thread_read,(void *)&fd) == -1){
		printf("Create the thread of read error!\n");
		return -1;
	}
	if(pthread_create(&check_info_com_t,NULL,(void *)answer_check_info,(void *)&fd) == -1){
		printf("Create the thread of write error!\n");
		return -1;
	}
	if(pthread_create(&heart_beat_com_t,NULL,(void *)request_heart_beat,(void *)&fd) == -1){
		printf("Create the thread of write error!\n");
		return -1;
	}
#endif
	//等待 线程结束
	pthread_join(read_com_t, NULL);
  pthread_join(check_info_com_t, NULL);
  pthread_join(heart_beat_com_t, NULL);
	return 0;
}
由于注释上面已经说得比较清楚了,所以希望大家能看到代码,主要流程如上面所述,关键内容还是在于线程通信的框架

如备注里面所说的pthread_cond_wait的用法,是关键。

其次便是那个所谓的list_head的结构体宏。这边我打算另起一问,专门介绍这种list的使用方法和技巧!

源代码下载:  http://download.csdn.net/detail/wanney216/8079915


转载请说明:http://blog.csdn.net/wanney216/article/details/40450847