DOS串口編程 第一章 串列通訊介面 串列通訊使用單條資料線代替了並行通訊的8位元據線,傳輸的距離更遠。通訊介面每次從CPU得到8位元據,然後通過一個並行入串列出的移位寄存器,轉換成串列位,每次發送一位,將資料發送出去。同樣,在接收端也必須有一個串列入並行出的移位寄存器來接收串列資料。並組合打包成一個位元組。 以串列方式進入資料線的是由0和1組成的資料,一組這樣的數叫做一個字元,一個字元可能有8位,或者7位,6位,5位。在傳輸中,每個字元都要加上起始位和終止位,起始位總是1位,終止位可以是1位或2位。為了保證傳輸資料的正確性,有時還包括一位效驗位。一般晶片都允許編程時設定效驗方式為奇效驗,偶效驗或者無效驗。 串列通訊的資料轉送速率用bps(bits per second)來表示。另外,還有一種表示訊號傳輸速率的單位是傳輸速率(band rate)。傳輸速率是一種訊號調製單位,和bps不一定相等,它定義每秒鐘傳輸的離散訊號的數目。所謂的離散訊號,就是指不均勻的,不連續的也不相關的訊號。更詳細的講解請查閱相關文檔。通訊連接埠的傳輸速率從110bps到115200bps,經驗表明,傳輸速率相當於9600bps的時候,傳輸相當穩定。 1960年電子工業協會(Electronics Industries Association,EIA)制定了RS-232介面標準,以後又陸續發布了修訂版本,這是目前廣泛應用於個人電腦上的串列介面,用於近程資料通訊,串連一些外部裝置。就是我們經常用到的9針RS-232插頭, 每個引腳的定義為: 引腳 方向 名稱 描述 含義 1 輸入 CD Carrier Detect 資料偵測載波 2 輸入 RXD Receive Data 資料接收端 3 輸出 TXD Transmit Data 資料發送端 4 輸出 DTR Data Terminal Ready 資料終端準備就緒(電腦) 5 - SG System Ground 訊號地 6 輸入 DSR Data Set Ready 資料裝置準備就緒 7 輸出 RTS Request to Send 請求發送(電腦要求發送資料) 8 輸入 CTS Clear to Send 清除發送(MODEM準備接收資料) 9 輸入 RI Ring Indicator 響鈴指示 以上訊號在通訊過程中可能會被全部或者部分使用,把兩台電腦通過串口串連起來,最簡單的通訊僅需TXD及RXD及SG即可完成。 第二章 連接埠設定 IBM PC和80x86相容機可以串連4個序列埠,即COM1~COM4,相應的BIOS中的編號為COM0~COM3,但程式每次只能對其中一個連接埠進行存取。電腦啟動時,自檢程式就會測試4個COM連接埠是否存在,並把每個COM連接埠的I/O地址寫到BIOS的資料區0040:0000~0040:0007共8個位元組,每個COM地址佔用2個位元組。如果系統沒有串連序列埠,BIOS資料區的這幾個單元內容就成為0。用debug可以查看COM連接埠地址。 C>debug -d 0040:0000 L08 0040:0000 F8 03 F8 02 E8 03 E8 02 上例查看結果表明系統中有4個COM連接埠,對應I/O地址分別為3F8,2F8,3E8,2E8。每個COM連接埠都包括一組8位的寄存器,這四個地址都叫做基地址,也就是第一個寄存器的I/O地址,其他寄存器的地址按照遞增的順序排列。COM1的基地址是3F8,COM2的基地址是2F8,COM3的基地址是3E8,COM4的基地址是2E8。我們通過這些寄存器編程式控制制資料接收或者發送。 COM1及COM3使用PC機中斷4,COM2及COM4使用中斷3。 第三章 寄存器 1 寄存器組 COM連接埠的寄存器組如下表所示,共有12個寄存器,使用了8個地址,其中部分寄存器共用一個地址,由DLAB=0/1來區分。DLAB是線路控制寄存器的第7位。 基地址 讀/寫 寄存器縮寫 描述 0 Write - 發送保持寄存器(DLAB=0) 0 Read - 接收資料寄存器(DLAB=0) 0 Read/Write - 傳輸速率低八位(DLAB=1) 1 Read/Write IER 中斷允許寄存器 1 Read/Write - 傳輸速率高八位(DLAB=1) 2 Read IIR 中斷標識寄存器 2 Write FCR FIFO控制寄存器 3 Read/Write LCR 線路控制寄存器 4 Read/Write MCR MODEM控制寄存器 5 Read LSR 線路狀態寄存器 6 Read MSR MODEM狀態寄存器 7 Read/Write - Scratch Register 2 中斷允許寄存器(IER) 位 描述 7 未使用 6 未使用 5 進入低功耗模式(16750) 4 進入睡眠模式(16750) 3 允許MODEM狀態中斷 2 允許接收線路狀態中斷 1 允許發送保持器空中斷 0 允許接收資料就緒中斷 Bit0置1將允許接收到資料時產生中斷,Bit1置1時允許發送保持寄存器空時產生中斷,Bit2置1將在LSR變化時產生中斷,相應的Bit3置位將在MSR變化時產生中斷。 3 中斷識別寄存器(IIR) 位 描述 Bit6:7=00 無FIFO Bit6:7=01 允許FIFO,但不可用 Bit6:7=11 允許FIFO Bit5 允許64位元組FIFO(16750) Bit4 未使用 Bit3 16550逾時中斷 Bit2:1=00 MODEM狀態中斷(CTS/RI/DTR/DCD) Bit2:1=01 發送保持寄存器空中斷 Bit2:1=10 接收資料就緒中斷 Bit2:1=11 接收線路狀態中斷 Bit0=0 有中斷產生 Bit0=1 無中斷產生 IIR為唯讀寄存器,Bit6:7用來指示FIFO的狀態,均為0時則無FIFO,此時為8250或16450晶片,為01時有FIFO但不可以使用,為11時FIFO有效並可以正常工作。Bit3用來指示逾時中斷(16550/16750)。Bit0用來指示是否有中斷髮生,Bit1:2標識具體的中斷類型,這些中斷具有不同的優先順序別,其中LSR中斷層級最高,其次是資料就緒中斷,然後是發送寄存器空中斷,而MSR中斷層級最低。 4 FIFO控制寄存器(FCR) 位 描述 Bit7:6=00 1Byte產生中斷 Bit7:6=01 4Byte產生中斷 Bit7:6=10 8Byte產生中斷 Bit7:6=11 14Byte產生中斷 Bit5 允許64位元組FIFO Bit4 未使用 Bit3 DMA模式選擇 Bit2 清除發送FIFO Bit1 清除接收FIFO Bit0 允許FIFO FCR可寫但不可以讀,該寄存器用來控制16550或16750的FIFO寄存器。Bit0置1將允許發送/接收的FIFO工作,Bit1和Bit2置1分別用來清除接收及發送FIFO。清除接收及發送FIFO並不影響移位寄存器。Bit1:2可自行複位,因此無需使用軟體對其清零。Bit6:7用來設定產生中斷的層級,發送/接收中斷將在發送/接收到對應位元組數時產生。 5 線路控制寄存器(LCR) 位 描述 Bit7=1 允許訪問傳輸速率因子寄存器 Bit7=0 允許訪問接收/發送及中斷允許寄存器 Bit6 設定間斷,0-禁止,1-設定 Bit5:3=XX0 無校正 Bit5:3=001 奇數同位 Bit5:3=011 偶校正 Bit5:3=101 奇偶保持為1 Bit5:3=111 奇偶保持為0 Bit2=0 1位停止位 Bit2=1 2位停止位(6-8位元據位),1.5位停止位(5位元據位) Bit1:0=00 5位元據位 Bit1:0=01 6位元據位 Bit1:0=10 7位元據位 Bit1:0=11 8位元據位 LCR用來設定通訊所需的一些基本參數。Bit7為1指定傳輸速率因子寄存器有效,為0則指定發送/接收及IER有效。Bit6置1會將發送端置為0,這將會使接收端產生一個“間斷”。Bit3-5用來設定是否使用同位以及同位的類型,Bit3=1時使用校正,Bit4為0則為奇數同位,1為偶校正,而Bit5則強制校正為1或0,並由Bit4決定具體為0或1。Bit2用來設定停止位的長度,0表示1位停止位,為1則根據資料長度的不同使用1.5-2位停止位。Bit0:1用來設定資料長度。 6 MODEM控制寄存器(MCR) 位 描述 Bit7 未使用 Bit6 未使用 Bit5 自動流量控制(僅16750) Bit4 環路測試 Bit3 輔助輸出2 Bit2 輔助輸出1 Bit1 設定RTS Bit0 設定DSR MCR寄存器可讀可寫,Bit4=1進入環路測試模式。Bit3-0用來控制對應的管腳。 7 線路狀態寄存器(LSR) 位 描述 Bit7 FIFO中接收資料錯誤 Bit6 發送移位寄存器空 Bit5 發送保持寄存器空 Bit4 間斷 Bit3 框架格式錯 Bit2 奇偶錯 Bit1 超越錯 Bit0 接收資料就緒 LSR為唯讀寄存器,當發生錯誤時Bit7為1,Bit6為1時標示發送保持及發送移位寄存器均空,Bit5為1時標示僅發送保持寄存器空,此時,可以由軟體發送下一資料。當線路狀態為0時Bit4置位為1,框架格式錯時Bit3置位為1,奇偶錯和超越錯分別將Bit2及Bit1置位為1。Bit0置位為1表示接收資料就緒。 在接收和發送過程中,錯誤狀態位(1,2,3,4位)一旦被置為1,則讀入的接收資料已不是有效資料,所以在串列應用程式中,應檢測資料轉送是否出錯。 奇偶錯:通訊線上(尤其是用電話線傳輸時)的雜訊引起某些資料位元的改變,產生奇偶錯,通檢測出奇偶錯時,要求正在接受的資料至少應重新發送一段。 超越錯:在上一個字元還未被CPU取走,又有字元要傳送到資料寄存器裡,則會引起超越錯。 框架格式錯:當接收/發送器未收到一個字元資料的終止位,會引起框架格式錯。這種錯誤可能是由於通訊線上的雜訊引起終止位的丟失,或者是由於接收方和發送方初始化不匹配。 間斷:間斷有有時候並不能算是一個錯誤,而是為某些特殊的通訊環境設定的“空格”狀態。當間斷為1時,這說明接收的“空格”狀態超過了一個完整的資料字傳輸時間。 8 MODEM狀態寄存器(MSR) 位 描述 Bit7 偵測載波 Bit6 響鈴指示 Bit5 DSR準備就緒 Bit4 CTS有效 Bit3 DCD已改變 Bit2 RI已改變 Bit1 DSR已改變 Bit0 CTS已改變 MSR寄存器的高4位分別對應MODEM的狀態線,低4位表示MODEM的狀態線是否發生了變化。到此,串口的寄存器就介紹完了,下面就要講如何編程。 第四章 BIOS串列通訊功能 BIOS int 14h提供了串列資料通訊功能,包括將串列口初始化為指定的位元組結構和傳輸速率,檢查控制器的狀態,讀寫字元等功能。 串列通訊口BIOS功能(int 14h) AH 功能 調用參數 返回參數 0 初始化序列埠 AL=初始化參數 DX=通訊口號 COM1=0,COM2=1 COM3=2,COM4=3 AH=線路狀態 AL=MODEL狀態 1 向串列通訊口寫字元 AL=要寫的字元 DX=通訊口號 寫字元成功:AH=0,AL=字元。 寫字元失敗:AH中,Bit7=1,Bit6:0=線路狀態。 2 從串列通訊口讀字元 DX=通訊口號 讀成功:AH中,Bit7=0;AL=字元 讀失敗:AH中,Bit7=1,Bit6:0=線路狀態 3 取線路狀態 DX=通訊口號 AH=線路狀態 AL=MODEL狀態 1 初始化串口 int 14h的AH=0功能把指定的串列通訊口初始化為希望的傳輸速率,奇偶性,字長和終止位的位元,這些初始化參數設定在AL寄存器中,其各位的含義見下表: AL 意義 值 Bit7:5 傳輸速率 000=110傳輸速率 001=150傳輸速率 010=300傳輸速率 011=600傳輸速率 100=1200傳輸速率 101=2400傳輸速率 110=4800傳輸速率 111=9600傳輸速率 Bit4:3 效驗 1=2位 Bit2 終止位 0=1位 Bit1:0 字長 10=7位 11=8位 例如要求COM1口的傳輸率為2400傳輸速率,字長為8位,1位終止位,無奇偶效驗: __asm { mov AH, 0 mov AL, 0A03h // 0A03h =10100011 mov DX, 0 // COM1 int 14h // 調用BIOS } 2 發送資料 char my_data = 65h; //假設要發送65h __asm { mov AL, my_data mov AH, 1 mov DX, 1 //通過COM2口發送出去 int 14h } 3 接受資料 為了接收字元,首先要用int 14h,AH=3來擷取COM連接埠的狀態,其傳回值在AH寄存器中。檢查AH的第0位,它是資料準備好位,如果該位是1,說明COM連接埠已經接收到字元。然後就可以用int 14h,AH=2功能,把字元讀到AL寄存器中來。 char LSR = 0; //線路狀態 char my_data; //接收到的資料 __asm { mov AH, 3 mov DX, 0 //COM1的線路狀態 int 14h mov LSR, AH } if(! (LSR & 01h)) //沒有資料到達 { printf("no data arrived"); goto somewhere; } else //有資料到達,接受資料 { __asm { mov AH, 2 //讀字元 mov DX, 0 int 14h mov my_data, AL //接受到的字元儲存在my_data中 } } 第五章 I/O寄存器編程 直接讀寫 I/O寄存器可以更靈活更可靠地操作串口通訊,很多程式都是用這種方式編寫的。 #define BASEADDR1 0x3F8 //COM1的寄存器基地址 1 初始化串口 void InitCom1() { //不允許中斷 outportb(BASEADDR1 + 1, 0); //設定DLAB=1, 允許訪問傳輸速率寄存器 outportb(BASEADDR1 + 3, 0x80); //設定傳輸速率9600, 設定的參數可以在下面表格中查到 outportb(BASEADDR1 + 0, 0x0C); //傳輸速率低8位 outportb(BASEADDR1 + 1, 0x00); //傳輸速率高8位 //設定8位元據位, 1位停止位, 無校正, DLAB="0" outportb(BASEADDR1 + 3, 0x03); } 在DLAB=1時去設定通訊所需的傳輸速率。常用的傳輸速率參數見下表: 速率(BPS) 傳輸速率高八位 傳輸速率低八位 50 09h 00h 300 01h 80h 600 00h C0h 1200 00h 60h 2400 00h 30h 4800 00h 18h 9600 00h 0Ch 19200 00h 06h 38400 00h 03h 57600 00h 02h 115200 00h 01h 2 發送資料 再多的語言都不如代碼錶達得清楚,所以我還是把代碼擺在下面,適當地做點注釋。下面這個函數是把一個字元通過COM1口發送出去,my_data是要發送的字元。如果COM1不能夠發送,就返回失敗。 bool send_com1(char my_data) { char status = inportb(BASEADDR1 + 5); //檢查線路狀態 int count = 10000; //一直等到發送寄存器空,或者逾時 while(!(status & 0x20) && (count > 0)) { count--; status = inportb(BASEADDR1 + 5); } if(count > 0) //發送寄存器空 { outportb(BASEADDR1 + 0, my_data); //發送字元 return true; } else return false; //逾時未發送 } 3 接收資料 下面這個函數是從COM1連接埠讀一個字元,儲存到pData指向的變數中,如果COM1口沒有資料,就返回失敗。 bool recv_com1(char* pData); { char status = inportb(BASEADDR1 + 5); //檢查線路狀態 if(status & 0x01) //接收資料就緒 { *pData = inportb(BASEADDR1 + 0); //讀一個字元 return true; } else return false; //沒有資料到達 } 4 開啟FIFO功能 串口中所謂的FIFO,也就是先入先出隊列功能,說的就是一組接收緩衝寄存器,和一組發送保持寄存器。接收緩衝寄存器和發送保持寄存器的大小是16個位元組,當FIFO功能關閉的時候,接收緩衝寄存器和發送保持寄存器都只能放一個資料,即相當於一個位元組大小。我們最好開始從資料的接收和發送說起。 資料接收:來自於線路上的資料首先進入接收移位寄存器(RSR),一個字元接收完成之後,資料移入接收緩衝寄存器(RBR),RBR實際就是一個16位元組的FIFO隊列。當中斷設定時,串口控制器會根據FIFO的設定和RBR中資料的數目產生中斷。FIFO功能關閉時,RBR只能放一個資料,如果主機裝置來不及從RBR中讀字元,那麼串口接收到的下一個資料會把RBR覆蓋,這樣,就會有資料丟失。FIFO功能開啟時,RBR中能夠放16個資料,串口把完成的字元推入到RBR中,RBR原有的數就會前移,這樣主機讀字元的時間變得寬裕了。 資料發送:發送操作和接收操作相反,主機資料寫入發送保持寄存器(THR),THR也是一個16位元組的FIFO,然後資料移入發送移位寄存器(TSR),之後送到線路上。當中斷設定時,串口控制器也會根據THR中資料的數目產生中斷。FIFO功能開啟時,主機可以把16位元組的資料一次寫入發送保持寄存器中。 我們接著就可以應用FIFO功能來完善先前的程式。發送資料: //pData指向要發送的資料,Len是資料長度,返回是否發送成功 bool fifo_send(char* pData, int Len) { int count; //禁用FIFO,目的是讓程式第一時間感知字元是否發送成功 outportb(BASEADDR1 + 2, 0x00); while(Len > 0) { //一直等到發送寄存器空,或者逾時 count = 10000; while(!(inportb(BASEADDR1 + 5) & 0x20) && (count > 0)) count--; if(count <= 0) //逾時未發送 return false; outportb(BASEADDR1 + 0, *pData); //發送字元 Len--; pData++; } //再次等到發送寄存器空,或者逾時 count = 10000; while(!(inportb(BASEADDR1 + 5) & 0x20) && (count > 0)) count--; if(count <= 0) return false; //逾時未發送 else return true; //確認,已經發送出去 } 接收資料: //pData指向要接收的資料,Len是資料長度,返回是否接收成功 bool fifo_recv(char *pData, int Len) { int count; //允許FIFO,清除接收FIFO,清除發送FIFO outportb(BASEADDR1 + 2, 0x07); while(Len > 0) { //一直等到接收資料就緒,或者逾時 count = 10000; while(!(inportb(BASEADDR1 + 5) & 0x01) && (count > 0)) count--; if(count <= 0) //逾時未收到 return false; *pData = inportb(BASEADDR1 + 0); //讀一個字元 Len--; pDate++ } return true; } 到此,通過I/O寄存器對串口編程的方法就介紹完了,希望給這些能給我們開發的項目提供一些協助,我們在具體使用的時候還要結合實際靈活運用。 總結 更具體的代碼還是參考dongsuoying的過電壓程式比接好。至於中斷的傳輸方式,它適用於使用較快的速率傳輸大量的資料,現在一般都採用網口或者USB,串口的這種方式在實際應用中很少見了,所以在這裡就暫不介紹了。 |