Android串口通訊整理

這一段時(shí)間做的項(xiàng)目自動(dòng)售貨機(jī)和無(wú)線(xiàn)終端設(shè)備的通訊,都是通過(guò)串口進(jìn)行對(duì)接和通訊。在Android中進(jìn)行串口通信方式可以用Google官方提供的demo代碼(android-serialport-api),也可以通過(guò)NDK的方式使用C/C++進(jìn)行實(shí)現(xiàn)(Android串口助手,C++實(shí)現(xiàn)),其底層原理都是通過(guò)調(diào)用open函數(shù)打開(kāi)設(shè)備文件來(lái)進(jìn)行讀寫(xiě)操作。對(duì)串口接觸下來(lái),發(fā)現(xiàn)真的可以做很多有意思的東西,很多硬件設(shè)備都可以通過(guò)串口進(jìn)行通訊,比如:打印機(jī)、ATM吐卡機(jī)、IC/ID卡讀卡等,以及物聯(lián)網(wǎng)相關(guān)的設(shè)備。所以有必有對(duì)相關(guān)知識(shí)進(jìn)行下梳理和總結(jié)。

串口簡(jiǎn)介

串口通信(Serial Communications)的概念非常簡(jiǎn)單,串口按位(bit)發(fā)送和接收字節(jié)。串口可以在使用一根線(xiàn)(Tx)發(fā)送數(shù)據(jù)的同時(shí)用另一根線(xiàn)(Rx)接收數(shù)據(jù)。

串口參數(shù)

波特率:串口傳輸速率,用來(lái)衡量數(shù)據(jù)傳輸?shù)目炻?,即單位時(shí)間內(nèi)載波參數(shù)變化的次數(shù),如每秒鐘傳送240個(gè)字符,而每個(gè)字符格式包含10位(1個(gè)起始位,1個(gè)停止位,8個(gè)數(shù)據(jù)位),這時(shí)的波特率為240Bd,比特率為10位*240個(gè)/秒=2400bps。波特率與距離成反比,波特率越大傳輸距離相應(yīng)的就越短。

數(shù)據(jù)位:這是衡量通信中實(shí)際數(shù)據(jù)位的參數(shù)。當(dāng)計(jì)算機(jī)發(fā)送一個(gè)信息包,實(shí)際的數(shù)據(jù)往往不會(huì)是8位的,標(biāo)準(zhǔn)的值是6、7和8位。如何設(shè)置取決于你想傳送的信息。

停止位:用于表示單個(gè)包的最后一位。典型的值為1,1.5和2位。由于數(shù)據(jù)是在傳輸線(xiàn)上定時(shí)的,并且每一個(gè)設(shè)備有其自己的時(shí)鐘,很可能在通信中兩臺(tái)設(shè)備間出現(xiàn)了小小的不同步。因此停止位不僅僅是表示傳輸?shù)慕Y(jié)束,并且提供計(jì)算機(jī)校正時(shí)鐘同步的機(jī)會(huì)。適用于停止位的位數(shù)越多,不同時(shí)鐘同步的容忍程度越大,但是數(shù)據(jù)傳輸率同時(shí)也越慢。

校驗(yàn)位:在串口通信中一種簡(jiǎn)單的檢錯(cuò)方式。有四種檢錯(cuò)方式:偶、奇、高和低。當(dāng)然沒(méi)有校驗(yàn)位也是可以的。對(duì)于偶和奇校驗(yàn)的情況,串口會(huì)設(shè)置校驗(yàn)位(數(shù)據(jù)位后面的一位),用一個(gè)值確保傳輸?shù)臄?shù)據(jù)有偶個(gè)或者奇?zhèn)€邏輯高位。

串口地址

如下表不同操作系統(tǒng)的串口地址,Android是基于Linux的所以一般情況下使用Android系統(tǒng)的設(shè)備串口地址為/dev/ttyS0...

System Port 1 Port 2
IRIX? /dev/ttyf1 /dev/ttyf2
HP-UX /dev/tty1p0 /dev/tty2p0
Solaris?/SunOS? /dev/ttya /dev/ttyb
Linux? /dev/ttyS0 /dev/ttyS1
Digital UNIX? /dev/tty01 /dev/tty02

Android串口實(shí)現(xiàn)

在Android上使用串口比較快速的方式就是直接套用google官方的串口demo代碼(android-serialport-api),基本上能夠應(yīng)付很多在Android設(shè)備使用串口的場(chǎng)景。比如簡(jiǎn)單的讀卡號(hào)。

但是問(wèn)題來(lái)了!

在收發(fā)數(shù)據(jù)頻率很快的情況下,實(shí)際測(cè)試這種方式接收數(shù)據(jù)會(huì)有延遲。比如:發(fā)送一個(gè)命令之后,設(shè)備會(huì)同時(shí)響應(yīng)兩條命令,一條是結(jié)果一條是校驗(yàn)且兩條命令間隔時(shí)間僅1ms,按理兩條命令會(huì)幾乎同時(shí)收到,但是實(shí)際使用該方式會(huì)出現(xiàn)10ms的延遲。所以只能著手優(yōu)化,嘗試使用C/C++的方式進(jìn)行串口數(shù)據(jù)的讀寫(xiě)。

一番查閱下來(lái),使用C/C++實(shí)現(xiàn)其實(shí)和上面的demo差別不大,同樣是那幾個(gè)步驟,設(shè)置串口參數(shù),通過(guò)調(diào)用open方法開(kāi)啟串口,再進(jìn)行數(shù)據(jù)的讀寫(xiě)操作。出現(xiàn)數(shù)據(jù)讀取延遲很可能的原因,就是因?yàn)楣俜絛emo是通過(guò)Java層的文件流(FileInputStream,F(xiàn)ileOutputStream)進(jìn)行讀寫(xiě)操作引起的。如果有大神懂這塊的可以說(shuō)明這種方式導(dǎo)致延遲的原因。

關(guān)于使用C、C++在Android上實(shí)現(xiàn)串口通訊的源代碼有很多,沒(méi)有實(shí)際做過(guò)C/C++開(kāi)發(fā),但是也容易看懂。

設(shè)置串口波特率、數(shù)據(jù)位、停止位、校驗(yàn)位主要操作的就是termios 結(jié)構(gòu)體,對(duì)應(yīng)的頭文件是termios.h。

比如設(shè)置波特率代碼:

int SerialPort::setSpeed(int fd, int speed) {
 speed_t b_speed;
 struct termios cfg;
 b_speed = getBaudrate(speed);
 if (tcgetattr(fd, &cfg)) {
       LOGE("tcgetattr invocation method failed!");
       close(fd);
       return FALSE;
 }
?
 cfmakeraw(&cfg);
 cfsetispeed(&cfg, b_speed);
 cfsetospeed(&cfg, b_speed);
?
 if (tcsetattr(fd, TCSANOW, &cfg)) {
       LOGE("tcsetattr invocation method failed!");
       close(fd);
       return FALSE;
 }
 return TRUE;
}

打開(kāi)串口就是簡(jiǎn)單的調(diào)用open函數(shù),設(shè)置相關(guān)讀寫(xiě)參數(shù),這個(gè)和官方推薦的demo一致,代碼如下:

       LOGD("Open device!");
       isClose = false;
       fd = open(path, O_RDWR);
       if (fd < 0) {
             LOGE("Error to read %s port file!", path);
             return FALSE;
 }
?
 if (!setSpeed(fd, config.baudrate)) {
       LOGE("Set Speed Error!");
       return FALSE;
 }
 if (!setParity(fd, config.databits, config.stopbits, config.parity)) {
       LOGE("Set Parity Error!");
       return FALSE;
 }
 LOGD("Open Success!");
 return TRUE;
}

串口數(shù)據(jù)讀取涉及兩個(gè)函數(shù) select和read ,函數(shù)相關(guān)的含義暫且沒(méi)去深究,屬于C/C++范湊了,讀取數(shù)據(jù)代碼如下:

int SerialPort::readData(BYTE *data, int size) {
?
 int ret, retval;
 fd_set rfds;
 ret = 0;
?
 if (isClose) return 0;
 for (int i = 0; i < size; i++) {
       data[i] = static_cast<char>(0xFF);
 }
 FD_ZERO(&rfds);     //清空集合
 FD_SET(fd, &rfds);  //把要檢測(cè)的句柄fd加入到集合里
 // TODO Async operation. Thread blocking.
 if (FD_ISSET(fd, &rfds)) {
       FD_ZERO(&rfds);
       FD_SET(fd, &rfds);
       retval = select(fd + 1, &rfds, NULL, NULL, NULL);
       if (retval == -1) {
             LOGE("Select error!");
       } else if (retval) {
             LOGD("This device has data!");
             ret = static_cast<int>(read(fd, data, static_cast<size_t>(size)));
       } else {
            LOGE("Select timeout!");
       }
 }
 if (isClose) close(fd);
       return ret;
}

串口寫(xiě)數(shù)據(jù)就是調(diào)用write函數(shù)了,代碼如下:

int SerialPort::writeData(BYTE *data, int len) {
       int result;
       result = static_cast<int>(write(fd, data, static_cast<size_t>(len)));
       return TRUE;
}

因?yàn)椴皇煜/C++,所以就參考網(wǎng)上相關(guān)源代碼,依葫蘆畫(huà)瓢實(shí)現(xiàn)了一個(gè)基于C++的Android串口通訊庫(kù),并對(duì)相關(guān)串口控制做了優(yōu)化,詳細(xì)見(jiàn)gayhub,地址:https://github.com/freyskill/SerialPortHelper,歡迎star。

通過(guò)該庫(kù),完美解決串口數(shù)據(jù)讀取延遲的問(wèn)題。

阻塞與非阻塞

在項(xiàng)目初期使用google官方的串口demo代碼調(diào)試設(shè)備串口是否能正常通信的時(shí)候,遇到在串口讀數(shù)據(jù)的線(xiàn)程中會(huì)卡死在inputStream.read(buffer);這個(gè)時(shí)候就讓人疑惑了,不知道問(wèn)題是出在硬件還是在串口讀取上,在沒(méi)有了解串口相關(guān)知識(shí)前,希望的場(chǎng)景是讀數(shù)據(jù)的線(xiàn)程能夠不阻塞,一直輪詢(xún)讀取數(shù)據(jù)。

出現(xiàn)讀取數(shù)據(jù)線(xiàn)程卡死的情況是因?yàn)樵?fd = open(path_utf, O_RDWR | flags); 設(shè)置相關(guān)參數(shù),讀取默認(rèn)為阻塞模式,若在open操作中設(shè)置O_NONBLOCK則是非阻塞模式。在阻塞模式中,read沒(méi)有讀到數(shù)據(jù)會(huì)阻塞住,直到收到數(shù)據(jù);非阻塞模式read沒(méi)有讀到數(shù)據(jù)會(huì)返回-1不會(huì)阻塞。

修改open方法:
fd = open(path_utf, O_RDWR | flags | O_NONBLOCK | O_NOCTTY | O_NDELAY);

讀取線(xiàn)程就不會(huì)再出現(xiàn)卡死了,這個(gè)時(shí)候仍然接收不到串口設(shè)備反饋的數(shù)據(jù),就可以斷定是串口設(shè)備的問(wèn)題了。

關(guān)于串口文件打開(kāi)方式,可采用下面的文件打開(kāi)模式,具體說(shuō)明如下:

O_RDONLY:以只讀方式打開(kāi)文件
O_WRONLY:以只寫(xiě)方式打開(kāi)文件
O_RDWR:以讀寫(xiě)方式打開(kāi)文件
O_APPEND:寫(xiě)入數(shù)據(jù)時(shí)添加到文件末尾
O_CREATE:如果文件不存在則產(chǎn)生該文件,使用該標(biāo)志需要設(shè)置訪問(wèn)權(quán)限位mode_t
O_EXCL:指定該標(biāo)志,并且指定了O_CREATE標(biāo)志,如果打開(kāi)的文件存在則會(huì)產(chǎn)生一個(gè)錯(cuò)誤
O_TRUNC:如果文件存在并且成功以寫(xiě)或者只寫(xiě)方式打開(kāi),則清除文件所有內(nèi)容,使得文件長(zhǎng)度變?yōu)?
O_NOCTTY:如果打開(kāi)的是一個(gè)終端設(shè)備,這個(gè)程序不會(huì)成為對(duì)應(yīng)這個(gè)端口的控制終端,如果沒(méi)有該標(biāo)志,任何一個(gè)輸入,例如鍵盤(pán)中止信號(hào)等,都將影響進(jìn)程。
O_NONBLOCK:該標(biāo)志與早期使用的O_NDELAY標(biāo)志作用差不多。程序不關(guān)心DCD信號(hào)線(xiàn)的狀態(tài),如果指定該標(biāo)志,進(jìn)程將一直在休眠狀態(tài),直到DCD信號(hào)線(xiàn)為0。

實(shí)際應(yīng)用中,都會(huì)選擇阻塞模式,這樣更節(jié)省資源。但是如果希望在一個(gè)線(xiàn)程中同時(shí)進(jìn)行讀寫(xiě)操作,沒(méi)數(shù)據(jù)反饋時(shí),線(xiàn)程就會(huì)阻塞等待,就無(wú)法進(jìn)行寫(xiě)數(shù)據(jù)了。

串口數(shù)據(jù)校驗(yàn)方式

一般情況下串口通訊協(xié)議都會(huì)在數(shù)據(jù)幀或者說(shuō)命令格式里定義一個(gè)校驗(yàn)方式,常用的有異或校驗(yàn)、和校驗(yàn)、CRC校驗(yàn)LRC校驗(yàn)。

注意:這里說(shuō)的校驗(yàn)和上面說(shuō)的校驗(yàn)位是不同的,校驗(yàn)位針對(duì)的是單個(gè)字節(jié),校驗(yàn)類(lèi)型針對(duì)的是單個(gè)數(shù)據(jù)幀。

校驗(yàn)方式一般放在命令最后,可以是一個(gè)byte,也可以是兩個(gè)byte或者其他,具體看協(xié)議設(shè)計(jì)。

比如命令格式如下,采用和校驗(yàn):

addr command data_length data1 data2 datan checksum
0x01 0x52 0x05 0x11 0xBA ... 8E

其中,獲取校驗(yàn)碼(checksum)就是將命令中的數(shù)據(jù)進(jìn)行相加生成,Checksum=256-(data1+data2+datan)算出校驗(yàn)碼為:8E。具體計(jì)算方式就是通過(guò)將十六進(jìn)制進(jìn)行相加算出校驗(yàn)碼的十進(jìn)制字符,詳細(xì)代碼如下:

/**
 *  獲取校驗(yàn)碼(計(jì)算方式如下:cs= 256-(data1+data2+data3+data4+datan))
 */
public static String getCheckSum(String data){
       Integer in = Integer.valueOf(makeChecksum(data),16);
       String st = Integer.toHexString(256 -in).toUpperCase();
       st = String.format("%2s",st);
       return st.replaceAll(" ","0");
}

十六進(jìn)制進(jìn)行相加代碼:

/**
* 生成校驗(yàn)碼,十六進(jìn)制相加
* @param data
* @return
*/
public static String makeChecksum(String data) {
 if (data == null || data.equals("")) {
      return "00";
 }
 int iTotal = 0;
 int iLen = data.length();
 int iNum = 0;
?
 while (iNum < iLen){
     String s = data.substring(iNum, iNum + 2);
     System.out.println(s);
     iTotal += Integer.parseInt(s, 16);
     iNum = iNum + 2;
 }
?
 /**
 * 用256求余最大是255,即16進(jìn)制的FF
 */
 int iMod = iTotal % 256;
 String sHex = Integer.toHexString(iMod);
 iLen = sHex.length();
 //如果不夠校驗(yàn)位的長(zhǎng)度,補(bǔ)0,這里用的是兩位校驗(yàn)
 if (iLen < 2){
     sHex = "0" + sHex;
  }
  return sHex;
}

再比如使用CRC校驗(yàn)(有CRC8,CRC16,CRC32),關(guān)于CRC校驗(yàn)的原理可以參考:https://blog.csdn.net/u011854789/article/details/80206676

/**
 * 獲取CRC檢驗(yàn)
 * @param command  命令集
 * @param len      命令長(zhǎng)度 
 * @return
*/
public static int CalCrc(byte[] command,int len){
 long MSBInfo;
 int i,j ;
 int nCRCData;
 nCRCData = 0xffff;
 for(i = 0; i < len ;i++) {
     int temp = (int)(command[i]&0xff);
     nCRCData = nCRCData ^ temp ;
     for(j= 0 ; j < 8 ;j ++){
         MSBInfo = nCRCData & 0x0001;
         nCRCData = nCRCData  >> 1;
         if(MSBInfo != 0 )
             nCRCData = nCRCData ^ 0xa001;
     }
 }
 return nCRCData;
}

串口設(shè)備問(wèn)題排查

在對(duì)接串口設(shè)備的過(guò)程中,負(fù)責(zé)硬件的同事說(shuō)在PC上通過(guò)串口助手收發(fā)數(shù)據(jù)沒(méi)有問(wèn)題,然鵝我在Android設(shè)備上,通過(guò)串口就是無(wú)法接收到數(shù)據(jù),于是乎雙方僵持,對(duì)方就差說(shuō):“如果我硬件有問(wèn)題我吃xiang...” 堅(jiān)稱(chēng)是Android板子串口問(wèn)題或者是我讀寫(xiě)數(shù)據(jù)的代碼有問(wèn)題。在沒(méi)有示波器的情況下,如何定位問(wèn)題呢?各方打聽(tīng)嘗試了如下方式:

  1. 直接短路Tx 與 Rx 兩條線(xiàn)

    不接設(shè)備,先確定Android設(shè)備(開(kāi)發(fā)板)上的串口是否可通,檢查方式:直接短路板子上的Tx和Rx兩個(gè)針腳,然后通過(guò)Android的串口demo或者相關(guān)串口助手進(jìn)行命令發(fā)送,看串口是否能夠接收響應(yīng)。也就是檢查板子串口是否可以自發(fā)自收。

  2. 直接與PC對(duì)接

    操作方式是將Android板子上的串口通過(guò)USB轉(zhuǎn)接頭直接插入PC,然后在PC和Android設(shè)備上同時(shí)打開(kāi)串口助手,波特率等參數(shù)保持一致。對(duì)接之后打開(kāi)串口,PC發(fā)命令看Android端是否能接收到,反之Android端發(fā)看PC端是否能接收到。

在嘗試了上面方法之后,發(fā)現(xiàn)Android端的串口是通的,那原因就只能出在要使用串口的設(shè)備(無(wú)線(xiàn)通訊模塊)上了,又是一段時(shí)間僵持之后,我說(shuō)這東西是不是要接電才行?結(jié)果一試,果然是沒(méi)有接電的原因,崩潰。為什么PC上不需要接電能通,然道是因?yàn)閁SB已經(jīng)帶電?不得而知。

以上,只是提供一種在沒(méi)有示波器情況下,檢查串口是否正常的方式,僅做參考。

數(shù)據(jù)轉(zhuǎn)換工具類(lèi)

串口開(kāi)發(fā)中比較常見(jiàn)進(jìn)制與進(jìn)制,進(jìn)制與字節(jié)間的轉(zhuǎn)換,比如:十六進(jìn)制轉(zhuǎn)十進(jìn)制,字節(jié)數(shù)組轉(zhuǎn)十六進(jìn)制字符串等。
相關(guān)代碼如下:

package top.keepempty.serialdemo;
/**
 *  數(shù)據(jù)轉(zhuǎn)換工具類(lèi)
 *  @author frey
 */
public class DataConversion {
?
 /**
 * 判斷奇數(shù)或偶數(shù),位運(yùn)算,最后一位是1則為奇數(shù),為0是偶數(shù)
 * @param num
 * @return
 */
 public static int isOdd(int num) {
     return num & 0x1;
 }
?
 /**
 * 將int轉(zhuǎn)成byte
 * @param number
 * @return
 */
 public static byte intToByte(int number){
     return hexToByte(intToHex(number));
 }
?
 /**
 * 將int轉(zhuǎn)成hex字符串
 * @param number
 * @return
 */
 public static String intToHex(int number){
     String st = Integer.toHexString(number).toUpperCase();
     return String.format("%2s",st).replaceAll(" ","0");
 }
?
 /**
 * 字節(jié)轉(zhuǎn)十進(jìn)制
 * @param b
 * @return
 */
 public static int byteToDec(byte b){
     String s = byteToHex(b);
     return (int) hexToDec(s);
 }
?
 /**
 * 字節(jié)數(shù)組轉(zhuǎn)十進(jìn)制
 * @param bytes
 * @return
 */
 public static int bytesToDec(byte[] bytes){
     String s = encodeHexString(bytes);
     return (int)  hexToDec(s);
 }
?
 /**
 * Hex字符串轉(zhuǎn)int
 *
 * @param inHex
 * @return
 */
 public static int hexToInt(String inHex) {
    return Integer.parseInt(inHex, 16);
 }
?
 /**
 * 字節(jié)轉(zhuǎn)十六進(jìn)制字符串
 * @param num
 * @return
 */
 public static String byteToHex(byte num) {
     char[] hexDigits = new char[2];
     hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);
     hexDigits[1] = Character.forDigit((num & 0xF), 16);
     return new String(hexDigits).toUpperCase();
 }
?
 /**
 * 十六進(jìn)制轉(zhuǎn)byte字節(jié)
 * @param hexString
 * @return
 */
 public static byte hexToByte(String hexString) {
     int firstDigit = toDigit(hexString.charAt(0));
     int secondDigit = toDigit(hexString.charAt(1));
     return (byte) ((firstDigit << 4) + secondDigit);
 }
?
 private static  int toDigit(char hexChar) {
      int digit = Character.digit(hexChar, 16);
      if(digit == -1) {
              throw new IllegalArgumentException(
                     "Invalid Hexadecimal Character: "+ hexChar);
      }
      return digit;
 }
?
 /**
 * 字節(jié)數(shù)組轉(zhuǎn)十六進(jìn)制
 * @param byteArray
 * @return
 */
 public static String encodeHexString(byte[] byteArray) {
    StringBuffer hexStringBuffer = new StringBuffer();
    for (int i = 0; i < byteArray.length; i++) {
          hexStringBuffer.append(byteToHex(byteArray[i]));
    }
    return hexStringBuffer.toString().toUpperCase();
 }
?
 /**
 * 十六進(jìn)制轉(zhuǎn)字節(jié)數(shù)組
 * @param hexString
 * @return
 */
 public static byte[] decodeHexString(String hexString) {
    if (hexString.length() % 2 == 1) {
           throw new IllegalArgumentException(
               "Invalid hexadecimal String supplied.");
    }
    byte[] bytes = new byte[hexString.length() / 2];
    for (int i = 0; i < hexString.length(); i += 2) {
           bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
    }
    return bytes;
 }
?
 /**
 * 十進(jìn)制轉(zhuǎn)十六進(jìn)制
 * @param dec
 * @return
 */
 public static String decToHex(int dec){
     String hex = Integer.toHexString(dec);
      if (hex.length() == 1) {
           hex = '0' + hex;
      }
     return hex.toLowerCase();
 }
?
 /**
 * 十六進(jìn)制轉(zhuǎn)十進(jìn)制
 * @param hex
 * @return
 */
 public static long hexToDec(String hex){
     return Long.parseLong(hex, 16);
 }
?
 /**
 * 十六進(jìn)制轉(zhuǎn)十進(jìn)制,并對(duì)卡號(hào)補(bǔ)位
 */
 public static String setCardNum(String cardNun){
     String cardNo1= cardNun;
     String cardNo=null;
     if(cardNo1!=null){
          Long cardNo2=Long.parseLong(cardNo1,16);
           //cardNo=String.format("%015d", cardNo2);
           cardNo = String.valueOf(cardNo2);
      }
      return cardNo;
     }
}

其他

串口中相關(guān)引腳說(shuō)明如下表,一般在開(kāi)發(fā)板子上可以看到Tx,Rx這兩個(gè)針腳,分別標(biāo)識(shí)串口的發(fā)送和接收。

序號(hào) 信號(hào)名稱(chēng) 符號(hào) 流向 功能
2 發(fā)送數(shù)據(jù) TXD DTE→DCE DTE 發(fā)送串行數(shù)據(jù)
3 接收數(shù)據(jù) RXD DTE←DCE DTE 接收串行數(shù)據(jù)
4 請(qǐng)求發(fā)送 RTS DTE→DCE DTE 請(qǐng)求 DCE 將線(xiàn)路切換到發(fā)送方式
5 允許發(fā)送 CTS DTE←DCE DCE 告訴 DTE 線(xiàn)路已接通可以發(fā)送數(shù)據(jù)
6 數(shù)據(jù)設(shè)備準(zhǔn)備好 DSR DTE←DCE DCE 準(zhǔn)備好
7 信號(hào)地 信號(hào)公共地
8 載波檢測(cè) DCD DTE←DCE 表示 DCE 接收到遠(yuǎn)程載波
20 數(shù)據(jù)終端準(zhǔn)備好 DTR DTE→DCE DTE 準(zhǔn)備好
22 振鈴指示 RI DTE←DCE 表示 DCE 與線(xiàn)路接通,出現(xiàn)振鈴

關(guān)于串口的相關(guān)知識(shí)可以參考這篇文章

參考:

https://www.cnblogs.com/hackfun/p/7612617.html

https://blog.csdn.net/tianruxishui/article/details/37592903

以上,只是個(gè)人學(xué)習(xí)整理,歡迎學(xué)習(xí)交流,如有紕漏歡迎指出,大神略過(guò)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容