下面我们就针对两种方式进行实现:
如果我们在app里直接调用getSystemService, 会发现没办法直接获取串口服务.需要对系统的接口进行修改.
参考
参考1
参考2
SerialManager mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);
启用SerialService
其掉这三个文件中的@hide标识:
frameworks/base/core/Java/Android/hardware/SerialManager.java
frameworks/base/core/java/android/hardware/SerialPort.java
frameworks/base/core/java/android/hardware/ISerialManager.aidl
SerialService文件位置:
frameworks/base/services/java/com/android/server/SerialService.java
该服务会在SystemSever.java中进行初始化,这里还需要将Context.java中的SERIAL_SERVICE的@hide去掉:
frameworks/base/core/java/android/content/Context.java
- /*
- * @hide
- */
public static final String SERIAL_SERVICE = "serial";
还有, SerialService是通过读取R.array.config_serialPorts这个String array来加载的/dev/设备节点:
public SerialService(Context context) {
mContext = context;
mSerialPorts = context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
所以还需要添加下,文件位置:
framework/base/core/res/res/values/config.xml
在config_serialPorts中添加相应设备节点
- "/dev/ttyS4"
修改后可以通过命令查看服务是否启动: adb shell service list
在使用时应用需要添加uses-permission权限 android.permission.SERIAL_PORT ;
安卓提供了个测试工具,在framework/base/tests/SerialChat, 可以进入该目录, 输入mm 进行编译.
权限授权问题, 我在测试这个SerialChat程序时发现android.permission.SERIAL_PORT并未被授权, 通过pm.checkPermission(“android.permission.SERIAL_PORT”, pinfo.packageName) 检查可以看到。
查看frameworks/base/core/res/AndroidManifest.xml中发现,该permission定义如下:
可以看到protectionLevel是signature|systeml;
signature表示当申请此权限的应用程序的签名与声明此权限的应用的签名相同时才会授权, 该应用是framwork-res.apk,使用的签名是platform,所以需要应用也要使用platform签名。
system表示是系统应用;
所以这里修改Android.mk添加LOCAL_CERTIFICATE := platform 再重新编译
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := SerialChat
include $(BUILD_PACKAGE)
同时也注意修改AndroidManifest.xml 添加 shared uid
1, 报错1
处理:
a. 问题应该是在Android.mk中添加了LOCAL_CERTIFICATE := platform引起的.
但是如果不加这个, 编译出来的app是普通的app , 并没有系统权限. 那就要查下为什么这个配置会导致编译不过.
后面发现是lunch的时候选的是lunch rk3399_firefly_box-userdebug, 所有这个TAGS不应该是tests, 改为lunch所选的userdebug即可.
把编译好的app(out/target/product/rk3399_firefly_box/system/app/SerialChat)
push到设备的/system/app/SerialChat下重启.
发现启动后无法进行串口操作. 发现我们通过String[] ports = mSerialManager.getSerialPorts();
获取的端口是空的. 这需要重新看rk3399的文档 , 看下串口的操作说明.
参考
Firefly-RK3399 支持五路UART:UART0, UART1, UART2, UART3, UART4,都拥有两个64字节的FIFO缓冲区,用于数据接收和发送。 其中:
UART0用于蓝牙传输,UART2用作调试串口,只有UART0和UART3支持硬件自动流控。
Firefly-RK3399开发板为了方便用户使用,引出了一排通用的GPIO,其对应原理图如下图:
GPIO1_A7和GPIO1_B0两个IO口可复用为uart4_rx和uart4_tx。
我们可以按照如上说明 , 选取uart4作为测试串口 , 将串口通过usb转串口工具和pc相连.
DTS配置
文件kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi 有UART相关节点的定义:
aliases {
...
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
serial4 = &uart4;
};
serial0等串口在该文件的 aliases 节点中被定义为:serial0 = &uart0;
因为Firefly-RK3399开发板引出了uart4供用户使用,所以这里就以uart4为例,介绍使用方法。下面是uart4节点相关定义:
uart4: serial@ff370000 {
compatible = "rockchip,rk3399-uart", "snps,dw-apb-uart";
reg = <0x0 0xff370000 0x0 0x100>;
clocks = <&pmucru SCLK_UART4_PMU>, <&pmucru PCLK_UART4_PMU>;
clock-names = "baudclk", "apb_pclk";
interrupts = ;
reg-shift = <2>;
reg-io-width = <4>;
pinctrl-names = "default";
pinctrl-0 = <&uart4_xfer>;
status = "disabled";
};
uart4 {
uart4_xfer: uart4-xfer {
rockchip,pins =
<1 7 RK_FUNC_1 &pcfg_pull_up>,
<1 8 RK_FUNC_1 &pcfg_pull_none>;
};
};
只需要在kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-port.dtsi文件中使能该节点即可使用,如下:
&uart4 {
current-speed = <115200>;
no-loopback-test;
status = "okay";
};
修改后重新编译kernel , 因为改动了dts, 所有需要将resource.img重新烧录.
修改完后 , 对应的串口设备目录是 /dev/ttyS4
crw-rw-rw- 1 gps gps 4, 68 2019-07-01 09:20 ttyS4
串口测试:
echo firefly uart4 test… > /dev/ttyS4
cat /dev/ttyS4
开pc端的串口工具测试, 是否可以收发数据.
如下可以看到可以正常收发数据.
现在我们要重新回到android的SerialChat ,配置的时候需要添加这一项.
在app的代码中将串口选中第3项>/dev/ttyS4
修改完后发现还是无法获取ports.
初步猜测是修改了framework/base/core/res/res/values/config.xml但是没有重新编译资源文件导致回去的ports是空.
重新编译资源文件:
参考
在framework/base/core/res/res 下添加资源文件后需要先编译资源 然后编译framework 才可正常引用
进入项目根目录 cd frameworks/base/core/res/ 执行mm命令(原生或高通), 编译 framework-res.apk
或 ./mk mm frameworks/base/core/res(mtk 依据各平台编译命令有所不同)
编译完后com.android.internal.R中会生成资源的引用。
在目录frameworks/base/ 下执行mm 编译 framework.jar (原生或高通)
或 ./mk mm frameworks/base (mtk 依据各平台编译命令有所不同)
如果 frameworks/base/services 下有修改,则也要编译
frameworks/base/services/java/ 执行mm 编译 services.jar
或./mk mm frameworks/base/services/java
执行如下命令
adb remount
adb push framework-res.apk /system/framework/
adb push framework.jar /system/framework/
adb push services.jar /system/framework/ (如果有修改的话)
有的系统还有编译framework后还生成了secondary_framework.jar
也要push。
push完成之后,可以cd system/framework 进入目录,
然后 ll 两个小写L命令 确认下是否push成功
adb reboot 重启设备。
可以看到, 修改完后, app启动可以获取到配置中的ports了
通过pc端的SecureCRT可以测试和app的通信.
但是遗留一个问题, 串口本来是设置了115200的波特率 , 但最后还是用9600才通信成功 , 待分析…
之前发现即使在dts中配置了uart4的baudrate为115200 , 但是实际测试中, 只有用9600的参数进行通信才正常.
后面参考网上操作 , 修改kernel/arch/arm64/boot/dts/rockchip/rk3399-android.dtsi, 将波特率改为115200.
再修改uboot中的配置.u-boot/configs/rk3399_box_defconfig
重新编译uboot. 发现报错.
修复: 编译前执行 make distclean 即可.
make distclean
make rk3399_box_defconfig
make ARCHV=aarch64 -j4
修改完后烧写uboot.img , 发现还行只能用9600的波特率进行串口通讯.⊙﹏⊙‖∣
后面发现是我理解存在问题.
重新将SerialChat.apk编译 , 指定baudrate = 115200 ,
pc端指定baudrate为115200 , 可以正常通讯 ,
所以前面通过cat /dev/ttyS4 和 echo 123 > /dev/ttyS4 的测试方式是系统默认用9600方式交互.
而后面app和pc端串口通信则保持相互统一即可.
最终我们可以看到app端在edit text里输入的文字在pc的串口工具正常接收:
用完了系统自带的工具进行串口通讯我们发现限制还是挺多的 ,
需要修改系统res的配置, 需要修改kernel等dts或uboot. 如果我们想用app放在不同系统上测试呢?
每次都用SerialChat.apk修改起来不方便(太依赖androidstudio了, _), 不通过系统服务的方式是否可以操作串口呢?
作为一个普通的app , 我们发现是没办法直接引用SerialManager. 除非把app像SerialChat一样, 放在系统源码一起编译.
那我们可以尝试将对系统串口驱动的访问放在native层实现 , 通过jni封装一层给到接口在java调用.
但是最后访问串口设备节点可能还是要手动修改节点权限才行.
整个代码可以参考google之前给的demo
android-serialport-api
先在工程中将android-serialport-api的SerialPort.java拷贝到java/com/aispeech/astools/jni目录下.
将android-serialport-api/jni下的SerialPort.c拷贝到当前工程的cpp/serial下
用javah生成头文件, 将生成的头文件中的函数名拷贝到cpp/serial/SerialPort.c中进行替换
javah -encoding UTF-8 -jni com.aispeech.astools.jni.SerialPort
由于之前工程用的是Android.mk来编译的 , 现在改成了cmakelist, 所有要稍稍修改下.
后面就是将收发代码copy过来修改测试.
try {
mSerialPort = new SerialPort(new File(UART_PATH), BAUDRATE, 0);
mOutputStream = mSerialPort.getOutputStream();
mInputStream = mSerialPort.getInputStream();
/* Create a receiving thread */
mReadThread = new ReadThread();
mReadThread.start();
// mSendingThread = new SendingThread();
// mSendingThread.start();
} catch (Exception e) {
e.printStackTrace();
}
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
while (!isInterrupted()) {
int size;
try {
byte[] buffer = new byte[64];
if (mInputStream == null) {
return;
}
size = mInputStream.read(buffer);
Log.d(TAG, "read size: " + size);
if (size > 0) {
onDataReceived(Arrays.copyOfRange(buffer, 0, size), size);
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}