串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。典型地,串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。一般调制速率大于波特率,比如曼彻斯特编码)。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。 [1]
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
RS-485和RS-232区别:
传输方式不同、传输距离不同、RS-232 只允许一对一通信。
1、传输方式不同。 RS-232采取不平衡传输方式,即所谓单端通讯. 而RS485则采用平衡传输,即差分传输方式。
2、传输距离不同。RS-232适合本地设备之间的通信,传输距离一般不超过20m。而RS-485的传输距离为几十米到上千米。
3、RS-232 只允许一对一通信,而RS-485 接口在总线上是允许连接多达128个收发器。
这些区别其实不是特别重要,因为一般移动开发一般遇到软件APP对接外部硬件设备,需要用的外部第三方设备的通讯协议,USB,232或而485协议进行交互通讯。
关于RS-485项目
首先我遇到的是安卓主板和外接的现金纸币机的交互,是这样一个需求:软件通过后台发来的socket指令,要求用户使用现金纸币支付订单(火车站,地铁,投入现金买票等场景),通过现金纸币机的验钞,计算现金额度,传输给软件进行找零计算,通过接口反馈到云端后台进行确认,开启其他权限操作。因为没有485串口线,所以采购量一根usb转串口,线里需要额外的芯片
usb转串口,通讯485模式
com.github.mik3y:usb-serial-for-android:3.3.0(具体可以详细查看)
首先是获取设备的属性权限
broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(INTENT_ACTION_GRANT_USB)) { usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) ? UsbPermission.Granted : UsbPermission.Denied; connect(); } } }; mainLooper = new Handler(Looper.getMainLooper());
private void connect() {//连接 UsbDevice device = null; UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE); for (UsbDevice v : usbManager.getDeviceList().values()) if (item != null) { if (v.getDeviceId() == item.getDevice().getDeviceId()) { device = v; } } if (device == null) { status("连接失败:未找到设备"); return; } UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); if (driver == null) { driver = CustomProber.getCustomProber().probeDevice(device); } if (driver == null) { status("连接失败:设备没有驱动程序"); return; } if (driver.getPorts().size() < item.getPort()) { status("连接失败:设备上没有足够的端口"); return; } usbSerialPort = driver.getPorts().get(item.getPort()); UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); if (usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { usbPermission = UsbPermission.Requested; PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), 0); usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); return; } if (usbConnection == null) { if (!usbManager.hasPermission(driver.getDevice())) status("连接失败:权限被拒绝"); else status("连接失败:打开失败"); return; } try { usbSerialPort.open(usbConnection); usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); if (withIoManager) { usbIoManager = new SerialInputOutputManager(usbSerialPort, this); Executors.newSingleThreadExecutor().submit(usbIoManager); } status("连接"); connected = true; } catch (Exception e) { status("连接失败: " + e.getMessage()); disconnect(); } }
*/ @Override public void onNewData(byte[] data) {//类似心跳操作+接收字节流接收操作 runOnUiThread(() -> { receive(data); }); }
@Override public void onRunError(Exception e) {//类似关流 mainLooper.post(() -> { disconnect(); }); }
private void send(String str) {//发送指令,485 以16进制转btye[]发送 Log.e("tag", "------------请求指令-----:" + str); if (!connected) { Toast.makeText(getActivity(), "未连接", Toast.LENGTH_SHORT).show(); return; } try { byte[] data = hexToByteArray(str); usbSerialPort.write(data, WRITE_WAIT_MILLIS); } catch (Exception e) { onRunError(e); } }
private void disconnect() { connected = false; // controlLines.stop(); if (usbIoManager != null) usbIoManager.stop(); usbIoManager = null; try { if (usbSerialPort != null) { usbSerialPort.close(); } } catch (IOException ignored) { } usbSerialPort = null; }
public static byte[] hexToByteArray(String inHex) { int hexlen = inHex.length(); byte[] result; if (hexlen % 2 == 1) { //奇数 hexlen++; result = new byte[(hexlen / 2)]; inHex = "0" + inHex; } else { //偶数 result = new byte[(hexlen / 2)]; } int j = 0; for (int i = 0; i < hexlen; i += 2) { result[j] = hexToByte(inHex.substring(i, i + 2)); j++; } return result; }
private void send(String str) { Log.e("tag", "------------请求指令-----:" + str); if (!connected) { Toast.makeText(getActivity(), "未连接", Toast.LENGTH_SHORT).show(); return; } try { byte[] data = hexToByteArray(str); usbSerialPort.write(data, WRITE_WAIT_MILLIS); } catch (Exception e) { onRunError(e); } }
另一个项目需要使用ETC天线进行扣费处理,232协议,一大堆国标接口协议。。。
提供一个Serial.cpp文件
#include#include #include #include #include #include #include #include "android/log.h" static const char *TAG = "serial_port"; #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) static speed_t getBaudrate(jint baudrate) { switch (baudrate) { case 0: return B0; case 50: return B50; case 75: return B75; case 110: return B110; case 134: return B134; case 150: return B150; case 200: return B200; case 300: return B300; case 600: return B600; case 1200: return B1200; case 1800: return B1800; case 2400: return B2400; case 4800: return B4800; case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; case 57600: return B57600; case 115200: return B115200; case 230400: return B230400; case 460800: return B460800; case 500000: return B500000; case 576000: return B576000; case 921600: return B921600; case 1000000: return B1000000; case 1152000: return B1152000; case 1500000: return B1500000; case 2000000: return B2000000; case 2500000: return B2500000; case 3000000: return B3000000; case 3500000: return B3500000; case 4000000: return B4000000; default: return -1; } } extern "C" { JNIEXPORT jobject JNICALL Java_com_sky_besthardtool_category_serialport_api_SerialPort_open(JNIEnv *env, jclass type, jstring path_, jint baudrate, jint flags) { const char *path = env->GetStringUTFChars(path_, 0); // TODO int fd; speed_t speed; jobject mFileDescriptor; /* Check arguments */ { speed = getBaudrate(baudrate); if (speed == -1) { /* TODO: throw an exception */ LOGE("Invalid baudrate"); return NULL; } } /* Opening device */ { jboolean iscopy; const char *path_utf = env->GetStringUTFChars(path_, &iscopy); LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); fd = open(path_utf, O_RDWR | flags); LOGD("open() fd = %d", fd); env->ReleaseStringUTFChars(path_, path_utf); if (fd == -1) { /* Throw an exception */ LOGE("Cannot open port"); /* TODO: throw an exception */ return NULL; } } /* Configure device */ { struct termios cfg; LOGD("Configuring serial port"); if (tcgetattr(fd, &cfg)) { LOGE("tcgetattr() failed"); close(fd); /* TODO: throw an exception */ return NULL; } cfmakeraw(&cfg); cfsetispeed(&cfg, speed); cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg)) { LOGE("tcsetattr() failed"); close(fd); /* TODO: throw an exception */ return NULL; } } /* Create a corresponding file descriptor */ { jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor"); jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, " ", "()V"); jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I"); mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor); env->SetIntField(mFileDescriptor, descriptorID, (jint) fd); } LOGD("Uart open succeed!!!"); env->ReleaseStringUTFChars(path_, path); return mFileDescriptor; } JNIEXPORT void JNICALL Java_com_sky_besthardtool_category_serialport_api_SerialPort_close(JNIEnv *env, jobject instance) { // TODO jclass SerialPortClass = env->GetObjectClass(instance); jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor"); if (FileDescriptorClass == NULL) { LOGD("java/lang/ClassNotFoundException::java.io.FileDescriptor"); } jfieldID mFdID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); if (mFdID == NULL) { LOGD("java/lang/NoSuchFieldException::FileDescriptor.readOnly(Z)"); } jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I"); jobject mFd = env->GetObjectField(instance, mFdID); jint descriptor = env->GetIntField(mFd, descriptorID); LOGD("close(fd = %d)", descriptor); close(descriptor); } }
CmakeList.txt文件配置
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. serial_port # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/Serial.cpp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. serial_port # Links the target library to the log library # included in the NDK. ${log-lib} )
public class TransferManager { private static TransferManager instance; private SerialPort serialPort; private FileInputStream inputStream; public FileOutputStream outputStream; private ReadThread mReadThread; private OnIOTransferListener listener; private TransferManager() { } public static TransferManager getInstance() { if (instance == null) { synchronized (TransferManager.class) { if (instance == null) { instance = new TransferManager(); } } } return instance; } public void connect(Context context) { String com = SpUtil.getCom(context); int baudRate = SpUtil.getBaudrate(context); try { serialPort = new SerialPort(com, baudRate); inputStream = (FileInputStream) serialPort.getInputStream(); outputStream = (FileOutputStream) serialPort.getOutputStream(); mReadThread = new ReadThread(); mReadThread.start(); } catch (Exception e) { listener.onSerialConnect(false); e.printStackTrace(); } } public void disConnect() { if (mReadThread != null) { mReadThread.interrupt(); mReadThread = null; } try { if (inputStream != null) { inputStream.close(); inputStream = null; } if (outputStream != null) { outputStream.close(); outputStream = null; } } catch (IOException e) { e.printStackTrace(); } if (serialPort != null) { serialPort.close(); } } private class ReadThread extends Thread { public void run() { int nMaxBufLength = 64; byte[] buffer = new byte[nMaxBufLength]; while (!isInterrupted()) { try { int byteRead; Thread.sleep(50); if (inputStream != null) { byteRead = inputStream.read(buffer); if (byteRead >= 0) { if (listener != null) { byte[] sendBytes = new byte[byteRead]; System.arraycopy(buffer, 0, sendBytes, 0, byteRead); listener.onReceive(sendBytes, byteRead); } } } else { // P("mInputStream==null"); break; } } catch (IOException e) { // P("IOException"); if (listener != null) { listener.onSerialConnect(false); } break; } catch (InterruptedException e) { // P("InterruptedException"); e.printStackTrace(); } }//while(!isInterrupted()) } } public void setOnIOTransferListener(OnIOTransferListener listener) { this.listener = listener; } }
完整的demo已经上传制github了