本项目基于C#编写智能家居系统PC客户端,同时编写安卓客户端,代码下载链接https://download.csdn.net/download/hzqgangtiexia/10435931
1、硬件选型及数据采集
节点板子以CC2530芯片为核心,PL2303芯片做usb转串口,
传感器数据采集及控制:
温度和湿度:DHT11 温度量程0-50oC,湿度量程20-90%RH 单总线协议读
光敏传感器(用于自动开灯关灯):用AD读,本质是电阻
红外传感器(用于安防):HC-SR501,直接判断高低电平,输出 高3.3v / 低0v
烟雾传感器(甲烷、液化气、可燃气):用AD读,本质是电阻
继电器: 高低电平
步进电机: 驱动芯片为ULN2003,5—12v宽电压驱动,五线四相,通过四个IO口设置的高电平外加延时2ms控制。
2、整体架构
2.1、各节点(路由器)采集数据
节点采集数据分别涉及温度、湿度、烟雾、光敏温度、红外传感器数据,并控制步进电机和继电器来控制用电器。
所有数据由统一的数组来发送,数组长度9,data_str[9],其data_str[0]=0X1A用于标记数据传输的方向是节点-à协调器—>PC,data_str[1]=0X60代表传感器数据,以后代表数据等等,PC进行解析的时候直接if(buf[1]==0x60)。
2.2、协调器
协调器不解析任何数据,只是做转发功能(因为网络一旦建立,它的作用就相当于路由器)
2.3、PC (C#上位机)
上位机通过串口接收来自协调器的数据,然后对串口数据进行解析。
上位机发送控制命令只需要按下按钮 Buf[6], Buf[0]=0X2A,代表数据向中端节点传输,Buf[1]=0X1灯,Buf[1]=0X2电机,Buf[1]=0X3等等,Buf[2]代表发送到哪个节点,其他是控制命令。
总结通讯数据的传输:自定义通信协议
采用结构体对数据帧进行封装,和用数组进行进行封装各有各用,最后都转化为字符(unsitned char)流取地址发送
设备自身的安全,第二是数据信息的安全。
C#整体编程
串口:
串口初始化
扫描显示串口
设置串口接收事件,并在其接收方法用this.invoke来访问UI资源
TCPserver:
获取Ip地址并显示
启动服务器,建立socket连接
将接收到的APP数据通过串口直接发送(不需要解析)
close();
3、性能调优:
关于利用休眠的节能
假设5号电池容量1500mA.h,两节就是3000mA.h,终端节点数据发送期间瞬时电流29mA,数据接收瞬时电流24mA,再加上各种传感器所需要的电流,假设共计60mA,那么两节5号电池可以工作50h,对应实际应用时,定时采集,假设每小时工作50s,其余时间在休眠(休眠时工作电压工作在微安级,可忽略不计)那么可以工作3600小时,那么可以工作半年的时间。
如何优化:
设置低功耗标志
通过可以被每一个任务调用的一个函数设置每个任务是否接受在低功耗运行
设置睡眠时间等等
1. voidosal_start_system( void )
所有应用程序,无论是自己写的最简单的测试程序还是复杂的OSAL操作系统,都必须从main( )来入口。所谓的OS操作系统,我们不妨这样想:自己写一个最简单的main( ),里面就一句打印“Hello, World”.如果需要加入Key, LED这样的输入输出功能,那么就需要扩充main( ),加入Key, LED的驱动,如果要实现多线程调度,就要加入Timer驱动等等。其实操作系统就是这么来的,简单吧:)。
一般在OS中都会有个死循环,在这个循环中去处理各种各样的事件。在Zstack OSAL中,这个死循环就存在于osal_start_system()这个函数中。下面来详细分析在这个死循环中到底在做哪些事情。
2. OSAL的“心跳”
在OSAL的死循环中,各个事件只是在某些特定的情况下发生,如果OSAL一刻不停去轮询去处理这些应用程序,迟早会累死(热量,功耗,寿命…),这样做是完全没有必要的。所以这里就引入了心跳的概念,也就是OS的时钟节奏。在Zstack OSAL中这个节奏定义为1ms, 由8 bits HW_TIMER4来控制,当然这些都可以由程序员来修改,后面就以系统的默认值来讲述。在void InitBoard( byte level )这个函数中有下面这段代码就是在定义系统的心跳Timer。
HalTimerConfig (OSAL_TIMER,
HAL_TIMER_MODE_CTC, HAL_TIMER_CHANNEL_SINGLE,
HAL_TIMER_CH_MODE_OUTPUT_COMPARE,
OnboardTimerIntEnable,
Onboard_TimerCallBack);
在OSAL的Timer定义好了以后,就要启动Timer, 至于如何启动Timer, 请自行查阅2430 Spec, 我这里想说的是,在一步步跟踪源码到死循环开始,都没有发现启动OSALTimer的代码,最后通过观察Timer相关的控制寄存器,发现,在网络层初始化函数nwk_init( taskID++ )执行完毕后Timer启动了,也就是说在网络层初始化函数中有启动Timer的语句,因为网络层初始化是不开源的,无从去看源代码验证,总之,Timer启动了就好。
每当1ms心跳来临时,Timer4的中断标志置位,这样在OSAL的死循环中检测到这个标志置位后,就去轮询处理各事件。没有检测到这个标志位则继续死循环。在死循环的开始有调用Hal_ProcessPoll()这条语句,实际上就是在查询中断标志并作相应的处理。
3. OSAL的“心跳”来临后的处理
上节提到Hal_ProcessPoll()这条语句,实际上就是在查询中断标志并作相应的处理。那么当1ms心跳来临时,我们跟踪进这个函数看看它到底干了些什么。
当判断到中断标志,表明1ms心跳来临了,就去调用Timer4相应的回调函数。这个回调函数由HalTimerConfig()的最后一个参数来定义,请回看上节,OSAL Timer的回调函数就是Onboard_TimerCallBack(),一步步跟进,最终调用osalTimerUpdate()这个函数。在这个函数中会去轮询Timer事件链表。
Timer事件链表是下面这样一个结构,next指向下一个Timer事件,timeout值表明本Timer事件还需要timeout个心跳才需要被处理,因为此处心跳是1ms,所以也就是说还需要timeout个ms才处理。所谓的处理也就是检测timeout是否小于1ms,如果小于1ms,则发出event_flag这个消息到消息队列,这个消息隶属于task_id这个任务。如果大于1ms,说明该Timer事件还不到处理的时候,则Timeout = Timeout-1,然后继续耐心等待下一次心跳。注:Timer事件链表的维护是通过osal_start_timerEx()这个函数来实现的。
typedef struct
{
void *next;
UINT16 timeout;
UINT16 event_flag;
byte task_id;
} osalTimerRec_t;
4. 消息发出后的处理
上节讲到在心跳中发出任务的事件消息到消息队列。那么这个消息由谁来处理?回头再看osal_start_system( )中的死循环,有检测消息队列的语句,当发现有消息时,判断该消息隶属于哪个任务就去调用对应于该任务的消息处理函数。各任务的消息处理函数是在tasksArr[]这个常量数组中定义。这个数组中定义的消息处理函数和任务初始化函数中的任务必须一一对应。
5. 节电模式
细心的同学会发现在死循环体的后面有调用osal_pwrmgr_powerconserve()这样一条语句。从名字及注释来看,属于节电模式的调用。此处不详细列举代码,只讲其工作原理。
上面章节讲到1ms心跳来临时去轮询各事件Timer是否需要处理。这里心跳很快(1ms),各事件的Timeout很慢(往往成百上千)。譬如Key检测的Timer事件的Timeout是100,意思是说100ms才去检测一次是否有Key按下。假如说Key检测的Timout在各Timer事件中Timeout最小,那么也就是说有99次心跳都不会有事件需要处理,但是死循环依然在跑,在做无用功,为了解决这个问题,就加入了节电模式。
在osal_pwrmgr_powerconserve()这个函数中会检测Timer时间链表中Timeout最小的值,假设为next, 然后设定CPU进入休眠模式next个毫秒。休眠时间到了苏醒过来立即就会有Timer事件需要处理,这样就可以达到省电的目的。
6. 小结
到此为止,OSAL神秘面纱已完全揭开,为了巩固知识,下面以Key为例讲述从Key按下到Key消息被处理的整个过程。
首先在Key的初始化过程中会调用下面这条语句:osal_start_timerEx(Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE),这条语句的功能就是将检测Key的这个事件放入Timer事件链表。这个事件隶属于Hal_Task, Timeout是HAL_KEY_POLLING_VALUE。
当1ms心跳来临时,判断timeout是否小于1,如果不小于,则timeout=timeout-1并等待下一次心跳。如果小于1,则发出HAL_KEY_EVENT这个消息到消息队列,然后调用Hal_Task的事件处理函数Hal_ProcessEvent()处理HAL_KEY_EVENT消息,在处理这个消息的过程中调用函数HalKeyPoll()。这个函数检测当前有无按键,如果有按键并且和上次按键值不同则认为有新的按键按下并发出相应的按键消息。