|
一
由于ZigBee是用USB转串口通讯,所以需要在内核加上PL2303驱动。进入内核源码:make menuconfig ->
Device Drivers ->
<*> USB support ->
<*> USB Serial Converter support ->
<*>USB Prolific 2303 Single Port Serial Driver
启用之后重新编译,并烧录到FS210中。
ZigBee 模块是通过串口通信,需要发送什么数据,只需要往串口写入数据,里面的ZigBee即可发送数据。
Fs210 应用程序测试:app.c
-------------------------------------------------------------------------------------------------------
#include <termios.h> //串口相关头文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
流程:
1、打开串口
2、初始化串口
3、读写串口数据
4、关闭串口
/* 初始化串口 */
void serial_init(int fd)
{
struct termios options;
tcgetattr(fd, &options);
options.c_cflag |= ( CLOCAL | CREAD );/*input mode flag:ignore modem
options.c_cflag &= ~CSIZE;
options.c_cflag &= ~CRTSCTS;
options.c_cflag |= CS8;
options.c_cflag &= ~CSTOPB; //停止位
options.c_iflag |= IGNPAR; // 忽略校验错误
options.c_oflag = 0;// 无输出模式
options.c_lflag = 0; //本地模式禁用
options.c_cc[VTIME] = 0; // 50 代表5秒 5秒内没有收到数据就返回
options.c_cc[VMIN] = 2; // 代表收到2个字节就返回
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
tcsetattr(fd,TCSANOW,&options);
}
/* 主函数 */
int main(int argc, char **args)
{
int fd = 0;
char a[3] = "v";
int size = 0;
/* 1、打开串口 */
fd = open("/dev/ttyUSB0", O_RDWR); // 因为该模块是USB转串口
if(fd<0) return 0;
puts("串口初始化....");
/* 2、初始化串口 */
serial_init(fd);
/* 3、读写数据 */
// 往串口写数据
write(fd, a, strlen(a));
puts("开始接受数据");
while(1)
{
memset(a, 0, sizeof(a));
// 从串口读数据
size = read(fd, a, sizeof(a));
printf("a = %s\n", a);
}
/* 4、关闭串口 */
close(fd);
return 0;
}
-------------------------------------------------------------------------------------------------------
编译脚本:Android.mk
-------------------------------------------------------------------------------------------------------
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := app.c
LOCAL_MODULE := app_c
LOCAL_SHARED_LIBRARIES := \
libcutils
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
-------------------------------------------------------------------------------------------------------
Fs210作为控制中心,需要时时刻刻与M0通讯,并把数据显示在apk应用上,需要采用JNI与上层APK交互。
需要采用回调机制,Jin回调Java的方法。可以采用NDK或独立的Jni库形式。
这里,我考虑到日后更新方便,就采用分离的Jni库形式。
控制中心与节点通信的数据结构:---> 后来想想,下面这些用联合体会更加合适一些
-------------------------------------------------------------------------------------------------------
// 节点标识
#define M0_NUM_1 1 // M0_1 标识
#define M0_NUM_2 2 // M0_2 标识
#define M0_NUM_3 3 // M0_3 标识
#define M0_NUM_4 4 // M0_4 标识
#define M0_NUM_5 5 // M0_5 标识
#define M0_NUM_6 6 // M0_6 标识
// 命令类型
#define Get_Data 21 // 获取数据
#define Set_Temp_Warning_val 22 // 设置温度报警阀值
#define Set_Hum_Warning_val 23 // 设置湿度报警阀值
#define Set_Auto_Send 25 // 设置主动上报数据
#define Set_NoAuto_Send 26 // 禁止主动上报数据
// 设备状态
#define FAN_ON 24 // 风扇开启状态
#define FAN_OFF 25 // 风扇关闭状态
#define BEEP_ON 26 // 蜂鸣器开启状态
#define BEEP_OFF 27 // 蜂鸣器关闭状态
#define ZigBee_Cmd_HEAD '$'
// 命令/数据 结构体
struct ZigBee_Cmd
{
char Head; // 包头 由于从串口接数据有可能第一个字节不是头,所以需要判断
int ID; // 节点标识
char CmdType; // 命令类型
char TempH; // 温度整数部分
char TempL; // 温度小数部分
char HumH; // 湿度整数部分
char HumL; // 湿度小数部分
char AdvH; // 电压整数
char AdvL; // 电压小数
signed char x; // 三轴传感器 x
signed char y; // 三轴传感器 y
signed char z; // 三轴传感器 z
int Light; // 亮度
char FAN; // 风扇状态
char BEEP; // 蜂鸣器状态
char Used; // CPU使用率
unsigned int chksum; // 校验码
};
-------------------------------------------------------------------------------------------------------
Jni代码
-------------------------------------------------------------------------------------------------------
在M0中提供两种数据发送方式:
被动获取数据:
APK通过Jni打开串口发送 Get_Data 指令获取数据,M0收到指令就发送一次数据。
主动获取数据:
APK通过Jni打开串口发送 Set_Auto_Send 指令,M0收到指令就连续发送数据。
Jni的难点在于回调Java的方法和串口操作,如数据接收。
回调Java方法:
需要创建新的线程,去回调Java的方法,所以需要利用全局变量将Jni的 JNIEnv *,, jobject* 保存,
然后在线程里面使用。
实现步骤:
APK 要点:
-------------------------------------------------------------------------------------------------------
步骤1:创建一个回调方法:
private void ZigBee_Call(int ID, char TempH, char TempL, char HumH, char HumL, char AdvH,
char AdvL, int x, int y, int z, int Light, char FAN, char BEEP, char CPUUSed)
{
// 通过发送Handler消息刷新APK的GUI
return;
}
步骤2:声明本地方法
public native int ZigBee_Open();
public native int ZigBee_Cmd(char cmd, int arg1, int arg2);
public native int ZigBee_Close();
public native int ZigBee_StartRevc();
public native int ZigBee_StopRevc();
// 添加这句是为了通过javap 命令获取其类型签名 获取完后可以删除掉
public native void ZigBee_Call(int ID, char TempH, char TempL, char HumH, char HumL, char AdvH, char AdvL,
int x, int y, int z, int Light, char FAN, char BEEP, char CPUUSed);
步骤2:利用 javap -s bin\classes\com\lmx\zigbee\MainActivity 可以获取到类型签名 用于给Jni构建映射表
-------------------------------------------------------------------------------------------------------
Jni 回调要点:
-------------------------------------------------------------------------------------------------------
要点1:创建两个全局变量。给线程备用。
JavaVM *gJavaVM = NULL;
jobject gJavaObj= NULL;
要点2:提供一个函数,或在何时的地方保存全局变量
//注意,直接通过定义全局的JNIEnv和jobject变量,在此保存env和thiz的值是不可以在线程中使用的
//线程不允许共用env环境变量,但是JavaVM指针是整个jvm共用的,所以可通过下面的方法保存JavaVM指针,在线程中使用
env->GetJavaVM(&gJavaVM);
//同理,jobject变量也不允许在线程中共用,因此需要创建全局的jobject对象在线程中访问该对象
gJavaObj = env->NewGlobalRef(thiz);
要点3:在线程里面通过保存的全局变量去获取Java环境变量、获取Java层对应类、获取回调函数。
//从全局的JavaVM中获取到环境变量
gJavaVM->AttachCurrentThread(&env, NULL);
//获取Java层对应的类
jclass javaClass = env->GetObjectClass(gJavaObj);;
//获取Java层被回调的函数
jmethodID javaCallback = env->GetMethodID(javaClass,"ZigBee_Call","(ICCCCCCIIIICCC)V");
//回调Java层的函数
env->CallVoidMethod(gJavaObj, javaCallback, ZData.ID, ZData.TempH, ZData.TempL,ZData.HumH,
ZData.HumL, ZData.AdvH, ZData.AdvL, ZData.x, ZData.y, ZData.z, ZData.Light, ZData.FAN,
ZData.BEEP,ZData.Used);
-------------------------------------------------------------------------------------------------------
串口操作要点:
-------------------------------------------------------------------------------------------------------
要点1:串口初始化,需要关注 .c_cc[VTIME]、.c_cc[VMIN]
void serial_init(int fd)
{
struct termios options;
tcgetattr(fd, &options);
options.c_cflag |= ( CLOCAL | CREAD ); options.c_cflag &= ~CSIZE;
options.c_cflag &= ~CRTSCTS;
options.c_cflag |= CS8; // 8位数据
options.c_cflag &= ~CSTOPB; //停止位
options.c_iflag |= IGNPAR; // 忽略校验错误
options.c_oflag = 0; // 无输出模式
options.c_lflag = 0; //本地模式禁用
options.c_cc[VTIME] = 10; // 表示 read() 超时值 50 表示 5秒后仍没有数据则返回
options.c_cc[VMIN] = 1; // 表示读到多少个字节就返回 5 表示读完5个字节就返回
cfsetispeed(&options, B115200); // 设置波特率
cfsetospeed(&options, B115200);
tcsetattr(fd,TCSANOW,&options);
}
要点2:读取数据的时候要注意,因为节点发送数据的时候,控制中心的ZigBee已经收到数据,而我们还未 做好准备,当ZigBee往串口写数据写到一半的时候,我们才做好准备收,这时候只能收到后半部分的数据,
如果节点是一直发送数据,那么我们在收数据的时候就很可能变成 第一个数据包的后半部分与第二个数据的
前半部分组合成一个数据包,那么解析的数据自然就不是正确的。
//线程循环
while(gIsThreadExit)
{
LOGD("ZigBee_Call...");
memset(&ZData, 0, sizeof(struct ZigBee_Cmd));
memset(&buf, 0, sizeof(buf));
size = 0;
while(size != sizeof(struct ZigBee_Cmd)) // 直至接受完一个数据包
{
ret = read(fd, &rd, 1);
if((size == 0) && (rd != ZigBee_Cmd_HEAD)
continue;
if( ret == -1 || ret == 0) // 若出错则跳出循环
breka;
buf[size++] = rd; // 若考虑优化性能,这里可以直接用结构体存,
// 存之前只需要用char*p = (char*)ZData 存的时候 *p++ = rd;
}
// 将数据拷贝到结构体
memcpy(&ZData, buf, sizeof(struct ZigBee_Cmd));
校验数据....
.........
LOGD("Revc Data OK``");
回调Java层的函数,将数据回传...
.........
}
|
|