</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