0、前言

网友提问如下:

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

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

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

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

一、环境简介

1. 软硬件环境

下位机:CC2530

OS:vmware + ubuntu

在这里彭老师采用的是CC2530,读者也可以采用其他的板子,我们只需要该板子有串口,可以和PC通信,同时板子上有可设置的led灯、继电器以及可以采集数据的传感器即可。

2. 硬件连接图

硬件连接图如下:

该款CC2530已经集成了CH340芯片,usb线连接电脑,即可被识别。

3. pc下识别串口

如果该串口被PC获取,名字为COMn【n为某整数】。

4. ubuntu下识别串口

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

但是往往ubuntu中没有ch340的驱动,经过实际测试,ubuntu14及之前的版本都没有这个驱动,ubuntu16以上的版本有这个驱动。

如果没有ch340驱动可以用以下方法安装对应的驱动:

1 make
2 sudo make load
3 ls /dev/ttyUSB0

按照上述步骤,会生成设备文件/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. 灭灯

4. 读取烟雾传感器数据

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

OK!

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

公号:一口Linux回复,【cc2530上位机】即可获得源码。

【粉丝问答8】如何用C语言在Linux下实现cc2530简单的上位机-v0.1的更多相关文章

  1. 20155212 C语言实现linux下pwd命令的两种方法

    20155212 C语言实现linux下pwd命令的两种方法 学习pwd命令 通过man pwd命令查看 pwd [OPTION],一般不加参数 -P显示当前目录的物理路径 -L显示当前目录的连接路径 ...

  2. 用Go语言在Linux下调用新中新DKQ-A16D读卡器,读二代证数据

    1.背景 前几天用Python在Linux下成功的获取了二代证数据,最近正在学Go语言,这两天想着用Go语言也实现一下试看看. 2.开搞C++ 这次就比较简单了,直接把CppDemo里面的SynRea ...

  3. C语言实现Linux下删除非空目录

    #include <sys/stat.h> #include <dirent.h> #include <fcntl.h> /** * 递归删除目录(删除该目录以及该 ...

  4. 【C语言】 Linux下编译提示pow未定义引用

    如下代码: #include <stdio.h> // 调用基本输入输出函数库 #include <math.h> #define PI 3.14 // 定义常量 float ...

  5. c语言之linux下gettimeofday函数windows替换方案

    * Copyright (C) 2008 mymtom (mymtom@hotmail.com) * All rights reserved. * * Redistribution and use i ...

  6. 软件素材---linux C语言:linux下获取可执行文件的绝对路径--getcwd函数

    //头文件:#include <unistd.h> //定义函数:char * getcwd(char * buf, size_t size); //函数说明:getcwd()会将当前的工 ...

  7. C语言在Linux下创建一个僵尸进程

    第三章编程题3.12 1.僵尸进程是什么 每一个进程都有一个PCB(进程控制块),其中包含进程执行的状态等一系列信息. 当父进程fork()出一个子进程,子进程执行结束后操作系统会回收子进程使用的内存 ...

  8. 在Linux下开始C语言的学习

    为什么要在linux下学习C语言? linux下可以体验到最纯粹的C语言编程,可以抛出其他IDE的影响 环境配置简单,一条命令就足够.甚至对于大多数linux发行版本,都已经不需要配置C语言的环境 查 ...

  9. 如何用C#语言构造蜘蛛程序

    "蜘蛛"(Spider)是Internet上一种很有用的程序,搜索引擎利用蜘蛛程序将Web页面收集到数据库,企业利用蜘蛛程序监视竞争对手的网站并跟踪变动,个人用户用蜘蛛程序下载We ...

  10. windows下的c语言和linux 下的c语言以及C标准库和系统API

    1.引出我们的问题? 标准c库都是一样的!大家想必都在windows下做过文件编程,在linux下也是一样的函数名,参数都一样.当时就有了疑问,因为我们非常清楚 其本质是不可能一样的,源于这是俩个操作 ...

随机推荐

  1. libevent之bufferevents

    目录 Bufferevents:概念和基础知识 Bufferevents 和 evbuffers 回调和水印 延迟回调 缓冲区事件的选项标志 使用基于套接字的缓冲区事件 创建基于套接字的缓冲区事件 在 ...

  2. ABP框架开发实例教程-生成框架代码

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称.ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB ...

  3. 韦东山freeRTOS系列教程之【第三章】任务管理

    目录 系列教程总目录 概述 3.1 基本概念 3.2 任务创建与删除 3.2.1 什么是任务 3.2.2 创建任务 3.2.3 示例1: 创建任务 3.2.4 示例2: 使用任务参数 3.2.5 任务 ...

  4. VulnHub_DC-2渗透流程

    VulnHub_DC-2 信息收集 探测目标主机IP地址 arp-scan -l nmap -sV -A -p- 192.168.157.143 得知开启80端口的http服务与7744端口开启的ss ...

  5. computed 和 watch 的区别和运用的场景?

    computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值: watch: ...

  6. javaweb上传图片存到本地,并存储地址到数据库

    前端使用layui的图片上传,将文件base64编码,然后在后端使用转码类来操作base64编码,并保存图片到本地,继而获取文件地址,将文件地址保存到数据库中 1.使用layui的图片上传 infos ...

  7. tp5命名规范

    tp5中对类,文件名,函数和方法的命名规范如下: 类名和类文件名保持一致,并统一采用驼峰法命名(首字母大写) 类的命名采用驼峰法,并且首字母大写,例如 User.UserType,不需要添加contr ...

  8. <el-table-column prop="item_no" label="料号"/>设置最小宽度

    可以通过min-width属性来设置el-table-column的最小宽度.以下是一个示例: <template> <el-table :data="tableData& ...

  9. element-plus如何隐藏el-row

    在 Element Plus 中,el-row 是用于布局的组件,如果你想要隐藏 el-row,你可以使用 CSS 的 display 属性将其设置为 none.以下是一个简单的示例: <tem ...

  10. SUM-ACM天梯赛

    第一次天梯赛: B-B:孵化小鸡 题解:二进制枚举所有可能性,一个一个枚举出来,@离散数学,真值表. 题目如下: 二进制枚举代码如下 点击查看代码 #include <bits/stdc++.h ...