正常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 ( 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<String> ports = new ArrayList<String>();
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
<string-array translatable="false" name="config_serialPorts">
<item>"/dev/ttyS3"</item>
</string-array>
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;
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