柚子快報邀請碼778899分享:網(wǎng)絡協(xié)議 串口通訊理解
柚子快報邀請碼778899分享:網(wǎng)絡協(xié)議 串口通訊理解
機器的通信方式有兩種,分別是并行通信與串行通信
并行通信:并行通信是指多比特數(shù)據(jù)同時通過并行線進行傳送,這樣數(shù)據(jù)傳送速度大大提高,但并行傳送的線路長度受到限制,因為長度增加,干擾就會增加,數(shù)據(jù)也就容易出錯。
串行通信:串行通信是指使用一條數(shù)據(jù)線,將數(shù)據(jù)一位一位地依次傳輸,每一位數(shù)據(jù)占據(jù)一個固定的時間長度。其只需要少數(shù)幾條線就可以在系統(tǒng)間交換信息,特別適用于計算機與計算機、計算機與外設之間的遠距離通信。
異步串行通信:異步串行通信是指通信雙方以一個字符(包括特定附加位)作為數(shù)據(jù)傳輸單位且發(fā)送方傳送字符的間隔時間不一定,具有不規(guī)則數(shù)據(jù)段傳送特性的串行數(shù)據(jù)傳輸。
同步串行通信:同步串行通信是指在約定的通信速率下(即相同波特率),發(fā)送端和接收端的時鐘信號頻率和相位始終保持一致(同步),這就保證了通信雙方在發(fā)送和接收數(shù)據(jù)時具有完全一致的定時關系。
比特率(Bitrate) 來表示,即每秒鐘傳輸?shù)亩M制位數(shù),單位為比特每秒(bit/s)。
“波特率”(Baudrate),它表示每秒鐘傳輸了多少個碼元。而碼元是通訊信號調制的概念,通訊中常用時間間隔相同的符號來表示一個二進制數(shù)字,這樣的信號稱為碼元。
波特率與比特率的關系為:比特率=波特率X單個調制狀態(tài)對應的二進制位數(shù)。
因為很多常見的通訊中一個碼元都是表示兩種狀態(tài),人們常常直接以波特率來表示比特率
串口通訊的數(shù)據(jù)包由發(fā)送設備通過自身的TXD 接口傳輸?shù)浇邮赵O備的RXD 接口。在串口通訊協(xié)議中,規(guī)定了數(shù)據(jù)包的內容,它由起始位、主體數(shù)據(jù)、校驗位以及停止位組成,通訊雙方的數(shù)據(jù)包格式要約定一致才能正常收發(fā)數(shù)據(jù)。
起始位占1位,為邏輯0。數(shù)據(jù)位占5 ~ 8位,可配置。校驗位占1位,可配置為奇校驗、偶校驗、無校驗,停止位的值為邏輯1。
串口通信即可以實現(xiàn)半雙工,也可以實現(xiàn)全雙工
單工:數(shù)據(jù)傳輸只支持數(shù)據(jù)在一個方向上傳輸
半雙工:允許數(shù)據(jù)在兩個方向上傳輸。但是,在某一時刻,只允許數(shù)據(jù)在一個方向上傳輸
全雙工:允許數(shù)據(jù)同時在兩個方向上傳輸
TTL 標準
理想狀態(tài)下,使用5V 表示二進制邏輯“1”,使用0V 表示邏輯“0”.
UART
UART(Universal Asynchronous Receiver Transmitter:通用異步收發(fā)器),是電腦硬件的一部分,它把將要傳輸?shù)馁Y料在串行通信與并行通信之間加以轉換,UART通常被集成于其他通訊接口的連接上。UART即我們通常說的“串口”。該總線有兩條數(shù)據(jù)線,可以實現(xiàn)全雙工的發(fā)送和接收,在嵌入式系統(tǒng)中常用于主機與輔助設備之間的通信。
UART進行串口通信使用TTL電平。5V工作電壓的MCU,使用0 ~ 0.5V表示邏輯0,2.5V ~ 5V表示邏輯1;3.3V工作電壓的MCU,使用0 ~ 0.5V表示邏輯0,2.5V ~ 3.3V表示邏輯1。5V的MUC不能與3.3V的MCU直接連接。
空閑位:不進行傳輸數(shù)據(jù)時,默認為邏輯1,為高電平;
起始位:先發(fā)出一個邏輯“0”,表示消息幀的開始;
數(shù)據(jù)位:緊接著起始位之后,可由5~8位組成,通常傳輸8位即一個字節(jié)。先發(fā)送數(shù)據(jù)的低位,后發(fā)送數(shù)據(jù)的高位;
奇偶校驗位:緊接著數(shù)據(jù)位后面(可有可無),使得“1”的位數(shù)應為偶數(shù)(偶校驗)或奇數(shù)(奇校驗),校驗數(shù)據(jù)傳輸是否正確;
停止位:它是消息傳輸結束的標志,它可以是1位、1.5位、2位的高電平, 由于數(shù)據(jù)是在傳輸線上定時的,并且每一個設備有其自己的時鐘,很可能在通信中兩臺設備間出現(xiàn)了小小的不同步。因此停止位不僅僅是表示傳輸?shù)慕Y束,并且提供計算機校正時鐘同步的機會。
波特率:是衡量數(shù)據(jù)傳輸速率的指標,表示每秒鐘傳輸?shù)奈粩?shù)。例如設置串口的波特率為9600,則表示是1s傳輸9600個bit的數(shù)據(jù),則傳送每個位的時間為 1s / 9600 ≈ 104us,從而區(qū)分消息幀中每個位傳輸?shù)臄?shù)據(jù);
缺點:UART一般直接使用TTL信號來表示0和1,但TTL信號抗干擾能力較差,數(shù)據(jù)在傳輸過程中很容易出錯;且TTL信號的通信距離也很短;
RS232
在目前的其它工業(yè)控制使用的串口通信中,一般只使用RXD、TXD 以及GND 三條信號線,直接傳輸數(shù)據(jù)信號,而RTS、CTS、DSR、DTR 及DCD 信號都被裁剪掉了,這主要是考慮到近程通信與遠程通信問題。
特性:
工作方式:單端(非平衡)
節(jié)點數(shù):點對點通訊(1收1發(fā))
最大傳輸距離:50ft ( 50 * 0.3048 = 15.24m)
最大傳輸速率:20kbit/s
連接方式:點對點(全雙工)
電氣特性:-3V ~ -15V表示邏輯1,3V ~ 15V表示邏輯0
常用芯片有max232、SP232等
缺點:通信距離短,速率低,而且只能點對點通信,無法組建多機通信系統(tǒng),且容易受外界電氣干擾導致信息傳輸錯誤。
RS-485
標準運行連接多個收發(fā)器,即具有多站能力,增加了多點、雙向的通信能力
RS-485采用平衡發(fā)送和差分接收,因此具有抑制共模干擾的能力。
RS485有兩線制和四線制兩種接線,四線制只能實現(xiàn)點對點的通信方式,現(xiàn)很少采用,多采用的是兩線制接線方式,這種接線方式為總線拓撲結構,在同一總線上最多可以掛接32個節(jié)點。
采用兩線半雙工傳輸,最大速率10Mb/s,電平邏輯是兩線的電平差來決定的,提高抗干擾能力,傳輸距離長(幾十米到上千米)。
特性:
工作方式:差分(平衡);
節(jié)點數(shù):點對多通訊(1發(fā)32收);
最大傳輸距離:4000ft ( 4000 * 0.3048 = 1219.2m);
最大傳輸速率:10Mbit/s;
連接方式:多點對多點(兩線制,半雙工);
電氣特性:2V ~ 6V表示邏輯1,-2V ~ -6V表示邏輯0;
具體編程
在32位的Windows系統(tǒng)中,串口和其它通信設備是作為文件處理的。串口的打開、關閉、讀取和寫入所用的函數(shù)與操作文件的函數(shù)完全一致。
打開串口
CreateFile()為讀訪問、寫訪問或讀寫訪問“打開”串口。返回一個句柄、
HANDLE CreateFile
(
LPCTSTR lpszName,
DWORD fdwAccess,
DWORD fdwShareMode,
LPSECURITY_ATTRIBUTES lpsa,
DWORD fdwCreate,
DWORD fdwAttrsAndFlags,
HANDLE hTemplateFile
)
lpszName:指定要打開的串口邏輯名,用字符串表示,如“COM1”和“COM2”分別表示串口1和串口2。
·fdwAccess:用來指定串口訪問的類型。與文件一樣,串口也是可以被打開以供讀取、寫入或者兩者兼有。因為大部分串口通信都是雙向的,因此常常在設置中將兩個標識符連接起來使用。如:
fdwAccess = GENERIC_READ | GENERIC_WRITE;
·fdwShareMode:指定該端口的共享屬性。對于不能共享的串口,它必須設置為0。如果在當前的應用程序調用CreateFile()時,另一個應用程序已經(jīng)打開了串口,該函數(shù)就會返回錯誤代碼,原因是兩個應用程序不能共享一個端口。
·Ipsa:引用安全性屬性結構(SECURITY_ARRTIBUTES)。將該參數(shù)設置為NULL將為該端口分配缺省的安全性屬性。
·fdwCreate:指定如果CreateFile()正在被已有的文件調用時應采取的動作。因為串口總是存在,fdwCreate必須設置成OPEN_EXISTING。該標志告訴Windows不用企圖創(chuàng)建新端口,而是打開已經(jīng)存在的端口
fdwAttrsAndFlags:描述了端口的各種屬性。對于文件來說,有可能具有很多屬性,但對于串口,異步設置為FILE_FLAG_OVERLAPPED。同步設置為0.
·hTemplateFile:指向模板文件的句柄,當端口處于打開狀態(tài)時,不使用該參數(shù),因而必須置成0。
//同步方式
hCom = CreateFileA(portname, //串口名
GENERIC_READ | GENERIC_WRITE, //支持讀寫
0, //獨占方式,串口不支持共享
NULL,//安全屬性指針,默認值為NULL
OPEN_EXISTING, //打開現(xiàn)有的串口文件
0, //0:同步方式,F(xiàn)ILE_FLAG_OVERLAPPED:異步方式
NULL);//用于復制文件句柄,默認值為NULL,對串口而言該參數(shù)必須置為NULL
關閉串口
只需要調用CloseHandle()函數(shù)關閉由CreateHandle()函數(shù)返回得句柄即可。
設置緩沖區(qū)
通過調用SetupComm()實現(xiàn)其它初始化工作。也可以不調用SetupComm()函數(shù),Windows系統(tǒng)也會分配缺省的發(fā)送和接收緩沖區(qū)。
BOOL SetupComm
(
HANDLE hFile, // 通信設備句柄
DWORD dwInQueue, // 輸入緩沖區(qū)大小
DWORD dwOutQueue // 輸出緩沖區(qū)大小
);
BOOL PurgeComm
(
HANDLE hFile, // 返回的句柄
DWORD dwFlags // 執(zhí)行的動作
);
參數(shù)hFile指向由CreateFile函數(shù)返回的句柄,dwFlags表示執(zhí)行的動作,這個參數(shù)可以是表表5中的任一個。參數(shù)hFile指向由CreateFile函數(shù)返回的句柄,可以調用GetLastError()函數(shù)獲得進一步的錯誤信息。
值 描述 PURGE_TXABORT即使發(fā)送操作沒有完成,也終止所有的重疊發(fā)送操作,立即返回 PURGE_RXABORT即使接收操作沒有完成,也終止所有的重疊接收操作,立即返回 PURGE_TXCLEAR清除發(fā)送緩沖區(qū) PURGE_RXCLEAR清除接收緩沖區(qū)
BOOL FlushFileBuffers
(
HANDLE hFile // 函數(shù)打開的句柄
);
如果要保證緩沖區(qū)的所有字符都被發(fā)送,應該調用FlushFileBuffer()函數(shù)。該函數(shù)只受流量控制的支配,不受超時控制的支配,它在所有的寫操作完成后才返回。
獲取配置
使用GetCommState()函數(shù)獲取串口的當前配置。
BOOL GetCommState
(
HANDLE hFile, // 通信設備句柄
LPDCB lpDCB // 指向device-control block structure的指針
);
如果GetCommState()函數(shù)調用成功,則返回值不為零。若函數(shù)調用失敗,則返回值為零,如果想得到進一步的錯誤信息,可以調用GetLastError()函數(shù)來獲取。
使用GetCommState()函數(shù)獲取串口的當前配置
設置配置
調用SetCommState()函數(shù)配置修改過的DCB來配置端口。
BOOL SetCommState
(
HANDLE hFile, // 已打開的串口的句柄
LPDCB lpDCB // 指向DCB結構的指針
);
DCB結構的主要參數(shù)說明如下:
·DCBLength: 一字節(jié)為單位指定的DCB結構的大小。
·Baudrate: 用于指定串口設備通信的數(shù)據(jù)傳輸速率,它可以是實際的數(shù)據(jù)傳輸速率數(shù)值,也可以是下列數(shù)據(jù)之一:CBR_110, CBR_19200, CBR_300, CBR_38400, CBR_600, CBR_56000, CBR_1200, CBR_57600, CBR_2400, CBR_115200, CBR_4800, CBR_12800, CBR_9600, CBR_25600, CBR_14400。
·fBinary: 指定是否允許二進制。Win32API不支持非二進制傳輸,因此這個參數(shù)必須設置為TRUE,如果設置為FALSE則不能正常工作。
·fParity: 指定是否允許奇偶校驗,如果這個參數(shù)設置為TRUE,則執(zhí)行奇偶校驗并報告錯誤信息。
·fOutxCtsFlow: 指定CTS是否用于檢測發(fā)送流控制。當該成員為TRUE,而CTS為OFF時,發(fā)送將被掛起,直到CTS置ON。
·fOutxDsrFlow: 指定DSR是否用于檢測發(fā)送流控制,當該成員為TRUE,而DSR為OFF時,發(fā)送將被掛起,直到DSR置ON。
·fDtrControl: 指定DTR流量控制,可以是表1中的任一值。
值 功能描述 DTR_CONTROL_DISABLE禁止DTR線,并保持禁止狀態(tài) DTR_CONTROL_ENABLE允許DTR線,并保持允許狀態(tài) DTR_CONTROL_HANDSHAKE允許DTR握手,如果允許握手,則不允許應用程序使用EscapeCommFunction函數(shù)調整線路
·fDsrSensitivity: 指定通信驅動程序對DTR信號線是否敏感,如果該位置設為TRUE時,DSR信號為OFF,接收的任何字節(jié)將被忽略。
·fTXContinueOnXoff: 指定當接收緩沖區(qū)已滿,并且驅動程序已經(jīng)發(fā)送出XoffChar字符時發(fā)送是否停止。當該成員為TRUE時,在接收緩沖區(qū)內接收到了緩沖區(qū)已滿的字節(jié)XoffLim,并且驅動程序已經(jīng)發(fā)送出XoffChar字符終止接收字節(jié)之后,發(fā)送繼續(xù)進行。該成員為FALSE時,接收緩沖區(qū)接收到代表緩沖區(qū)已空的字節(jié)XonLim,并且驅動程序已經(jīng)發(fā)送出恢復發(fā)送的XonChar字符后,發(fā)送可以繼續(xù)進行。
·fOutX: 該成員為TRUE時,接收到XoffChar之后停止發(fā)送,接收到XonChar之后發(fā)送將重新開始。
·fInX: 該成員為TRUE時,接收緩沖區(qū)內接收到代表緩沖區(qū)滿的字節(jié)XoffLim之后,XoffChar發(fā)送出去,接收緩沖區(qū)接收到代表緩沖區(qū)已空的字節(jié)XonLim之后,XonChar發(fā)送出去。
·fErrorChar: 當該成員為TRUE,并且fParity為TRUE時,就會用ErrorChar成員指定的字符來代替奇偶校驗錯誤的接收字符。
·fNull: 指明是否丟棄接收到的NULL( ASCII 0 )字符,該成員為TRUE時,接收時去掉空(零值)字節(jié);反之則不丟棄。
表2 RTS 流量控制
值 功能描述RTS_CONTROL_DISABLE打開設備時禁止RTS線,并保持禁止狀態(tài)RTS_CONTROL_ENABLE打開設備時允許RTS線,并保持允許狀態(tài)DTR_CONTROL_HANDSHAKE允許握手。在接收緩沖區(qū)小于半滿時將RTS 置為ON,在接收緩沖區(qū)超過3/4時將RTS置為OFF。如果允許握手,則不允許應用程序使用EscapeCommFunction函數(shù)調整線路DTR_CONTROL_TOGGLE當發(fā)送的字節(jié)有效,將RTS置為 ON,發(fā)送完緩沖區(qū)的所有字節(jié)后, RTS置為OFF
·fRtsControl: 指定 RTS 流量控制,可以取表2中的值。0值和DTR_CONTROL_HANDSHAKE等價。
·fAbortOnError: 如果發(fā)送錯誤,指定是否可以終止讀、寫操作。如果該位為TRUE,當發(fā)生錯誤時,驅動程序以出錯狀態(tài)終止所有的讀寫操作。只有當應用程序調用ClearCommError()函數(shù)處理后,串口才能接收隨后的通信操作。
·fDummy2: 保留的位,沒有使用。
·wReserved:沒有使用,必須為零。
·XonLim: 指定在XOFF字符發(fā)送之前接收到緩沖區(qū)中可允許的最小字節(jié)數(shù)。
·XoffLim: 指定在XOFF字符發(fā)送之前緩沖區(qū)中可允許的最小可用字節(jié)數(shù)
·ByteSize: 指定端口當前使用的數(shù)據(jù)位數(shù)。
·Parity: 指定端口當前使用的奇偶校驗方法。它的可能值如表3所示。
·StopBits: 指定串口當前使用的停止位數(shù),可能值如表4所示。
表3 奇偶校驗方法
值 功能描述 EVENPARITY 偶校驗 MARKPARITY 標號校驗 NOPARITY 無校驗 ODDPARITY 奇校驗 SPACEPARITY 空格效益
表4 停止位數(shù)描述
值 功能描述 ONESTOPBIT 1位停止位 ONE5STOPBITS 1.5位停止位 TWOSTOPBITS 2位停止位
·XonChar: 指明發(fā)送和接收的XON字符值,它表明允許繼續(xù)傳輸。
·XoffChar: 指明發(fā)送和接收的XOFF字符值,它表示暫停數(shù)據(jù)傳輸。
·ErrorChar: 本字符用來代替接收到的奇偶校驗發(fā)生錯誤的字符。
·EofChar: 用來表示數(shù)據(jù)的結束。
·EvtChar: 事件字符。當接收到此字符的時候,會產(chǎn)生一個事件。
·wReserved1: 保留的位,沒有使用。
// 配置參數(shù)
DCB p;
memset(&p, 0, sizeof(p));
p.DCBlength = sizeof(p);
p.BaudRate = baudrate; // 波特率
p.ByteSize = databit; // 數(shù)據(jù)位
switch (parity) //校驗位
{
case 0:
p.Parity = NOPARITY; //無校驗
break;
case 1:
p.Parity = ODDPARITY; //奇校驗
break;
case 2:
p.Parity = EVENPARITY; //偶校驗
break;
case 3:
p.Parity = MARKPARITY; //標記校驗
break;
}
switch (stopbit) //停止位
{
case 1:
p.StopBits = ONESTOPBIT; //1位停止位
break;
case 2:
p.StopBits = TWOSTOPBITS; //2位停止位
break;
case 3:
p.StopBits = ONE5STOPBITS; //1.5位停止位
break;
}
if (!SetCommState(hCom, &p))
{
// 設置參數(shù)失敗
return false;
}
超時設置
超時結構直接影響讀和寫的操作行為。當事先設定的超時間隔消逝時,ReadFile() 、ReadFileEx()、 WriteFile()和 WriteFileEx()操作仍未結束,那么超時設置將無條件結束讀寫操作,而不管是否已讀出或已寫入指定數(shù)量的字符。
在讀或寫操作期間發(fā)生的超時將不按錯誤處理,即讀或寫操作返回指定成功的值。對于同步讀或寫操作,實際傳輸?shù)淖止?jié)數(shù)由ReadFile()和Write()函數(shù)報告。對于異步操作,則有OVERLAPPED結構來獲取。
如果欲獲得當前超時參數(shù),應用程序可以調用GetCommTimeouts()函數(shù)
BOOL GetCommTimeouts
(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
如果要設置或改變原來的超時參數(shù),應用程序可以調用SetCommTimeouts()函數(shù)
BOOL SetCommTimeouts
(
HANDLE hFile,
LPCOMMTIMEOUTS lpCommTimeouts
);
typedef struct_COMMTIMEOUTS
{
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
ReadIntervalTimeout:以ms為單位指定通信線路上兩個字符到達之間的最大時間間隔。在ReadFile()操作期間,從接收到第一個字符時開始計時。如果任意兩個字符到達之間的時間間隔超過這個最大值,則ReadFile()操作完成,并返回緩沖數(shù)據(jù)。如果被置為0,則表示不使用間隔超時。
·ReadTotalTimeoutMultiplier:以ms為單位指定一個系數(shù),該系數(shù)用來計算讀操作的總超時時間。
·ReadTotalTimeoutConstant:以ms為單位指定一個常數(shù),該常數(shù)也用來計算讀操作的總超時時間。
·WriteTotalTimeoutMultiplier:以ms為單位指定一個系數(shù),該系數(shù)用來計算寫操作的總超時時間。
·WriteTotalTimeoutConstant:以ms為單位指定一個常數(shù),該常數(shù)也用來計算寫操作的總超時時間。
Windows使用下面的式子計算總超時時間:
ReadTotalTimeout=( ReadTotalTimeoutMultiplier*bytes_to_read )+ ReadTotalTimeoutConstant;
WriteTotalTimeout=( WriteTotalTimeoutMultiplier*bytes_to_write )+ WriteTotalTimeoutConstant;
//超時處理,單位:毫秒
//總超時=時間系數(shù)×讀或寫的字符數(shù)+時間常量
COMMTIMEOUTS TimeOuts;
TimeOuts.ReadIntervalTimeout = 1000; //讀間隔超時
TimeOuts.ReadTotalTimeoutMultiplier = 500; //讀時間系數(shù)
TimeOuts.ReadTotalTimeoutConstant = 5000; //讀時間常量
TimeOuts.WriteTotalTimeoutMultiplier = 500; // 寫時間系數(shù)
TimeOuts.WriteTotalTimeoutConstant = 2000; //寫時間常量
SetCommTimeouts(hCom, &TimeOuts);
讀串口
程序可以使用Win32API ReadFile()函數(shù)或者ReadFileEx()函數(shù)從串口中讀取數(shù)據(jù)。ReadFile()函數(shù)對同步或異步操作都支持,而ReadFileEx()只支持異步操作。這兩個函數(shù)都受到函數(shù)是否異步操作、超時操作等有關參數(shù)的影響和限定。
BOOL ReadFile
(
HANDLE hFile, // 指向標識的句柄
LPVOID lpBuffer, // 指向一個緩沖區(qū)
DWORD nNumberOfBytesToRead, // 讀取的字節(jié)數(shù)
LPDWORD lpNumberOfBytesRead, // 指向調用該函數(shù)讀出的字節(jié)數(shù)
LPOVERLAPPED lpOverlapped // 一個OVERLAPPED的結構
);
·hFile:指向標識的句柄。對串口來說,就是由CreateFile函數(shù)返回的句柄。該句柄必須擁有GENERIC_READ的權限。
·lpBuffer:指向一個緩沖區(qū),該緩沖區(qū)主要用來存放從串口設備中讀取的數(shù)據(jù)。
·nNumberOfBytesToRead:指定要從串口設備讀取的字節(jié)數(shù)。
·lpNumberOfBytesRead:指向調用該函數(shù)讀出的字節(jié)數(shù)。ReadFile()在讀操作前,首先將其設置為0。Windows NT/2000中當lpOverlapped沒有設置時,lpNumberOfBytesRead必須設置。當lpOverlapped設置時,lpNumberOfBytesRead可以不設置。這是可以調用GetOverlappedResult()函數(shù)獲取實際的讀取數(shù)值。Windows 9x中這個參數(shù)一定要設置。
·lpOverlapped:是一個OVERLAPPED的結構,該結構將在后面介紹。如果hFile以FILE_FLAG_OVERLAPPED方式常見,則需要此結構;否則,不需要此結構。
需要注意的是如果該函數(shù)因為超時而返回,那么返回值是TRUE。參數(shù)lpOverlapped 在操作時應該指向一個OVERLAPPED的結構,如果該參數(shù)為NULL ,那么函數(shù)將進行同步操作,而不管句柄是否是由 FILE_FLAG_OVERLAPPED 標志建立的。當ReadFile返回FALSE時,不一定就是操作失敗,線程應該調用GetLastError函數(shù)分析返回的結果。例如,在重疊操作時如果操作還未完成函數(shù)返回,那么函數(shù)就返回FALSE,而且GetLastError函數(shù)返回ERROR_IO_PENDING。
寫串口操作
可以使用Win32API函數(shù)WriteFile() 或者WriteFileEx()向串口中寫數(shù)據(jù)。WriteFile()函數(shù)對同步或異步操作都支持,而WriteFileEx()只支持異步操作。這兩個函數(shù)都受到函數(shù)是否異步操作、超時操作等有關參數(shù)的影響和限定。
BOOL WriteFile
(
HANDLE hFile, // 指向標識的句柄
LPCVOID lpBuffer, // 指向一個緩沖區(qū)
DWORD nNumberOfBytesToWrite, // 指定要向串口設備寫入的字節(jié)數(shù)
LPDWORD lpNumberOfBytesWritten, // 指向調用該函數(shù)已寫入的字節(jié)數(shù)
LPOVERLAPPED lpOverlapped // 一個OVERLAPPED的結構
);
·hFile:指向標識的句柄。對串口來說,就是由CreateFile函數(shù)返回的句柄。該句柄必須擁有GENERIC_WRITE的權限。
·lpBuffer:指向一個緩沖區(qū),該緩沖區(qū)主要用來存放待寫入串口設備的數(shù)據(jù)。
·nNumberOfBytesToWrite:指定要向串口設備寫入的字節(jié)數(shù)。
·lpNumberOfBytesWritten:指向調用該函數(shù)已寫入的字節(jié)數(shù)。WriteFile()在寫操作前,首先將其設置為0。Windows NT/2000中當lpOverlapped沒有設置時,lpNumberOfBytesWritten必須設置。當lpOverlapped設置時,lpNumberOfBytesWritten可以不設置。這是可以調用GetOverlappedResult()函數(shù)獲取實際的讀取數(shù)值。Windows 9x中這個參數(shù)一定要設置。
·lpOverlapped:是一個OVERLAPPED的結構,該結構將在后面介紹。如果hFile以FILE_FLAG_OVERLAPPED方式常見,則需要此結構;否則,不需要此結構。
如果函數(shù)調用成功,則返回值不為零;若函數(shù)調用失敗,則返回值為零。調用GetLastError()函數(shù)可以獲得進一步的出錯信息。
通信狀態(tài)和通信錯誤
如果在串口通信中發(fā)生錯誤,如發(fā)生中斷,奇偶錯誤等,I/O操作將會終止。如果程序要進一步執(zhí)行I/O操作,必須調用ClearCommError()函數(shù)。ClearCommError()函數(shù)有兩個作用:第一個作用是清除錯誤條件;第二個作用是確定串口通信狀態(tài)。ClearCommError()函數(shù)的聲明如下:
BOOL ClearCommError
(
HANDLE hFile,
LPDWORD lpErrors,
LPCOMSTAT lpStat
);
其中主要參數(shù)介紹如下:
·hFile :標識通信設備,CreateFile()函數(shù)返回該句柄。
·lpErrors:指向用一個指明錯誤類型的掩碼填充的32位變量。該參數(shù)可以是表6中各值的組合。
·lpStat:指向一個COMSTAT結構,該結構接收設備的狀態(tài)信息。如果lpStat參數(shù)不設置,則沒有設備狀態(tài)信息被返回。
表6 通信錯誤列表
值 描述 CE_BREAK硬件檢測到一個中斷條件 CE_FRAME硬件檢測到一個幀出錯 CE_IOE發(fā)生I/O錯誤 CE_MODE模式出錯,或者是句柄無效 CE_OVERRUN超速錯誤 CE_RXOVER接收緩沖區(qū)超限,或者是輸入緩沖區(qū)中沒有空間,或者實在文件結束符(EOF)接收后接收到一個字符 CE_RXPARITY奇偶校驗錯誤 CE_TXFULL發(fā)送緩沖區(qū)滿 CE_DNS沒有檢測到并行設備 CE_OOP并行設備缺紙 CE_PTO并行設備發(fā)生超時錯誤
如果該函數(shù)調用成功,則返回值不為零;若函數(shù)調用失敗,則返回值為零。調用GetLastError()函數(shù)可以獲得進一步的出錯信息。在同步操作時,可以調用ClearCommError()函數(shù)來確定串口的接收緩沖區(qū)處于等待狀態(tài)的字節(jié)數(shù),而后可以使用ReadFile()或者WriteFile()函數(shù)一次讀寫完。
COMSTAT結構存放有關通信設備的當前信息。該結構內容由ClearCommError()函數(shù)填寫。COMSTAT結構聲明如下:
typedef struct_COMSTAT
(
DWORD fCtsHold: 1;
DWORD fDsrHold: 1;
DWORD fRlsdHold: 1;
DWORD fXoffSent: 1;
DWORD fEof: 1;
DWORD fTxim: 1;
DWORD fReserved: 25;
DWORD cbInQue;
DWORD cbOutQue;
} COMSTAT,*LPCOMSTAT;
其中主要參數(shù)介紹如下:
·fCtsHold:指明是否等待CRS信號,如果為1,則發(fā)送等待。
·fDsrHold:指明是否等到DRS信號,如果為1,則發(fā)送等待。
·fRlsdHold:指明是否等待RLSD信號,如果為1,則發(fā)送等待。
·fXoffSent:指明收到XOFF字符后發(fā)送是否等待。如果為1,則發(fā)送等待。如果把XOFF字符發(fā)送給一系統(tǒng)時,該系統(tǒng)就把下一個字符當成XON,而不管實際字符是什么,此時發(fā)送將停止。
·fEof:EOF字符送出。
·fTxim:指明字符是否正等待被發(fā)送,如果為1,則字符正等待被發(fā)送。
·fReserved:系統(tǒng)保留。
·cbInQue:指明串行設備接收到的字節(jié)數(shù)。并不是指ReadFile操作要求讀的字節(jié)數(shù)。
·cbOutQue:指明發(fā)送緩沖區(qū)尚未發(fā)送的字節(jié)數(shù)。如果進行不重疊寫操作時值為0。
代碼示例
#ifndef _WZSERIALPORT_H
#define _WZSERIALPORT_H
#include
#include
#include
using namespace std;
class WZSerialPort
{
public:
WZSerialPort();
~WZSerialPort();
// 打開串口,成功返回true,失敗返回false
// portname(串口名): 在Windows下是"COM1""COM2"等,在Linux下是"/dev/ttyS1"等
// baudrate(波特率): 9600、19200、38400、43000、56000、57600、115200
// parity(校驗位): 0為無校驗,1為奇校驗,2為偶校驗,3為標記校驗
// databit(數(shù)據(jù)位): 4-8,通常為8位
// stopbit(停止位): 1為1位停止位,2為2位停止位,3為1.5位停止位
// synchronizable(同步、異步): 0為異步,1為同步
//在這里已經(jīng)對配置進行了初始化,你們可以自己改配置使用
bool open(const char* portname, int baudrate = 115200, char parity = 0, char databit = 8, char stopbit = 1, char synchronizeflag = 1);
//關閉串口,參數(shù)待定
void close();
//發(fā)送數(shù)據(jù)或寫數(shù)據(jù),成功返回發(fā)送數(shù)據(jù)長度,失敗返回0
int send(string dat);
//接受數(shù)據(jù)或讀數(shù)據(jù),成功返回讀取實際數(shù)據(jù)的長度,失敗返回0
string receive();
//vector
private:
int pHandle[16];
char synchronizeflag;
};
#endif
#include "client.h"
#include
#include
#include
#include
WZSerialPort::WZSerialPort()
{
}
WZSerialPort::~WZSerialPort()
{
}
bool WZSerialPort::open(const char* portname,
int baudrate,
char parity,
char databit,
char stopbit,
char synchronizeflag)
{
this->synchronizeflag = synchronizeflag;
HANDLE hCom = NULL;
if (this->synchronizeflag)
{
//同步方式
hCom = CreateFileA(portname, //串口名
GENERIC_READ | GENERIC_WRITE, //支持讀寫
0, //獨占方式,串口不支持共享
NULL,//安全屬性指針,默認值為NULL
OPEN_EXISTING, //打開現(xiàn)有的串口文件
0, //0:同步方式,F(xiàn)ILE_FLAG_OVERLAPPED:異步方式
NULL);//用于復制文件句柄,默認值為NULL,對串口而言該參數(shù)必須置為NULL
}
else
{
//異步方式
hCom = CreateFileA(portname, //串口名
GENERIC_READ | GENERIC_WRITE, //支持讀寫
0, //獨占方式,串口不支持共享
NULL,//安全屬性指針,默認值為NULL
OPEN_EXISTING, //打開現(xiàn)有的串口文件
FILE_FLAG_OVERLAPPED, //0:同步方式,F(xiàn)ILE_FLAG_OVERLAPPED:異步方式
NULL);//用于復制文件句柄,默認值為NULL,對串口而言該參數(shù)必須置為NULL
}
if (hCom == (HANDLE)-1)
{
return false;
}
//配置緩沖區(qū)大小
if (!SetupComm(hCom, 1024, 1024))
{
return false;
}
// 配置參數(shù)
DCB p;
memset(&p, 0, sizeof(p));
p.DCBlength = sizeof(p);
p.BaudRate = baudrate; // 波特率
p.ByteSize = databit; // 數(shù)據(jù)位
switch (parity) //校驗位
{
case 0:
p.Parity = NOPARITY; //無校驗
break;
case 1:
p.Parity = ODDPARITY; //奇校驗
break;
case 2:
p.Parity = EVENPARITY; //偶校驗
break;
case 3:
p.Parity = MARKPARITY; //標記校驗
break;
}
switch (stopbit) //停止位
{
case 1:
p.StopBits = ONESTOPBIT; //1位停止位
break;
case 2:
p.StopBits = TWOSTOPBITS; //2位停止位
break;
case 3:
p.StopBits = ONE5STOPBITS; //1.5位停止位
break;
}
if (!SetCommState(hCom, &p))
{
// 設置參數(shù)失敗
return false;
}
//超時處理,單位:毫秒
//總超時=時間系數(shù)×讀或寫的字符數(shù)+時間常量
COMMTIMEOUTS TimeOuts;
TimeOuts.ReadIntervalTimeout = 1000; //讀間隔超時
TimeOuts.ReadTotalTimeoutMultiplier = 500; //讀時間系數(shù)
TimeOuts.ReadTotalTimeoutConstant = 5000; //讀時間常量
TimeOuts.WriteTotalTimeoutMultiplier = 500; // 寫時間系數(shù)
TimeOuts.WriteTotalTimeoutConstant = 2000; //寫時間常量
SetCommTimeouts(hCom, &TimeOuts);
PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);//清空串口緩沖區(qū)
memcpy(pHandle, &hCom, sizeof(hCom));// 保存句柄
return true;
}
void WZSerialPort::close()
{
HANDLE hCom = *(HANDLE*)pHandle;
CloseHandle(hCom);
}
int WZSerialPort::send(string dat)
{
HANDLE hCom = *(HANDLE*)pHandle;
if (this->synchronizeflag)
{
// 同步方式
DWORD dwBytesWrite = dat.length(); //成功寫入的數(shù)據(jù)字節(jié)數(shù)
BOOL bWriteStat = WriteFile(hCom, //串口句柄
(char*)dat.c_str(), //數(shù)據(jù)首地址
dwBytesWrite, //要發(fā)送的數(shù)據(jù)字節(jié)數(shù)
&dwBytesWrite, //DWORD*,用來接收返回成功發(fā)送的數(shù)據(jù)字節(jié)數(shù)
NULL); //NULL為同步發(fā)送,OVERLAPPED*為異步發(fā)送
if (!bWriteStat)
{
return 0;
}
return dwBytesWrite;
}
else
{
//異步方式
DWORD dwBytesWrite = dat.length(); //成功寫入的數(shù)據(jù)字節(jié)數(shù)
DWORD dwErrorFlags; //錯誤標志
COMSTAT comStat; //通訊狀態(tài)
OVERLAPPED m_osWrite; //異步輸入輸出結構體
//創(chuàng)建一個用于OVERLAPPED的事件處理,不會真正用到,但系統(tǒng)要求這么做
memset(&m_osWrite, 0, sizeof(m_osWrite));
m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, L"WriteEvent");
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通訊錯誤,獲得設備當前狀態(tài)
BOOL bWriteStat = WriteFile(hCom, //串口句柄
(char*)dat.c_str(), //數(shù)據(jù)首地址
dwBytesWrite, //要發(fā)送的數(shù)據(jù)字節(jié)數(shù)
&dwBytesWrite, //DWORD*,用來接收返回成功發(fā)送的數(shù)據(jù)字節(jié)數(shù)
&m_osWrite); //NULL為同步發(fā)送,OVERLAPPED*為異步發(fā)送
if (!bWriteStat)
{
if (GetLastError() == ERROR_IO_PENDING) //如果串口正在寫入
{
WaitForSingleObject(m_osWrite.hEvent, 1000); //等待寫入事件1秒鐘
}
else
{
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通訊錯誤
CloseHandle(m_osWrite.hEvent); //關閉并釋放hEvent內存
return 0;
}
}
return dwBytesWrite;
}
}
string WZSerialPort::receive()
{
HANDLE hCom = *(HANDLE*)pHandle;
string rec_str = "";
char buf[1024];
if (this->synchronizeflag)
{
//同步方式
DWORD wCount = 1024; //成功讀取的數(shù)據(jù)字節(jié)數(shù)
BOOL bReadStat = ReadFile(hCom, //串口句柄
buf, //數(shù)據(jù)首地址
wCount, //要讀取的數(shù)據(jù)最大字節(jié)數(shù)
&wCount, //DWORD*,用來接收返回成功讀取的數(shù)據(jù)字節(jié)數(shù)
NULL); //NULL為同步發(fā)送,OVERLAPPED*為異步發(fā)送
for (int i = 0; i < strlen(buf); i++)
{
if (buf[i] != -52)
{
//cout << buf[i];
rec_str += buf[i];
//revcmsg.push_back(buf[i]);
}
else
{
break;
}
}
return rec_str;
}
else
{
//異步方式
DWORD wCount = 1024; //成功讀取的數(shù)據(jù)字節(jié)數(shù)
DWORD dwErrorFlags; //錯誤標志
COMSTAT comStat; //通訊狀態(tài)
OVERLAPPED m_osRead; //異步輸入輸出結構體
//創(chuàng)建一個用于OVERLAPPED的事件處理,不會真正用到,但系統(tǒng)要求這么做
memset(&m_osRead, 0, sizeof(m_osRead));
m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, L"ReadEvent");
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通訊錯誤,獲得設備當前狀態(tài)
if (!comStat.cbInQue)
return ""; //如果輸入緩沖區(qū)字節(jié)數(shù)為0,則返回false
//std::cout << comStat.cbInQue << std::endl;
BOOL bReadStat = ReadFile(hCom, //串口句柄
buf, //數(shù)據(jù)首地址
wCount, //要讀取的數(shù)據(jù)最大字節(jié)數(shù)
&wCount, //DWORD*,用來接收返回成功讀取的數(shù)據(jù)字節(jié)數(shù)
&m_osRead); //NULL為同步發(fā)送,OVERLAPPED*為異步發(fā)送
if (!bReadStat)
{
if (GetLastError() == ERROR_IO_PENDING) //如果串口正在讀取中
{
//GetOverlappedResult函數(shù)的最后一個參數(shù)設為TRUE
//函數(shù)會一直等待,直到讀操作完成或由于錯誤而返回
GetOverlappedResult(hCom, &m_osRead, &wCount, TRUE);
}
else
{
ClearCommError(hCom, &dwErrorFlags, &comStat); //清除通訊錯誤
CloseHandle(m_osRead.hEvent); //關閉并釋放hEvent的內存
return "";
}
}
for (int i = 0; i < strlen(buf); i++)
{
if (buf[i] != -52)
{
rec_str += buf[i];
//revcmsg.push_back(buf[i]);
}
else
{
break;
}
}
return rec_str;
}
}
// test.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結束。
//
#include
#include "client.h"
using namespace std;
int main()
{
std::cout << "Hello World!\n";
WZSerialPort w;
//這里是選擇端口號,其他波特率信息可在頭文件修改,或者在下面重新賦值。
if (w.open("COM3"))
{
cout << "打開成功" << endl;
cout << "在這里我發(fā)送:恭喜發(fā)財" << endl;
w.send("恭喜發(fā)財");
//w.close();
}
else
{
cout << "打開失敗" << endl;
}
while (true)
{
//w.receive();
cout << "receive: " << w.receive() << endl;
//w.revcmsg.clear();
}
}
// 運行程序: Ctrl + F5 或調試 >“開始執(zhí)行(不調試)”菜單
// 調試程序: F5 或調試 >“開始調試”菜單
// 入門使用技巧:
// 1. 使用解決方案資源管理器窗口添加/管理文件
// 2. 使用團隊資源管理器窗口連接到源代碼管理
// 3. 使用輸出窗口查看生成輸出和其他消息
// 4. 使用錯誤列表窗口查看錯誤
// 5. 轉到“項目”>“添加新項”以創(chuàng)建新的代碼文件,或轉到“項目”>“添加現(xiàn)有項”以將現(xiàn)有代碼文件添加到項目
// 6. 將來,若要再次打開此項目,請轉到“文件”>“打開”>“項目”并選擇 .sln 文件
虛擬串口工具和串口工具見:
鏈接:https://pan.baidu.com/s/15BfvpeWpIPRauCico3Y44Q
提取碼:zd4y
另外可以借鑒:https://blog.csdn.net/qq_41480046/article/details/82220155?spm=1001.2101.3001.6650.13&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-13-82220155-blog-104156394.pc_relevant_aa2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-13-82220155-blog-104156394.pc_relevant_aa2&utm_relevant_index=21
柚子快報邀請碼778899分享:網(wǎng)絡協(xié)議 串口通訊理解
參考文章
本文內容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。