网友提问如下:
粉丝提问
项目框架 汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块: 下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命令配置对应的外设、读取对应的传感器的数据上传到上位机;主程序串口操作模块:通过串口下发命令或者读取下位机上传的数据信息;主程序网络通信模块:接收远程服务器下发的命令,并将下位机采集的数据上传到服务器。 整体看来,这个相当于是一个小的项目了,内容难度都比较大,下面我们会分为几篇独立的文章来讲解。 本篇只讨论如何给下位机编写一个简单的上位机。 下位机:CC2530 OS:vmware + ubuntu 在这里彭老师采用的是CC2530,读者也可以采用其他的板子,我们只需要该板子有串口,可以和PC通信,同时板子上有可设置的led灯、继电器以及可以采集数据的传感器即可。 硬件连接图如下:
物理连接图 该款CC2530已经集成了CH340芯片,usb线连接电脑,即可被识别。 如果该串口被PC获取,名字为COMn【n为某整数】。
windows下串口 首先需要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】,操作串口设备就只需要操作该字符设备即可。 下面我们设计上下位机的软件模块。 设计上位机,首先需要设计上位机下发给下位机的指令格式,上位机按照该指令格式发送命令给下位机,下位需严格按照该指令格式进行解析指令。
信令格式 含义如下: 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 【注意】 为便于理解,我们暂不考虑效率问题。 下位机需要采集传感器的数据并通过串口上传,数据结构定义如下: struct data{ unsigned char device; unsigned char crc; unsigned short data; }; device 设备data 采集的数据crc 校验码 现在就可以开始设计软件的各个功能模块了。
下位机流程图 下位主要任务就是循环接收上位机通过串口下发的数据,然后解析该指令内容,操作对应的硬件。
上位机 上位机主要任务是打印菜单,由用户针对菜单做出选择,然后按照指令格式封装命令,并通过串口将该命令下发给下位机。 cc2530的操作原理,本文不讨论,如果是其他开发板,只需要修改串口操作函数。 /**************************************************************************** * 名 称: InitLed() * 功 能: 设置LED灯相应的IO口 * 入口参数: 无 * 出口参数: 无 ****************************************************************************/ void InitLed(void) { P1DIR |= 0x01; //P1.0定义为输出口 LED1 = 0; } /**************************************************************** * 名 称: 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; //开总中断允许接收中断 } /********************************************************************** * 名 称: UartSendString() * 功 能: 串口发送函数 * 入口参数: Data:发送缓冲区 len:发送长度 * 出口参数: 无 ***********************************************************************/ void UartSendString(char *Data, int len) { uint i; for(i=0; i<len; i++) { U0DBUF = *Data++; while(UTX0IF == 0); UTX0IF = 0; } } /********************************************************************** * 名 称: UART0_ISR(void) 串口中断处理函数 * 描 述: 当串口0产生接收中断,将收到的数据保存在RxBuf中 **********************************************************************/ #pragma vector = URX0_VECTOR __interrupt void UART0_ISR(void) { URX0IF = 0; // 清中断标志 RxBuf = U0DBUF; } /**************************************************************** * 名 称: 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); } /**************************************************************** * 名 称: 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; } /**************************************************************************** * 主程序入口函数 ****************************************************************************/ 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 "); exit(1); } nset1 = set_opt(serial_fd, 115200, 8, 'N', 1); if(nset2 == -1) { printf("set_opt() error "); exit(1); } } int Menu() { int option; system("clear"); printf(" ************************************************ "); printf(" ** ALARM SYSTERM ** "); printf(" ** 1----LED ** "); printf(" ** 2----GAS ** "); printf(" ** 0----EXIT ** "); printf(" ************************************************ "); while(1) { printf("Please choose what you want: "); scanf("%d",&option); if(option<0||option>2) printf(" choose error! "); 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] #"); scanf("%d",&lednum); //check if(lednum<1 || lednum >2) { printf("invalid led number "); system("clear"); continue; }else{ break; } } printf("operation: 1 on , 0 off "); 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 "); 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(" "); 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 ",gas); getchar(); } void run() { int x; while(1) { x=Menu(); switch(x) { case 1: led(); break; case 2: gas(); break; case 0: printf(" exit! "); close(serial_fd); exit(0); default: fg=1; break; } if(fg) break; } } int main() { uart_init(); run(); return 0; }
主菜单 点亮led1:
点亮led1
熄灭led1
获取烟雾数据 烟雾的数据是079,可以点根华子,你会发现每次读取的值都是在变化。 OK! 至此为止,一个简易的CC2530上位机我们就编写完毕,如果想将从串口获取的数据的值发送到远端服务器,后续文章我们将继续讨论。 |
|
来自: 新用户0175WbuX > 《待分类》