【粉丝问答8】用C语言在Linux下实现CC2530上位机-1

一口Linux 2021-01-20 00:00

所有粉丝问答见如下链接:

《粉丝问答合集》

0、前言

网友提问如下:

本地进程之间 pipe shm msg  消息队列, sem

两个pc之间 socket /unix
raw 套接字:

BSD socket 
unix -> bill joy bsd分支,

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

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

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

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

一、环境简介

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 1880 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_ReadGasLevelvoid )
{
  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, 1152008'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上位机我们就编写完毕,如果想将从串口获取的数据的值发送到远端服务器,后续文章我们将继续讨论。

代码后台回复,**【cc2530上位机】**即可获得。

 



推荐阅读


【1】 12. 如何基于Cortex-A9的UART 详解  必读
【2】网络/命令行抓包工具tcpdump详解 必读
【3】 13.一文搞懂Cortex-A9 RTC  必读
【4】 DNS 原理入门指南
【5】 14. 从0学ARM Cortex-A9 看门狗入门
【6】apt 和 apt-get 之间有什么区别?
【7】16.从0学arm,基于Cortex-A9 ADC裸机驱动详解
【8】 17.基于Cortex-A9,i2c 外设详解

 


 

进群,请加一口君个人微信,带你嵌入式入门进阶。


在公众号内回复「1024」,即可免费获取学习资料,期待你的关注~


一口Linux 写点代码,写点人生!
评论
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 161浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 81浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 127浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 72浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 172浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 78浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 122浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 111浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 100浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 227浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 145浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 211浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦