背景:RS485是最常用的工業(yè)現(xiàn)場通訊手段,它的傳輸字節(jié)采用了異步串口UART的規(guī)范。在通常的工控應(yīng)用中,需要傳輸由多個字節(jié)組成的數(shù)據(jù)幀,而RS485并沒有對數(shù)據(jù)幀有任何規(guī)范,需要應(yīng)用程序自己做數(shù)據(jù)幀的鑒別。
本文介紹在ESM6800、ESM7000和ESM8000主板上,利用iMX6/7/8串口的9bit RS485模式,實現(xiàn)RS485通訊網(wǎng)絡(luò)的整幀數(shù)據(jù)收發(fā)的功能。該功能可大大簡化應(yīng)用程序接收線程的復(fù)雜性,提高RS485通訊的效率。
整幀數(shù)據(jù)擁有固定的數(shù)據(jù)長度,由地址和數(shù)據(jù)構(gòu)成,地址為一個字節(jié),其余都為數(shù)據(jù)字節(jié),如下圖:
9bit RS485模式使用了串口固定校驗位的功能,定義了地址字節(jié)和數(shù)據(jù)字節(jié),地址字節(jié)是指固定校驗位始終為1的字節(jié)。而數(shù)據(jù)字節(jié)則是指固定校驗位始終為0的字節(jié)。同時9bit RS485模式實現(xiàn)了一些硬件過濾的功能,在接收的時候,必須要先接收到地址字節(jié)才會開始接收數(shù)據(jù)字節(jié),否則硬件會將收到的數(shù)據(jù)字節(jié)全部過濾掉,通過這種方式降低了設(shè)備的負載。所以9bit RS485模式顧名思義,通常使用在RS485模式上面,因為RS485可以作為總線掛接多個設(shè)備,在多路設(shè)備通訊的情況下通過這種校驗方式可以有效的降低設(shè)備負載和軟件的復(fù)雜程度。
英創(chuàng)工控主板中,能夠支持9bit RS485模式的主板和串口如下表,其中ES6801/ES6801L和ESM6800L這三款核心板能夠滿足低成本的需求,可以考慮作為RS485網(wǎng)絡(luò)中的Slave端:
主板型號 | 支持9bit RS485模式的串口 | 備注 |
ES6801(L) | ttyS1—ttyS6 | 適合作為Slave |
ESM6800(H) | ttyS1—ttyS5 | 適合作為Master |
ESM6800E | ttyS1—ttyS6 | 適合作為Master |
ESM6800L | ttyS1—ttyS6 | 適合作為Slave |
ESM6802 | ttyS1—ttyS4 | 適合作為Master |
ESM7000 | ttyS1—ttyS6 | 適合作為Master |
如果要使用9bit RS485模式,需要在程序中進行使能,使能后串口就會進入到該模式中,在發(fā)送數(shù)據(jù)的時候,可以支持兩種方式,一種是發(fā)送地址字節(jié),另一種是發(fā)送數(shù)據(jù)字節(jié)。在接收數(shù)據(jù)字節(jié)的時候,分為master和slave兩種模式,這兩種模式都需要先接收地址字節(jié),才能夠接收數(shù)據(jù)字節(jié),如果沒有接收到地址字節(jié),會自動將數(shù)據(jù)字節(jié)自動全部濾掉。他們的區(qū)別在于master模式下,只要接收到地址字節(jié),就會將這之后的數(shù)據(jù)字節(jié)全部接收,并交給應(yīng)用程序處理。而在slave模式下,需要先設(shè)置設(shè)備地址,只有接收到的地址字節(jié)和設(shè)備地址相同時,才會開始接收數(shù)據(jù)字節(jié)。
master模式下,接收數(shù)據(jù)示意圖:
Slave模式下,接收數(shù)據(jù)示意圖:
采用9bit RS485模式,有兩個優(yōu)點,第一點是不需要判斷是否接收到地址字節(jié),因為串口要在接收到地址字節(jié)(校驗位為1)后,才會接收數(shù)據(jù)字節(jié),特別是在slave模式下,只有當(dāng)?shù)刂纷止?jié)和設(shè)置的設(shè)備地址相等時,才會接收數(shù)據(jù)。第二點是不需要切換校驗方式,當(dāng)串口啟用了9bit RS485模式,就可以正常接收所有地址字節(jié)和數(shù)據(jù)字節(jié)了,只有在發(fā)送地址字節(jié)和數(shù)據(jù)字節(jié)的時候需要切換不同的設(shè)置,可以減少軟件上的操作。
英創(chuàng)公司在提供的例程Step2_serialtest中封裝的串口類CSerial的基礎(chǔ)上派生出一個專用于9bit RS485的類CRS485,在這個類中我們增加使能9bit RS485模式的函數(shù),讓客戶可以直接調(diào)用來實現(xiàn)相關(guān)功能。
/** * 派生用于9bit RS485的類 * **/ class CRS485 : public CSerial { private: //串口模式、設(shè)備地址和接收超時時間 int serial_mode; int serial_addr; public: //接收數(shù)據(jù)緩存和長度 char frame[100]; int frame_len; /** * 派生類的構(gòu)造函數(shù) * * 在構(gòu)造函數(shù)中初始化變量,以及設(shè)置9-bit RS485模式下的串口是處于master還是slave模式 * * 參數(shù)說明: * mode:值為0對應(yīng)master模式,值為1對應(yīng)slave模式 * addr:設(shè)備地址,大小為8bit,當(dāng)且僅當(dāng)mode為1是有效。 * **/ CRS485(int mode, int addr); /** * 發(fā)送9bit RS485整包數(shù)據(jù) * * 函數(shù)會將地址字節(jié)和數(shù)據(jù)字節(jié)填寫,并設(shè)置為相應(yīng)的模式一并發(fā)送 * * 參數(shù)說明: * addr:設(shè)備地址,大小為8bit,填入發(fā)送數(shù)據(jù)的地址字節(jié)中 * Buf:發(fā)送的數(shù)據(jù)字節(jié) * len:發(fā)送數(shù)據(jù)字節(jié)的長度 * * 返回值說明: * len:成功 * -1:失敗 * **/ int send_rs485_frame(char addr, char *Buf, int len); /** * 接收9bit RS485整包數(shù)據(jù) * * 函數(shù)會阻塞接收指定長度的數(shù)據(jù),可以設(shè)置超時時間,如果超過超時時間沒有接收到指定長度的數(shù)據(jù),則返回-1 * * 參數(shù)說明: * Buf:接收的數(shù)據(jù)字節(jié) * len:發(fā)送數(shù)據(jù)字節(jié)的長度 * timeout:超時時間,單位毫秒。如果在超時時間內(nèi)沒有收到指定長度的數(shù)據(jù),則返回-1。值為0則不阻塞,讀取不到數(shù)據(jù)立即返回。值為-1則沒有超時時間,如果接受不到指定長度數(shù)據(jù)會一直等待 * * 返回值說明: * 成功則返回接收到的數(shù)據(jù)長度 * -1:超時 * **/ int recv_rs485_frame(char *Buf, int len, int timeout); /** * 繼承自CSerial類的接收處理函數(shù) * * 在CSerial類的接收線程中會調(diào)用這個函數(shù),可以在函數(shù)中調(diào)用recv_rs485_frame()函數(shù),并處理接收到的數(shù)據(jù)字節(jié) * * **/ int PackagePro(); };
在類實例化的時候,代入?yún)?shù)就可以決定串口是處于master模式還是slave模式,如果是出于slave模式可以一起代入需要設(shè)定的設(shè)備地址:
//master模式 class CRS485 m_Serial(0, 0); //slave模式,設(shè)備地址為0x55 class CRS485 m_Serial(1, 0x55);
接收處理的時候,數(shù)據(jù)的長度通過宏DATA_LEN定義,客戶可以在PackagePro()函數(shù)中可以定義超時時間,然后調(diào)用recv_rs485_frame()函數(shù)來接收整包數(shù)據(jù),recv_rs485_frame()函數(shù)會阻塞,直至收到指定長度的數(shù)據(jù),或者到達超時時間才會返回。接收到整包數(shù)據(jù)后,就可以開始進行數(shù)據(jù)的處理,在接收線程調(diào)中循環(huán)調(diào)用PackagePro函數(shù):
#define DATA_LEN 10 // 數(shù)據(jù)長度 // 接收串口數(shù)據(jù)處理函數(shù) int CRS485::PackagePro() { int i1, timeout; //設(shè)置超時時間,單位毫秒 timeout = 500; //調(diào)用接收函數(shù)來獲取指定長度的整包數(shù)據(jù) i1 = recv_rs485_frame(DatBuf, m_DatLen, timeout); //接收到整包數(shù)據(jù),調(diào)用處理程序,這里只是簡單的打印 if(i1 != -1) { printf("frame addr = 0x%x\n", frame[0]); printf("frame data = "); for(i1=1; i1<DATA_LEN; i1++) { printf("0x%x ", frame[i1]); } printf("\n"); //處理完數(shù)據(jù),清除各個變量,重新設(shè)置串口以等待下一包數(shù)據(jù) memset(frame, 0, 100); frame_len = 0; } else printf("time out!\n"); return i1; }
在線程中的處理,循環(huán)調(diào)用接收處理函數(shù)即可,因為recv_rs485_frame()函數(shù)會阻塞,直至收到指定長度的數(shù)據(jù),或者到達超時時間才會返回:
int CSerial::ReceiveThreadFunc(void* lparam) { CSerial *pSer = (CSerial*)lparam; //定義讀事件集合 fd_set fdRead; int ret; struct timeval aTime; while( 1 ) { //接收處理函數(shù) pSer->PackagePro( pSer->DatBuf, pSer->m_DatLen); } printf( "ReceiveThreadFunc finished\n"); pthread_exit( NULL ); return 0; }
串口在發(fā)送的時候,比較簡單,直接調(diào)用send_rs485_frame()函數(shù),填入需要發(fā)送的地址和數(shù)據(jù)即可,使用下面的代碼來測試:
char addr = 0x55; char Buf[2]; Buf[0] = 0x55; Buf[1] = 0xaa; //發(fā)送地址字節(jié)和數(shù)據(jù)字節(jié) m_Serial.send_rs485_frame(addr, Buf, sizeof(Buf));
主板實際輸出的波形如下:
感興趣的客戶可以和英創(chuàng)的工程師聯(lián)系,索取完整的測試工程。
成都英創(chuàng)信息技術(shù)有限公司 028-8618 0660