前言
最近在做手機(jī)跟外設(shè)交互,因?yàn)橹皼](méi)有涉獵過(guò)這方面,做起來(lái)真的是頭大。幸好有萬(wàn)能的百度和無(wú)所不能的google,以及程序員的小幫手github,多方查詢資料,咨詢同事,以及萬(wàn)能的群友幫助,終于順利實(shí)現(xiàn)了第一款串口編程的App。不得不說(shuō)現(xiàn)在的手機(jī)越來(lái)越強(qiáng)大,都可以通過(guò)USB接口,直接讀取其它外設(shè)的數(shù)據(jù)了。寫這篇博客一是為了記錄一下這次開發(fā)的經(jīng)驗(yàn),二是給后來(lái)的同學(xué)提供一些經(jīng)驗(yàn)。
基本常識(shí)
串口通信:指串口按位(bit)發(fā)送和接收字節(jié)。盡管比按字節(jié)(byte)的并行通信慢,但是串口可以使用一根線發(fā)送數(shù)據(jù)的同時(shí)接收數(shù)據(jù)。
在串口通信中,常用的協(xié)議包括RS232、RS-422和RS-485.
我這次工作中對(duì)接的是RS232,當(dāng)然具體是哪種協(xié)議和你選擇的硬件有關(guān),將你的硬件插到對(duì)應(yīng)的協(xié)議的串口即可。
開發(fā)前準(zhǔn)備
1.檢查你的硬件裝備
正確連接你的設(shè)備,向你的硬件提供商索要開發(fā)資料,或者說(shuō)明書。基本的資料包括硬件的通訊命令格式。類似下圖,
2.正確的連接,測(cè)試你的硬件與系統(tǒng)
串口電腦調(diào)試助手
Android USB串口調(diào)試助手
下載一個(gè)串口助手,按照資料輸入命令。測(cè)試是否能夠成功的啟動(dòng)設(shè)備,收到對(duì)應(yīng)的返回?cái)?shù)據(jù)。
我的硬件設(shè)備連接線是DB9的USB-RS-232轉(zhuǎn)接線,再配一個(gè)轉(zhuǎn)接頭,即可

開發(fā)階段
整體的開發(fā)流程如下:打開串口–>開啟接收線程(ReadThread)–>發(fā)送串口數(shù)據(jù)–>接收數(shù)據(jù),處理返回信息–>關(guān)閉接收數(shù)據(jù)線程–>關(guān)閉串口。
獲取權(quán)限
1.要使用手機(jī)的USB接口首先要獲取相關(guān)的權(quán)限
<!--USB權(quán)限-->
<uses-feature android:name="android.hardware.usb.host" />
<!---->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--這里是為了硬件調(diào)試時(shí),保存日志,申請(qǐng)的權(quán)限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2.在需要打開串口的activity,添加如下數(shù)據(jù)
<activity android:name=".MainActivity" android:windowSoftInputMode="stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!--添加 1-->
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!--添加 2-->
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
3.在res目錄下新建xml目錄,新建device_filter.xml文件,內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<resource xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 要進(jìn)行通信的USB設(shè)備的供應(yīng)商ID(VID)和產(chǎn)品識(shí)別碼(PID)
(如果這個(gè)不對(duì),需要你跟你的硬件供應(yīng)商索要這個(gè)參數(shù))-->
<usb-device vendor-id="1027" product-id="24577" /> <!-- FT232RL -->
</resource>
導(dǎo)入jar包
這里是我自己的獨(dú)家jar包,簡(jiǎn)單明了,好用,易上手,里面已經(jīng)封裝好了,不需要自己進(jìn)行繁瑣的JNI操作,特別是對(duì)JNI不熟悉的同學(xué)。這個(gè)獨(dú)家秘笈我就不免費(fèi)放出來(lái)了,如果你用這個(gè)手機(jī)調(diào)試助手能正常調(diào)試,再去這里下載助手源碼。
USBSerialPortUtils
下面看重點(diǎn),我封裝到utils中的方法
- 初始化基本參數(shù)
/**
* oncreate
* 為了準(zhǔn)確提示,使用位置,故定義成了onCreate
* 1個(gè)停止位,8個(gè)數(shù)據(jù)位,奇校驗(yàn),16進(jìn)制,波特率
* 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進(jìn)制HEX
bFormatHex = true;
configParams();
}
2.打開串口,配置基本參數(shù)
/**
* 打開串口
*/
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();
}
}
/***
* 配置參數(shù)
*/
private void configParams() {
createDeviceList();
if (DevCount > 0) {
connectFunction();
}
if (DeviceStatus.DEV_NOT_CONNECT == checkDevice()) {
return;
}
setConfig(baudRate, dataBit, stopBit, parity, flowControl);
uart_configured = true;
}
/**
* 配置數(shù)據(jù)位,校驗(yàn)位,停止位
* @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.開啟接收數(shù)據(jù)線程
//開啟接收線程
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,發(fā)送消息到handler,切換到主線程處理數(shù)據(jù)
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.發(fā)送數(shù)據(jù):
/**
* 寫數(shù)據(jù)
* @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