分解一下串口及握手的基础知识(UTF-8的区别)

抒情君 9

小伙子,简历上说你搞过串口通信,说说吧!

1、串口通信是什么

串行通信技术,是指通信双方按位进行,遵守时序的一种通信方式说人话就是将数据按位依次传输画个图

串口就相当于一个管道,在硬件方面也有表示,有三根跳线, 一个是Tx线,一个是Rx线,还有一根是地线,这个管道传输的数据,也就是bit是串行的,有顺序的

2、串口的应用场景

串口通信这个东西,在Android开发中用到的并不多,我们绝大多数App都是用Http和后台进行通信,获取后台数据并展示,而串口通信是应用在,智能家居,和单片机通信的场景,人脸识别门禁,利用串口控制门开关,自动售货机Android收到付款成功的消息后,发送串口指令,控制货道进行出货等等 Android的设备已经超过20亿了,相对来说串口在Android应用还是挺广泛的

3、Android怎么实现串口通信的

3.1、第一步找到串口文件

Android的串口文件是有一个单独的目录的

我们操作的就是这个ttys开头的文件用代码是怎么操作的呢

privateArrayListgetDrivers() throws IOException{
ArrayList drivers =newArrayList<>();
LineNumberReader lineNumberReader =newLineNumberReader(newFileReader(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:unknownLog.d(T.TAG,"SerialPortFinder getDrivers() driverName:"+ driverName/*+ " readLine:" + readLine*/);if((fields.length >=5) && (fields[fields.length -1].equals(SERIAL_FIELD))) {// 判断第四个等不等于serial// 找到了新串口驱动是:serial 此串口系列名是:/dev/ttySLog.d(T.TAG,"SerialPortFinder getDrivers() 找到了新串口驱动是:"+ driverName +" 此串口系列名是:"+ fields[fields.length -4]);
drivers.add(newDriver(driverName, fields[fields.length -4]));
}
}returndrivers;
}

复制代码

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);
}returnfalse;
}
}/**
* 文件设置最高权限 777 可读 可写 可执行
*@paramfile 你要对那个文件,获取root权限
*@return权限修改是否成功- 返回:成功 与 失败 结果
*/
boolean chmod777(File file) {if(null== file || !file.exists()) {// 文件不存在returnfalse;
}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()) {returntrue;
}
}catch(IOException | InterruptedException e) {// 没有ROOT权限e.printStackTrace();
}returnfalse;
}

复制代码

检验完权限之后,我们就要用ndk的代码去打开串口进行操作,然后java层和 Native层的联系就是文件句柄FileDescriptor也就是代码中的fd,Native层返回FileDescriptor,然后Java层的FileInputStream、FileOutputStream和FileDescriptor进行绑定,这样Java层就能读取到数据

try{
mFd = openNative(device.getAbsolutePath(), baudRate,0);// 打开串口-native函数mFileInputStream =newFileInputStream(mFd);// 读取的流 绑定了 (mFd文件句柄)-通过文件句柄(mFd)包装出 输入流mFileOutputStream =newFileOutputStream(mFd);// 写入的流 绑定了 (mFd文件句柄)-通过文件句柄(mFd)包装出 输出流Log.i(T.TAG,"SerialPortManager openSerialPort: 串口已经打开 "+ mFd);// 串口已经打开 FileDescriptor[35] 【2】if(null!= mOnOpenSerialPortListener) {
mOnOpenSerialPortListener.onSuccess(device);
}
startSendThread();// 开启发送消息的线程startReadThread();// 开启接收消息的线程returntrue;// 【3】}catch(Exceptione) {
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("无效的波特率,证明用户选择的波特率 是错误的");returnNULL;
}
}// TODO 第一步:打开串口{
jboolean iscopy;constchar *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
LOGD("打开串口 路径是:%s", path_utf);// 打开串口 路径是:/dev/ttyS0fd = 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("无法打开端口");returnNULL;
}
}
LOGD("第一步:打开串口,成功了√√√");// TODO 第二步:获取和设置终端属性-配置串口设备/* TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
注意:当进行多重修改时,应当在这个函数之后再次调用 tcgetattr() 来检测是否所有修改都成功实现。
*/
{
struct termios cfg;
LOGD("执行配置串口中...");if(tcgetattr(fd, &cfg)) {// 获取串口属性LOGE("配置串口tcgetattr() 失败");
close(fd);// 关闭串口returnNULL;
}

cfmakeraw(&cfg);// 将串口设置成原始模式,并且让fd(文件句柄 对串口可读可写)cfsetispeed(&cfg, speed);// 设置串口读取波特率cfsetospeed(&cfg, speed);// 设置串口写入波特率if(tcsetattr(fd, TCSANOW, &cfg)) {// 根据上面的配置,再次获取串口属性LOGE("再配置串口tcgetattr() 失败");
close(fd);// 关闭串口returnNULL;
}
}
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对象,并赋予丰富串口相关的值,成功了√√√");returnmFileDescriptor;// 把最终的成果,返回会Java层}
复制代码

这样我们就完成了整个打开串口的操作

3.3、发送和读取数据

我们读取和发送数据是对文件IO进行操作,我们肯定要在子线程中进行,

privatevoidstartReadThread(){
mSerialPortReadThread =newSerialPortReadThread(mFileInputStream) {@OverridepublicvoidonDataReceived(byte[] bytes){if(null!= mOnSerialPortDataListener) {
mOnSerialPortDataListener.onDataReceived(bytes);
}
}
};
mSerialPortReadThread.start();
}/**
* 串口消息读取线程
* 开启接收消息的线程
* 读取 串口数据 需要用到线程
*/
publicabstractclassSerialPortReadThreadextendsThread{publicabstractvoidonDataReceived(byte[] bytes);privatestaticfinalString TAG = SerialPortReadThread.class.getSimpleName();privateInputStream mInputStream;// 此输入流==mFileInputStream(关联mFd文件句柄)privatebyte[] mReadBuffer;// 用于装载读取到的串口数据publicSerialPortReadThread(InputStream inputStream){
mInputStream = inputStream;
mReadBuffer =newbyte[1024];// 缓冲区}@Overridepublicvoidrun(){super.run();// 相当于是一直执行?为什么要一直执行?因为只要App存活,就需要读取 底层发过来的串口数据while(!isInterrupted()) {try{if(null== mInputStream) {return;
}

Log.i(TAG,"run: ");intsize = mInputStream.read(mReadBuffer);if(-1== size ||0>= size) {return;
}byte[] readBytes =newbyte[size];// 拷贝到缓冲区System.arraycopy(mReadBuffer,0, readBytes,0, size);

Log.i(TAG,"run: readBytes = "+newString(readBytes));
onDataReceived(readBytes);// 发出去-(间接的发到SerialPortActivity中去打印显示)}catch(IOException e) {
e.printStackTrace();return;
}
}
}@Overridepublicsynchronizedvoidstart(){super.start();
}/**
* 关闭线程 释放资源
*/
publicvoidrelease(){
interrupt();if(null!= mInputStream) {try{
mInputStream.close();
mInputStream =null;
}catch(IOException e) {
e.printStackTrace();
}
}
}
}privatevoidstartSendThread(){// 开启发送消息的线程mSendingHandlerThread =newHandlerThread("mSendingHandlerThread");
mSendingHandlerThread.start();// HandlermSendingHandler =newHandler(mSendingHandlerThread.getLooper()) {@OverridepublicvoidhandleMessage(Message msg){byte[] sendBytes = (byte[]) msg.obj;if(null!= mFileOutputStream &&null!= sendBytes &&0< sendBytes.length) {try{
mFileOutputStream.write(sendBytes);if(null!= mOnSerialPortDataListener) {
mOnSerialPortDataListener.onDataSent(sendBytes);// 【发送 1】}
}catch(IOException e) {
e.printStackTrace();
}
}
}
};
}
复制代码

读取和写入数据,其实就是对那两个读入,读处流进行操作,就这样我们就完成了对串口的收发数据

3.4关闭串口

我们用完串口之后,肯定会把串口关闭的,关闭串口,我们就把启动的读和写的线程关掉,在Native层也把串口关掉,将文件句柄绑定的两个流也关掉

/**
* 关闭串口
*/
publicvoidcloseSerialPort(){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有用,希望兄弟们一键三连!

上一篇:

下一篇:

  推荐阅读

分享