您的位置:首页 > 运维架构 > Linux

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

2014-10-25 10:57 956 查看
</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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐