前言
我第一次听说串口完全是懵逼状态,脑子没一点概念,于是我就到处百度,但是发现网上的例子都是一些乱七八糟的代码,有代码也基本都不会解释,现在做这个串口开发也有段时间了,现在根据我的理解写了这篇博客。
串口是什么?
串口就是一个可以将我们的android设备和一些驱动设备数据交互的通道,通过串口我们从android设备端发送数据到驱动设备,驱动设备会随之返回一串数据给我们,通常会用usb数据线作为android设备和驱动设备之间的中间件以形成串口,当然我们需要定义好驱动设备看得明白的数据,不然它是不会理你的,就像你对着李四喊王五过来一下,李四根本就不叫王五,你叫人家怎么会理你呢。
串口数据是什么?
串口数据一般是我们提前和驱动设备提前协商好的数据格式,只有发送协商好的数据格式,驱动设备才能看懂并且回应你,这时候你就有感觉好玩了,人居然可以和物品交流,驱动设备只是一件物品,当然不能和他协商,所以我们协商的对象是c程序员,因为驱动开发都是c程序员开发的,我们只要找到那个驱动设备开发的c程序员,并且和他一起定义好一个数据协议,当然,如果c程序员以前提前定义好了,一般他定义好数据格式会写好数据格式参考文档,你就可以直接拿文档,对着文档去定义串口数据了。
比如:我做的项目是关于公司非接读卡器和平板进行非接卡交易的一个项目,如果读者不理解非接也没关系,就是我们常用的芯片银行卡,你只要知道我这里的非接读卡器就是我们前面所说的驱动设备就好了,由于我这里的c程序员已经定义好了数据格式并写好了文档,所以我直接拿到了他的文档,对着他的文档进行定义数据格式
关于串口:
google官网已经有了开源的例子,读者也可以去下载(http://code.google.com/p/android-serialport-api/)
本人也去下载了,但是我发现他的代码封装的还不是很好,因为他只是将打开关闭写在了底层,读取和写入在android操作的,而我下面会把把这些步骤都封装在底层,android只要调用就好了
怎么使用串口?
首先如果你已经有了驱动设备并且通过usb数据线之类的和我们的android设备已经连接好了,那这个时候我们就可以进行我们的jni串口开发了
如果读者还不会jni,可以去学习我的这篇博客,点击进入
使用串口的步骤:
打开串口
写入数据到驱动设备
读取驱动设备返回的数据
关闭串口
首先在jni目录中创建底层的.c文件serialprot.c
1.打开串口
再打开之前我们需要只要我们android设备的串口号和波特率,这个主要是android设备方需要给我们的
得到波特率
/**
* 得到对应的波特率
*/
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;
}
}
/**
* 打开串口
*/
jobject serialportOpen(JNIEnv *env, jobject obj) {
LOGI("serialportOpen");
char* deviceName = "dev/ttyS4";
int baudrate = 115200;
int flags = 0;
chmod(deviceName, 0666);
speed_t speed;
jobject mFileDescriptor;
/* 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, deviceName, &iscopy);
LOGI("Opening serial port %s with flags 0x%x",
deviceName, O_RDWR | flags);
mTtyfd = open(deviceName, O_RDWR | flags);
LOGI("open() mTtyfd = %d", mTtyfd);
//(*env)->ReleaseStringUTFChars(env, deviceName, path_utf);
if (mTtyfd == -1) {
/* Throw an exception */
LOGE("Cannot open port");
/* TODO: throw an exception */
return NULL;
}
}
/* Configure device */
{
struct termios cfg;
LOGI("Configuring serial port");
if (tcgetattr(mTtyfd, &cfg)) {
LOGE("tcgetattr() failed");
close(mTtyfd);
/* TODO: throw an exception */
return NULL;
}
cfmakeraw(&cfg);
cfsetispeed(&cfg, speed);
cfsetospeed(&cfg, speed);
if (tcsetattr(mTtyfd, TCSANOW, &cfg)) {
LOGE("tcsetattr() failed");
close(mTtyfd);
/* 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) mTtyfd);
}
LOGI("serialportOpen success");
return mFileDescriptor;
}
关于以上代码就是一个打开串口和配置串口的过程
代码可以直接考到自己的项目中,这里只需要修改前面说所的android设备所对应的串口号和波特率:
//串口设备
char* deviceName = "dev/ttyS4";
//波特率
int baudrate = 115200;
2.发送数据到驱动设备:
/**
* 写入数据
* inputData 发送给读卡器的数据
* len 发送数据的长度
*/
jint serialportWrite(JNIEnv *env, jbyteArray inputData, jint len) {
LOGI("serialportWrite");
if (mTtyfd == -1) {
LOGE("mTtyfd open failure");
return -1;
}
if (len > 1024)
return 0;
int length;
char DataBuff[1024] = { 0x00 };
LOGI("lenFileld=%d", len);
memcpy(DataBuff, inputData, len);
LOGD_HEX(env, "Inputdata=", DataBuff, len);
length = write(mTtyfd, DataBuff, len);
sleep(1); //写完之后睡一秒
if (length > 0) {
LOGI("write device success");
return length;
} else {
LOGE("write device error");
}
return -1;
}
jbyteArray inputData : 就是我们需要发给驱动设备的指令数据
jint len : 为数据的长度大小
3.读取驱动设备返回的数据
/**
* 读取数据
* outputData 读卡器返回的数据
* timeOut 读取超时时间
*/
jint serialportRead(JNIEnv *env, jbyteArray outputData, jint timeOut) {
LOGI("serialportRead");
if (mTtyfd == -1) {
LOGE("mTtyfd open failure");
return -1;
}
int ret;
fd_set readfd;
struct timeval timeout;
while (mTtyfd != -1) {
timeout.tv_sec = 0; //设定超时秒数
timeout.tv_usec = timeOut; //设定超时毫秒数
FD_ZERO(&readfd); //清空集合
FD_SET(mTtyfd, &readfd); // 把要检测的句柄mTtyfd加入到集合里
ret = select(mTtyfd + 1, &readfd, NULL, NULL, &timeout); // 检测我们上面设置到集合readfd里的句柄是否有可读信息
LOGI("ret=%d", ret);
switch (ret) {
case -1: // 这说明select函数出错
LOGE("mTtyfd read failure");
return -1;
break;
case 0: // 说明在我们设定的时间值5秒加0毫秒的时间内,mTty的状态没有发生变化
LOGE("mTtyfd read timeOut");
return -2;
default: //说明等待时间还未到0秒加500毫秒,mTty的状态发生了变化
if (FD_ISSET(mTtyfd, &readfd)) { // 先判断一下mTty这外被监视的句柄是否真的变成可读的了
jbyte tempBuff[1024];
bzero(tempBuff, 1024);
if ((nread = read(mTtyfd, tempBuff, 1024)) > 0) {
LOGI("nread=%d", nread);
if (nread >= 1024)
return 0;
tempBuff[nread + 1] = '\0';
(*env)->SetByteArrayRegion(env, outputData, 0, nread,
tempBuff);
char DataBuff[1024] = { 0x00 };
jbyte* data = (*env)->GetByteArrayElements(env, outputData,
JNI_FALSE);
memcpy(DataBuff, data, nread);
LOGD_HEX(env, "outputData=", DataBuff, nread);
(*env)->ReleaseByteArrayElements(env, outputData, data, 0);
LOGI("serialportRead success");
return nread;
}
}
break;
}
}
return -1;
}
jbyteArray outputData :从驱动设备读取的数据返回到android端
,jint timeOut :读取的超时时间
其中主要的读出过程为default:中的代码:
jbyte tempBuff[1024];
bzero(tempBuff, 1024);
if ((nread = read(mTtyfd, tempBuff, 1024)) > 0) {
LOGI("nread=%d", nread);
if (nread >= 1024)
return 0;
tempBuff[nread + 1] = '\0';
(*env)->SetByteArrayRegion(env, outputData, 0, nread,
tempBuff);
char DataBuff[1024] = { 0x00 };
jbyte* data = (*env)->GetByteArrayElements(env, outputData,
JNI_FALSE);
memcpy(DataBuff, data, nread);
LOGD_HEX(env, "outputData=", DataBuff, nread);
(*env)->ReleaseByteArrayElements(env, outputData, data, 0);
LOGI("serialportRead success");
return nread;
}
因为读出的数据我们一般是乱码的,其中LOGD_HEX是将读出的数据16进制打印出来:
/**
* 以16进制显示
*/
void LOGD_HEX(JNIEnv *env, char* msg, char* data, int len) {
int i;
char tStr[5];
char MsgBuf[1024];
memset(MsgBuf, 0, sizeof(MsgBuf));
sprintf(MsgBuf, "%s[%d][", (char*) msg, len);
// jint* arr = (*env)->GetIntArrayElements(env,data,0);
if (len > 1024)
len = 1024;
for (i = 0; i < len; i++) {
sprintf((char*) tStr, "%02X ", data[i]);
strcat((char*) MsgBuf, tStr);
}
strcat((char*) MsgBuf, "]\r\n");
printf("%s", MsgBuf);
//LOGD("%s", MsgBuf);
LOGI("%s", MsgBuf);
}
4、关闭串口
/**
* 关闭串口
*/
void serialportClose() {
LOGI("serialportClose");
if (mTtyfd == -1) {
LOGE("mTtyfd open failure");
return;
}
LOGI("mTtyfd=%d", mTtyfd);
c = close(mTtyfd);
LOGI("c=%d", c);
if (c < 0) {
LOGE("mTtyfd close failure");
return;
}
LOGI("close device success");
}
其实这个关闭不关闭都不重要,因为用不到。打开之后我们肯定一直想发送指令给驱动设备并读取驱动设备返回的数据
总结
串口交互数据:
使用c语言中的open()方法打开串口,使用c语言中的write()写入数据,使用c语言中的read()函数读出数据,最后还可以使用close()函数关闭串口