搞懂Android串口通信 安卓系统串口通信
xsobi 2024-12-24 16:26 1 浏览
小伙子,简历上说你搞过串口通信,说说吧!
1、串口通信是什么
串行通信技术,是指通信双方按位进行,遵守时序的一种通信方式
说人话就是将数据按位依次传输
画个图
串口就相当于一个管道,在硬件方面也有表示,有三根跳线, 一个是Tx线,一个是Rx线,还有一根是地线,这个管道传输的数据,也就是bit是串行的,有顺序的
2、串口的应用场景
串口通信这个东西,在Android开发中用到的并不多,我们绝大多数App都是用Http和后台进行通信,获取后台数据并展示,而串口通信是应用在,智能家居,和单片机通信的场景,人脸识别门禁,利用串口控制门开关,自动售货机Android收到付款成功的消息后,发送串口指令,控制货道进行出货等等 Android的设备已经超过20亿了,相对来说串口在Android应用还是挺广泛的
3、Android怎么实现串口通信的
3.1、第一步找到串口文件
Android的串口文件是有一个单独的目录的
我们操作的就是这个ttys开头的文件
用代码是怎么操作的呢
private ArrayList<Driver> getDrivers() throws IOException {
ArrayList<Driver> 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" + "exit\n";
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, "<init>", "()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 < sendBytes.length) {
try {
mFileOutputStream.write(sendBytes);
if (null != mOnSerialPortDataListener) {
mOnSerialPortDataListener.onDataSent(sendBytes); // 【发送 1】
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
}
复制代码
读取和写入数据,其实就是对那两个读入,读处流进行操作,就这样我们就完成了对串口的收发数据
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有用,希望兄弟们一键三连!
相关推荐
- 斗鱼针针成旻云个人资料 针针年龄身高演艺经历介绍
-
[闽南网]针针成旻云曾是七煌旗下签约艺人,经常在斗鱼进行直播身高超过170cm的她更因为有一双大长腿而被称为“斗鱼第一美腿”、“电竞第一腿”。本文这就来对针针的个人资料进行详细介绍,想知道她的年龄身高...
- 轻量级RTSP服务模块和RTSP推流模块适用场景区别
-
好多开发者一直搞不清轻量级RTSP服务SDK和RTSP推流SDK的区别,以下是相关区别:1.轻量级RTSP服务模块:轻量级RTSP服务解决的核心痛点是避免用户或者开发者单独部署RTSP或者RTMP服...
- 《新·奥特曼》11月18日国内视频平台上线
-
《新·奥特曼》海报。新京报讯11月14日,由上海新创华文化发展有限公司授权引进电影《新·奥特曼》宣布正式定档11月18日(周五)00:00上线视频平台,上线版本为日语配音中文字幕版。影片由庵野秀明(...
- 剑指Apple Watch!Android Wear也将支持视频功能
-
想必智能手表发展到现在,大家最期待的还是视频功能,近日AndroidWear就实现了这一功能,以后就能在手表上看视频了,想想就挺激动的,快来看看吧!其实早在WWDC大会上,老对手AppleWatc...
- QT应用编程:基于VLC开发音视频播放器(句柄方式)
-
一、环境介绍操作系统:win1064位QT版本:QT5.12.6编译器:MinGW32VLC版本:...
- OBS 源码分析 obs开发
-
本文将按照数据源的获取、渲染、推送的直播流程来让大家深入了解一下。1、直播源数据获取obs在启动时会优先加载libobs核心库,这个库初始化obs很多内容,包括crash模块、com、性能监...
- Android和iOS端Moments更新:支持视频分享功能
-
Moments是社交网络巨头Facebook推出的一款私人照片分享应用,今天公司宣布对Android端和iOS端应用同时升级,新增对视频分享功能的支持。事实上早在数周之前,Facebook就曾表示Mo...
- 您很快就可以在Android Galaxy设备之间传输视频通话
-
在阅读此文之前,辛苦点击右上角的“关注”,既方便您进行讨论与分享,又能给您带来不一样的参与感,感谢您的支持!导语:在科技领域,每时每刻都有新的发展,令人兴奋的创新不断涌现。早在八月份,Android系...
- 一篇文章带你FFmpeg到流媒体服务器开发
-
安装ffmpeg:下载FFmpeg和libx264的包ffmpeg-2.4.1.tar.bz2last_x264.tar.bz2libx264需要yasm,所以先安装yasmapt-getinst...
- YouTube 为 Android 平台提供 1440P 视频
-
安锋网8月10日消息,Android从起初的480P的屏幕分辨率发展到2014年的1440P花了将近六年的时间,一般认为1080P的屏幕分辨率已经是人眼可以识别的极限,但是...
- FFmpeg 调用 Android MediaCodec 进行硬解码(附源码)
-
FFmpeg在3.1版本之后支持调用平台硬件进行解码,也就是说可以通过FFmpeg的C代码去调用Android上的MediaCodec了。在官网上有对应说明,地址如下:trac....
- Android FFmpeg + OpenGL ES YUV Player
-
1、FFmpeg解出YUV帧数据1.1方法介绍打开封装格式上下文...
- 基于WebRTC的Android移动端无线视频传输
-
摘要:视频传输技术在现代社会广泛应用,人们对其的要求也越来越高,其发展的趋势是方便、快捷、随时随地。传统的视频传输过于依赖线路,线路的走向限制了传输的很多可能,所以无线传输才是发展的方向。本文提出...
- 使用python爬取抖音app视频 python爬取抖音视频数据
-
记录一下如何用python爬取app数据,本文以爬取抖音视频app为例。编程工具:pycharm...
- Android IOS WebRTC 音视频开发总结(七七)-- WebRTC的架构和协议栈
-
本文主要介绍WebRTC的架构和协议栈(我们翻译和整理的,译者:litie),最早发表在【编风网】为了便于理解,我们来看一个最基本的三角形WebRTC架构(见下图)。在这个架构中,移动电话用“浏览器M...
- 一周热门
- 最近发表
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)
- android studio 3 0 (34)
- android开发视频 (30)