在安卓中实现Zigbee串口设备采集模块

前段时间做了一个移动开发的嵌入式项目,目前有时间所以写这篇博客,第一次写,多有不对望指正
好入正题,希望通俗易懂
安卓系统是架构在Linux上的,如果要将一个串口设备让应用层APP识别,或者说将数据读取出来应该有一下的几个必要的事要做
1、APP是用java写的,因此要实现Java调用C/Cpp代码,
2、应该有C/Cpp编写的Linux应用
3、应该有Linux驱动
4、应该要定义协议
——————————————————————————————>
上面的4步实际上google[今天这小伙18岁了],在设计安卓的时候已经做好了,我们只需要依葫芦画瓢而已。
1、 实际上是android的JNI开发,有两种方式:NDK和直接使用CPP写一个native方法的调用表然后再JNI代码中实现。
2、 这就对应着android中的HAL开发,和开发Linux的应用是一样的。
3、 这个通常不需要去开发,因为linux自带PL2303的USB转串口的驱动,如果是纯粹的串口设备的话需要自己去开发驱动。
4、协议的定义是许多外设IC和SOC通信时必须做的如GPRS模块的AT指令,GPS的nema数据,但是这里指的是和Cyber_M0板子通信的协议。
——————————————————————————————>
设计的思路是这四步,但是凡事总有仙人发明的轮子,linux哲学就是不要发明重复的轮子,哈哈,实际上android的GPS代码和我们要开发的几乎是一样的。
一、 JNI的开发:(设计从简,因为把握主线理解这种移动开发模式为主,不再模仿GPS那种直接将他做成安卓的服务,做成Framework层的东西)。

static JNINativeMethod zgbMethods[] = {
    {"InitZgb", "()I", (void *)Init_zgb},
    {"CloseZgb", "()I", (void *)close_zgb},
    {"Ledctrl", "(I)I", (void *)Ledctrl_zgb},
    {"Fanctrl", "(I)I", (void *)Fanctrl_zgb},
};

代码只设计打开四个native的接口。
InitZgb:负责初始化设备,主要是打开设备节点,绑定串口的一次参数。

/*------------------------------------------------------
注册相应的回调方法
加载HAL库
获取HAL的接口
打开设备创建设备节点
成功返回0
失败返回-1
------------------------------------------------------*/
jint Init_zgb(JNIEnv *env, jobject thiz)
{

    ALOGD("-----%s--------\n", __FUNCTION__);

    jint ret,err;
    /*将从主线程传递过来的对象做成本地全局的*/
    if (!mCallbacksObj)
        mCallbacksObj = env->NewGlobalRef(thiz);

    if (!mCallbacksObj)
        ALOGD("mCallbacksObj get error \n");

#ifdef NEVER/*这里将数据单独做成一个类在JNI无法找到,水很深没研究透*/
    jclass SensorCls = env->FindClass(
    "com/wl/zgb/bean/SensorData");
#else/*因为接口简单所以Java里面的方法设计在native类里面*/
    jclass SensorCls = env->FindClass(
    "com/android/zgb/ZgbComCtrl");
#endif 

    method_reportHumTemp = env->GetMethodID(SensorCls, "ReportHumTemp", "(Ljava/lang/String;)V");

    ret = hw_get_module(ZGB_MODULE_ID,(const struct hw_module_t * * )&pModule);
    if(ret == 0)
    {
        ALOGD("hw_get_module ok\n");

        pModule->common.methods->open(&pModule->common, NULL,
                (struct hw_device_t** )&pDevice);
        ALOGD("zgb_module_open ok\n");

        if(pDevice != NULL)
        {
        sZgbInterface = pDevice->get_zgb_interface(pDevice);
            ALOGD("Init_zgb get_hal_interface ok\n");
        }

        if (!sZgbInterface || sZgbInterface->init() < 0)
        {
            ALOGD("Init_zgb hal init error\n");
            return -1;
        }

        /*从主界面退出时希望能结束*/
        isRunflag = 1;
        /*创建用于异步接收的线程*/
if(pthread_create(&JNI_thread_id,NULL,JNI_thread,NULL) != 0) 
        {
            ALOGD("Init_zgb pthread_create fail !");
            return -1;
        }

    }else{
        ALOGD("hw_get_module error\n");
        return -1;
    }
    ALOGD("every ok\n");
    return 0;

}

I、从上面的代码看当时自己的设计是从传统的设计方式出发的,不过一些常规的做法:使用两个指针将HAL层的设备HMI【硬件抽象层模块接口】对象拿到,再调用sZgbInterface = pDevice->get_zgb_interface(pDevice);获取HAL的接口,然后从接口中调用sZgbInterface->init() 方法打开设备方法打开初始化硬件设备。
II、这里也有还创建了一个线程JNI_thread,具体下面会分析。

二、 JNI里面实现线程

static void*  JNI_thread(void *arg) 
{
    char buff[1024]="wellcome";
    JNIEnv *env;
    ALOGD("start-----%s--------\n", __FUNCTION__);  
    //从全局的JavaVM中获取到环境变量
    gJavaVM->AttachCurrentThread(&env, NULL);
    int cnt=0;
    //线程循环
    while(isRunflag) {
        ALOGD("JNI_thread:------%d--\nbuff:%s",cnt++ ,buff);    
        //调用HAL层的方法获取数据
        memset(buff,0,1024);
        if(sZgbInterface!=NULL)
            sZgbInterface->zgb_recvmsg(buff);

        //回调Java层的函数
        if(strlen(buff)>0)
            env->CallVoidMethod(mCallbacksObj,method_reportHumTemp,env->NewStringUTF((const char *)buff));
//      checkAndClearExceptionFromCallback(env, __FUNCTION__);
        //休眠1秒
//      sleep(1);
    }

    gJavaVM->DetachCurrentThread();
    ALOGD("end-----%s--------\n", __FUNCTION__);        
    return (void*)0;
}

1、设计思路:许多时候当外面的事件是异步发生的时候我们希望有一种中断的机制来处理这件事,所以在当APP层使用Handler向界面更新【不理解的请了解Android的APP里面的Handler对象的作用】希望是一种中断模式,这也是回调的设计初衷,如同select到epoll的改进也差不多这个设计模式【这个设计模式被广泛采纳了】。
2、JNI中创建线程方法:在jni中创建线程需要搞明白几个东西;这里有篇博客需要您先阅读下http://www.2cto.com/kf/201407/319308.html主要介绍的是JNI中的几个量{JNIEnv,JavaVM}的概念和作用,贴个图
在安卓中实现Zigbee串口设备采集模块_第1张图片
关于JNI创建线程的方式很多,这里列举两种可以尝试
a) gJavaVM->AttachCurrentThread(&env, NULL);绑定的
b) 也有使用AndroidRuntime去创建的。
在线程中主要是调用HAL接口区监控读取相应串口设备的fd,当有数据的时候就回调java的回调函数进行上报。这样一来上层就可以异步的做自己的事情了。

三、控制命令往下传递的过程
数据往下传递一般是通信的数据或命令,用于控制相关的底层硬件
1、java里面操作硬件有多种做法,这里列举出两中
a) 参数传递方式,java调用native方法直接将参数往下传递,类也可以传递到JNI层
b) 使用UNIX的域套接字在线程间直接通信,这样一来上层【一般在framework】和下层JNI就不再有层之间关系了,都是进程,可以发送一些简单的命令数据,不能发送大量的数据。有人可能会想当往上层送数据是否也可以?哈哈我没有探究过,有兴趣的可以试试。Android的GPS子系统便是使用了这种方式。
这里采用的是传统的第一种参数传递。

/*从上往下传递数据使用的是传参方式jint on命令*/
jint Ledctrl_zgb(JNIEnv *env, jobject thiz,jint on)
{
    int ret = -1; 
    if(sZgbInterface){
        ret = sZgbInterface->zgb_ledctrl(on);
        ALOGD("--------------Ledctrl_zgb\n");
    }
    return ret;
}

其中在调用了HAL封装的接口sZgbInterface->zgb_ledctrl(on);去控制相关的write 方法或者ioctrl去调用Linux驱动中的相关函数。

四、HAL中接口实现和数据协议解析

HAL(Hardware Abstract Layer)这一层在Android里面是用来规避GPL保护厂商的利益的,感觉也有点意思,知道那个意思就好:这里做硬件抽象的,将硬件做成一种类,含有方法和属性,这里不多说,上代码。

/*对该模块抽象的接口:打开初始化、控制、处理*/
static const ZgbInterface  MyZgbInterface = {
    sizeof(ZgbInterface),
    zgb_devopen,
    zgb_ledctrl,
    zgb_fanctrl,
    zgb_rcvmsg,
};

/*传接口给JNI使用*/
const ZgbInterface* zgb_get_interface(struct zgb_hw_device_t* dev)
{
    dev=dev;
    return &MyZgbInterface;
}

这里设计依赖JNI,这样设计他们两没法分开。

/*
    打开设备节点和配置串口,
    成功返回>0的数
    失败返回 -1
*/
static int
zgb_devopen(void) 
{
    char buff[50]="in HAL zgb_devopen";
    /*只打开设备节点*/
    ALOGD("start-----%s--------\n", __FUNCTION__);
    struct termios p, newp; 
    if( (zgb_fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY)) < 0) {       
        ALOGE("open device in hal error\n");
        ALOGD("end-----%s--------\n", __FUNCTION__);
        return -1;
    }   

/*相关串口编程很多默认的参数需要修改的*/
    bzero(&p, sizeof(p));
    tcgetattr(zgb_fd, &p);
    cfsetispeed(&p, B115200);
    cfsetospeed(&p, B115200);

    newp = p;
    newp.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
    newp.c_oflag &= ~OPOST;
    newp.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
    newp.c_cflag &= ~(CSIZE|PARENB);
    newp.c_cflag |= CS8;
    //newp.c_iflag |= IGNBRK;
    tcsetattr(zgb_fd, TCSANOW, &newp);

    ALOGD("open device in hal ok the fd = %d\n",zgb_fd);
    ALOGD("end-----%s--------\n", __FUNCTION__);
    return zgb_fd;
}

这里就是打开设备节点和配置相关串口【这个需要和串口设备模块配合,如果是GPS模块的话你可能需要查看提供商的数据手册】,相关串口编程的参数非常之多不再此处多说了,可以查看先关博客
http://blog.csdn.net/mr_raptor/article/details/8349323
介绍的很清楚。
这里需要注意的是当使用的模块是单工模式的时候在读写串口是需要加锁【线程间的互斥量也可以】保护不让数据被破坏。

/*自定义帧识别收消息*/   
void zgb_rcvmsg(char *storedata)
{
    int nread = 0;
    int sumrcv = 0;
    char temp;
    if((NULL==storedata)||(zgb_fd < 0))return;
    memset(storedata,0,1024);
    while(1){
        /*一个字节一个字节的读取*/
        nread = read(zgb_fd, &temp, 1);

        ALOGD("in recv  nread = %d sumrcv=%d\n",nread,sumrcv);

        if(nread==0||nread < 0 )
        {
            ALOGD("/dev/ttyUSB0 close!!\n");
            break;
        }

        if(sumrcv>1020){
            memset(storedata,0,1024);
            break;/*这一帧数据太长了*/
        }

        if(nread == 1)
        {
            if(temp=='$'){/*帧头*/
                ALOGD("frame start\n");
                memset(storedata,0,1024);
                nread=0;
                sumrcv=0;
            }else if(temp=='#'){/*帧尾*/
                ALOGD("frame end\n");
                nread=0;
                break;
            }else{/*数据段*/
                storedata[sumrcv]=temp;
                sumrcv+=nread;
                nread=0;
                /*bug here fix me with crc check*/
            }
        }
    }
    ALOGD("complete frame(len:%d): %s\n",strlen(storedata),storedata);
}

这里定义了一个简单的协议,将$作为帧的开始,#作为帧的结束,这里是一个字节一个字节的读取串口的数据的,主要是因为Zigbee模块在一次性读取多个的时候协议判别难写还会出现读到下一帧的情况,这里牺牲了相关的性能来获取稳定性。
这个借口会被JNI的线程来回的调用,在JNI的线程中会由此接口获取串口的数据,并回调java的方法对数据进行显示

//调用HAL的接口
if(sZgbInterface!=NULL)
    sZgbInterface->zgb_recvmsg(buff);
//回调Java层的函数
if(strlen(buff)>0)
env->CallVoidMethod(mCallbacksObj,method_reportHumTemp,
                env->NewStringUTF((const char *)buff));

在定义协议的时候这里可以使用相对合适方式使得编程简化,例如将发送过来的字符串一JSON格式封包,当数据传递到上层的时候解析就会变得十分的简单,但是会失去性能,在小数据量的项目中可以使用单数数据量大了这可以优化或者重新定义一种协议或技术。
到此基本介绍完了,谢谢!

你可能感兴趣的:(安卓底层,Linux应用,Zigbee串口)