最近在开发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权限
现将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 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 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 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 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孔插口串口的读取方式,希望本篇能帮助到你们。