Young man, your resume says you’ve been involved in serial communication, so tell me!
1. What is serial communication
Serial communication technology refers to the communication between the two parties according to the bit, a way of communication in accordance with the time sequence
Serial port is equivalent to a pipe, which is also represented in hardware. There are three jumpers, one is Tx line, one is Rx line, and one is ground wire. The data transmitted by this pipe, namely bit, is serial and sequential
2. Application scenarios of serial ports
Serial communication is not used much in the development of Android. Most of our apps use Http to communicate with the background, obtain background data and display, while serial communication is applied in the scene of smart home and MCU communication, face recognition access control, and the use of serial port to control the door switch. Vending machine Android received a successful payment message, send serial port instructions, control the goods channel for shipment and so on Android devices have more than 2 billion, relatively speaking, serial port in Android applications are still quite extensive
3, Android how to achieve serial communication
Step 1 Find the serial port file
Android serial files have a separate directory
This is the file that starts with TTys
How does that work in code
private ArrayList<Driver> getDrivers(a) throws IOException {
ArrayList<Driver> drivers = new ArrayList<>();
LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(DRIVERS_PATH));
String readLine;
while((readLine = lineNumberReader.readLine()) ! =null) {
String driverName = readLine.substring(0.0x15).trim();
String[] fields = readLine.split("+");
// driverName:/dev/tty
// driverName:/dev/console
// driverName:/dev/ptmx
// driverName:/dev/vc/0
// driverName:serial
// driverName:pty_slave
// driverName:pty_master
// driverName:unknown
Log.d(T.TAG, "SerialPortFinder getDrivers() driverName:" + driverName /*+ " readLine:" + readLine*/);
if ((fields.length >= 5) && (fields[fields.length - 1].equals(SERIAL_FIELD))) { // Determine that the fourth level is not equal to serial
// The new serial driver is found :serial The serial driver is named /dev/ttys
Log.d(T.TAG, "SerialPortFinder getDrivers() found the new serial driver is :" + driverName + "This serial port series name is :" + fields[fields.length - 4]);
drivers.add(new Driver(driverName, fields[fields.length - 4])); }}return drivers;
}
Copy the code
3.2. Step 2 Open the serial port file
When we operate the serial port we first check the permissions
if(! device.canRead() || ! device.canWrite()) {boolean chmod777 = chmod777(device);
if(! chmod777) { Log.i(T.TAG,"SerialPortManager openSerialPort: No read/write permission");
if (null! = mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.NO_READ_WRITE_PERMISSION); }return false; }}/** * File set the highest permission 777 read/write/execute *@paramFile You need to get root permission for that file *@returnCheck whether the permission is successfully modified. - Success or failure */ is returned
boolean chmod777(File file) {
if (null== file || ! file.exists()) {// File does not exist
return false;
}
try {
// Obtain the ROOT permission
Process su = Runtime.getRuntime().exec("/system/bin/su");
// Change the file property to readable, writable, and executable.
String cmd = "chmod 777 " + file.getAbsolutePath() + "\n" + "exit\n";
su.getOutputStream().write(cmd.getBytes());
if (0 == su.waitFor() && file.canRead() && file.canWrite() && file.canExecute()) {
return true; }}catch (IOException | InterruptedException e) {
// No ROOT permission
e.printStackTrace();
}
return false;
}
Copy the code
So once we’re done checking permissions, we’re going to open the serial port using code in the NDK, and then the connection between the Java layer and the Native layer is the FileDescriptor which is the fd in the code, and the Native layer returns the FileDescriptor, And then the Java layer’s FileInputStream, FileOutputStream, and FileDescriptor are bound so that the Java layer can read the data
try {
mFd = openNative(device.getAbsolutePath(), baudRate, 0); // Open the serial port native function
mFileInputStream = new FileInputStream(mFd); // The stream read is bound (mFd file handle)- the input stream is wrapped with a file handle (mFd)
mFileOutputStream = new FileOutputStream(mFd); // The written stream is bound (mFd file handle)- the output stream is wrapped with a file handle (mFd)
Log.i(T.TAG, "SerialPortManager openSerialPort: Serial port is open" + mFd); // The serial port has been opened with FileDescriptor[35] [2]
if (null! = mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onSuccess(device); } startSendThread();// Start the thread that sends messages
startReadThread(); // Start the thread that receives the message
return true; / / [3]
} catch (Exception e) {
e.printStackTrace();
if (null != mOnOpenSerialPortListener) {
mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.OPEN_FAIL);
}
}
Copy the code
Native
JNIEXPORT jobject JNICALL Java_com_test_openNative
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) {
int fd; // Linux serial file handle (key result of this whole function)
speed_t speed; // Baud type value
jobject mFileDescriptor; // File handle (final result returned)
// Check the baud rate parameters.
{
speed = getBaudrate(baudrate);
if (speed == - 1) {
LOGE("Invalid baud rate, proving that the user selected the wrong baud rate.");
return NULL; }}// TODO step 1: Open the serial port
{
jboolean iscopy;
const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
LOGD("Open serial port path :%s", path_utf); // The path to open the serial port is /dev/ttys0
fd = open(path_utf, O_RDWR /*| flags*/); // Open the serial port function O_RDWR(read and write)
LOGD(Open () fd = %d, fd); // open() fd = 44
(*env)->ReleaseStringUTFChars(env, path, path_utf); // Release operation
if (fd == - 1) {
LOGE("Unable to open port");
return NULL;
}
}
LOGD("Step 1: Open serial port successfully √√√");
// TODO Step 2: Get and set terminal properties - configure the serial port device
/* TCSANOW: Change properties immediately after data transfer. TCSADRAIN: Wait until all data transfer ends before changing properties. TCSAFLUSH: Empty the input and output buffers before changing properties. Note: When multiple changes are made, tcgetattr() should be called again after this function to check that all changes were successfully implemented. * /
{
struct termios cfg;
LOGD("Execute configuration serial port...");
if (tcgetattr(fd, &cfg)) { // Get serial port properties
LOGE("Failed to configure serial port TCgetattr ()");
close(fd); // Close the serial port
return NULL;
}
cfmakeraw(&cfg); // Set the serial port to original mode, and make fd(file handle to serial port readable and writable)
cfsetispeed(&cfg, speed); // Set the serial port read baud rate
cfsetospeed(&cfg, speed); // Set the serial port write baud rate
if (tcsetattr(fd, TCSANOW, &cfg)) { // Obtain the serial port properties again according to the above configuration
LOGE("Failed to reconfigure serial port TCgetattr ()");
close(fd); // Close the serial port
return NULL;
}
}
LOGD("Step 2: Obtaining and setting terminal properties - Configuring the serial port device succeeded √√√");
// TODO Step 3: Build the FileDescription. Java object and give it rich serial port values
{
jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>"."()V");
jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor"."I");
// Reflection generates FileDescriptor objects and assigns (fd==Linux serial file handle) constructor instantiation of FileDescriptor
mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd); // fd is the key result of opening the serial port
}
LOGD("Step 3: Construct FileDescriptor. Java object and assign rich serial port values. Success √√√");
return mFileDescriptor; // Return the final result to the Java layer
}
Copy the code
This completes the operation of opening the serial port
3.3. Send and read data
When we read and send data, we’re doing operations on file IO, and we definitely want to do that in child threads,
private void startReadThread(a) {
mSerialPortReadThread = new SerialPortReadThread(mFileInputStream) {
@Override
public void onDataReceived(byte[] bytes) {
if (null! = mOnSerialPortDataListener) { mOnSerialPortDataListener.onDataReceived(bytes); }}}; mSerialPortReadThread.start(); }/** * Serial port message reader thread * start the thread that receives the message * read serial port data requires thread */
public abstract class SerialPortReadThread extends Thread {
public abstract void onDataReceived(byte[] bytes);
private static final String TAG = SerialPortReadThread.class.getSimpleName();
private InputStream mInputStream; // This input stream ==mFileInputStream(associated mFd file handle)
private byte[] mReadBuffer; // Used to load the read serial port data
public SerialPortReadThread(InputStream inputStream) {
mInputStream = inputStream;
mReadBuffer = new byte[1024]; / / the buffer
}
@Override
public void run(a) {
super.run();
// Is that the same as always executing? Why keep doing it? As long as the App is alive, it needs to read the serial port data sent from the bottom layer
while(! isInterrupted()) {try {
if (null == mInputStream) {
return;
}
Log.i(TAG, "run: ");
int size = mInputStream.read(mReadBuffer);
if (-1 == size || 0 >= size) {
return;
}
byte[] readBytes = new byte[size];
// Copy to buffer
System.arraycopy(mReadBuffer, 0, readBytes, 0, size);
Log.i(TAG, "run: readBytes = " + new String(readBytes));
onDataReceived(readBytes); // Send out -(indirectly to SerialPortActivity to print display)
} catch (IOException e) {
e.printStackTrace();
return; }}}@Override
public synchronized void start(a) {
super.start();
}
/** * Close the thread to release resources */
public void release(a) {
interrupt();
if (null! = mInputStream) {try {
mInputStream.close();
mInputStream = null;
} catch(IOException e) { e.printStackTrace(); }}}}private void startSendThread(a) {
// Start the thread that sends messages
mSendingHandlerThread = new HandlerThread("mSendingHandlerThread");
mSendingHandlerThread.start();
// Handler
mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
byte[] sendBytes = (byte[]) msg.obj;
if (null! = mFileOutputStream &&null! = sendBytes &&0 < sendBytes.length) {
try {
mFileOutputStream.write(sendBytes);
if (null! = mOnSerialPortDataListener) { mOnSerialPortDataListener.onDataSent(sendBytes);//}}catch(IOException e) { e.printStackTrace(); }}}}; }Copy the code
Read and write data, in fact, is the two read, read stream operation, so we completed the serial port transceiver data
3.4 Closing the Serial Port
After we use the serial port, we will certainly close the serial port, close the serial port, we will start the read and write thread shut down, in the Native layer also shut down the serial port, file handle bound to the two streams also shut down
/** * Close the serial port */
public void closeSerialPort(a) {
if (null! = mFd) { closeNative();// Disable the serial port native function
mFd = null;
}
stopSendThread(); // Stop the thread that sent the message
stopReadThread(); // Stop the thread receiving the message
if (null! = mFileInputStream) {try {
mFileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
mFileInputStream = null;
}
if (null! = mFileOutputStream) {try {
mFileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
mFileOutputStream = null;
}
mOnOpenSerialPortListener = null;
mOnSerialPortDataListener = null;
}
Copy the code
Native layer
Class: cedric_serial_SerialPort * Method: close * Signature: ()V */
JNIEXPORT void JNICALL Java_com_test_closeNative
(JNIEnv *env, jobject thiz) {
jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd"."Ljava/io/FileDescriptor;");
jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor"."I");
jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
LOGD(Close (fd = %d), descriptor);
close(descriptor); InputStream/OutputStream= Serial port send/receive
}
Copy the code
4, summarize
Serial communication, in fact, is the operation of the file, while reading and writing, just keep up with school when you and your deskmate pass paper like, the above code reference is Google’s open source code, from looking for serial port to close the serial port, combing the basic process of serial communication! Hope to XDM useful, hope brothers one key three connect!