前言
最近在做手机跟外设交互,因为之前没有涉猎过这方面,做起来真的是头大。幸好有万能的百度和无所不能的google,以及程序员的小帮手github,多方查询资料,咨询同事,以及万能的群友帮助,终于顺利实现了第一款串口编程的App。不得不说现在的手机越来越强大,都可以通过USB接口,直接读取其它外设的数据了。写这篇博客一是为了记录一下这次开发的经验,二是给后来的同学提供一些经验。
基本常识
串口通信:指串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以使用一根线发送数据的同时接收数据。
在串口通信中,常用的协议包括RS232、RS-422和RS-485.
我这次工作中对接的是RS232,当然具体是哪种协议和你选择的硬件有关,将你的硬件插到对应的协议的串口即可。
开发前准备
1.检查你的硬件装备
正确连接你的设备,向你的硬件提供商索要开发资料,或者说明书。基本的资料包括硬件的通讯命令格式。类似下图,
2.正确的连接,测试你的硬件与系统
串口电脑调试助手
Android USB串口调试助手
下载一个串口助手,按照资料输入命令。测试是否能够成功的启动设备,收到对应的返回数据。
我的硬件设备连接线是DB9的USB-RS-232转接线,再配一个转接头,即可
开发阶段
整体的开发流程如下:打开串口–>开启接收线程(ReadThread)–>发送串口数据–>接收数据,处理返回信息–>关闭接收数据线程–>关闭串口。
获取权限
1.要使用手机的USB接口首先要获取相关的权限
2.在需要打开串口的activity,添加如下数据
3.在res目录下新建xml目录,新建device_filter.xml文件,内容如下:
导入jar包
这里是我自己的独家jar包,简单明了,好用,易上手,里面已经封装好了,不需要自己进行繁琐的JNI操作,特别是对JNI不熟悉的同学。这个独家秘笈我就不免费放出来了,如果你用这个手机调试助手能正常调试,再去这里下载助手源码。
USBSerialPortUtils
下面看重点,我封装到utils中的方法
- 初始化基本参数
/**
* oncreate
* 为了准确提示,使用位置,故定义成了onCreate
* 1个停止位,8个数据位,奇校验,16进制,波特率
* flowControl None
* @param context
*/
public void onCreate(Context context) {
try {
ftD2xx = D2xxManager.getInstance(context);
} catch (D2xxManager.D2xxException e) {
Log.e("FTDI_HT", "getInstance fail!!");
}
//这里为了从子线程切换到主线程
handler = new MyHandler(context);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "My Lock");
global_context = context;
// init modem variables
modemReceiveDataBytes = new int[1];
modemReceiveDataBytes[0] = 0;
/* allocate buffer */
writeBuffer = new byte[512];
readBuffer = new byte[UI_READ_BUFFER_SIZE];
readDataBuffer = new byte[MAX_NUM_BYTES];
actualNumBytes = 0;
// start main text area read thread
HandlerThread handlerThread = new HandlerThread(handler);
handlerThread.start();
baudRate = Integer.parseInt(PublicCache.getBaudRate());
stopBit = 1;
dataBit = 8;
/**
*
None parity = 0;
Odd parity = 1;
Even parity = 2;
Mark parity = 3;
Space parity = 4;
*/
parity = 1;
/**
None flowControl = 0;
CTS/RTS flowControl = 1;
DTR/DSR flowControl = 2;
XOFF/XON flowControl = 3;
*/
flowControl = 0;
portIndex = 0;
//16进制HEX
bFormatHex = true;
configParams();
}
2.打开串口,配置基本参数
/**
* 打開串口
*/
private void connectFunction() {
if (portIndex + 1 > DevCount) {
portIndex = 0;
}
if (currentPortIndex == portIndex && ftDev != null && ftDev.isOpen()) {
//Toast.makeText(global_context,"Port("+portIndex+") is already opened.", Toast.LENGTH_SHORT).show();
return;
}
if (bReadTheadEnable) {
bReadTheadEnable = false;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ftDev = ftD2xx.openByIndex(global_context, portIndex);
uart_configured = false;
if (ftDev == null) {
if (!isMainThread()) {
Looper.prepare();
Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
Looper.loop();
} else
Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
if (onReceivedListener != null)
onReceivedListener.openFailure();
return;
}
if (ftDev.isOpen()) {
currentPortIndex = portIndex;
if (!isMainThread()) {
Looper.prepare();
Toast.makeText(global_context, "open device port(" + portIndex + ") OK", Toast.LENGTH_SHORT).show();
Looper.loop();
} else {
Toast.makeText(global_context, "open device port(" + portIndex + ") OK", Toast.LENGTH_SHORT).show();
}
if (onReceivedListener != null)
onReceivedListener.openSuccess();
if (!bReadTheadEnable) {
ReadThread readThread = new ReadThread(handler);
readThread.start();
}
} else {
if (!isMainThread()) {
Looper.prepare();
Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
Looper.loop();
} else {
Toast.makeText(global_context, "Open port(" + portIndex + ") NG!", Toast.LENGTH_LONG).show();
}
if (onReceivedListener != null)
onReceivedListener.openFailure();
}
}
/***
* 配置参数
*/
private void configParams() {
createDeviceList();
if (DevCount > 0) {
connectFunction();
}
if (DeviceStatus.DEV_NOT_CONNECT == checkDevice()) {
return;
}
setConfig(baudRate, dataBit, stopBit, parity, flowControl);
uart_configured = true;
}
/**
* 配置数据位,校验位,停止位
* @param baud
* @param dataBits
* @param stopBits
* @param parity
* @param flowControl
*/
private void setConfig(int baud, byte dataBits, byte stopBits, byte parity, byte flowControl) {
// configure port
// reset to UART mode for 232 devices
ftDev.setBitMode((byte) 0, D2xxManager.FT_BITMODE_RESET);
ftDev.setBaudRate(baud);
switch (dataBits) {
case 7:
dataBits = D2xxManager.FT_DATA_BITS_7;
break;
case 8:
dataBits = D2xxManager.FT_DATA_BITS_8;
break;
default:
dataBits = D2xxManager.FT_DATA_BITS_8;
break;
}
switch (stopBits) {
case 1:
stopBits = D2xxManager.FT_STOP_BITS_1;
break;
case 2:
stopBits = D2xxManager.FT_STOP_BITS_2;
break;
default:
stopBits = D2xxManager.FT_STOP_BITS_1;
break;
}
switch (parity) {
case 0:
parity = D2xxManager.FT_PARITY_NONE;
break;
case 1:
parity = D2xxManager.FT_PARITY_ODD;
break;
case 2:
parity = D2xxManager.FT_PARITY_EVEN;
break;
case 3:
parity = D2xxManager.FT_PARITY_MARK;
break;
case 4:
parity = D2xxManager.FT_PARITY_SPACE;
break;
default:
parity = D2xxManager.FT_PARITY_NONE;
break;
}
ftDev.setDataCharacteristics(dataBits, stopBits, parity);
short flowCtrlSetting;
switch (flowControl) {
case 0:
flowCtrlSetting = D2xxManager.FT_FLOW_NONE;
break;
case 1:
flowCtrlSetting = D2xxManager.FT_FLOW_RTS_CTS;
break;
case 2:
flowCtrlSetting = D2xxManager.FT_FLOW_DTR_DSR;
break;
case 3:
flowCtrlSetting = D2xxManager.FT_FLOW_XON_XOFF;
break;
default:
flowCtrlSetting = D2xxManager.FT_FLOW_NONE;
break;
}
ftDev.setFlowControl(flowCtrlSetting, XON, XOFF);
uart_configured = true;
}
3.开启接收数据线程
//开启接收线程
class ReadThread extends Thread {
final int USB_DATA_BUFFER = 8192;
Handler mHandler;
ReadThread(Handler h) {
mHandler = h;
this.setPriority(MAX_PRIORITY);
}
@Override
public void run() {
byte[] usbdata = new byte[USB_DATA_BUFFER];
int readcount = 0;
int iWriteIndex = 0;
bReadTheadEnable = true;
while (bReadTheadEnable) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (iTotalBytes > (MAX_NUM_BYTES - (USB_DATA_BUFFER + 1))) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
readcount = ftDev.getQueueStatus();
//Log.e(">>@@","iavailable:" + iavailable);
if (readcount > 0) {
if (readcount > USB_DATA_BUFFER) {
readcount = USB_DATA_BUFFER;
}
ftDev.read(usbdata, readcount);
if ((MODE_X_MODEM_CHECKSUM_SEND == transferMode)
|| (MODE_X_MODEM_CRC_SEND == transferMode)
|| (MODE_X_MODEM_1K_CRC_SEND == transferMode)) {
} else {
//DLog.e(TT,"totalReceiveDataBytes:"+totalReceiveDataBytes);
//DLog.e(TT,"readcount:"+readcount);
for (int count = 0; count < readcount; count++) {
readDataBuffer[iWriteIndex] = usbdata[count];
iWriteIndex++;
iWriteIndex %= MAX_NUM_BYTES;
}
if (iWriteIndex >= iReadIndex) {
iTotalBytes = iWriteIndex - iReadIndex;
} else {
iTotalBytes = (MAX_NUM_BYTES - iReadIndex) + iWriteIndex;
}
//DLog.e(TT,"iTotalBytes:"+iTotalBytes);
if ((MODE_X_MODEM_CHECKSUM_RECEIVE == transferMode)
|| (MODE_X_MODEM_CRC_RECEIVE == transferMode)
|| (MODE_X_MODEM_1K_CRC_RECEIVE == transferMode)
|| (MODE_Y_MODEM_1K_CRC_RECEIVE == transferMode)
|| (MODE_Z_MODEM_RECEIVE == transferMode)
|| (MODE_Z_MODEM_SEND == transferMode)) {
modemReceiveDataBytes[0] += readcount;
}
}
}
}
}
}
// Update UI content,发送消息到handler,切换到主线程处理数据
class HandlerThread extends Thread {
Handler mHandler;
HandlerThread(Handler h) {
mHandler = h;
}
public void run() {
byte status;
Message msg;
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (bContentFormatHex) // consume input data at hex content format
{
status = readData(UI_READ_BUFFER_SIZE, readBuffer);
} else if (MODE_GENERAL_UART == transferMode) {
status = readData(UI_READ_BUFFER_SIZE, readBuffer);
if (0x00 == status) {
if (!WriteFileThread_start) {
checkZMStartingZRQINIT();
}
// save data to file
if (WriteFileThread_start && buf_save != null) {
try {
buf_save.write(readBuffer, 0, actualNumBytes);
} catch (IOException e) {
e.printStackTrace();
}
}
msg = mHandler.obtainMessage(UPDATE_TEXT_VIEW_CONTENT);
mHandler.sendMessage(msg);
}
}
}
}
}
4.发送数据:
/**
* 写数据
* @param command
* @param dataBlock
*/
public void writeData(String command, String dataBlock) {
String hexStr = FormatUtils.createCommand(command, dataBlock);
if (DeviceStatus.DEV_CONFIG != checkDevice()) {
return;
}
// check whether there is some