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驱动可以用以下方法安装对应的驱动:
- 1 make
- 2 sudo make load
- 3 ls /dev/ttyUSB0
ubuntu安装串口驱动
按照上述步骤,会生成设备文件**/dev/ttyUSB0**。
- ls /dev/ttyUSB0 -l
- crw-rw---- 1 root dialout 188, 0 Jan 15 05:45 /dev/ttyUSB0
c : 字符设备 rw-rw---- :文件操作权限
188, 0 :主次设备号
3、4节提到的usb转串口驱动和linux下驱动源码后台【GH】回复 ch340 即可获得
【注意】如果是其他开发板,自行安装其他的串口驱动。
二、模块设计
上位机和下位机的通信往往都是通过串口,linux下往往生成字符设备ttyUSB0【有的是ttyS0】,操作串口设备就只需要操作该字符设备即可。
下面我们设计上下位机的软件模块。
1. 信令
设计上位机,首先需要设计上位机下发给下位机的指令格式,上位机按照该指令格式发送命令给下位机,下位需严格按照该指令格式进行解析指令。
含义如下:
- device:要操作的设备
- data :对应的设备及其额外的数据
- CRC :校验码
- # :信令终止符
信令格式可以根据需要扩展或者精简。
其中device定义如下【可以根据实际情况进行扩展】:
- #define DEV_ID_LED_ON 0X1
- #define DEV_ID_LED_OFF 0X2
- #define DEV_ID_DELAY 0X3
- #define DEV_ID_GAS 0X4
【注意】为便于理解,我们暂不考虑效率问题。
2. 上传数据
下位机需要采集传感器的数据并通过串口上传,数据结构定义如下:
- struct data{
- unsigned char device;
- unsigned char crc;
- unsigned short data;
- };
- device 设备
- data 采集的数据
- crc 校验码
3. 功能模块
现在就可以开始设计软件的各个功能模块了。
下位机
下位机流程图
下位主要任务就是循环接收上位机通过串口下发的数据,然后解析该指令内容,操作对应的硬件。
上位机
上位机
上位机主要任务是打印菜单,由用户针对菜单做出选择,然后按照指令格式封装命令,并通过串口将该命令下发给下位机。
三、 下位机功能函数
cc2530的操作原理,本文不讨论,如果是其他开发板,只需要修改串口操作函数。
1. LED初始化
- /****************************************************************************
- * 名 称: InitLed()
- * 功 能: 设置LED灯相应的IO口
- * 入口参数: 无
- * 出口参数: 无
- ****************************************************************************/
- void InitLed(void)
- {
- P1DIR |= 0x01; //P1.0定义为输出口
- LED1 = 0;
- }
2. 初始化UART
- /****************************************************************
- * 名 称: InitUart()
- * 功 能: 串口初始化函数
- * 入口参数: 无
- * 出口参数: 无
- *****************************************************************/
- void InitUart(void)
- {
- PERCFG = 0x00; //外设控制寄存器 USART 0的IO位置:0为P0口位置1
- P0SEL = 0x0c; //P0_2,P0_3用作串口(外设功能)
- P2DIR &= ~0xC0; //P0优先作为UART0
- U0CSR |= 0x80; //设置为UART方式
- U0GCR |= 11;
- U0BAUD |= 216; //波特率设为115200
- UTX0IF = 0; //UART0 TX中断标志初始置位0
- U0CSR |= 0x40; //允许接收
- IEN0 |= 0x84; //开总中断允许接收中断
- }
3. 串口发送函数
- /**********************************************************************
- * 名 称: UartSendString()
- * 功 能: 串口发送函数
- * 入口参数: Data:发送缓冲区 len:发送长度
- * 出口参数: 无
- ***********************************************************************/
- void UartSendString(char *Data, int len)
- {
- uint i;
- for(i=0; i<len; i++)
- {
- U0DBUF = *Data++;
- while(UTX0IF == 0);
- UTX0IF = 0;
- }
- }
4. 串口中断处理函数
- /**********************************************************************
- * 名 称: UART0_ISR(void) 串口中断处理函数
- * 描 述: 当串口0产生接收中断,将收到的数据保存在RxBuf中
- **********************************************************************/
- #pragma vector = URX0_VECTOR
- __interrupt void UART0_ISR(void)
- {
- URX0IF = 0; // 清中断标志
- RxBuf = U0DBUF;
- }
5. 烟雾传感器数据读取
- /****************************************************************
- * 名 称: myApp_ReadGasLevel()
- * 功 能: 烟雾传感器数据读取
- * 入口参数: 无
- * 出口参数: 无
- *****************************************************************/
- uint16 myApp_ReadGasLevel( void )
- {
- uint16 reading = 0;
- /* Enable channel */
- ADCCFG |= 0x80;
- /* writing to this register starts the extra conversion */
- ADCCON3 = 0x87;
- /* Wait for the conversion to be done */
- while (!(ADCCON1 & 0x80));
- /* Disable channel after done conversion */
- ADCCFG &= (0x80 ^ 0xFF);
- /* Read the result */
- reading = ADCH;
- reading |= (int16) (ADCH << 8);
- reading >>= 8;
- return (reading);
- }
6. LED灯控制函数
- /****************************************************************
- * 名 称: led_opt()
- * 功 能: LED灯控制函数
- * 入口参数: RxData:接收到的指令 flage:led的操作,点亮或者关闭
- * 出口参数: 无
- *****************************************************************/
- void led_opt(char RxData[],unsigned char flage)
- {
- switch(RxData[1])
- {
- case 1:
- LED1 = (flage==DEV_ID_LED_ON)?ON:OFF;
- break;
- /* TBD for led2 led3*/
- default:
- break;
- }
- return;
- }
7. 主程序
- /****************************************************************************
- * 主程序入口函数
- ****************************************************************************/
- void main(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])
- {
- case DEV_ID_LED_ON :
- led_opt(RxData,DEV_ID_LED_ON);
- break;
- case DEV_ID_LED_OFF:
- led_opt(RxData,DEV_ID_LED_OFF);
- break;
- case DEV_ID_DELAY:
- break;
- case DEV_ID_GAS:
- send_gas();
- break;
- default:
- break;
- }
- }
- UartState = UART0_RX;
- count = 0;
- //清空接收缓冲区
- memset(RxData, 0, SIZE);
- }
- }
- }
四、 上位机功能函数
结构体
- #define DEV_ID_LED_ON 0X1
- #define DEV_ID_LED_OFF 0X2
- #define DEV_ID_DELAY 0X3
- #define DEV_ID_GAS 0X4
- struct data{
- unsigned char device;
- unsigned char crc;
- unsigned short data;
- };
函数
- void uart_init(void )
- {
- int nset1,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);
- }
- }
- int Menu()
- {
- int option;
- system("clear");
- printf("\n\t\t************************************************\n");
- printf("\n\t\t** ALARM SYSTERM **\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("Please choose what you want: ");
- scanf("%d",&option);
- if(option<0||option>2)
- printf("\t\t choose error!\n");
- else
- break;
- }
- return option;
- }
- // RxData[]: | device | data |crc | # |
- void led()
- {
- int lednum = 0;
- int onoff;
- char cmd[4];
- //选择led灯
- while(1)
- {
- printf("input led number :[1 2]\n#");
- scanf("%d",&lednum);
- //check
- if(lednum<1 || lednum >2)
- {
- printf("invalid led number\n");
- system("clear");
- continue;
- }else{
- break;
- }
- }
- printf("operation: 1 on , 0 off\n");
- scanf("%d",&onoff);
- if(onoff == 1)
- {
- cmd[0] = DEV_ID_LED_ON;
- }else if(onoff == 0)
- {
- cmd[0] = DEV_ID_LED_OFF;
- }else{
- printf("invalid led number\n");
- return;
- }
- cmd[1] = lednum;
- //fulfill crc area
- cmd[2] = cmd[0]^cmd[1];
- cmd[3] = '#';//表示结束符
- tcflush(serial_fd, TCIOFLUSH);
- int i = 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 | # |
- void gas()
- {
- int len ;
- unsigned short GasLevel;
- struct data msg;
- char gas[4]={0};
- char cmd[4];
- cmd[0] = DEV_ID_GAS;
- cmd[3] = '#';//表示结束符
- write(serial_fd,&cmd,sizeof(cmd));
- sleep(1);
- len = read(serial_fd,&msg,sizeof(struct data));
- //转换读取的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();
- }
- void run()
- {
- int x;
- while(1)
- {
- x=Menu();
- switch(x)
- {
- case 1:
- led();
- break;
- case 2:
- gas();
- break;
- case 0:
- printf("\n\t\t exit!\n\n");
- close(serial_fd);
- exit(0);
- default:
- fg=1;
- break;
- }
- if(fg)
- break;
- }
- }
- int main()
- {
- uart_init();
- run();
- return 0;
- }
五、 运行结果
1. 上位机运行界面
主菜单
2. 点亮led灯
点亮led1:
3. 灭灯
熄灭led1
4. 读取烟雾传感器数据
获取烟雾数据
烟雾的数据是079,可以点根华子,你会发现每次读取的值都是在变化。
OK!至此为止,一个简易的CC2530上位机我们就编写完毕,如果想将从串口获取的数据的值发送到远端服务器,后续文章我们将继续讨论。