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