侵权投诉
订阅
纠错
加入自媒体

如何给下位机编写一个简单的上位机?

2021-01-20 14:48
一口Linux
关注

0、前言

网友提问如下:

本地进程之间pipeshmmsg  消息队列,sem

两个pc之间socket /unix

raw 套接字:

BSD socket 

unix ->   bill joy  bsd分支,

汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:

下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命令配置对应的外设、读取对应的传感器的数据上传到上位机;主程序串口操作模块:通过串口下发命令或者读取下位机上传的数据信息;主程序网络通信模块:接收远程服务器下发的命令,并将下位机采集的数据上传到服务器。

整体看来,这个相当于是一个小的项目了,内容难度都比较大,下面我们会分为几篇独立的文章来讲解。

本篇只讨论如何给下位机编写一个简单的上位机。

一、环境简介

1. 软硬件环境

下位机:CC2530OS: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");
 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;
}、

五、 运行结果

1. 上位机运行界面

主菜单2. 点亮led灯

点亮led1:

3. 灭灯

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

获取烟雾数据

烟雾的数据是079,可以点根华子,你会发现每次读取的值都是在变化。

OK!至此为止,一个简易的CC2530上位机我们就编写完毕,如果想将从串口获取的数据的值发送到远端服务器,后续文章我们将继续讨论。

你的关注~

声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

    电子工程 猎头职位 更多
    扫码关注公众号
    OFweek电子工程网
    获取更多精彩内容
    文章纠错
    x
    *文字标题:
    *纠错内容:
    联系邮箱:
    *验 证 码:

    粤公网安备 44030502002758号