0、前言
网友提问如下:


本地进程之间 pipe shm msg 消息队列, sem
两个pc之间 socket /unix
raw 套接字:
BSD socket unix -> bill joy bsd分支,
汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:
下位机,通过串口与上位机相连;
下位机要能够接收上位机下发的命令,并解析这些命令;
下位机能够根据这些命令配置对应的外设、读取对应的传感器的数据上传到上位机;
主程序串口操作模块:通过串口下发命令或者读取下位机上传的数据信息;
主程序网络通信模块:接收远程服务器下发的命令,并将下位机采集的数据上传到服务器。
整体看来,这个相当于是一个小的项目了,内容难度都比较大,下面我们会分为几篇独立的文章来讲解。
本篇只讨论如何给下位机编写一个简单的上位机。
一、环境简介
1. 软硬件环境
下位机:CC2530 OS:vmware + ubuntu
在这里彭老师采用的是CC2530,读者也可以采用其他的板子,我们只需要该板子有串口,可以和PC通信,同时板子上有可设置的led灯、继电器以及可以采集数据的传感器即可。
2. 硬件连接图
硬件连接图如下:

该款CC2530已经集成了CH340芯片,usb线连接电脑,即可被识别。
3. pc下识别串口
如果该串口被PC获取,名字为COMn【n为某整数】。

windows下串口
4. ubuntu下识别串口
首先需要vmware抓取串口【串口在同一时刻要么被windows抓取要么被vmware抓取】,按下图所示,点击连接即可:

虚拟机抓取串口
但是往往ubuntu中没有ch340的驱动,经过实际测试,ubuntu14及之前的版本都没有这个驱动,ubuntu16以上的版本有这个驱动。
如果没有ch340驱动可以用以下方法安装对应的驱动:
make
sudomakeload
ls/dev/ttyUSB0

ubuntu安装串口驱动
按照上述步骤,会生成设备文件**/dev/ttyUSB0**。
ls/dev/ttyUSB0-l
crw-rw----1rootdialout188,0Jan1505:45/dev/ttyUSB0
c : 字符设备 rw-rw---- :文件操作权限
188, 0 :主次设备号
3、4节提到的usb转串口驱动和linux下驱动源码后台【GH】回复 ch340 即可获得

【注意】如果是其他开发板,自行安装其他的串口驱动。
二、模块设计
上位机和下位机的通信往往都是通过串口,linux下往往生成字符设备ttyUSB0【有的是ttyS0】,操作串口设备就只需要操作该字符设备即可。
下面我们设计上下位机的软件模块。
1. 信令
设计上位机,首先需要设计上位机下发给下位机的指令格式,上位机按照该指令格式发送命令给下位机,下位需严格按照该指令格式进行解析指令。

含义如下:
- device:要操作的设备
- data :对应的设备及其额外的数据
- CRC :校验码
- # :信令终止符
信令格式可以根据需要扩展或者精简。
其中device定义如下【可以根据实际情况进行扩展】:
#defineDEV_ID_LED_ON0X1
#defineDEV_ID_LED_OFF0X2
#defineDEV_ID_DELAY0X3
#defineDEV_ID_GAS0X4
【注意】为便于理解,我们暂不考虑效率问题。
2. 上传数据
下位机需要采集传感器的数据并通过串口上传,数据结构定义如下:
structdata{
unsignedchardevice;
unsignedcharcrc;
unsignedshortdata;
};
- device 设备
- data 采集的数据
- crc 校验码
3. 功能模块
现在就可以开始设计软件的各个功能模块了。
下位机

下位机流程图
下位主要任务就是循环接收上位机通过串口下发的数据,然后解析该指令内容,操作对应的硬件。
上位机

上位机
上位机主要任务是打印菜单,由用户针对菜单做出选择,然后按照指令格式封装命令,并通过串口将该命令下发给下位机。
三、 下位机功能函数
cc2530的操作原理,本文不讨论,如果是其他开发板,只需要修改串口操作函数。
1. LED初始化
/****************************************************************************
*名称:InitLed()
*功能:设置LED灯相应的IO口
*入口参数:无
*出口参数:无
****************************************************************************/
voidInitLed(void)
{
P1DIR|=0x01;//P1.0定义为输出口
LED1=0;
}
2. 初始化UART
/****************************************************************
*名称:InitUart()
*功能:串口初始化函数
*入口参数:无
*出口参数:无
*****************************************************************/
voidInitUart(void)
{
PERCFG=0x00;//外设控制寄存器USART0的IO位置:0为P0口位置1
P0SEL=0x0c;//P0_2,P0_3用作串口(外设功能)
P2DIR&=~0xC0;//P0优先作为UART0
U0CSR|=0x80;//设置为UART方式
U0GCR|=11;
U0BAUD|=216;//波特率设为115200
UTX0IF=0;//UART0TX中断标志初始置位0
U0CSR|=0x40;//允许接收
IEN0|=0x84;//开总中断允许接收中断
}
3. 串口发送函数
/**********************************************************************
*名称:UartSendString()
*功能:串口发送函数
*入口参数:Data:发送缓冲区len:发送长度
*出口参数:无
***********************************************************************/
voidUartSendString(char*Data,intlen)
{
uinti;
for(i=0;i<len;i++)
{
U0DBUF=*Data++;
while(UTX0IF==0);
UTX0IF=0;
}
}
4. 串口中断处理函数
/**********************************************************************
*名称:UART0_ISR(void)串口中断处理函数
*描述:当串口0产生接收中断,将收到的数据保存在RxBuf中
**********************************************************************/
#pragmavector=URX0_VECTOR
__interruptvoidUART0_ISR(void)
{
URX0IF=0;//清中断标志
RxBuf=U0DBUF;
}
5. 烟雾传感器数据读取
/****************************************************************
*名称:myApp_ReadGasLevel()
*功能:烟雾传感器数据读取
*入口参数:无
*出口参数:无
*****************************************************************/
uint16myApp_ReadGasLevel(void)
{
uint16reading=0;
/*Enablechannel*/
ADCCFG|=0x80;
/*writingtothisregisterstartstheextraconversion*/
ADCCON3=0x87;
/*Waitfortheconversiontobedone*/
while(!(ADCCON1&0x80));
/*Disablechannelafterdoneconversion*/
ADCCFG&=(0x80^0xFF);
/*Readtheresult*/
reading=ADCH;
reading|=(int16)(ADCH<<8);
reading>>=8;
return(reading);
}
6. LED灯控制函数
/****************************************************************
*名称:led_opt()
*功能:LED灯控制函数
*入口参数:RxData:接收到的指令flage:led的操作,点亮或者关闭
*出口参数:无
*****************************************************************/
voidled_opt(charRxData[],unsignedcharflage)
{
switch(RxData[1])
{
case1:
LED1=(flage==DEV_ID_LED_ON)?ON:OFF;
break;
/*TBDforled2led3*/
default:
break;
}
return;
}
7. 主程序
/****************************************************************************
*主程序入口函数
****************************************************************************/
voidmain(void)
{
CLKCONCMD&=~0x40;//设置系统时钟源为32MHZ晶振
while(CLKCONSTA&0x40);//等待晶振稳定为32M
CLKCONCMD&=~0x47;//设置系统主时钟频率为32MHZ
InitLed();//设置LED灯相应的IO口
InitUart();//串口初始化函数
UartState=UART0_RX;//串口0默认处于接收模式
memset(RxData,0,SIZE);
while(1)
{
//接收状态
if(UartState==UART0_RX)
{//读取数据,遇到字符'#'或者缓冲区字符数量超过4就设置UartState为CONTROL_DEV状态
if(RxBuf!=0)
{
//以'#'为结束符,一次最多接收4个字符
if((RxBuf!='#')&&(count<4))
{
RxData[count++]=RxBuf;
}
else
{
//判断数据合法性,防止溢出
if(count>=4)
{
//计数清0
count=0;
//清空接收缓冲区
memset(RxData,0,SIZE);
}
else{
//进入发送状态
UartState=CONTROL_DEV;
}
}
RxBuf=0;
}
}
//控制控制外设状态
if(UartState==CONTROL_DEV)
{
//判断接收的数据合法性
//RxData[]:|device|data|crc|#|
//check_crc:crc=device^data
//if(RxData[2]==(RxData[0]^RxData[1]))
{
switch(RxData[0])
{
caseDEV_ID_LED_ON:
led_opt(RxData,DEV_ID_LED_ON);
break;
caseDEV_ID_LED_OFF:
led_opt(RxData,DEV_ID_LED_OFF);
break;
caseDEV_ID_DELAY:
break;
caseDEV_ID_GAS:
send_gas();
break;
default:
break;
}
}
UartState=UART0_RX;
count=0;
//清空接收缓冲区
memset(RxData,0,SIZE);
}
}
}
四、 上位机功能函数
结构体
#defineDEV_ID_LED_ON0X1
#defineDEV_ID_LED_OFF0X2
#defineDEV_ID_DELAY0X3
#defineDEV_ID_GAS0X4
structdata{
unsignedchardevice;
unsignedcharcrc;
unsignedshortdata;
};
函数
voiduart_init(void)
{
intnset1,nset2;
serial_fd=open("/dev/ttyUSB0",O_RDWR);
if(serial_fd==-1)
{
printf("open()error\n");
exit(1);
}
nset1=set_opt(serial_fd,115200,8,'N',1);
if(nset2==-1)
{
printf("set_opt()error\n");
exit(1);
}
}
intMenu()
{
intoption;
system("clear");
printf("\n\t\t************************************************\n");
printf("\n\t\t**ALARMSYSTERM**\n");
printf("\n\t\t**1----LED**\n");
printf("\n\t\t**2----GAS**\n");
printf("\n\t\t**0----EXIT**\n");
printf("\n\t\t************************************************\n");
while(1)
{
printf("Pleasechoosewhatyouwant:");
scanf("%d",&option);
if(option<0||option>2)
printf("\t\tchooseerror!\n");
else
break;
}
returnoption;
}
//RxData[]:|device|data|crc|#|
voidled()
{
intlednum=0;
intonoff;
charcmd[4];
//选择led灯
while(1)
{
printf("inputlednumber:[12]\n#");
scanf("%d",&lednum);
//check
if(lednum<1||lednum>2)
{
printf("invalidlednumber\n");
system("clear");
continue;
}else{
break;
}
}
printf("operation:1on,0off\n");
scanf("%d",&onoff);
if(onoff==1)
{
cmd[0]=DEV_ID_LED_ON;
}elseif(onoff==0)
{
cmd[0]=DEV_ID_LED_OFF;
}else{
printf("invalidlednumber\n");
return;
}
cmd[1]=lednum;
//fulfillcrcarea
cmd[2]=cmd[0]^cmd[1];
cmd[3]='#';//表示结束符
tcflush(serial_fd,TCIOFLUSH);
inti=0;
for(i=0;i<4;i++)
{
printf("%d",cmd[i]);
}
printf("\n");
write(serial_fd,&cmd,sizeof(cmd));
sleep(1);
}
//RxData[]:|device|data|crc|#|
voidgas()
{
intlen;
unsignedshortGasLevel;
structdatamsg;
chargas[4]={0};
charcmd[4];
cmd[0]=DEV_ID_GAS;
cmd[3]='#';//表示结束符
write(serial_fd,&cmd,sizeof(cmd));
sleep(1);
len=read(serial_fd,&msg,sizeof(structdata));
//转换读取的gas数据格式
GasLevel=msg.data;
gas[0]=GasLevel/100+'0';
gas[1]=GasLevel/10%10+'0';
gas[2]=GasLevel%10+'0';
printf("%s\n",gas);
getchar();
}
voidrun()
{
intx;
while(1)
{
x=Menu();
switch(x)
{
case1:
led();
break;
case2:
gas();
break;
case0:
printf("\n\t\texit!\n\n");
close(serial_fd);
exit(0);
default:
fg=1;
break;
}
if(fg)
break;
}
}
intmain()
{
uart_init();
run();
return0;
}
五、 运行结果
1. 上位机运行界面

主菜单
2. 点亮led灯
点亮led1:

3. 灭灯

熄灭led1
4. 读取烟雾传感器数据

获取烟雾数据
烟雾的数据是079,可以点根华子,你会发现每次读取的值都是在变化。
OK!至此为止,一个简易的CC2530上位机我们就编写完毕,如果想将从串口获取的数据的值发送到远端服务器,后续文章我们将继续讨论。
原文地址:https://mp.weixin.qq.com/s/4abx3fahrHW3d264paP3mA








发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。