s5p4418 android 驱动 hal 应用之led 串口 rs485 can总线应用(JNI层)

本篇文章用于记录Android开发学习过程中的一些理解和遇到的一些问题解决,以防忘记,好记性不如烂博客,O(∩_∩)O。

本篇相对于前面介绍的led操作增加了串口、485和can的应用。对于led的驱动、hal、app我这里就不再介绍,可以参考前面的文章Android应用开发 led 驱动层 hal硬件抽象层 应用层 详细教程记录(含源码)。

一般来说,Linux系统下对串口、485和can的操作都是基于节点的操作,如tty***、can*这样的节点,所以对于应用程序来说,只要有这些设备节点即可完成操作了,所以本篇文章介绍的串口和485应用可以兼容任何一款Android设备或者开发板,can不一定,因为can在系统服务中做了固定设置,默认使用的是can0,一般来系统中只有一个can节点,且为can0,当然不排除其他can1的情况,读者在使用时这个要注意。由于485跟串口区别不是很大,所以直接可以按照串口协议来进行操作,使用的也是tty***这样的节点设备,当然如果要像使用串口一样来使用485,那么在硬件上是要做特别设计的,可以参考下图所示的电路。

s5p4418 android 驱动 hal 应用之led 串口 rs485 can总线应用(JNI层)_第1张图片

下面就主要介绍hal和app这两层,驱动层是由设备节点完成,只要系统中的设备节点没有问题,那么通信就是正常的,测试节点可在Linux系统下使用nfs进行调试测试(nfs个人认为有助于加快调试速度,因为无需下载等繁琐的操作)。

对于hal层我这里就直接贴代码,不再一步一步做出解释,代码后边会做一些必要的说明,主要是有疑问,需要注意的地方。

代码如下:

#include <stdio.h>  
#include <stdlib.h>
#include <termios.h>
#include <unistd.h> 
#include <sys/types.h>
#include <sys/stat.h> 
#include <fcntl.h>
#include <string.h>
#include "jni.h"
#include "JNIHelp.h"
#include <assert.h> 
#include "can.h"
#include <sys/socket.h>
#include <net/if.h>
#include <cutils/properties.h>
#include <sys/wait.h>
// 引入log头文件
#include <android/log.h>  
// log标签
#define  TAG    "Led_Load_JNI"
// 定义info信息
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
// 定义debug信息
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义error信息
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

#define DEVICE_NAME		"/dev/real_led"

//#define NO_REGISTER
/*LED JNI*/
#ifndef NO_REGISTER
static jint Java_realarm_hardware_HardwareControl_LedSetState
  	(JNIEnv *env, jobject thiz,jint ledNum,jint ledState)
#else
JNIEXPORT jint JNICALL Java_realarm_hardware_HardwareControl_LedSetState
	(JNIEnv *env, jobject thiz,jint ledNum,jint ledState)
#endif
{
	int fd = open(DEVICE_NAME, 0);
	
	if (fd == -1)
	{
		LOGE("led open error");
		return 1;	
	}
	
	if(ledState == 0)
		LOGD("Led close success");
	else if(ledState == 1)
		LOGD("Led open success");
	else {
		LOGD("Led ledState parameters ERROR.Only 0 or 1.");
		return 1;
	}
	ledState &= 0x01;
	ioctl(fd, ledState, 0);

	close(fd);
	return 0;
}

/*LED JNI*/

/*UART JNI*/
int serialfd=-1;
static speed_t getBaudrate(jint baudrate)
{
	switch(baudrate) {
	case 0: return B0;
	case 50: return B50;
	case 75: return B75;
	case 110: return B110;
	case 134: return B134;
	case 150: return B150;
	case 200: return B200;
	case 300: return B300;
	case 600: return B600;
	case 1200: return B1200;
	case 1800: return B1800;
	case 2400: return B2400;
	case 4800: return B4800;
	case 9600: return B9600;
	case 19200: return B19200;
	case 38400: return B38400;
	case 57600: return B57600;
	case 115200: return B115200;
	case 230400: return B230400;
	case 460800: return B460800;
	case 500000: return B500000;
	case 576000: return B576000;
	case 921600: return B921600;
	case 1000000: return B1000000;
	case 1152000: return B1152000;
	case 1500000: return B1500000;
	case 2000000: return B2000000;
	case 2500000: return B2500000;
	case 3000000: return B3000000;
	case 3500000: return B3500000;
	case 4000000: return B4000000;
	default: return -1;
	}
}

/*
 * Class:     cedric_serial_SerialPort
 * Method:    open
 * Signature: (Ljava/lang/String;)V
 */
static jobject Java_realarm_hardware_HardwareControl_OpenSerialPort
  (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint flags)
{
	int fd;
	speed_t speed;
	jobject mFileDescriptor;

	LOGD("baudrate %d", baudrate);
	/* Check arguments */
	{
		speed = getBaudrate(baudrate);
		if (speed == -1) {
			/* TODO: throw an exception */
			LOGE("Invalid baudrate");
			return NULL;
		}
	}

	/* Opening device */
	{
		jboolean iscopy;
		const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
		LOGD("Opening serial port %s", path_utf);
		fd = open(path_utf, O_RDWR);
		serialfd=fd;
		LOGD("open() fd = %d", fd);
		(*env)->ReleaseStringUTFChars(env, path, path_utf);
		if (fd == -1)
		{
			/* Throw an exception */
			LOGE("Cannot open port");
			/* TODO: throw an exception */
			return NULL;
		}
	}

	/* Configure device */
	{
		struct termios cfg;
		LOGD("Configuring serial port");
		if (tcgetattr(fd, &cfg))
		{
			LOGE("tcgetattr() failed");
			close(fd);
			/* TODO: throw an exception */
			return NULL;
		}

		cfmakeraw(&cfg);
		cfsetispeed(&cfg, speed);
		cfsetospeed(&cfg, speed);

		if (tcsetattr(fd, TCSANOW, &cfg))
		{
			LOGE("tcsetattr() failed");
			close(fd);
			/* TODO: throw an exception */
			return NULL;
		}
	}

	// Create a corresponding file descriptor 
	{
		jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
		jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
		jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
		mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
		(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
	}

	return mFileDescriptor;
}


/*
 * Class:     cedric_serial_SerialPort
 * Method:    close
 * Signature: ()V
 */
static void Java_realarm_hardware_HardwareControl_CloseSerialPort
  (JNIEnv *env, jobject thiz)
{

/*	jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
	jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");

	jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
	jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");

	jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
	jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);

	ALOGD("close(fd = %d)", descriptor);
	close(descriptor);
*/
	LOGE("close(fd = %d)", serialfd);
	if(-1!=serialfd)
		close(serialfd);
}


/*UART JNI*/


/*RS485 JNI*/
/*realarm开发板对485的使能做了硬件处理,所以485操作与串口并无区别*/
/*RS485 JNI*/


/*CAN JNI*/

int canfd=-1;
struct sockaddr_can addr;

void my_strcpy(char *dest, char *src, size_t n)
{
	char i = 0;
	while(i < n)
	{	
		*(dest++) = *(src++);
		i++;
	}
}
int my_system(const char * cmd) 
{ 
	FILE * fp; 
	int res; char buf[1024]; 
	if (cmd == NULL) 
	{ 
		LOGD("my_system cmd is NULL!\n");
		return -1;
	} 
	if ((fp = popen(cmd, "r") ) == NULL) 
	{ 
		LOGE("popen");
	 	LOGE("popen error: %s/n", strerror(errno)); return -1; 
	} 
	else
	{
	 	while(fgets(buf, sizeof(buf), fp)) 
		{ 
			LOGD("%s", buf); 
		} 
		  
		if ( (res = pclose(fp)) == -1) 
		{ 
			LOGE("close popen file pointer fp error!\n"); return WEXITSTATUS(res);
		} 
		else if (res == 0) 
		{
		 	return WEXITSTATUS(res);
		} 
		else 
		{ 
			LOGD("popen res is :%d\n", res); return WEXITSTATUS(res); 
		} 
	}
	LOGI("popen success!\n");
	return -1;
} 
static void Java_realarm_hardware_HardwareControl_InitCan
  (JNIEnv *env, jobject thiz, jint baudrate)
{

	/* Check arguments */
	switch (baudrate)
	{
		case 5000   :
		case 10000  :
		case 20000  :
		case 50000  :
		case 100000 :
		case 125000 :
			LOGI("Can Bus Speed is %d",baudrate);
		break;
		default:
			LOGI("Can Bus Speed is %d.if it do not work,try 5000~125000",baudrate);
	}

	/* Configure device */
	if(baudrate!=0)
	{
		char str_baudrate[16];
		sprintf(str_baudrate,"%d", baudrate);
		property_set("net.can.baudrate", str_baudrate); 
		LOGI("str_baudrate is:%s", str_baudrate);
		property_set("net.can.change", "yes");
		/*//下面的方法无法实现波特率的设置,命令未正确执行
		char cmd[100];
		sprintf(cmd, "%s%s", "su ip link set can0 up type can bitrate ", str_baudrate);
		LOGI("cmd is:%s", cmd);
		my_system("busybox ifconfig can0 down");
		my_system(cmd); 
		my_system("busybox ls");
		*/
	}	
}
static jint Java_realarm_hardware_HardwareControl_OpenCan
  (JNIEnv *env, jobject thiz)
{

	struct ifreq ifr;
	int ret;     
	
	/* Opening device */
	canfd = socket(PF_CAN,SOCK_RAW,CAN_RAW);

	if(canfd==-1)
	{
		LOGE("Can Write Without Open"); 
		return   0;
	}

	strcpy((char *)(ifr.ifr_name),"can0");
	ioctl(canfd,SIOCGIFINDEX,&ifr);



	addr.can_family = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
	bind(canfd,(struct sockaddr*)&addr,sizeof(addr));

	return canfd;
}

static jint Java_realarm_hardware_HardwareControl_CanWrite
  (JNIEnv *env, jobject thiz, jint canId, jstring data)
{

	int nbytes;
	int num = 0, i = 0;
	struct can_frame frame;

	jboolean iscopy;
	const char *send_data = (*env)->GetStringUTFChars(env, data, &iscopy);	
	
	frame.can_id = canId;

	if(strlen(send_data) > 8)//用于支持当输入的字符大于8时的情况,分次数发送
	{
		num = strlen(send_data) / 8;
		for(i = 0;i < num;i++)
		{
			my_strcpy((char *)frame.data, &send_data[8 * i], 8);
			frame.can_dlc = 8;
			sendto(canfd,&frame,sizeof(struct can_frame),0,(struct sockaddr*)&addr,sizeof(addr));
		}
		memset((char *)frame.data, 0, 8);
		my_strcpy((char *)frame.data, &send_data[8 * i], strlen(send_data) - num * 8);
		frame.can_dlc = strlen(send_data) - num * 8;
		sendto(canfd,&frame,sizeof(struct can_frame),0,(struct sockaddr*)&addr,sizeof(addr));
		nbytes = strlen(send_data);
	}
	else
	{	
		my_strcpy((char *)frame.data, send_data, strlen(send_data));
		frame.can_dlc = strlen(send_data);
		sendto(canfd,&frame,sizeof(struct can_frame),0,(struct sockaddr*)&addr,sizeof(addr));
		nbytes = strlen(send_data);
	}
	(*env)->ReleaseStringUTFChars(env, data, send_data);
	LOGD("write nbytes=%d",nbytes);
	return nbytes;
}

static jobject Java_realarm_hardware_HardwareControl_CanRead
  (JNIEnv *env, jobject thiz, jobject obj, jint time)
{

	unsigned long nbytes,len;

	struct can_frame frame = {0};
	int k=0;
	jstring   jstr; 
	
	char temp[16];

	fd_set rfds;
	int retval;
	struct timeval tv;
        tv.tv_sec = time;  		
        tv.tv_usec = 0;

	bzero(temp,16);
	if(canfd==-1){
		LOGE("Can Read Without Open");
		frame.can_id=0;
		frame.can_dlc=0;
	}else{
		FD_ZERO(&rfds);
		FD_SET(canfd, &rfds);
		retval = select(canfd+1 , &rfds, NULL, NULL, &tv);
		if (retval == -1){
			LOGE("Can Read slect error");
			frame.can_dlc=0;
			frame.can_id=0;
		}else if (retval){
			nbytes = recvfrom(canfd, &frame, sizeof(struct can_frame), 0, (struct sockaddr *)&addr,&len);
		
			for(k = 0;k < frame.can_dlc;k++)
				temp[k] = frame.data[k];
			temp[k] = 0;
			
			frame.can_id = frame.can_id - 0x80000000;//读得的id比实际的有个80000000差值,这里需要处理一下
			LOGD("Can Read slect success.");
		}else{
			frame.can_dlc=0;
			frame.can_id=0;
			//LOGD("Can no data.");
		}

	}
	
		
    jclass objectClass = (*env)->FindClass(env,"realarm/hardware/CanFrame");
    jfieldID id = (*env)->GetFieldID(env,objectClass,"can_id","I");
    jfieldID leng = (*env)->GetFieldID(env,objectClass,"can_dlc","C");
    jfieldID str = (*env)->GetFieldID(env,objectClass,"data","Ljava/lang/String;");
    
	if(frame.can_dlc) {	
		LOGD("can_id is :%d", frame.can_id);
		LOGD("can read nbytes=%d", frame.can_dlc);
		LOGD("can data is:%s", temp);
	}
	
    (*env)->SetCharField(env, obj, leng, frame.can_dlc);
    (*env)->SetObjectField(env, obj, str, (*env)->NewStringUTF(env,temp));
    (*env)->SetIntField(env, obj, id, frame.can_id);
	 
	return   obj;
}

static void Java_realarm_hardware_HardwareControl_CloseCan
  (JNIEnv *env, jobject thiz)
{

	if(canfd!=-1)
		close(canfd);
	canfd=-1;
	LOGD("close can0");
}

/*CAN JNI*/

#ifndef NO_REGISTER
static JNINativeMethod gMethods[] = {  
	{"LedSetState", "(II)I", (void *)Java_realarm_hardware_HardwareControl_LedSetState}, 
	{"OpenSerialPort", "(Ljava/lang/String;II)Ljava/io/FileDescriptor;", (void *)Java_realarm_hardware_HardwareControl_OpenSerialPort},
	{"CloseSerialPort", "()V", (void *)Java_realarm_hardware_HardwareControl_CloseSerialPort},
	{"InitCan", "(I)V", (void *)Java_realarm_hardware_HardwareControl_InitCan},  
	{"OpenCan", "()I", (void *)Java_realarm_hardware_HardwareControl_OpenCan},
	{"CanWrite", "(ILjava/lang/String;)I", (void *)Java_realarm_hardware_HardwareControl_CanWrite}, 
	{"CanRead", "(Lrealarm/hardware/CanFrame;I)Lrealarm/hardware/CanFrame;", 
						(void *)Java_realarm_hardware_HardwareControl_CanRead},
	{"CloseCan", "()V", (void *)Java_realarm_hardware_HardwareControl_CloseCan}, 
}; 
 
static int register_android_realarm_test(JNIEnv *env)  
{  
   	jclass clazz;
    static const char* const kClassName =  "realarm/hardware/HardwareControl";

    /* look up the class */
    clazz = (*env)->FindClass(env, kClassName);
    //clazz = env->FindClass(env,kClassBoa);
    if (clazz == NULL) {
        LOGE("Can't find class %s\n", kClassName);
        return -1;
    }

    /* register all the methods */
    if ((*env)->RegisterNatives(env,clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    //if (env->RegisterNatives(env,clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    {
        LOGE("Failed registering methods for %s\n", kClassName);
        return -1;
    }

    /* fill out the rest of the ID cache */
    return 0;
}
#endif

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    
#ifndef NO_REGISTER
	JNIEnv *env = NULL;
	if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
	//if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {  
		LOGI("Error GetEnv\n");  
		return -1;  
	} 
	assert(env != NULL);  
	if (register_android_realarm_test(env) < 0) {  
		printf("register_android_realarm_test error.\n"); 
		LOGE("register_android_realarm_test error."); 
		return -1;  
	}
#endif
    /* success -- return valid version number */
	LOGI("/*****************realarm**********************/");

    return JNI_VERSION_1_4;
}
led需要注意的地方没什么可说的,串口部分我会在can中做说明。

下面是第需要说明的知识点:

// 引入log头文件
#include <android/log.h>  
// log标签
#define  TAG    "Led_Load_JNI"
// 定义info信息
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
// 定义debug信息
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义error信息
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
可以看到这部分是一些宏定义,定义了LOGI、LOGD、LOGE这三个宏。目的是能够在Android调试的时候可以通过logcat查看到调试信息,对应的Android层的api分别是Log.i、Log.d、Log.e,所以是很必要的。

可以看到要使用这个功能需要__android_log_print这个函数,当然也需要#include <android/log.h>这个头文件。__android_log_print这个函数有三个参数,这里拿LOGI来说明,可以看到LOGI的实际代码是__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__),第一个参数ANDROID_LOG_INFO是告诉Android系统当前是属于信息类log,第二个参数TAG是指示是哪一个部分的信息(这里是#define  TAG    "Led_Load_JNI",这样易区分是哪里打印的信息,也有助于调试),第三个参数__VA_ARGS__就是LOGI输入的要打印的信息了。具体的解释,大家谷歌吧。

当然了,如果不想这么定义直接使用__android_log_print函数也是可以的,不过看起应该很烦吧。

下面是第二个解释的函数:

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"></span><pre name="code" class="cpp">static void Java_realarm_hardware_HardwareControl_InitCan
  (JNIEnv *env, jobject thiz, jint baudrate)
{

	/* Check arguments */
	switch (baudrate)
	{
		case 5000   :
		case 10000  :
		case 20000  :
		case 50000  :
		case 100000 :
		case 125000 :
			LOGI("Can Bus Speed is %d",baudrate);
		break;
		default:
			LOGI("Can Bus Speed is %d.if it do not work,try 5000~125000",baudrate);
	}

	/* Configure device */
	if(baudrate!=0)
	{
		char str_baudrate[16];
		<span style="color:#ff0000;">sprintf(str_baudrate,"%d", baudrate);
		property_set("net.can.baudrate", str_baudrate); 
		LOGI("str_baudrate is:%s", str_baudrate);
		property_set("net.can.change", "yes");</span>
		<span style="color:#3333ff;">/*//下面的方法无法实现波特率的设置,命令未正确执行
		char cmd[100];
		sprintf(cmd, "%s%s", "su ip link set can0 up type can bitrate ", str_baudrate);
		LOGI("cmd is:%s", cmd);
		my_system("ifconfig can0 down");
		my_system(cmd); 
		my_system("busybox ls");
		*/</span>
	}	
}
 
 
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">首先来看上面函数的红色部分,sprintf和LOGI函数我这里就不说了,自行谷歌或百度。这里重要说明的是property_set这个函数,该函数是用于访问设置系统的环境属性,该函数是用于c/c++中的,在java中有其他的函数来代替,具体的可以参考http://blog.sina.com.cn/s/blog_55465b470101ngpv.html、http://blog.csdn.net/jackyu613/article/details/6136620等文章,百度或谷歌有很多。</span>
现在关键的是使用这个环境变量设置怎么就能够能够完成can的波特率设置呢。说起还是比较曲折呢,思路是这样的,在Android系统的时候设置一个can波特率设置的服务,并设置一个启动该服务的一个条件,当启动条件变成yes的时候,can波特率设置的服务就会重启一次,进而完成设置。

具体实现方法下面介绍。

/device/nexell/realarm目录下的init.realarm.rc最后的代码如下:

##############################**CAN**####################################  
chmod 0777 /system/bin/can.sh  
service can /system/bin/can.sh
    oneshot

on property:net.can.change=yes
    restart can
#########################################################################
可以看到添加了一个can服务,以及net.can.change这个系统属性,那么启动can服务就是通过net.can.change这个系统属性是否为yes来触发的。

前面的红色代码最后一句可以看到就是设置net.can.change为yes属性。

那么net.can.baudrate这个属性怎么用的呢,可以看到红色代码中设置了这个属性,它是存储波特率的。它的使用时在启动的服务can.sh中,代码如下(位置在/device/nexell/realarm/can):

#!/system/bin/sh

setprop net.can.change no

new_baudrate=`getprop net.can.baudrate`

ifconfig can0 down
ip link set can0 up type can bitrate $new_baudrate
从can.sh代码中可以知道,先设置net.can.change为no以关闭触发,然后通过getprop(Android系统提供getprop和setprop来读取和设置系统属性的值)命令来获取net.can.baudrate属性的值(在上面红色代码中所设置的值),然后通过ip link set can0 up type can bitrate $new_baudrate命令来完成can的设置和启动。

这样can的设置就可以完成了。


另外前面代码还有蓝色部分,该部分被注释掉了,这个是我在尝试另一种设置波特率的方法,只是未成功,不过这里我也说一下原理,若有朋友找到了原因,可以告知我,感激不敬。

蓝色代码部分主要想通过使用my_system这个函数来执行shell命令完成can的设置,理论上是可以的,可是实际上却不行,因为无论怎么样都无法正确执行ifconfig can0 down和ip命令,但是ls等其他指令却可以正常执行,搞不懂了。如果ifconfig can0 down和ip能够正常执行,那么代码就更简单了。

下面是my_system()函数的实现:

int my_system(const char * cmd) 
{ 
	FILE * fp; 
	int res; char buf[1024]; 
	if (cmd == NULL) 
	{ 
		LOGD("my_system cmd is NULL!\n");
		return -1;
	} 
	if ((fp = popen(cmd, "r") ) == NULL) 
	{ 
		LOGE("popen");
	 	LOGE("popen error: %s/n", strerror(errno)); return -1; 
	} 
	else
	{
	 	while(fgets(buf, sizeof(buf), fp)) 
		{ 
			LOGD("%s", buf); 
		} 
		  
		if ( (res = pclose(fp)) == -1) 
		{ 
			LOGE("close popen file pointer fp error!\n"); return WEXITSTATUS(res);
		} 
		else if (res == 0) 
		{
		 	return WEXITSTATUS(res);
		} 
		else 
		{ 
			LOGD("popen res is :%d\n", res); return WEXITSTATUS(res); 
		} 
	}
	LOGI("popen success!\n");
	return -1;
}
关于popen和pclose的使用,以及c提供的system()函数,大家百度谷歌吧。

下面是第三个要解释的部分:

#ifndef NO_REGISTER
static JNINativeMethod gMethods[] = {  
	{"LedSetState", "(II)I", (void *)Java_realarm_hardware_HardwareControl_LedSetState}, 
	{"OpenSerialPort", "(Ljava/lang/String;II)Ljava/io/FileDescriptor;", (void *)Java_realarm_hardware_HardwareControl_OpenSerialPort},
	{"CloseSerialPort", "()V", (void *)Java_realarm_hardware_HardwareControl_CloseSerialPort},
	{"InitCan", "(I)V", (void *)Java_realarm_hardware_HardwareControl_InitCan},  
	{"OpenCan", "()I", (void *)Java_realarm_hardware_HardwareControl_OpenCan},
	{"CanWrite", "(ILjava/lang/String;)I", (void *)Java_realarm_hardware_HardwareControl_CanWrite}, 
	<span style="color:#ff0000;">{"CanRead", "(Lrealarm/hardware/CanFrame;I)Lrealarm/hardware/CanFrame;", 
						(void *)Java_realarm_hardware_HardwareControl_CanRead},</span>
	{"CloseCan", "()V", (void *)Java_realarm_hardware_HardwareControl_CloseCan}, 
}; 
 
static int register_android_realarm_test(JNIEnv *env)  
{  
   	jclass clazz;
    static const char* const kClassName =  "realarm/hardware/HardwareControl";

    /* look up the class */
    clazz = (*env)->FindClass(env, kClassName);
    //clazz = env->FindClass(env,kClassBoa);
    if (clazz == NULL) {
        LOGE("Can't find class %s\n", kClassName);
        return -1;
    }

    /* register all the methods */
    if ((*env)->RegisterNatives(env,clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    //if (env->RegisterNatives(env,clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    {
        LOGE("Failed registering methods for %s\n", kClassName);
        return -1;
    }

    /* fill out the rest of the ID cache */
    return 0;
}
#endif

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    
#ifndef NO_REGISTER
	JNIEnv *env = NULL;
	if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
	//if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {  
		LOGI("Error GetEnv\n");  
		return -1;  
	} 
	assert(env != NULL);  
	if (register_android_realarm_test(env) < 0) {  
		printf("register_android_realarm_test error.\n"); 
		LOGE("register_android_realarm_test error."); 
		return -1;  
	}
#endif
    /* success -- return valid version number */
	LOGI("/*****************realarm**********************/");

    return JNI_VERSION_1_4;
}
这部分是关于jni向上层提供api注册的部分。关于注册可以参考文章http://blog.csdn.net/wang_shuai_ww/article/details/44456755,关于JNINativeMethod这个结构体,百度谷歌更好。我这里只解释上面代码中红色部分。

一般的类型有如下:

字符  Java类型 C类型

V       void            void
Z        jboolean     boolean
I         jint              int
J        jlong            long
D       jdouble       double
F       jfloat            float
B       jbyte            byte
C       jchar           char
S       jshort          short

数组则以"["开始,用两个字符表示

[I        jintArray      int[]
[F      jfloatArray    float[]
[B      jbyteArray    byte[]
[C     jcharArray    char[]
[S     jshortArray   short[]
[D     jdoubleArray double[]
[J     jlongArray     long[]
[Z     jbooleanArray boolean[]

Ljava/lang/String;  String  jstring
Ljava/net/Socket;  Socket  jobject
我们需要的结构是CanFrame结构,这个在Android系统中并未提供,而是我们应用的java代码中自定义的一个类,所在的包是realarm.hardware,类名是CanFrame,所以红色代码的写法就是Lrealarm/hardware/CanFrame,指定包和类名。从下图可以看到我们定义的类:

s5p4418 android 驱动 hal 应用之led 串口 rs485 can总线应用(JNI层)_第2张图片

所以对于这类自定义的类型,在native注册时,参数(JNINativeMethod第二个参数)说明的方法是L+包名+类名。这个一定要理解,否则对于写jni有很大障碍,不懂的多谷歌JNINativeMethod这个结构的说明。


下面是第四个要解释的部分:

从第三部分的CanRead函数可知,Java_realarm_hardware_HardwareControl_CanRead函数要接收一个参数jobject obj,是jobject 类型的,实际上是CanFrame类型的,从第三个解释的部分可以知道。CanFrame和jni这层定义的can_frame怎么联系的呢。

先贴出代码,下面慢慢说。

static jobject Java_realarm_hardware_HardwareControl_CanRead
  (JNIEnv *env, jobject thiz, jobject obj, jint time)
{

	unsigned long nbytes,len;

	struct can_frame frame = {0};
	int k=0;
	jstring   jstr; 
	
	char temp[16];

	fd_set rfds;
	int retval;
	struct timeval tv;
        tv.tv_sec = time;  		
        tv.tv_usec = 0;

	bzero(temp,16);
	if(canfd==-1){
		LOGE("Can Read Without Open");
		frame.can_id=0;
		frame.can_dlc=0;
	}else{
		FD_ZERO(&rfds);
		FD_SET(canfd, &rfds);
		retval = select(canfd+1 , &rfds, NULL, NULL, &tv);
		if (retval == -1){
			LOGE("Can Read slect error");
			frame.can_dlc=0;
			frame.can_id=0;
		}else if (retval){
			nbytes = recvfrom(canfd, &frame, sizeof(struct can_frame), 0, (struct sockaddr *)&addr,&len);
		
			for(k = 0;k < frame.can_dlc;k++)
				temp[k] = frame.data[k];
			temp[k] = 0;
			
			frame.can_id = frame.can_id - 0x80000000;//读得的id比实际的有个80000000差值,这里需要处理一下
			LOGD("Can Read slect success.");
		}else{
			frame.can_dlc=0;
			frame.can_id=0;
			//LOGD("Can no data.");
		}

	}
	
		
<span style="color:#ff0000;">    jclass objectClass = (*env)->FindClass(env,"realarm/hardware/CanFrame");
    jfieldID id = (*env)->GetFieldID(env,objectClass,"can_id","I");
    jfieldID leng = (*env)->GetFieldID(env,objectClass,"can_dlc","C");
    jfieldID str = (*env)->GetFieldID(env,objectClass,"data","Ljava/lang/String;");</span>
    
	if(frame.can_dlc) {	
		LOGD("can_id is :%d", frame.can_id);
		LOGD("can read nbytes=%d", frame.can_dlc);
		LOGD("can data is:%s", temp);
	}
	
<span style="color:#ff0000;">    </span><span style="color:#3333ff;">(*env)->SetCharField(env, obj, leng, frame.can_dlc);
    (*env)->SetObjectField(env, obj, str, (*env)->NewStringUTF(env,temp));
    (*env)->SetIntField(env, obj, id, frame.can_id);</span>
	 
	return   obj;
}
在上面代码中最不好理解的可能就是红色部分了,而这部分就是关键的部分。

(*env)->FindClass:获得CanFrame结构,并保存于objectClass
(*env)->GetFieldID:或者objectClass类中的成员,也就是CanFrame结构里的成员。该函数前两个参数不解释了容易理解,第三个参数和第四个参数比较重要,第三个参数标示CanFrame结构里的成员名称(需要名字一致,否则会出错的),第四个是该成员的类型。

下面举个例子

(*env)->GetFieldID(env,objectClass,"can_id","I")意思是获取objectClass类中的成员can_id,且类型为int型。与下图中CanFrame的定义对比:

s5p4418 android 驱动 hal 应用之led 串口 rs485 can总线应用(JNI层)_第3张图片

can_id为Int型,是对应的。其他两个也同理,都是对应的。获得了java的类型后,下面看看是怎么联系的。

数据交换就是上面蓝色部分的代码了。

(*env)->SetCharField(env, obj, leng, frame.can_dlc);第二个参数就是Java_realarm_hardware_HardwareControl_CanRead接收的jobject obj参数,leng就是指can_dlc的fieldID,frame.can_dlc就是can读取到的字节数数据了。通过这样一句话,就把frame.can_dlc数据放到了obj中的can_dlc变量中了。

其他两个函数都类似,只是数据类型不同而已。最后把obj返回给java层,那么一个完整测CanFrame帧就读取完成了。


到此本篇介绍的JNI层,以及需要注意的地方就介绍完了,下一篇是app应用。


源码下载地址:http://download.csdn.net/detail/u010406724/8539263

转载请注明出处:http://blog.csdn.net/wang_shuai_ww/article/details/44672531

你可能感兴趣的:(android,jni,串口,应用程序,can)