0、前言
网友提问如下:
粉丝提问
项目框架
汇总下这个网友的问题,也许就是实现一个网段程序,内容分为几块:
下位机,通过并口与上位机相连;
下位机要才能接收上位机下发的命令c语言开发上位机,并解析这种命令;
下位机才能依据这种命令配置对应的外设、读取对应的传感的数据上传到上位机;
主程序并口操作模块:通过并口下发命令或则读取下位机上传的数据信息;
主程序网路通讯模块:接收远程服务器下发的命令,并将下位机采集的数据上传到服务器。
整体看来,这个相当于是一个小的项目了,内容难度都比较大,下边我们会分为几篇独立的文章来讲解。
本篇只讨论怎样给下位机编撰一个简单的上位机。
一、环境简介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 load3 ls /dev/ttyUSB0
ubuntu安装并口驱动
根据上述步骤,会生成设备文件**/dev/ttyUSB0**。
ls /dev/ttyUSB0 -lcrw-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】c语言开发上位机,操作并口设备就只须要操作该字符设备即可。
下边我们设计上下位机的软件模块。
1.鉴权
设计上位机,首先须要设计上位机下发给下位机的指令格式,上位机根据该指令格式发送命令给下位机,下位需严格依照该指令格式进行解析指令。
鉴权格式
涵义如下:
鉴权格式可以按照须要扩充或则精简。
其中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;
};
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
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:
照亮led1
3.灭灯
熄灭led1
4.读取烟雾传感数据
获取烟雾数据
烟雾的数据是079,可以点根华子,你会发觉每次读取的值都是在变化。
OK!至此为止,一个简易的CC2530上位机我们就编撰完毕,假如想将从并口获取的数据的值发送到远端服务器,后续文章我们将继续讨论。