【粉丝问答8】如何用C语言在Linux下实现cc2530简单的上位机-v0.1
0、前言
网友提问如下:


汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:
- 下位机,通过串口与上位机相连;
 - 下位机要能够接收上位机下发的命令,并解析这些命令;
 - 下位机能够根据这些命令配置对应的外设、读取对应的传感器的数据上传到上位机;
 - 主程序串口操作模块:通过串口下发命令或者读取下位机上传的数据信息;
 - 主程序网络通信模块:接收远程服务器下发的命令,并将下位机采集的数据上传到服务器。
 
整体看来,这个相当于是一个小的项目了,内容难度都比较大,下面我们会分为几篇独立的文章来讲解。
本篇只讨论如何给下位机编写一个简单的上位机。
一、环境简介
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的更多相关文章
- 20155212 C语言实现linux下pwd命令的两种方法
		
20155212 C语言实现linux下pwd命令的两种方法 学习pwd命令 通过man pwd命令查看 pwd [OPTION],一般不加参数 -P显示当前目录的物理路径 -L显示当前目录的连接路径 ...
 - 用Go语言在Linux下调用新中新DKQ-A16D读卡器,读二代证数据
		
1.背景 前几天用Python在Linux下成功的获取了二代证数据,最近正在学Go语言,这两天想着用Go语言也实现一下试看看. 2.开搞C++ 这次就比较简单了,直接把CppDemo里面的SynRea ...
 - C语言实现Linux下删除非空目录
		
#include <sys/stat.h> #include <dirent.h> #include <fcntl.h> /** * 递归删除目录(删除该目录以及该 ...
 - 【C语言】 Linux下编译提示pow未定义引用
		
如下代码: #include <stdio.h> // 调用基本输入输出函数库 #include <math.h> #define PI 3.14 // 定义常量 float ...
 - c语言之linux下gettimeofday函数windows替换方案
		
* Copyright (C) 2008 mymtom (mymtom@hotmail.com) * All rights reserved. * * Redistribution and use i ...
 - 软件素材---linux C语言:linux下获取可执行文件的绝对路径--getcwd函数
		
//头文件:#include <unistd.h> //定义函数:char * getcwd(char * buf, size_t size); //函数说明:getcwd()会将当前的工 ...
 - C语言在Linux下创建一个僵尸进程
		
第三章编程题3.12 1.僵尸进程是什么 每一个进程都有一个PCB(进程控制块),其中包含进程执行的状态等一系列信息. 当父进程fork()出一个子进程,子进程执行结束后操作系统会回收子进程使用的内存 ...
 - 在Linux下开始C语言的学习
		
为什么要在linux下学习C语言? linux下可以体验到最纯粹的C语言编程,可以抛出其他IDE的影响 环境配置简单,一条命令就足够.甚至对于大多数linux发行版本,都已经不需要配置C语言的环境 查 ...
 - 如何用C#语言构造蜘蛛程序
		
"蜘蛛"(Spider)是Internet上一种很有用的程序,搜索引擎利用蜘蛛程序将Web页面收集到数据库,企业利用蜘蛛程序监视竞争对手的网站并跟踪变动,个人用户用蜘蛛程序下载We ...
 - windows下的c语言和linux 下的c语言以及C标准库和系统API
		
1.引出我们的问题? 标准c库都是一样的!大家想必都在windows下做过文件编程,在linux下也是一样的函数名,参数都一样.当时就有了疑问,因为我们非常清楚 其本质是不可能一样的,源于这是俩个操作 ...
 
随机推荐
- golang如何使用指针灵活操作内存?unsafe包原理解析
			
Hi 你好,我是k哥.一个大厂工作6年,还在继续搬砖的后端程序员. 我们都知道,C/C++提供了强大的万能指针void*,任何类型的指针都可以和万能指针相互转换.并且指针还可以进行加减等算数操作.那么 ...
 - 修改Git Commit提交记录的用户名Name和邮箱Email
			
修改Git 本次Commit提交记录的用户名Name和邮箱Email git commit --amend --author="new-name <xxx@new.com>&qu ...
 - 『vulnhub系列』HMS-1
			
『vulnhub系列』HMS?-1 下载地址: https://www.vulnhub.com/entry/hms-1,728/ 信息搜集: 使用nmap进行存活主机探测,发现开启了21端口(ftp) ...
 - MinIO使用记录
			
探索MinIO:高性能.分布式对象存储解决方案 注:本文除代码外多数为AI生成 最近因为有项目需要换成Amazon S3的云存储,所以把之前做过的minio部分做一个记录,后面也会把基于这版改造的S3 ...
 - yb课堂 搭建node环境和npm安装 《二十六》
			
搭建node环境和npm安装 什么是NodeJS? Node.js就是运行在服务端得JavaScript 什么是npm? nodejs的包管理工具,可以下载使用公共仓库的包,类似maven包安装分为本 ...
 - react为什么不用数组的下标来绑定key
			
最近在看一本名叫<深入浅出React和Redux>这一书,里面谈到了react的dom更新比对,记录一下. 假设有这么一个组件 <ul> <ListItem text=& ...
 - TIOBE 7月编程排行榜出炉!Python再次出圈
			
又到了周三,本周有过半了,大家好呀 ~~ 每月的TIOBE编程排行榜都是技术社区关注的焦点,作为编程语言流行度的晴雨表,它反映了行业趋势和 技术走向.2024年7月的榜单揭晓了一个重要变化:Pytho ...
 - PHP 程序员是学 Swoole ?还是学 Go ?
			
大家好,我是码农先森. 面临现状 这次为什么要讨论这个话题,因为 Swoole 和 Go 在 PHP 程序员坊间一直都是茶语饭后的谈资,觉得懂 Swoole 和 Go 的就高人一等.相信有很多的 PH ...
 - 解决方案 | MiKTex SSL connect error code 35
			
可能是:你的网络屏蔽了需要连接的网站,更换手机流量热点即可解决
 - 机器学习策略篇:详解处理数据不匹配问题(Addressing data mismatch)
			
处理数据不匹配问题 如果您的训练集来自和开发测试集不同的分布,如果错误分析显示有一个数据不匹配的问题该怎么办?这个问题没有完全系统的解决方案,但可以看看一些可以尝试的事情.如果发现有严重的数据不匹配问 ...