1.概述
串口主要用于一些系统之间的轻量级数据传递,比如android系统与功能机系统直接两块板子需要传递一些数
据,类似下面说到的,心率,血压血氧,房颤,运动睡眠相关的一些传感器数据通常没有Android平台驱动和算法,经常需要在52832这样的系统里集成,再将需要的数据传递到android平台去。由于是两个独立的系统,52832系统与android系统本身毫无关联,所以没有其他办法可使用,只能通过串口。特别是当前物联网盛行的传感器时代,各种各样的传感器数据,那些没有兼容到android系统的传感器想要在android上应用,就只能采取这样的方式,才比较简单易开发。
1.1串口和波特率
想要通过串口读取和写入数据进行传递,首先需要驱动断提供一个可读写的串口,如/dev/ttyMT1,ttyMT2等。
并设置一个固定的波特率如115200,9600等。下面说到的使用的是串口/dev/ttyMT1 和 115200 。
1.2串口库和jar包开发
这里已经有一个开发好的串口libserialportJni.so和serialport.jar,我们直接导入
androidStudio,目录如下:
app/libs/serialport.jar app/libs/armeabi-v7a/libserialportJni.so
app/build.grade 配置:
android {
...
ndk {
//选择要添加的对应cpu类型的.so库(不需要的删除即可)。
abiFilters 'armeabi-v7a'
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
...
compile files('libs/serialport.jar')
}
1.3SerialPortMananger.java类的开发
这里我们封装一个管理类来统一写入和发送协议字串,这个管理类需要一个发送线程和一个写入线程循环写入
和接收数据;
内容如下:
public class SerialPortMananger {
//定义一个字节数组临时存放读取内容
private byte[] mReadBuffer =new byte[2048];
//定义一个监听器,一个接收到字串内容的方法和一个接收内容的方法
OnSerialPortDataListener onSerialPortDataListener;
SerialPort serialPort;
ReadDataThread mReadDataThread;
SendDataThread mSendDataThread;
private LinkedBlockingQueue<String> msgQueue;
public SerialPortMananger(){
}
public void setOnSerialPortDataListener(OnSerialPortDataListener onSerialPortDataListener){
this.onSerialPortDataListener = onSerialPortDataListener;
}
//打开串口节点方法
public void operSerialPort(){
serialPort = new SerialPort(new File(SerialPortConfig.SERIALPORT),SerialPortConfig.BAUDRATE,0);
startThread(getSendThread());
}
public SendDataThread getSendThread(){
if(mSendDataThread == null || mSendDataThread.isInterrupted() ||
mSendDataThread.getState() == Thread.State.TERMINATED){
mSendDataThread = new SendDataThread();
}
return mSendDataThread;
}
public ReadDataThread getReadThread(){
if(mReadDataThread == null || mReadDataThread.isInterrupted() ||
mReadDataThread.getState() == Thread.State.TERMINATED){
mReadDataThread = new ReadDataThread();
}
return mReadDataThread;
}
private void startThread(Thread thread) {
boolean isInterrupted = thread.isInterrupted();
LogUtils.e("SERIALPORT.startThread isInterrupted " + isInterrupted);
Thread.State state = thread.getState();
LogUtils.e("SERIALPORT.startThread state " + state.toString());
if (!isInterrupted && state == Thread.State.RUNNABLE) {
//do nothing
} else {
thread.start();
}
}
public void stopSendThread(){
if(mSendDataThread != null){
mSendDataThread.interrupt();
mSendDataThread =null;
}
LogUtils.e("SERIALPORT stopSendThread 停止发送线程");
}
public void stopReadThread(){
if(mReadDataThread != null){
mReadDataThread.interrupt();
mReadDataThread = null;
}
LogUtils.e("SERIALPORT stopReadThread 停止接收线程");
}
//开启接收数据线程
public void startReceiveData(){
startThread(getReadThread());
}
//关闭串口
public void closePort(){
stopSendThread();
stopReadThread();
if(serialPort != null){
serialPort.SerialClose();
serialPort = null;
}
}
//发送方法(封装为字串方便处理)
public void sendBytes(String command){
boolean result = enqueueTcpMsg(command);
LogUtils.e("SERIALPORT result =" + result);
}
protected LinkedBlockingQueue<String> getMsgQueue() {
if (msgQueue == null) {
msgQueue = new LinkedBlockingQueue<>();
}
return msgQueue;
}
//消息轮询队列
public boolean enqueueTcpMsg(final String command) {
if (command == null || getMsgQueue().contains(command)) {
return false;
}
try {
getMsgQueue().put(command);
return true;
} catch (InterruptedException e) {
LogUtils.e("SERIALPORT enqueueSerialPort InterruptedException " + e);
}
return false;
}
//发送线程
class SendDataThread extends Thread{
public SendDataThread( ){
}
@Override
public void run() {
String sendCommand = null;
try {
while (!Thread.currentThread().interrupted()) {
sendCommand = getMsgQueue().take();
if (serialPort != null && !TextUtils.isEmpty(sendCommand) ) {
byte[] sendBytes = UnicodeUtils.UnicodetoBytes(sendCommand);
LogUtils.e("SERIALPORT 开始写入 sendCommand=" + Arrays.toString(sendBytes));
boolean isSuccess = serialPort.SerialSendData(sendBytes);
onSerialPortDataListener.onDataSent(sendBytes, isSuccess);
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} catch (Exception e) {
LogUtils.e("SERIALPORT 发生异常=" + e);
}
}
}
//读取线程
class ReadDataThread extends Thread{
@Override
public void run() {
super.run();
if (serialPort != null) {
try {
while (!isInterrupted()) {
LogUtils.e("SERIALPORT 开始读取");
int size = serialPort.SerialReadData(mReadBuffer);
LogUtils.e("SERIALPORT 开始读取 size=" + size);
if (size > 0) {
byte[] readbytes = new byte[size];
System.arraycopy(mReadBuffer, 0, readbytes, 0, size);
LogUtils.e("SERIALPORT readbytes=" + Arrays.toString(readbytes));
onSerialPortDataListener.onDataReceived(readbytes);
}
}
}catch (Exception e){
LogUtils.e("SERIALPORT 发生异常 e=" + e);
}
}
}
}
}
1.3.1监听器写法:
public interface OnSerialPortDataListener {
/**
* 数据接收
*
* @param bytes 接收到的数据
*/
void onDataReceived(byte[] bytes);
/**
* 数据发送
*
* @param bytes 发送的数据
*/
void onDataSent(byte[] bytes,boolean isSuccess);
}
1.4调用方法
实现发送和接收监听器
xxxxxx implements OnSerialPortDataListener
初始化各个线程(也可将下面方法再封装为一个init方法)
serialPortMananger = new SerialPortMananger(); //获得管理类对象
serialPortMananger.setOnSerialPortDataListener(this);//监听串口发送和读取内容
serialPortMananger.operSerialPort();//打开串口和发送线程
serialPortMananger.startReceiveData();//开启接收数据线程
1.5发送内容和接收内容解析
1.5.1发送的内容,只是打印出来看看,保证自己发送的内容没有错误即可
@Override
public void onDataSent(byte[] bytes, boolean isSuccess) {
String sendContent = UnicodeUtils.bytesToHexFun1(bytes);
LogUtils.e("SERIALPORT onDataSent sendContent=" + sendContent);
}
1.5.2接收内容解析
发送的内容,最终都是字节数组形式,通常我们都是先使用16进制字符串,再转成字节数据比较方便。这个可以根据
串口协议而定。
@Override
public void onDataReceived(byte[] bytes) {
String hexContent = UnicodeUtils.bytesToHexFun1(bytes);
LogUtils.e("SERIALPORT onDataReceived hexContent=" + hexContent);
}
协议解析示例:
发送以下指令后计步传感器开始工作并返回数据:
-------------------------------------
0 1 2 3
-------------------------------------
数据头 命令 数据长度 结束包尾
0xaa 0xa0 0x04 0x55
-------------------------------------
那么按照管理类封装的方法,发送方法如下:
SerialPortConfig.MSG_STEP_START = "aaa00455";
serialPortMananger.sendBytes(SerialPortConfig.MSG_STEP_START);
如计步协议:
接收:
---------------------------------------------------------------------
0 1 2 3 4 5 6 7
---------------------------------------------------------------------
数据头 命令 数据长度 步数 步数 校验 校验 结束包尾
0xaa 0xa0 0x04 低8位 高8位 0x55
---------------------------------------------------------------------
接收到的步数十六进制字串如下:
//aaa0040A8200ffff55
isFinisedToRead = true;//保证数据完整性,是否读完
String headCache = "";
@Override
public void onDataReceived(byte[] bytes) {
String hexContent = UnicodeUtils.bytesToHexFun1(bytes);
LogUtils.e("SERIALPORT onDataReceived hexContent=" + hexContent);
if(hexContent.contains("aaa004") && hexContent.length() < 10){//有效数据到第十位,所以判断到第十位即可
headCache = hexContent;//若数据未读完,现将读取到的暂存
isFinisedToRead = false;
}
if(!isFinisedToRead) {//若数据没读完整,将暂存的字串组装在一起
hexContent = headCache + endCache;
}
if(hexContent.length()>6){
String HEAD = hexContent.substring(0, 6);
//判断若头部和计步协议相等,开始解析获取步数的高低位
if(HEAD.equalsIgnoreCase("aaa004") && hexContent.length() >= 10){
String steplow = hexContent.substring(6, 8);//获取低位
String stepHigh = hexContent.substring(8, 10);//获取高位
long step = Long.parseLong(stepHigh+steplow,16);//高低位合并并转为10进制步数
LogUtils.d("SERIALPORT step=" + step);
isFinisedToRead = true;//还原标志位
}
}
}
其他心率,血压,房颤,血氧等协议类似,不一一详述。