android 串口通信,安卓串口通信
1、串口通信是什么串行通信技术,是指通信双方按位进行,遵守时序的一种通信方式说人话就是将数据按位依次传输画个图
串口就相当于一个管道,在硬件方面也有表示,有三根跳线, 一个是Tx线,一个是Rx线,还有一根是地线,这个管道传输的数据,也就是bit是串行的,有顺序的2、串口的应用场景串口通信这个东西,在Android开发中用到的并不多,我们绝大多数App都是用Http和后台进行通信,获取后台数据并展示,而串口通信是应用在,智能家居,和单片机通信的场
小伙子,简历上说你搞过串口通信,说说吧!
1、串口通信是什么
串行通信技术,是指通信双方按位进行,遵守时序的一种通信方式说人话就是将数据按位依次传输画个图
串口就相当于一个管道,在硬件方面也有表示,有三根跳线, 一个是Tx线,一个是Rx线,还有一根是地线,这个管道传输的数据,也就是bit是串行的,有顺序的
2、串口的应用场景串口通信这个东西,在Android开发中用到的并不多,我们绝大多数App都是用Http和后台进行通信,获取后台数据并展示,而串口通信是应用在,智能家居,和单片机通信的场景,人脸识别门禁,利用串口控制门开关,自动售货机Android收到付款成功的消息后,发送串口指令,控制货道进行出货等等 Android的设备已经超过20亿了,相对来说串口在Android应用还是挺广泛的
3、Android怎么实现串口通信的3.1、第一步找到串口文件Android的串口文件是有一个单独的目录的
我们操作的就是这个ttys开头的文件用代码是怎么操作的呢
private ArrayList getDrivers() throws IOException { ArrayList drivers = new ArrayList(); LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(DRIVERS_PATH)); String readLine; while ((readLine = lineNumberReader.readLine()) != null) { String driverName = readLine.substring(0, 0x15).trim(); String[] fields = readLine.split(" "); // driverName:/dev/tty // driverName:/dev/console // driverName:/dev/ptmx // driverName:/dev/vc/0 // driverName:serial // driverName:pty_slave // driverName:pty_master // driverName:unknown Log.d(T.TAG, "SerialPortFinder getDrivers() driverName:" driverName /* " readLine:" readLine*/); if ((fields.length >= 5) && (fields[fields.length - 1].equals(SERIAL_FIELD))) { // 判断第四个等不等于serial // 找到了新串口驱动是:serial 此串口系列名是:/dev/ttyS Log.d(T.TAG, "SerialPortFinder getDrivers() 找到了新串口驱动是:" driverName " 此串口系列名是:" fields[fields.length - 4]); drivers.add(new Driver(driverName, fields[fields.length - 4])); } } return drivers;}复制代码3.2、第二步打开串口文件我们操作串口的时候我们首先要检验一下权限
if (!device.canRead() || !device.canWrite()) { boolean chmod777 = chmod777(device); if (!chmod777) { Log.i(T.TAG, "SerialPortManager openSerialPort: 没有读写权限"); if (null != mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.NO_READ_WRITE_PERMISSION); } return false; }}/** * 文件设置最高权限 777 可读 可写 可执行 * @param file 你要对那个文件,获取root权限 * @return 权限修改是否成功- 返回:成功 与 失败 结果 */boolean chmod777(File file) { if (null == file || !file.exists()) { // 文件不存在 return false; } try { // 获取ROOT权限 Process su = Runtime.getRuntime().exec("/system/bin/su"); // 修改文件属性为 [可读 可写 可执行] String cmd = "chmod 777 " file.getAbsolutePath() "n" "exitn"; su.getOutputStream().write(cmd.getBytes()); if (0 == su.waitFor() && file.canRead() && file.canWrite() && file.canExecute()) { return true; } } catch (IOException | InterruptedException e) { // 没有ROOT权限 e.printStackTrace(); } return false;}复制代码检验完权限之后,我们就要用ndk的代码去打开串口进行操作,然后java层和 Native层的联系就是文件句柄FileDescriptor也就是代码中的fd,Native层返回FileDescriptor,然后Java层的FileInputStream、FileOutputStream和FileDescriptor进行绑定,这样Java层就能读取到数据
try { mFd = openNative(device.getAbsolutePath(), baudRate, 0); // 打开串口-native函数 mFileInputStream = new FileInputStream(mFd); // 读取的流 绑定了 (mFd文件句柄)-通过文件句柄(mFd)包装出 输入流 mFileOutputStream = new FileOutputStream(mFd); // 写入的流 绑定了 (mFd文件句柄)-通过文件句柄(mFd)包装出 输出流 Log.i(T.TAG, "SerialPortManager openSerialPort: 串口已经打开 " mFd); // 串口已经打开 FileDescriptor[35] 【2】 if (null != mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onSuccess(device); } startSendThread(); // 开启发送消息的线程 startReadThread(); // 开启接收消息的线程 return true; // 【3】} catch (Exception e) { e.printStackTrace(); if (null != mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.OPEN_FAIL); }}复制代码Native
JNIEXPORT jobject JNICALL Java_com_test_openNative (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) { int fd; // Linux串口文件句柄(本次整个函数最终的关键成果) speed_t speed; // 波特率类型的值 jobject mFileDescriptor; // 文件句柄(最终返回的成果) //检查参数,获取波特率参数信息 [先确定好波特率] { speed = getBaudrate(baudrate); if (speed == -1) { LOGE("无效的波特率,证明用户选择的波特率 是错误的"); return NULL; } } // TODO 第一步:打开串口 { jboolean iscopy; const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy); LOGD("打开串口 路径是:%s", path_utf); // 打开串口 路径是:/dev/ttyS0 fd = open(path_utf, O_RDWR /*| flags*/); // 打开串口的函数,O_RDWR(读 和 写) LOGD("打开串口 open() fd = %d", fd); // open() fd = 44 (*env)->ReleaseStringUTFChars(env, path, path_utf); // 释放操作 if (fd == -1) { LOGE("无法打开端口"); return NULL; } } LOGD("第一步:打开串口,成功了√√√"); // TODO 第二步:获取和设置终端属性-配置串口设备 /* TCSANOW:不等数据传输完毕就立即改变属性。 TCSADRAIN:等待所有数据传输结束才改变属性。 TCSAFLUSH:清空输入输出缓冲区才改变属性。 注意:当进行多重修改时,应当在这个函数之后再次调用 tcgetattr() 来检测是否所有修改都成功实现。 */ { struct termios cfg; LOGD("执行配置串口中..."); if (tcgetattr(fd, &cfg)) { // 获取串口属性 LOGE("配置串口tcgetattr() 失败"); close(fd); // 关闭串口 return NULL; } cfmakeraw(&cfg); // 将串口设置成原始模式,并且让fd(文件句柄 对串口可读可写) cfsetispeed(&cfg, speed); // 设置串口读取波特率 cfsetospeed(&cfg, speed); // 设置串口写入波特率 if (tcsetattr(fd, TCSANOW, &cfg)) { // 根据上面的配置,再次获取串口属性 LOGE("再配置串口tcgetattr() 失败"); close(fd); // 关闭串口 return NULL; } } LOGD("第二步:获取和设置终端属性-配置串口设备,成功了√√√"); // TODO 第三步:构建FileDescriptor.java对象,并赋予丰富串口相关的值 { jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()V"); jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); // 反射生成FileDescriptor对象,并赋值 (fd==Linux串口文件句柄) FileDescriptor的构造函数实例化 mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd); // 这里的fd,就是打开串口的关键成果 } LOGD("第三步:构建FileDescriptor.java对象,并赋予丰富串口相关的值,成功了√√√"); return mFileDescriptor; // 把最终的成果,返回会Java层}复制代码这样我们就完成了整个打开串口的操作
3.3、发送和读取数据
我们读取和发送数据是对文件IO进行操作,我们肯定要在子线程中进行,
private void startReadThread() { mSerialPortReadThread = new SerialPortReadThread(mFileInputStream) { @Override public void onDataReceived(byte[] bytes) { if (null != mOnSerialPortDataListener) { mOnSerialPortDataListener.onDataReceived(bytes); } } }; mSerialPortReadThread.start();}/** * 串口消息读取线程 * 开启接收消息的线程 * 读取 串口数据 需要用到线程 */public abstract class SerialPortReadThread extends Thread { public abstract void onDataReceived(byte[] bytes); private static final String TAG = SerialPortReadThread.class.getSimpleName(); private InputStream mInputStream; // 此输入流==mFileInputStream(关联mFd文件句柄) private byte[] mReadBuffer; // 用于装载读取到的串口数据 public SerialPortReadThread(InputStream inputStream) { mInputStream = inputStream; mReadBuffer = new byte[1024]; // 缓冲区 } @Override public void run() { super.run(); // 相当于是一直执行?为什么要一直执行?因为只要App存活,就需要读取 底层发过来的串口数据 while (!isInterrupted()) { try { if (null == mInputStream) { return; } Log.i(TAG, "run: "); int size = mInputStream.read(mReadBuffer); if (-1 == size || 0 >= size) { return; } byte[] readBytes = new byte[size]; // 拷贝到缓冲区 System.arraycopy(mReadBuffer, 0, readBytes, 0, size); Log.i(TAG, "run: readBytes = " new String(readBytes)); onDataReceived(readBytes); // 发出去-(间接的发到SerialPortActivity中去打印显示) } catch (IOException e) { e.printStackTrace(); return; } } } @Override public synchronized void start() { super.start(); } /** * 关闭线程 释放资源 */ public void release() { interrupt(); if (null != mInputStream) { try { mInputStream.close(); mInputStream = null; } catch (IOException e) { e.printStackTrace(); } } }}private void startSendThread() { // 开启发送消息的线程 mSendingHandlerThread = new HandlerThread("mSendingHandlerThread"); mSendingHandlerThread.start(); // Handler mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { byte[] sendBytes = (byte[]) msg.obj; if (null != mFileOutputStream && null != sendBytes && 0 读取和写入数据,其实就是对那两个读入,读处流进行操作,就这样我们就完成了对串口的收发数据3.4关闭串口我们用完串口之后,肯定会把串口关闭的,关闭串口,我们就把启动的读和写的线程关掉,在Native层也把串口关掉,将文件句柄绑定的两个流也关掉
/** * 关闭串口 */public void closeSerialPort() { if (null != mFd) { closeNative(); // 关闭串口-native函数 mFd = null; } stopSendThread(); // 停止发送消息的线程 stopReadThread(); // 停止接收消息的线程 if (null != mFileInputStream) { try { mFileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } mFileInputStream = null; } if (null != mFileOutputStream) { try { mFileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } mFileOutputStream = null; } mOnOpenSerialPortListener = null; mOnSerialPortDataListener = null;}复制代码Native层
/* * 关闭串口 * Class: cedric_serial_SerialPort * Method: close * Signature: ()V */JNIEXPORT void JNICALL Java_com_test_closeNative (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); LOGD("关闭串口 close(fd = %d)", descriptor); close(descriptor); // 把此串口文件句柄关闭掉-文件读写流(文件句柄) InputStream/OutputStream=串口 发/收}复制代码4、总结串口通信,其实就是对文件进行操作,一边读一边写,就跟上学时你和同桌传纸条似得,以上代码参考的是谷歌的开源的代码,从寻找串口到关闭串口,梳理了一下串口通信的基本流程!希望对XDM有用,希望兄弟们一键三连!
android虚拟机串口通信
工具:Virtual Serial Port Driver.用这个工具虚拟出一对串口。
下载地址
2.用串口调试助手,测试串口通信。
3.用这个命令启动虚拟机:emulator @2.2 -scale auto -qemu -serial COM3 &
说明:
2.2:是虚拟机的名称。
COM3是你要选择的串口。
ps:在cmd中使用这个命令有两种方式:1)将安卓的sdk的tools文件夹加入到path环境变量中,2)在安卓的sdk的tools文件夹下打开cmd。
4.虚拟机中测试串口通信用谷歌的一个开源项目:android_serialport_api
5.在虚拟机中运行项目。
说明:运行前要获取设备的权限
1)在cmd中用adb shell命令,进入虚拟机命令行环境。
2)打开dev文件夹:cd dev
3)获取权限:chmod 777 ttyS2
6.谷歌的开源项目不能导入进eclipse,我整理了一下,调通了。
文章评论