CH34串列埠讀取
最近在開發android工控機同硬體裝置通訊的時候,用到了ch340U轉串,所以把關於這個串列埠的一些知識分享給大家。
簡介:
CH34x 系列晶片是 USB 匯流排的轉接晶片,主要包含 CH340、CH341、CH345,通過 USB 匯流排提供異 步串列埠、列印口、並口、MIDI 以及常用的 2 線和 4 線等介面。
CH34x 串列埠提供的 Android 介面需要基於 Android 3.1 及以上版本系統,使用 CH34x 串列埠 Android 驅動條件:
1、需要基於 Android 3.1 及以上版本系統
2、Android 裝置具有 USB Host 或 OTG 介面
介面說明:
EnumerateDevice:列舉 CH34x 裝置
函式原型 :public UsbDevice EnumerateDevice()
返回列舉到的 CH34x 的裝置,若無裝置則返回 null
OpenDevice:開啟 CH34x 裝置
函式原型 :public void OpenDevice(UsbDevice mDevice)
mDevice :需要開啟的 CH34x 裝置
ResumeUsbList:列舉並開啟 CH34x 裝置,這個函式包含了 EnumerateDevice,OpenDevice 操作
函式原型 :public int ResumeUsbList()
返回 0 則成功,否則失敗
UartInit:設定初始化 CH34x 晶片
函式原型 :public boolean UartInit()
若初始化失敗,則返回 false,成功返回 true
SetConfig:設定 UART 介面的波特率、資料位、停止位、奇偶校驗位以及流控
函式原型 :public boolean SetConfig(int baudRate, byte dataBit, byte stopBit, byte parity, byte flowControl)
baudRate :波特率:300,600,1200、2400、4800、9600、19200、38400、57600、115200、 230400、460800、921600,預設:9600
dataBits :5 個數據位、6 個數據位、7 個數據位、8 個數據位,預設:8 個數據位
stopBits :0:1 個停止位,1:2 個停止位,預設:1 個停止位
parity :0:none,1:add,2:even,3:mark 和 4:space,預設:none
flowControl :0:none,1:cts/rts,預設:none
若設定失敗,則返回 false,成功返回 true
WriteData:傳送資料
函式原型 :public int WriteData(byte[] buf, int length)
buf :傳送緩衝區 length :傳送的位元組數
返回值為寫成功的位元組數
ReadData:讀取資料
函式原型 :public int ReadData(char[] data, int length)
data :接收緩衝區,資料型別為
char length :讀取的位元組數 返回實際讀取的位元組數
函式原型 :public int ReadData(byte[] data, int length)
data :接收緩衝區
length :讀取的位元組數 返回實際讀取的位元組數
CloseDevice:關閉串列埠。
函式原型 :public void CloseDevice()
isConnected:判斷裝置是否已經連線到 Android 系統
函式原型 :public boolean isConnected() 返回為 false 時表示裝置未連線到系統,true 表示裝置已連線
除了上述提供的介面 API,使用者還可以根據自己的裝置來設定讀寫超時時間:
函式原型:public boolean SetTimeOut(int WriteTimeOut, int ReadTimeOut)
WriteTimeOut:設定寫超時時間,預設為 10000ms
ReadTimeOut :設定讀超時時間,預設為 10000ms
使用:
新增usb許可權
<!-- USB許可權 -->
<uses-feature android:name="android.hardware.usb.host"/>
現將jar包拷貝到lib資料夾下https://pan.baidu.com/s/1SG-9MWgZ9OfLLxTTXv8RPg
然後就可以呼叫所提供的的方法了,為了方便理解我另外寫了點簡單的程式碼,多的不說直接上程式碼,註釋很詳細:
//開啟串列埠
public void open() {
try {
device = new CH34xUARTDriver((UsbManager) context.getSystemService(Context.USB_SERVICE), context, ACTION_USB_PERMISSION);
if (!device.UsbFeatureSupported())// 判斷系統是否支援USB HOST
{ return; }
//判斷是否授權 如果沒有等待幾秒讓使用者授權
if (device.ResumeUsbPermission() == -2) {
timer.schedule( authorizeTimeTask, 1000, 1000);
} else {
//如果已授權 直接開啟連線
if (setCon()) {
ConThread = new Thread(Conrunnable);
ConThread.start();
}
}
} catch (Exception e) {
sentConMsg("初始化USB串列埠異常");
}
}
//連線串列埠
private boolean setCon() {
if (device == null) {
return false;
}
try {
//檢查是否授權
if (device.ResumeUsbPermission() == -2) {
sentConMsg("授權失敗");
}
//得到裝置名稱
UsbName = device.EnumerateDevice().getDeviceName();
//開啟裝置
retval = device.ResumeUsbList();
if (retval == -1) {
sentConMsg("開啟串列埠失敗1");
device.CloseDevice();
isStart = false;
} else if (retval == 0) {
if (!device.UartInit()) {
sentConMsg("開啟串列埠失敗2");
isStart = false;
return false;
}
//配置串列埠波特率
is = device.SetConfig(baudRate, dataBit, stopBit, parity, flowControl);
if (is) {
sentConMsg("開啟串列埠成功");
isStart = true;
return true;
} else {
sentConMsg("開啟串列埠失敗3");
}
}
} catch (Exception e) {
sentConMsg("開啟串列埠異常4");
}
return false;
}
本想不停地請求開啟裝置直到使用者授權,但是不行,因為不停地請求會報異常,所以我是設定了個計時器(待優化),在規定的時間內授權後,再次請求開啟串列埠就行了。
//幾秒後 使用者已授權 然後開始連線串列埠
final Handler timeConnectHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
if (recLen < 0) {
timer.cancel();
if (setCon()) {
ConThread = new Thread(Conrunnable);
ConThread.start();
}
}
}
}
};
TimerTask authorizeTimeTask = new TimerTask() {
@Override
public void run() {
recLen--;
Message message = Message.obtain();
message.what = 1;
timeConnectHandler.sendMessage(message);
}
};
因為我們需要在不同的介面知道現在串列埠的連線情況所以寫了一個介面,專門用來傳輸連線狀態:
/**
* 連線狀態事件
*/
private List<ConnectStateListener> lstConnect;
public void addConnectState(ConnectStateListener connectStateListener) {
if (lstConnect == null) {
lstConnect = new ArrayList<>();
}
lstConnect.add(connectStateListener);
}
public void removeConnectState(ConnectStateListener connectStateListener) {
if (lstConnect == null) {
lstConnect = new ArrayList<>();
}
lstConnect.remove(connectStateListener);
}
private Handler ConnetHandler = new Handler() {
public void handleMessage(Message msg) {
if (lstConnect == null)
return;
for (ConnectStateListener item : lstConnect) {
item.ConnectState((String) msg.obj);
}
}
};
//傳送狀態
private void sentConMsg(String msg) {
Message message = Message.obtain();
message.obj = msg;
ConnetHandler.sendMessage(message);
}
做到現在應該是可以讀取到資料了的,開啟執行緒讀取資料:
private Runnable Conrunnable = new Runnable() {
@Override
public void run() {
try {
byte[] buffer;
while (isRead) {
try {
if (!isStart) {
break;
}
buffer = new byte[520];
int length = device.ReadData(buffer, 520);
if (length > 0) {
sentConMsg("串列埠連線成功");
byte[] temp = new byte[length];
System.arraycopy(buffer, 0, temp, 0, length);
sentData(temp );
}
if (length == 0) {
if (_Timer == null) {
_Timer = new Timer();
_Timer.schedule(new DataTimer(), 0, 1000);
}
continue;
} else {
_Datatime = 0;
}
} catch (Exception e) {
sentConMsg("讀取資料異常1");
continue;
}
}
close();
return;
} catch (Exception e) {
sentConMsg("連線異常");
}
}
};
當資料在幾秒內持續為空是,視為連線已斷開。讀取的資料與介面的互動同樣使用介面:
/**
* 資料傳輸事件
*/
private List<GetDataListener> lsDatas;
public void addGetData(GetDataListener dataListener) {
if (lsDatas == null) {
lsDatas = new ArrayList<>();
}
lsDatas.add(dataListener);
}
public void removeGetData(GetDataListener dataListener) {
if (lsDatas == null) {
lsDatas = new ArrayList<>();
}
lsDatas.remove(dataListener);
}
private Handler DataHandler = new Handler() {
public void handleMessage(Message msg) {
if (lsDatas == null)
return;
for (GetDataListener item : lsDatas) {
item.getData((byte[]) msg.obj);
}
}
};
//傳送資料
private void sentData(byte[] bytes) {
Message message = Message.obtain();
message.obj = bytes;
DataHandler.sendMessage(message);
}
關閉串列埠,就是回收一下資源,停止讀取資料就行
public void close() {
if (_Timer != null) {
_Timer.cancel();
_Timer = null;
_Datatime = 0;
}
UsbName = "";
isRead = false;
if (ConThread != null) {
ConThread.interrupt();
ConThread = null;
}
_Timer = null;
_Datatime = 0;
isStart = false;
}
傳送資料給硬體或者是什麼主要就是要判一下空
//傳送資料
public void send(byte[] data) {
if (device != null && data != null) {
int retval = device.WriteData(data, data.length);
if (retval > 0) {
//傳送成功
} else {
//傳送失敗
}
}
}
開啟以及關閉的方法需要這樣呼叫:
switch (v.getId()) {
case R.id.OpenButton:
Ch34Helper.getInstance(this).open();
break;
case R.id.WriteButton:
byte[] to_send = toByteArray(writeText.getText().toString());
Ch34Helper.getInstance(this).send(to_send);
break;
case R.id.CloseButton:
Ch34Helper.getInstance(this).close();
break;
}
在介面上的呼叫就很簡單了,資料接收需要實現兩個介面,然後把資料顯示到介面:
//初始化連線以及資料傳輸事件
private void initData() {
Ch34Helper.getInstance(this).addConnectState(this);
Ch34Helper.getInstance(this).addGetData(this);
}
//設定連線狀態顯示
@Override
public void ConnectState(String s) {
if (s != null && s.length() > 0)
connectText.setText(s);
}
//設定接收資料顯示
@Override
public void getData(byte[] bytes) {
if (bytes != null && bytes.length > 0)
readText.setText(bytes2HexString(bytes));
}
還有就是最後要記得在介面銷燬的時候回收資源
@Override
protected void onDestroy() {
super.onDestroy();
Ch34Helper.getInstance(this).removeConnectState(this);
Ch34Helper.getInstance(this).removeGetData(this);
Ch34Helper.getInstance(this).close();
}
讀取類完整程式碼:
public class Ch34Helper {
private static Context context;
private static CH34xUARTDriver device = null;
private Thread ConThread;
private boolean isStart = false;
private static final String ACTION_USB_PERMISSION = "cn.wch.wchusbdriver.USB_PERMISSION";
private int baudRate = 115200;
private byte stopBit = 1;
private byte dataBit = 8;
private byte parity = 0;
private byte flowControl = 0;
private int retval;
private boolean is;
private String UsbName;
private boolean isRead = true;
private int recLen = 8;
private Timer timer = new Timer();
private Timer _Timer = null;
private int _Datatime = 0;
private static Ch34Helper ch34Helper;
public static Ch34Helper getInstance(Context con) {
if (null == ch34Helper) {
ch34Helper = new Ch34Helper();
context = con;
}
return ch34Helper;
}
private class DataTimer extends TimerTask {
@Override
public void run() {
//超過3秒沒有資料 視為連線斷開
_Datatime++;
if (_Datatime > 3) {
sentConMsg("串列埠已斷開");
}
}
}
//開啟串列埠
public void open() {
try {
device = new CH34xUARTDriver((UsbManager) context.getSystemService(Context.USB_SERVICE), context, ACTION_USB_PERMISSION);
if (!device.UsbFeatureSupported())// 判斷系統是否支援USB HOST
{
return;
}
//判斷是否授權 如果沒有等待幾秒讓使用者授權
if (device.ResumeUsbPermission() == -2) {
timer.schedule(authorizeTimeTask, 1000, 1000);
} else {
//如果已授權 直接開啟連線
if (setCon()) {
ConThread = new Thread(Conrunnable);
ConThread.start();
}
}
} catch (Exception e) {
sentConMsg("初始化USB串列埠異常");
}
}
//連線串列埠
private boolean setCon() {
if (device == null) {
return false;
}
try {
//檢查是否授權
if (device.ResumeUsbPermission() == -2) {
sentConMsg("授權失敗");
}
//得到裝置名稱
UsbName = device.EnumerateDevice().getDeviceName();
//開啟裝置
retval = device.ResumeUsbList();
if (retval == -1) {
sentConMsg("開啟串列埠失敗1");
device.CloseDevice();
isStart = false;
} else if (retval == 0) {
if (!device.UartInit()) {
sentConMsg("開啟串列埠失敗2");
isStart = false;
return false;
}
//配置串列埠波特率
is = device.SetConfig(baudRate, dataBit, stopBit, parity, flowControl);
if (is) {
sentConMsg("開啟串列埠成功");
isStart = true;
return true;
} else {
sentConMsg("開啟串列埠失敗3");
}
}
} catch (Exception e) {
sentConMsg("開啟串列埠異常4");
}
return false;
}
//幾秒後 使用者已授權 然後開始連線串列埠
final Handler timeConnectHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
if (recLen < 0) {
timer.cancel();
if (setCon()) {
ConThread = new Thread(Conrunnable);
ConThread.start();
}
}
}
}
};
TimerTask authorizeTimeTask = new TimerTask() {
@Override
public void run() {
recLen--;
Message message = Message.obtain();
message.what = 1;
timeConnectHandler.sendMessage(message);
}
};
//得到搜尋到的裝置
public UsbDevice getDevice() {
if (device == null) {
device = new CH34xUARTDriver((UsbManager) context.getSystemService(Context.USB_SERVICE), context, ACTION_USB_PERMISSION);
return device.EnumerateDevice();
}
return device.EnumerateDevice();
}
private Runnable Conrunnable = new Runnable() {
@Override
public void run() {
try {
byte[] buffer;
while (isRead) {
try {
if (!isStart) {
break;
}
buffer = new byte[520];
int length = device.ReadData(buffer, 520);
if (length > 0) {
sentConMsg("串列埠連線成功");
byte[] temp = new byte[length];
System.arraycopy(buffer, 0, temp, 0, length);
sentData(temp );
}
if (length == 0) {
if (_Timer == null) {
_Timer = new Timer();
_Timer.schedule(new DataTimer(), 0, 1000);
}
continue;
} else {
_Datatime = 0;
}
} catch (Exception e) {
sentConMsg("讀取資料異常1");
continue;
}
}
close();
return;
} catch (Exception e) {
sentConMsg("連線異常");
}
}
};
public void close() {
if (_Timer != null) {
_Timer.cancel();
_Timer = null;
_Datatime = 0;
}
UsbName = "";
isRead = false;
if (ConThread != null) {
ConThread.interrupt();
ConThread = null;
}
_Timer = null;
_Datatime = 0;
isStart = false;
}
//傳送資料
public void send(byte[] data) {
if (device != null && data != null) {
int retval = device.WriteData(data, data.length);
if (retval > 0) {
//傳送成功
} else {
//傳送失敗
}
}
}
/**
* 資料傳輸事件
*/
private List<GetDataListener> lsDatas;
public void addGetData(GetDataListener dataListener) {
if (lsDatas == null) {
lsDatas = new ArrayList<>();
}
lsDatas.add(dataListener);
}
public void removeGetData(GetDataListener dataListener) {
if (lsDatas == null) {
lsDatas = new ArrayList<>();
}
lsDatas.remove(dataListener);
}
private Handler DataHandler = new Handler() {
public void handleMessage(Message msg) {
if (lsDatas == null)
return;
for (GetDataListener item : lsDatas) {
item.getData((byte[]) msg.obj);
}
}
};
//傳送資料
private void sentData(byte[] bytes) {
Message message = Message.obtain();
message.obj = bytes;
DataHandler.sendMessage(message);
}
/**
* 連線狀態事件
*/
private List<ConnectStateListener> lstConnect;
public void addConnectState(ConnectStateListener connectStateListener) {
if (lstConnect == null) {
lstConnect = new ArrayList<>();
}
lstConnect.add(connectStateListener);
}
public void removeConnectState(ConnectStateListener connectStateListener) {
if (lstConnect == null) {
lstConnect = new ArrayList<>();
}
lstConnect.remove(connectStateListener);
}
private Handler ConnetHandler = new Handler() {
public void handleMessage(Message msg) {
if (lstConnect == null)
return;
for (ConnectStateListener item : lstConnect) {
item.ConnectState((String) msg.obj);
}
}
};
//傳送狀態
private void sentConMsg(String msg) {
Message message = Message.obtain();
message.obj = msg;
ConnetHandler.sendMessage(message);
}
}
activity程式碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, ConnectStateListener, GetDataListener {
private EditText readText, writeText;
private TextView connectText;
private Button writeButton, openButton, CloseButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
//初始化連線以及資料傳輸事件
private void initData() {
Ch34Helper.getInstance(this).addConnectState(this);
Ch34Helper.getInstance(this).addGetData(this);
}
//設定連線狀態顯示
@Override
public void ConnectState(String s) {
if (s != null && s.length() > 0)
connectText.setText(s);
}
//設定接收資料顯示
@Override
public void getData(byte[] bytes) {
if (bytes != null && bytes.length > 0)
readText.setText(bytes2HexString(bytes));
}
private void initView() {
connectText = (TextView) findViewById(R.id.ConectValues);
readText = (EditText) findViewById(R.id.ReadValues);
writeText = (EditText) findViewById(R.id.WriteValues);
writeButton = (Button) findViewById(R.id.WriteButton);
openButton = (Button) findViewById(R.id.OpenButton);
CloseButton = (Button) findViewById(R.id.CloseButton);
CloseButton.setOnClickListener(this);
writeButton.setOnClickListener(this);
openButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.OpenButton:
Ch34Helper.getInstance(this).open();
break;
case R.id.WriteButton:
byte[] to_send = toByteArray(writeText.getText().toString());
Ch34Helper.getInstance(this).send(to_send);
break;
case R.id.CloseButton:
Ch34Helper.getInstance(this).close();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Ch34Helper.getInstance(this).removeConnectState(this);
Ch34Helper.getInstance(this).removeGetData(this);
Ch34Helper.getInstance(this).close();
}
/**
* 將String轉化為byte[]陣列
*
* @param arg 需要轉換的String物件
* @return 轉換後的byte[]陣列
*/
private byte[] toByteArray(String arg) {
if (arg != null) {
/* 1.先去除String中的' ',然後將String轉換為char陣列 */
char[] NewArray = new char[1000];
char[] array = arg.toCharArray();
int length = 0;
for (int i = 0; i < array.length; i++) {
if (array[i] != ' ') {
NewArray[length] = array[i];
length++;
}
}
/* 將char陣列中的值轉成一個實際的十進位制陣列 */
int EvenLength = (length % 2 == 0) ? length : length + 1;
if (EvenLength != 0) {
int[] data = new int[EvenLength];
data[EvenLength - 1] = 0;
for (int i = 0; i < length; i++) {
if (NewArray[i] >= '0' && NewArray[i] <= '9') {
data[i] = NewArray[i] - '0';
} else if (NewArray[i] >= 'a' && NewArray[i] <= 'f') {
data[i] = NewArray[i] - 'a' + 10;
} else if (NewArray[i] >= 'A' && NewArray[i] <= 'F') {
data[i] = NewArray[i] - 'A' + 10;
}
}
/* 將 每個char的值每兩個組成一個16進位制資料 */
byte[] byteArray = new byte[EvenLength / 2];
for (int i = 0; i < EvenLength / 2; i++) {
byteArray[i] = (byte) (data[i * 2] * 16 + data[i * 2 + 1]);
}
return byteArray;
}
}
return new byte[]{};
}
/* *
* @param bytes 位元組陣列
* @return 16進位制大寫字串
*/
public static String bytes2HexString(byte[] bytes) {
if (bytes == null) return null;
int len = bytes.length;
if (len <= 0) return null;
char[] ret = new char[len << 1];
for (int i = 0, j = 0; i < len; i++) {
ret[j++] = hexDigits[bytes[i] >>> 4 & 0x0f];
ret[j++] = hexDigits[bytes[i] & 0x0f];
}
return new String(ret);
}
private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
}
佈局就不貼了,應該難不倒你們。
總的來說各種各樣的串列埠讀取方式其實相差不大,主要就是自己試一把,那麼就沒多大問題了,過兩天再更新一個4孔插口串列埠的讀取方式,希望本篇能幫助到你們。