正常Android设备的串口一般是用作debug调试使用,随着Android设备使用越来越广,比如智能pos、智能扫码机都会用到Android主板和单片机进行通信,如果Android主板和单片机通信数据量大可以使用USB,若是通信数据量小可以使用串口进行通信,因为串口通信简单并且稳定性高,最近做的一个项目就是Android使用串口和加密芯片进行通信,Android主板使用的是rk3288和rk3368. 在rk3288源码中集成了一个串口demo,目录在
rk3288/frameworks/base/tests/SerialChat
1
网上存在一个demo是通过 java->jni->Serial驱动,虽然也可以正常读写,这样做缺点是
1、如果突然串口不能正常通信可能会导致app崩溃,
2、这个串口只能被当前的app使用,其他app 不能使用。
3、违背正常Android设计初衷,Android标准流程是
java->service->jni->hal->serial驱动。
所以我在项目中使用了Android源码中的demo
public class SerialChat extends Activity implements Runnable, TextView.OnEditorActionListener {
private static final String TAG = "SerialChat";
private TextView mLog;
private EditText mEditText;
private ByteBuffer mInputBuffer;
private ByteBuffer mOutputBuffer;
private SerialManager mSerialManager;
private SerialPort mSerialPort;
private boolean mPermissionRequestPending;
private static final int MESSAGE_LOG = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);
setContentView(R.layout.serial_chat);
mLog = (TextView)findViewById(R.id.log);
mEditText = (EditText)findViewById(R.id.message);
mEditText.setOnEditorActionListener(this);
if (false) {
mInputBuffer = ByteBuffer.allocateDirect(1024);
mOutputBuffer = ByteBuffer.allocateDirect(1024);
} else {
mInputBuffer = ByteBuffer.allocate(1024);
mOutputBuffer = ByteBuffer.allocate(1024);
}
}
@Override
public void onResume() {
super.onResume();
String[] ports = mSerialManager.getSerialPorts();
for(int 1 = 0;i < ports.length;i++ ){
Log.e("PPTV", "ports is ====" + ports[i] );
}
if (ports != null && ports.length > 0) {
try {
mSerialPort = mSerialManager.openSerialPort(ports[1], 115200);
if (mSerialPort != null) {
new Thread(this).start();
}
} catch (IOException e) {
}
}
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onDestroy() {
if (mSerialPort != null) {
try {
mSerialPort.close();
} catch (IOException e) {
}
mSerialPort = null;
}
super.onDestroy();
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (/* actionId == EditorInfo.IME_ACTION_DONE && */ mSerialPort != null) {
try {
String text = v.getText().toString();
Log.d(TAG, "write: " + text);
byte[] bytes = text.getBytes();
mOutputBuffer.clear();
mOutputBuffer.put(bytes);
mSerialPort.write(mOutputBuffer, bytes.length);
} catch (IOException e) {
Log.e(TAG, "write failed", e);
}
v.setText("");
return true;
}
Log.d(TAG, "onEditorAction " + actionId + " event: " + event);
return false;
}
public void run() {
Log.d(TAG, "run");
int ret = 0;
byte[] buffer = new byte[1024];
while (ret >= 0) {
try {
Log.d(TAG, "calling read");
mInputBuffer.clear();
ret = mSerialPort.read(mInputBuffer);
Log.d(TAG, "read returned " + ret);
mInputBuffer.get(buffer, 0, ret);
} catch (IOException e) {
Log.e(TAG, "read failed", e);
break;
}
if (ret > 0) {
Message m = Message.obtain(mHandler, MESSAGE_LOG);
String text = new String(buffer, 0, ret);
Log.d(TAG, "chat: " + text);
m.obj = text;
mHandler.sendMessage(m);
}
}
Log.d(TAG, "thread out");
}
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_LOG:
mLog.setText(mLog.getText() + (String)msg.obj);
break;
}
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
首先是获取串口服务
mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);
1
如果要想通过getSystemService获取serial服务,首先要在
core/java/android/content/Context.java:
添加
public static final String SERIAL_SERVICE = "serial";
1
在ContextImpl.java 中注册服务
registerService(SERIAL_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(SERIAL_SERVICE);
return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
}});
1
2
3
4
5
每次开机会自动启动。可以通过命令查看当前服务是否启动
adb shell service list
1
可以看到服务已经启动。
目前启动的服务Java层面的服务,只是对我们C语言串口服务做了一层封装,真正工作的是我们native serial(SerialService).
所以registerService 函数中会获取SerialService的代理端,
IBinder b = ServiceManager.getService(SERIAL_SERVICE);
return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
1
2
SerialService 注册在SystemServer.java 中
try {
Slog.i(TAG, "Serial Service");
// Serial port support
serial = new SerialService(context);
ServiceManager.addService(Context.SERIAL_SERVICE, serial);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting SerialService", e);
}
}
1
2
3
4
5
6
7
8
9
10
SerialService.java 源码
public class SerialService extends ISerialManager.Stub {
private final Context mContext;
private final String[] mSerialPorts;
public SerialService(Context context) {
mContext = context;
mSerialPorts = context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
public String[] getSerialPorts() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
ArrayList
for (int i = 0; i < mSerialPorts.length; i++) {
String path = mSerialPorts[i];
if (new File(path).exists()) {
ports.add(path);
}
}
String[] result = new String[ports.size()];
ports.toArray(result);
return result;
}
public ParcelFileDescriptor openSerialPort(String path) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
for (int i = 0; i < mSerialPorts.length; i++) {
if (mSerialPorts[i].equals(path)) {
return native_open(path);
}
}
throw new IllegalArgumentException("Invalid serial port " + path);
}
private native ParcelFileDescriptor native_open(String path);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
mContext = context;
mSerialPorts = context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
1
2
3
会获取目前存在可以使用串口列表。这个根据自己需要添加。
我的就使用一个串口3
1
2
3
4
回到SerialChat.java
String[] ports = mSerialManager.getSerialPorts();
1
结果就是/dev/ttyS3
mSerialPort = mSerialManager.openSerialPort(ports[1], 115200);
1
打开串口3
public SerialPort openSerialPort(String name, int speed) throws IOException {
try {
ParcelFileDescriptor pfd = mService.openSerialPort(name);//打开串口
if (pfd != null) {
SerialPort port = new SerialPort(name);
port.open(pfd, speed);//设置波特率及其他属性
return port;
} else {
throw new IOException("Could not open serial port " + name);
}
} catch (RemoteException e) {
Log.e(TAG, "exception in UsbManager.openDevice", e);
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
结果会调用SerialService.openSerialPort(name)
public ParcelFileDescriptor openSerialPort(String path) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
for (int i = 0; i < mSerialPorts.length; i++) {
if (mSerialPorts[i].equals(path)) {
return native_open(path);
}
}
throw new IllegalArgumentException("Invalid serial port " + path);
}
1
2
3
4
5
6
7
8
9
10
最终打开调用native_open 通过jni方式打开串口3。
static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path)
{
const char *pathStr = env->GetStringUTFChars(path, NULL);
int fd = open(pathStr, O_RDWR | O_NOCTTY);
if (fd < 0) {
ALOGE("could not open %s", pathStr);
env->ReleaseStringUTFChars(path, pathStr);
return NULL;
}
env->ReleaseStringUTFChars(path, pathStr);
jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
if (fileDescriptor == NULL) {
return NULL;
}
return env->NewObject(gParcelFileDescriptorOffsets.mClass,
gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
阻塞的方式打开串口
设置串口波特率已经属性
android_hardware_SerialPort.cpp
static void
android_hardware_SerialPort_open(JNIEnv *env, jobject thiz, jobject fileDescriptor, jint speed)
{
switch (speed) {
case 50:
speed = B50;
break;
case 75:
speed = B75;
break;
case 110:
speed = B110;
break;
case 134:
speed = B134;
break;
case 150:
speed = B150;
break;
case 200:
speed = B200;
break;
case 300:
speed = B300;
break;
case 600:
speed = B600;
break;
case 1200:
speed = B1200;
break;
case 1800:
speed = B1800;
break;
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
case 230400:
speed = B230400;
break;
case 460800:
speed = B460800;
break;
case 500000:
speed = B500000;
break;
case 576000:
speed = B576000;
break;
case 921600:
speed = B921600;
break;
case 1000000:
speed = B1000000;
break;
case 1152000:
speed = B1152000;
break;
case 1500000:
speed = B1500000;
break;
case 2000000:
speed = B2000000;
break;
case 2500000:
speed = B2500000;
break;
case 3000000:
speed = B3000000;
break;
case 3500000:
speed = B3500000;
break;
case 4000000:
speed = B4000000;
break;
default:
jniThrowException(env, "java/lang/IllegalArgumentException",
"Unsupported serial port speed");
return;
}
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
// duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
fd = dup(fd);
if (fd < 0) {
jniThrowException(env, "java/io/IOException", "Could not open serial port");
return;
}
env->SetIntField(thiz, field_context, fd);
struct termios tio;
if (tcgetattr(fd, &tio))
memset(&tio, 0, sizeof(tio));
tio.c_cflag = speed | CS8 | CLOCAL | CREAD;
// Disable output processing, including messing with end-of-line characters.
tio.c_oflag &= ~OPOST;
tio.c_iflag = IGNPAR;// 忽略奇偶校验错误
tio.c_lflag = 0; /* turn of CANON, ECHO*, etc */
/* no timeout but request at least one character per read */
tio.c_cc[VTIME] = 0;//如果读取不到就一直等待
tio.c_cc[VMIN] = 1;//读取一个byte就返回
tcsetattr(fd, TCSANOW, &tio);
tcflush(fd, TCIOFLUSH);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
到这里open 串口完成
串口写很简单。就不解析了。重点解析read ,由于我们是block 方式打开的串口,所以如果没有数据流返回,串口会一直阻塞
public void run() {
Log.d(TAG, "run");
int ret = 0;
byte[] buffer = new byte[1024];
while (ret >= 0) {
try {
Log.d(TAG, "calling read");
mInputBuffer.clear();
ret = mSerialPort.read(mInputBuffer);
Log.d(TAG, "read returned " + ret);
mInputBuffer.get(buffer, 0, ret);
} catch (IOException e) {
Log.e(TAG, "read failed", e);
break;
}
if (ret > 0) {
Message m = Message.obtain(mHandler, MESSAGE_LOG);
String text = new String(buffer, 0, ret);
Log.d(TAG, "chat: " + text);
m.obj = text;
mHandler.sendMessage(m);
}
}
Log.d(TAG, "thread out");
}
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_LOG:
mLog.setText(mLog.getText() + (String)msg.obj);
break;
}
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
开启一个thread方式read 串口数据,
无数据会一直阻塞在ret = mSerialPort.read(mInputBuffer);
另外注意 的是读写buffer 要是用
private ByteBuffer mInputBuffer;
private ByteBuffer mOutputBuffer;
1
2
收到数据可以保存到普通的 byte[]中。
public int read(ByteBuffer buffer) throws IOException {
if (buffer.isDirect()) {
return native_read_direct(buffer, buffer.remaining());
} else if (buffer.hasArray()) {
return native_read_array(buffer.array(), buffer.remaining());
} else {
throw new IllegalArgumentException("buffer is not direct and has no array");
}
}
1
2
3
4
5
6
7
8
9
10
调用底层的c语言的read函数
static jint
android_hardware_SerialPort_read_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
{
int fd = env->GetIntField(thiz, field_context);
jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
if (!buf) {
jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
return -1;
}
int ret = read(fd, buf, length);
if (ret < 0)
jniThrowException(env, "java/io/IOException", NULL);
return ret;
}
---------------------
版权声明:本文为CSDN博主「九霄的爸爸」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lb5761311/article/details/80618834