文章目录
- 前言
- 硬件平台相关配置
- APP主要实现的功能
- APP层代码分析
- JNI native层代码分析
- 完整的工程代码
前言
现在一直在弄蓝牙的项目,已经有一年时间没有弄Android的东西了。现在有时间想把以前弄的东西整理一下,方便自己以后需要用时翻出来看看。
这个APP是以前在MT6735
平台Android 5.1(L1)
调试验证UART外设发送过来的数据是否正确,想着也许后面调试还用的着,就记录一下。
硬件平台相关配置
APP
需要操作平台设备硬件时,需要了解以下几点信息(我这里是在MTK
平台上,其它Android
平台也类似):
- 需要操作的UART是否有权限?系统默认其它APP没有读写权限,这里需要添加权限,以下有2种方式:
// 方式一:使用adb直接给UART设备添加权限
$ adb shell
$ chmod 0666 /dev/ttyMT*
// 方式二:在init.rc文件里面添加权限,每次开机它都自动添加权限。和方式一相比就不用每次手动添加权限;
chmod 0666 /dev/ttyMT*
chown system system /dev/ttyMT*
-
MTK
平台UART
硬件物理端口名称和软件字符设备名称对应关系:
硬件物理端口名称 | 软件字符设备名称 |
UART1 | /dev/ttyMT0 |
UART2 | /dev/ttyMT1 |
UART3 | /dev/ttyMT2 |
UART4 | /dev/ttyMT3 |
- 如何关闭SELinux权限? 在
Android 5.0
以上添加了这个权限,字符设备有read/write
权限APP也不能直接访问字符设备。这里有3种处理方式:
- 设备是ENG版本(有root权限),可以使用adb将SELinux关闭。
adb shell setenforce 0
- 将APP操作UART需要的权限添加到SELinux。(这里暂不介绍)
在Kernel LOG / Main Log 中查询关键字 “avc:” 看看是否有SELinux Policy Exception。
- 代码中关闭selinux机制。
文件路径:
bootable/bootloader/lk/platform/mt6735/rules.mk
文件中对应的内容:
// choose one of following value -> 1: disabled/ 2: permissive /3: enforcing
把SELINUX_STATUS := 3
修改为SELINUX_STATUS := 1
APP主要实现的功能
功能很简单,主要是将UART外设发送的数据实时的在APP上面显示出来。
APP层代码分析
APP层代码主要是2个class:MainActivity
和UartJniTool
- MainActivity是主界面窗口类,主要处理步骤如下:
-
uartJniTool = new UartJniTool(mhandler);
实例化UartJniTool
类,并且将mhandler传递给UartJniTool
对象保存起来,方便后面更新UI。(Android规定只有主线程才能更新UI) - 定义一个
readUartBtn
,被点击后uartJniTool.uartToolStart();
开始获取uart data,再次被点击停止获取数据并且关闭uart。 - 定义一个
writeUartBtn
,发送固定的一段字符串,验证APP是否可以正常的发送数据。 - 定义的mhandler主要是将接收到消息的数据,显示在TextView上;
public class MainActivity extends AppCompatActivity {
private static final String TAG= "MainActivity";
private static final int UARTDATA= 8001;
UartJniTool uartJniTool;
ToggleButton readUartBtn;
Button writeUartBtn;
TextView uartText;
private int offset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uartJniTool = new UartJniTool(mhandler);
//uartJniTool.uartToolStart();
uartText = (TextView)findViewById(R.id.uartText);
uartText.setMovementMethod(ScrollingMovementMethod.getInstance());
readUartBtn = (ToggleButton)findViewById(R.id.readUartBtn);
writeUartBtn = (Button)findViewById(R.id.writeUartBtn);
readUartBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (readUartBtn.isChecked()){
int result = uartJniTool.uartToolStart();
if (result < 0){
readUartBtn.setChecked(false);
}
}else {
uartJniTool.uartToolStop();
}
}
});
writeUartBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
uartJniTool.writeUartData();
}
});
}
Handler mhandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UARTDATA:
int uartData = msg.getData().getInt("UARTDATA");
Log.i(TAG, "handleMessage---->"+uartData);
uartText.append(numToHex8(uartData)+" ");
offset=uartText.getLineCount()*uartText.getLineHeight();
if(offset>uartText.getHeight()){
uartText.scrollTo(0,offset-uartText.getHeight());
}
break;
}
super.handleMessage(msg);
}
};
public static String numToHex8(int b) {
return String.format("0x%02x", b);//2表示需要两个16进行数
}
- UartJniTool是调用JNI native方法接口类,主要处理步骤如下:
- 加载本地
libUartToothJni.so
库,通过方法System.loadLibrary("UartToothJni");
来加载。 -
getUartDataHandler
方法是JNI层获取到UART数据的后,主动回调该方法。getUartDataHandler
方法拿到数据后,通过Handler发送给MainActivity显示出来。 -
uartToolStart
功能是打开和读取UART的数据,uartToolStop
功能是关闭UART,writeUartData
功能是发送固定的数据验证APP是否可以操作UART。
public class UartJniTool {
private static final String TAG= "UartJniTool";
private static final int UARTDATA= 8001;
private Handler mhandler;
static {
System.loadLibrary("UartToothJni");
}
public UartJniTool(Handler mhandler) {
this.mhandler = mhandler;
}
public void getUartDataHandler(int data)
{
Log.i(TAG, "getUartDataHandler---->"+data);
Message msg = new Message();
msg.what = UARTDATA;
Bundle bundle = new Bundle();
bundle.putInt("UARTDATA", data);
msg.setData(bundle);//mes利用Bundle传递数据
mhandler.sendMessage(msg);//用activity中的handler发送消息
}
public native int uartToolStart();
public native int uartToolStop();
//public native String getUartData();
public native int writeUartData();
}
JNI native层代码分析
我们需要定义JNI的头文件,一般都是通过命令自动生成的,如下(具体操作百度吧,很多的):
javah -jni package name.class name
如果你比较熟悉JNI了,其实也不用javah
命令生成头文件也可以。JNI函数的名称定义是有一定的规律:
Java flag + package name + class name + method name
For example:Java_com_lututong_uarttools_UartJniTool_uartToolStart
-
Java_com_lututong_uarttools_UartJniTool_uartToolStart
对应app层UartJniTool
类的uartToolStart
方法,主要功能如下:
- 获取并且保存Java虚拟机和调用JNI的对象,为
read thread
主动调用app层方法做好准备。 -
uart_tool_start
主要open和init uart;
-
Java_com_lututong_uarttools_UartJniTool_uartToolStop
主要功能是停止获取数据,并且关闭UART。 -
Java_com_lututong_uarttools_UartJniTool_writeUartData
主要功能是发送固定的数据验证APP是否可以操作UART。
/*
* Class: com_lututong_uarttools_UartJniTool
* Method: uartToolStart
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_uartToolStart(JNIEnv *env, jobject obj)
{
int ret;
//get java VM and object
env->GetJavaVM(&gs_jvm);
gs_object = env->NewGlobalRef(obj);
// open and init uart
ret = uart_tool_start();
return ret;
}
/*
* Class: com_lututong_uarttools_UartJniTool
* Method: uartToolStop
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_uartToolStop(JNIEnv *env, jobject obj)
{
uart_tool_stop();
return 0;
}
/*
* Class: com_lututong_uarttools_UartJniTool
* Method: writeUartData
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_writeUartData(JNIEnv *env, jobject obj)
{
char *buffer = "cai.zhong!!!";
int ret = -1;
if(serial_fd >0){
ret = write(serial_fd, buffer, 9);
//for (int i = 0; i < 7; i++)
//ret = write(serial_fd, &buffer[0], 1);
}
if(ret > 0)
LOGE("[cai.zhong]Write data successfully!\n");
return 0;
}
- 打开UART并且设置波特率和其它属性。
int uart_speed(int speed) {
switch (speed) {
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
case 230400:
return B230400;
case 460800:
return B460800;
case 500000:
return B500000;
case 576000:
return B576000;
case 921600:
return B921600;
default:
return B57600;
}
}
int init_serial(int speed) {
struct termios ti;
int baudenum;
int fd_bt;
// fd_bt = open(UART_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
fd_bt = open(UART_DEVICE, O_RDWR | O_NOCTTY );
if (fd_bt < 0) {
LOGE("Can't open serial port %s\n", UART_DEVICE);
return -1;
}
else
LOGI("open %s successfully!!! fd_bt=%d", UART_DEVICE, fd_bt);
tcflush(fd_bt, TCIOFLUSH);
if (tcgetattr(fd_bt, &ti) < 0) {
LOGE("Can't get serial port setting\n");
close(fd_bt);
fd_bt=-1;
return -1;
}
cfmakeraw(&ti);
ti.c_cflag |= CLOCAL;
ti.c_lflag = 0;
ti.c_cflag &= ~CRTSCTS;
ti.c_iflag &= ~(IXON | IXOFF | IXANY);
/* Set baudrate */
baudenum = uart_speed(speed);
// if ((baudenum == B115200) && (speed != 115200)) {
if ((baudenum == B9600) && (speed != 9600)) {
LOGE("Serial port baudrate not supported!\n");
close(fd_bt);
fd_bt=-1;
return -1;
}
cfsetospeed(&ti, baudenum);
cfsetispeed(&ti, baudenum);
if (tcsetattr(fd_bt, TCSANOW, &ti) < 0) {
LOGE("Can't set serial port setting\n");
close(fd_bt);
fd_bt=-1;
return -1;
}
tcflush(fd_bt, TCIOFLUSH);
return fd_bt;
}
-
thread_exit
关闭read线程。 -
bt_rx_monitor
read线程将获取到的数据发送给APP层,主要的步骤如下:
- 通过
gs_jvm
获取JNIEnv
环境变量,通过gs_object
获取jclass
对象类; - 调用
GetMethodID
方法拿到UartJniTool
类的getUartDataHandler
方法; - 通过
read
获取UART接收到的数据; - 通过
CallVoidMethod
方法来将数据传给UartJniTool
类的getUartDataHandler
方法;
-
uart_tool_start
初始化串口、创建和启动read thread
; -
uart_tool_stop
杀掉read thread
和关闭UART;
static void thread_exit(int signo)
{
pthread_t tid = pthread_self();
LOGI("Thread %lu exits\n", tid);
pthread_exit(0);
}
void *bt_rx_monitor(void *ptr)
{
char ucRxBuf;
int ret;
JNIEnv *env;
jclass ClassCJM;
jmethodID MethodGetUartDataHandler;
jobject getUartDataHandlerDescriptor;
LOGI("Thread %lu starts\n", rxThread);
#if 1
if (gs_jvm != NULL) {
gs_jvm->AttachCurrentThread((JNIEnv **)&env, NULL);
ClassCJM = env->GetObjectClass(gs_object);
MethodGetUartDataHandler = env->GetMethodID(ClassCJM, "getUartDataHandler", "(I)V");
//etUartDataHandlerDescriptor = env->NewObject(ClassCJM, MethodGetUartDataHandler);
}
#endif
while (1) {
if(serial_fd >0)
{
ret = read(serial_fd, &ucRxBuf, 1);
LOGE("[cai.zhong]Receive data= %d ret=%d\n",ucRxBuf,ret);
if(ret < -1){
LOGE("Receive packet from external device fails\n");
break;
}else if(ret >0){
if (gs_object) {
env->CallVoidMethod(gs_object, MethodGetUartDataHandler, ucRxBuf);
}
}
}
}
//#endif
return 0;
}
int uart_tool_start(void)
{
serial_fd = init_serial(9600);//115200
if (serial_fd < 0) {
LOGE("Initialize serial port to Device fails\n");
return -1;
}
//signal(SIGRTMIN, thread_exit);
/* Create RX monitor thread */
pthread_create(&rxThread, NULL, bt_rx_monitor, NULL);
LOGI("UART TOOL mode start\n");
return 0;
}
void uart_tool_stop(void)
{
/* Wait until thread exit */
pthread_kill(rxThread, 0);
//pthread_join(rxThread, NULL);
//signal(SIGRTMIN, SIG_DFL);
close(serial_fd);
serial_fd = -1;
}
完整的工程代码
gitee地址: https://gitee.com/dianqi0901zc/UartTools