Android 串口通讯设置校验位、速率、停止位等参数
最近业余时间有个android项目需要和硬件进行通信,通讯方式都是通过串口,android串口通讯google有个开源的demo 和很多人一样我也是通过下载这个demo进行开发和研究的。
google android串口通讯开源demo地址:https://code.google.com/archive/p/android-serialport-api/
串口通讯中我们可能会要设置校验位、速率、停止位等参数,但一般情况下还是不用设置的,只需要设置波特率就行。google提供的demo中就只提供一个波特率的设置其他的参数一并没有提供。
在使用google 源码的时候一定要注意 jni 当中.h和.c文件中的方法命名的规则,是java关键字+包名+类名+方法名。一开始我没注意,程序报错走了好多弯路。所以画图具体解释下。
android串口通讯说到底其实还是linux串口通讯。我们jni中的c代码其实就是操作linux中提供的串口文件。一开始并不知道这个原理,只是后来在网上找如何设置校验位、速率等问题时才明白的。
比如打开串口 fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); 其实就是打开linux设备中串口文件。串口文件都是以ttys开头 如ttys0 等。这个fd很重要,应该就是代表当前串口文件对象。只要这里打开成功了,C代码其他地方对串口的操作以及配置都是基于这个fd来进行的。
在adb 中可以用 cd dev来查看是否有串口文件。如果看到了 ttys0等这样的文件说明目前设备上有串口否则没有找到串口设备。
adb 中通过命令向串口发送数据:echo -e "AT ">/dev/ttyS0
顺便说下 如何在模拟器中使用电脑上的串口 这个只能使用android自带的模拟器。 一直想如果第三方模拟器能加载电脑的串口设备就好了。
emulator @模拟器名称 -qemu -serial COM1 // 这个命令会自动启动安卓模拟器
chmod 777 ttyS2 //提示权限 串口设备文件 ttys2
废话不多说,下面附上完整的代码。
注:代码是google的源代码+网上找的一些参考资料=现在的一个整合代码。
注意C和h代码中的数据类型,尽量以jint、jstring等来定义。(jint相当于java的int...)因为要被java调用,所以做好这样定义
.h代码(用于jni方法声明 即要在java中调用的方法声明)注意包名+类名+方法名
SerialPort.h
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class org_winplus_serial_utils_SerialPort */ 4 5 #ifndef _Included_android_serialport_SerialPort 6 #define _Included_android_serialport_SerialPort 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Class: org_winplus_serial_utils_SerialPort 12 * Method: open 13 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; 14 */ 15 JNIEXPORT jobject JNICALL Java_android_serialport_SerialPort_open 16 (JNIEnv *env, jclass thiz, jstring path, jint baudrate, 17 jint databits, jint stopbits, jchar parity); 18 /* 19 * Class: org_winplus_serial_utils_SerialPort 20 * Method: close 21 * Signature: ()V 22 */ 23 JNIEXPORT void JNICALL Java_android_serialport_SerialPort_close 24 (JNIEnv *, jobject); 25 26 #ifdef __cplusplus 27 } 28 #endif 29 #endif
.C代码(用于jni方法的具体实现代码本例中串口操作的所有代码)注意包名+类名+方法名
注意C代码中 写一个方法,一定要写到调用地方的前面。或者前面写声明;即 A要调用B方法,B方法代码必须要在A方法前面。由于平时写java或者c#这些代码是不需要这样的写法。一时切换到C代码可能容易忘记。
SerialPort.C
1 #include <termios.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <string.h> 7 #include <jni.h> 8 9 #include "SerialPort.h" 10 11 #include "android/log.h" 12 static const char *TAG = "serial_port"; 13 #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) 14 #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) 15 #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) 16 int fd; 17 static speed_t getBaudrate(jint baudrate) 18 { 19 switch(baudrate) 20 { 21 case 0: 22 return B0; 23 case 50: 24 return B50; 25 case 75: 26 return B75; 27 case 110: 28 return B110; 29 case 134: 30 return B134; 31 case 150: 32 return B150; 33 case 200: 34 return B200; 35 case 300: 36 return B300; 37 case 600: 38 return B600; 39 case 1200: 40 return B1200; 41 case 1800: 42 return B1800; 43 case 2400: 44 return B2400; 45 case 4800: 46 return B4800; 47 case 9600: 48 return B9600; 49 case 19200: 50 return B19200; 51 case 38400: 52 return B38400; 53 case 57600: 54 return B57600; 55 case 115200: 56 return B115200; 57 case 230400: 58 return B230400; 59 case 460800: 60 return B460800; 61 case 500000: 62 return B500000; 63 case 576000: 64 return B576000; 65 case 921600: 66 return B921600; 67 case 1000000: 68 return B1000000; 69 case 1152000: 70 return B1152000; 71 case 1500000: 72 return B1500000; 73 case 2000000: 74 return B2000000; 75 case 2500000: 76 return B2500000; 77 case 3000000: 78 return B3000000; 79 case 3500000: 80 return B3500000; 81 case 4000000: 82 return B4000000; 83 default: 84 return -1; 85 } 86 } 87 88 /** 89 90 * 设置串口数据,校验位,速率,停止位 91 92 * @param nBits 类型 int数据位 取值 位7或8 93 94 * @param nEvent 类型 char 校验类型 取值N ,E, O,,S 95 96 * @param mStop 类型 int 停止位 取值1 或者 2 97 98 */ 99 100 int set_opt(jint nBits, jchar nEvent, jint nStop) 101 { 102 103 LOGE("set_opt:nBits=%d,nEvent=%c,nSpeed=%d,nStop=%d", nBits, nEvent, nStop); 104 105 106 struct termios newtio; 107 108 if(tcgetattr(fd, & newtio) != 0) 109 { 110 111 LOGE("setup serial failure"); 112 113 return -1; 114 115 } 116 117 bzero( & newtio, sizeof(newtio)); 118 119 //c_cflag标志可以定义CLOCAL和CREAD,这将确保该程序不被其他端口控制和信号干扰,同时串口驱动将读取进入的数据。CLOCAL和CREAD通常总是被是能的 120 121 newtio.c_cflag |= CLOCAL | CREAD; 122 123 124 switch(nBits) //设置数据位数 125 { 126 127 case 7: 128 129 newtio.c_cflag &= ~CSIZE; 130 131 newtio.c_cflag |= CS7; 132 133 break; 134 135 case 8: 136 137 newtio.c_cflag &= ~CSIZE; 138 139 newtio.c_cflag |= CS8; 140 141 break; 142 143 default: 144 145 146 break; 147 148 } 149 150 switch(nEvent) //设置校验位 151 { 152 153 case 'O': 154 155 newtio.c_cflag |= PARENB; //enable parity checking 156 157 newtio.c_cflag |= PARODD; //奇校验位 158 159 newtio.c_iflag |= (INPCK | ISTRIP); 160 161 162 163 break; 164 165 case 'E': 166 167 newtio.c_cflag |= PARENB; // 168 169 newtio.c_cflag &= ~PARODD; //偶校验位 170 171 newtio.c_iflag |= (INPCK | ISTRIP); 172 173 174 175 break; 176 177 case 'N': 178 179 newtio.c_cflag &= ~PARENB; //清除校验位 180 181 182 183 break; 184 185 186 default: 187 188 189 break; 190 191 } 192 switch(nStop) //设置停止位 193 { 194 195 case 1: 196 197 newtio.c_cflag &= ~CSTOPB; 198 199 break; 200 201 case 2: 202 203 newtio.c_cflag |= CSTOPB; 204 205 break; 206 207 default: 208 209 // LOGW("nStop:%d,invalid param", nStop); 210 211 break; 212 213 } 214 215 newtio.c_cc[VTIME] = 0;//设置等待时间 216 217 newtio.c_cc[VMIN] = 0;//设置最小接收字符 218 219 tcflush(fd, TCIFLUSH); 220 221 if(tcsetattr(fd, TCSANOW, & newtio) != 0) 222 { 223 224 LOGE("options set error"); 225 226 return -1; 227 228 } 229 230 231 LOGE("options set success"); 232 return 1; 233 234 } 235 236 237 /* 238 * Class: android_serialport_SerialPort 239 * Method: open 240 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; 241 */ 242 JNIEXPORT jobject JNICALL Java_android_serialport_SerialPort_open 243 (JNIEnv *env, jclass thiz, jstring path, jint baudrate, 244 jint databits, jint stopbits, jchar parity) 245 { 246 247 speed_t speed; 248 jobject mFileDescriptor; 249 250 /*波特率 */ 251 { 252 speed = getBaudrate(baudrate); 253 if (speed == -1) 254 { 255 /* TODO: throw an exception */ 256 LOGE("Invalid baudrate"); 257 return NULL; 258 } 259 } 260 261 /* Opening device */ 262 { 263 jint flags = 0; 264 jboolean iscopy; 265 const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy); 266 LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); 267 fd = open(path_utf, O_RDWR | O_NONBLOCK); 268 //fd=fd; 269 LOGD("open() fd = %d", fd); 270 (*env)->ReleaseStringUTFChars(env, path, path_utf); 271 if (fd == -1) 272 { 273 /* Throw an exception */ 274 LOGE("Cannot open port"); 275 /* TODO: throw an exception */ 276 return NULL; 277 } 278 } 279 280 /* Configure device */ 281 { 282 struct termios cfg; 283 LOGD("Configuring serial port"); 284 if (tcgetattr(fd, &cfg)) 285 { 286 LOGE("tcgetattr() failed"); 287 close(fd); 288 /* TODO: throw an exception */ 289 return NULL; 290 } 291 292 cfmakeraw(&cfg); 293 //设置波特率 294 cfsetispeed(&cfg, speed); 295 cfsetospeed(&cfg, speed); 296 297 if (tcsetattr(fd, TCSANOW, &cfg)) 298 { 299 LOGE("tcsetattr() failed"); 300 close(fd); 301 /* TODO: throw an exception */ 302 return NULL; 303 } 304 305 //配置校验位 停止位等等 306 set_opt(databits, parity, stopbits); 307 } 308 309 /* Create a corresponding file descriptor */ 310 { 311 jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); 312 jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V"); 313 jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); 314 mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); 315 (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd); 316 } 317 318 return mFileDescriptor; 319 320 } 321 322 /* 323 * Class: cedric_serial_SerialPort 324 * Method: close 325 * Signature: ()V 326 */ 327 JNIEXPORT void JNICALL Java_android_serialport_SerialPort_close 328 (JNIEnv *env, jobject thiz) 329 { 330 jclass SerialPortClass = (*env)->GetObjectClass(env, thiz); 331 jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); 332 333 jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); 334 jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); 335 336 jobject mFd = (*env)->GetObjectField(env, thiz, mFdID); 337 jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); 338 339 LOGD("close(fd = %d)", descriptor); 340 close(descriptor); 341 }
java代码 调用jni提供的方法进行串口操作
1 package android.serialport; 2 3 import java.io.File; 4 import java.io.FileDescriptor; 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.io.OutputStream; 10 11 import android.util.Log; 12 13 public class SerialPort { 14 private static final String TAG = "SerialPort"; 15 16 private FileDescriptor mFd; 17 private FileInputStream mFileInputStream; 18 private FileOutputStream mFileOutputStream; 19 /*** 20 * 构造方法 21 * @param device 串口文件 22 * @param baudrate 波特率 23 * @param dataBits 数据位 24 * @param stopBits 停止位 25 * @param parity 校验位 26 * @throws SecurityException 27 * @throws IOException 28 */ 29 public SerialPort(File device, int baudrate, int dataBits,int stopBits,char parity) 30 throws SecurityException, IOException { 31 32 /* Check access permission */ 33 if (!device.canRead() || !device.canWrite()) { 34 try { 35 /* Missing read/write permission, trying to chmod the file */ 36 Process su; 37 su = Runtime.getRuntime().exec("/system/bin/su"); 38 String cmd = "chmod 666 " + device.getAbsolutePath() + " " 39 + "exit "; 40 su.getOutputStream().write(cmd.getBytes()); 41 if ((su.waitFor() != 0) || !device.canRead() 42 || !device.canWrite()) { 43 throw new SecurityException(); 44 } 45 } catch (Exception e) { 46 e.printStackTrace(); 47 throw new SecurityException(); 48 } 49 } 50 51 mFd = open(device.getAbsolutePath(), baudrate, dataBits,stopBits,parity); 52 if (mFd == null) { 53 Log.e(TAG, "native open returns null"); 54 throw new IOException(); 55 } 56 mFileInputStream = new FileInputStream(mFd); 57 mFileOutputStream = new FileOutputStream(mFd); 58 } 59 60 // Getters and setters 61 public InputStream getInputStream() { 62 return mFileInputStream; 63 } 64 65 public OutputStream getOutputStream() { 66 return mFileOutputStream; 67 } 68 69 70 // 调用JNI中 打开方法的声明 71 private native static FileDescriptor open(String path, int baudrate, 72 int dataBits,int stopBits,char parity); 73 74 public native void close(); 75 76 static { 77 System.loadLibrary("serial_port"); 78 } 79 }