Android串口Serial服务解析

正常Android设备的串口一般是用作debug调试使用,随着Android设备使用越来越广,比如智能pos、智能扫码机都会用到Android主板和单片机进行通信,如果Android主板和单片机通信数据量大可以使用USB,若是通信数据量小可以使用串口进行通信,因为串口通信简单并且稳定性高,最近做的一个项目就是Android使用串口和加密芯片进行通信,Android主板使用的是rk3288和rk3368. 在rk3288源码中集成了一个串口demo,目录在

rk3288/frameworks/base/tests/SerialChat

网上存在一个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;
             }
        }
    };
}

首先是获取串口服务

 mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);

如果要想通过getSystemService获取serial服务,首先要在
core/java/android/content/Context.java:
添加

public static final String SERIAL_SERVICE = "serial";

在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));
                }});

每次开机会自动启动。可以通过命令查看当前服务是否启动

adb shell service list

这里写图片描述
可以看到服务已经启动。
目前启动的服务Java层面的服务,只是对我们C语言串口服务做了一层封装,真正工作的是我们native serial(SerialService).
所以registerService 函数中会获取SerialService的代理端,

 IBinder b = ServiceManager.getService(SERIAL_SERVICE);
                    return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));

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);
                }
            }

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);
}
 mContext = context;
        mSerialPorts = context.getResources().getStringArray(
                com.android.internal.R.array.config_serialPorts);

会获取目前存在可以使用串口列表。这个根据自己需要添加。
我的就使用一个串口3

  <string-array translatable="false" name="config_serialPorts">
    <item>"/dev/ttyS3"</item>
    </string-array>

回到SerialChat.java

String[] ports = mSerialManager.getSerialPorts(); 

结果就是/dev/ttyS3

mSerialPort = mSerialManager.openSerialPort(ports[1], 115200);

打开串口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;
    }

结果会调用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);
    }

最终打开调用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);
}

阻塞的方式打开串口

设置串口波特率已经属性

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);
}

到这里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;
             }
        }
    };
}

开启一个thread方式read 串口数据,
无数据会一直阻塞在ret = mSerialPort.read(mInputBuffer);
另外注意 的是读写buffer 要是用

  private ByteBuffer mInputBuffer;
    private ByteBuffer mOutputBuffer;

收到数据可以保存到普通的 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");
        }
    }

调用底层的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;
}

猜你喜欢

转载自blog.csdn.net/lb5761311/article/details/80618834
今日推荐