序列埠程式設計/8250 UART程式設計
序列埠程式設計: 簡介和 OSI 網路模型 -- RS-232 線路和連線 -- 典型的 RS232 硬體配置 -- 8250 UART -- DOS -- MAX232 驅動器/接收器系列 -- Windows 中的 TAPI 通訊 -- Linux 和 Unix -- Java -- Hayes 相容調變解調器和 AT 命令 -- 通用序列匯流排 (USB) -- 形成資料包 -- 錯誤校正方法 -- 雙向通訊 -- 資料包恢復方法 -- 序列資料網路 -- 實際應用開發 -- 序列連線上的 IP
最後,我們從線纜、電壓和硬核電子工程應用中抽離出來,儘管我們仍然需要了解相當多的計算機晶片架構知識。雖然本節的主要內容將集中在 8250 UART 上,但實際上我們將接觸到三種計算機晶片
- 8250 UART
- 8259 PIC(可程式設計中斷控制器)
- 8086 CPU(中央處理器)
請記住,這些是晶片系列,而不僅僅是晶片本身的編號。計算機設計多年來已發展相當多,通常所有這三種晶片都被放置在同一塊矽片上,因為它們之間聯絡緊密,並且為了降低裝置的總體成本。所以當我提到 8086 時,我同時也指的是後續晶片,包括 80286、80386、奔騰以及英特爾以外的製造商生產的相容晶片。除了 8086 之外,不同晶片之間在序列資料通訊方面有一些細微的差異和需要關注的地方,但在許多情況下,理論上你可以為最初的 IBM PC 編寫序列通訊軟體,並且它應該可以完美地在你的現代計算機上執行,而你的現代計算機正在執行最新的 Linux 或 Windows XP 版本。
現代作業系統透過低階驅動程式處理這裡涵蓋的大部分細節,因此這更像是一個快速瞭解這種工作原理,而不是你可能自己實現的東西,除非你正在編寫自己的作業系統。對於那些設計小型嵌入式計算機裝置的人來說,在如此深的層次上了解 8250 變得非常重要。
就像 8086 一樣,8250 已經發展了相當多,例如發展成了 16550 UART。在下面,我將介紹如何檢測 PC 上許多不同的 UART 晶片,以及一些影響每個晶片的怪癖或變化。這些差異並沒有 CPU 架構的變化那麼顯著,更新 UART 晶片的主要原因是使其與現有的速度快得多的 CPU 相容。8250 本身無法跟上奔騰晶片的速度。
請記住,這正試圖為軟體方面的序列程式設計打下基礎。雖然這對於硬體設計也有用,但這裡描述的內容缺少很多,無法實現一個完整的系統。
我們應該回到英特爾 8086 之前,回到最初的英特爾 CPU,即 4004 以及它的後續產品 8008。8008 的所有計算機指令或操作碼在今天的英特爾晶片中仍然有效,因此即使是 30 年前的埠 I/O 教程在今天仍然有效。較新的 CPU 增強了處理更多資料更有效率的指令,但原始指令仍然存在。
當 8008 釋出時,英特爾試圖設計一種方法,讓 CPU 與外部裝置進行通訊。他們選擇了一種稱為 I/O 埠架構的方法,這意味著晶片有一組特殊的引腳專用於與外部裝置進行通訊。在 8008 中,這意味著總共有十六 (16) 個引腳專用於與晶片進行通訊。確切的細節根據晶片設計和其他因素有所不同,這些因素超出了當前討論的範圍,但總體理論相當簡單。
八個引腳代表一個 I/O 程式碼,該程式碼指示一個特定的裝置。這就是我們所知的 I/O 埠。由於這只是一個二進位制程式碼,因此它代表著將 256 個不同的裝置連線到 CPU 的可能性。它比這更復雜一些,但你仍然可以把它想象成軟體中的一個小鎮郵局,那裡有一個擁有 256 個郵箱供顧客使用的郵箱組。
下一組引腳代表正在交換的實際資料。你可以把它想象成放進或取出的明信片。
外部裝置所需要做的就是查詢它的 I/O 程式碼,然後當它匹配它被“分配”的查詢物件時,它就控制了相應的埠。一個引腳訊號指示資料是傳送到 CPU 還是從 CPU 傳送。對於那些熟悉設定早期 PC 的人來說,這也是發生 I/O 衝突的地方:當兩個或多個裝置試圖同時訪問相同的 I/O 埠時。在那些早期系統中,這曾是一個令人頭疼的問題,特別是在新增新裝置時。
順便說一下,這與傳統 RAM 的工作原理非常相似,一些 CPU 設計直接在 RAM 中模擬整個過程,保留一塊記憶體用於 I/O 控制。這有一些問題,包括它佔用了一部分本來可以用於軟體的潛在記憶體。最終,在 IBM PC 和後來的 PC 系統中,記憶體對映 I/O (MMIO) 和埠對映 I/O (PMIO) 被廣泛使用,因此情況變得非常複雜。但是,對於序列通訊,我們將堅持使用埠 I/O 方法,因為 8250 晶片就是這麼工作的。
當你真正開始在軟體中使用它時,將資料傳送到或接收自埠 9 的組合語言指令看起來像這樣
out 9, al ; sending data from register al out to port 9 in al, 9 ; getting data from port 9 and putting it in register al
在更高層次的語言中進行程式設計時,它變得更簡單一些。一個典型的 C 語言埠 I/O 庫通常像這樣編寫
char test; test = 255; outp(9,test); inp(9,&test);
對於許多版本的 Pascal 來說,它將 I/O 埠視為一個可以訪問的大型陣列,該陣列簡稱為 Port
procedure PortIO(var Test: Byte); begin Port[9] := Test; Test := Port[9]; end;
警告!!這確實是警告。在不知道它連線到什麼的情況下隨機訪問計算機中的 I/O 埠,可能會嚴重破壞你的計算機。最起碼,它會使作業系統崩潰,導致計算機無法工作。寫入某些 I/O 埠會導致永久更改計算機的內部配置,使得需要去維修店才能修復你透過軟體造成的損壞。更糟的是,在某些情況下,它會對計算機造成實際損壞。這意味著計算機內部的一些晶片將不再工作,需要更換這些元件才能讓計算機重新工作。損壞的晶片表明計算機的工程設計很糟糕,但不幸的是,這種情況確實會發生,你應該意識到這一點。
不要害怕使用 I/O 埠,只要確保你知道你正在寫入什麼,以及如果你打算使用特定 I/O 埠,你知道哪些裝置“對映”到它。我們稍後將更詳細地介紹如何識別序列通訊的 I/O 埠。最後,我們開始編寫一些程式碼,後面還有更多內容。
x86 埠 I/O 擴充套件
[edit | edit source]8088 CPU 和 8086 CPU 之間存在一些差異。對軟體開發影響最大的是,8086 能夠訪問 65536 個不同的 I/O 埠,而不是隻有 256 個。但是,計算機配置可能使用不到 16 根 I/O 地址匯流排線,例如 IBM PC 只使用 10 根線,這使得只有 1024 個不同的埠。埠號的高位被忽略,這使得多個埠號成為同一埠的別名。
此外,除了簡單地傳送或接收單個字元外,8086 還可以一次傳送和接收 16 位資料。16 位字位元組使用連續的埠號以小端序方式讀寫。386 晶片甚至可以讓你同時傳送和接收 32 位資料。對超過 65536 個不同 I/O 埠的需求從來都不是一個嚴重的問題,如果裝置需要更大的記憶體空間,可以直接記憶體訪問 (DMA) 方法可用。在這種情況下,裝置直接讀寫計算機的 RAM,而不是透過 CPU。我們這裡不討論這個主題。
此外,雖然 8086 CPU 能夠定址 65536 個不同的 I/O 埠,但在實際應用中並沒有這樣做。英特爾晶片設計師為了節省成本,只為地址線留了 10 位,這對必須處理遺留系統的軟體設計師來說意味著很多。這也意味著 I/O 埠地址 $1E8 和 $19E8(以及其他,這只是一個例子)在這些早期 PC 上會解析到同一個 I/O 埠。奔騰 CPU 沒有這個限制,但為早期一些硬體編寫的軟體有時會寫入“別名”的 I/O 埠地址,因為那些高位被忽略了。還有一些其他的遺留問題,但幸運的是,對於 8250 晶片和序列通訊來說,這並不是一個問題,除非你碰巧有一個“利用”這種別名情況的序列驅動程式。這個問題通常只會在你使用超過典型 PC 上的 2 個或 4 個序列 COM 埠時才會出現。
x86 處理器中斷
[edit | edit source]8086 CPU 和相容晶片有一個被稱為中斷線的線。這實際上是一根連線到計算機其他部分的線,它可以被開啟,讓 CPU 知道是時候停止它正在做的事情,並關注一些 I/O 情況。
在 8086 中,有兩種中斷:硬體中斷和軟體中斷。兩種中斷之間有一些有趣的差異,但從軟體的角度來看,它們本質上是一樣的。8086 CPU 允許 256 箇中斷,但可供裝置執行硬體中斷的數量受到相當大的限制。
IRQ 解釋
[edit | edit source]硬體中斷從 IRQ 0 到 IRQ 15 編號。IRQ 代表中斷請求。總共有十五個不同的硬體中斷。在你認為我不知道如何計數或做數學運算之前,我們需要在這裡進行一些歷史課,我們將在繼續討論 8259 晶片時完成。當原始的 IBM-PC 被製造出來時,它只有八個 IRQ,標記為 IRQ 0 到 IRQ 7。當時人們認為這對於幾乎所有可能放在 PC 上的東西都足夠了,但很快人們就發現這對於所有正在新增的東西來說遠遠不夠。當 IBM-PC/AT 被製造出來時(第一個使用 80286 CPU 的 PC,以及今天 PC 上常見的許多增強功能),人們決定不再使用單個 8259 晶片,而是使用兩個相同的晶片,並將它們“連結”在一起,以將中斷的數量從 8 個擴充套件到 15 個。為了完成這項任務,必須犧牲一個 IRQ,那就是 IRQ 2。
這裡的要點是,如果一個裝置想要通知 CPU 它有一些資料準備好了,它會發送一個訊號,表明它想要停止計算機上當前執行的任何軟體,而是執行一個被稱為中斷處理程式的特殊“小程式”。一旦中斷處理程式完成,計算機就可以回到之前正在做的事情。如果中斷處理程式足夠快,你甚至不會注意到處理程式已被使用。
事實上,如果你正在 PC 上閱讀這篇文章,在你閱讀這句話的時間內,你的計算機已經使用了幾個中斷處理程式。每次你使用鍵盤或滑鼠,或者透過網際網路接收一些資料時,你的計算機的某個地方都使用了一箇中斷處理程式來檢索這些資訊。
中斷處理程式
[edit | edit source]我們將在稍後詳細介紹中斷處理程式,但現在我想解釋一下它們到底是什麼。中斷處理程式是一種向 CPU 顯示當中斷觸發時應該執行哪一段軟體的方法。
8086 CPU 的 RAM 中有一部分被指定為“指向”中斷軟體位於 RAM 中的其他位置。採用這種方法的優勢在於,CPU 只需簡單地查詢就可以找到軟體的具體位置,然後將軟體執行轉移到 RAM 中的該位置。這也允許你作為程式設計師更改 CPU 在 RAM 中“指向”的位置,而不是指向作業系統中的某個位置,你可以自定義中斷處理程式並在那裡新增其他內容。
最佳方法在很大程度上取決於你的作業系統。對於像 MS-DOS 這樣的簡單作業系統,它實際上鼓勵你直接編寫這些中斷處理程式,特別是在你處理外部外設時。其他作業系統,如 Linux 或 MS-Windows,使用的是一種“驅動程式”方法,它鉤接到這些中斷處理程式或服務程式,然後應用程式軟體與驅動程式互動,而不是直接與裝置互動。程式實際如何做到這一點,很大程度上取決於你使用的特定作業系統。如果你要編寫自己的作業系統,你必須直接編寫這些中斷處理程式,並建立訪問這些處理程式以傳送和檢索資料的協議。
軟體中斷
[edit | edit source]在我們繼續之前,我想簡單地提一下軟體中斷。軟體中斷使用 8086 彙編指令“int”來呼叫,例如
int $21
從軟體應用程式的角度來看,這實際上只是另一種呼叫子程式的方式,但它有一個變化。在中斷處理程式中執行的“軟體”不必來自同一個應用程式,甚至不必來自同一個編譯器。事實上,這些子程式通常直接用匯編語言編寫。在上面的例子中,這個中斷實際上呼叫了一個“DOS”子程式,它允許你執行一些與 DOS 直接相關的 I/O 訪問。根據暫存器的值(在本例中通常是 8086 中的 AX 暫存器),它可以確定你想要從 DOS 獲取什麼資訊,例如當前時間、日期、磁碟大小,以及幾乎所有你通常與 DOS 相關聯的東西。編譯器通常會隱藏這些細節,因為設定這些中斷例程可能有點棘手。
現在真正要搞亂事情了。“硬體中斷”也可以從“軟體中斷”中呼叫,實際上,這是一種確保你正確編寫軟體的合理方法。這裡的區別在於,軟體中斷只有在透過這個彙編操作碼顯式呼叫後才會被呼叫,或者它們的部分軟體程式碼才會在 CPU 中執行。
8259 PIC(可程式設計中斷控制器)
[edit | edit source]8259 晶片是整個硬體中斷過程的“核心”。外部裝置直接連線到這個晶片,或者在 PC-AT 相容機(你對現代 PC 最熟悉的東西)的情況下,它將有兩個連線在一起的裝置。實際上有十六根線進入這對晶片,每根線都標記為 IRQ-0 到 IRQ-15。
這些晶片的目的是幫助“優先順序排序”中斷訊號,並以某種有序的方式組織它們。無法預測某個裝置何時會“請求”中斷,因此,多個裝置經常會爭奪 CPU 的注意力。
一般來說,編號較低的 IRQ 優先順序較高。換句話說,如果 IRQ-1 和 IRQ-4 同時請求注意,IRQ-1 優先順序較高,並且在 CPU 看來會首先被觸發。IRQ-4 必須等到 IRQ-1 完成其“中斷服務例程”或 ISR 之後才能執行。
但是,如果相反的情況發生,IRQ-4 正在執行其 ISR(請記住,這是軟體,就像你可能通常編寫的任何計算機應用程式一樣),IRQ-1 將“中斷”IRQ-4 的 ISR,並推送其自己的 ISR 以代替它執行,並在完成時返回到 IRQ-4 的 ISR。當然也有例外情況,但現在讓我們保持簡單。
讓我們回到原始的 IBM-PC 一分鐘。當它被製造出來時,主機板上只有一個 8259 晶片。當 IBM-AT 問世時,IBM 的工程師決定新增第二個 8259 晶片以新增一些額外的 IRQ 訊號。由於 CPU(此時是 80286)上只有一個引腳可以接收中斷通知,因此人們決定從原始的 8259 晶片中獲取 IRQ-2,並用它連結到下一個晶片。對於任何依賴 IRQ-2 的裝置來說,IRQ-2 被重新路由到 IRQ-9。採用這種方案的好處是,即使有七個其他裝置現在“共享”這個中斷,打算使用 IRQ-2 的軟體仍然會收到“通知”。這些是 IRQ-8 到 IRQ-15。
然而,就優先順序而言,這意味著 IRQ-8 到 IRQ-15 的優先順序高於 IRQ-3。這主要是在您試圖弄清楚哪個裝置可以優先於另一個,以及當裝置試圖引起您的注意時通知它的重要性時會引起關注。如果您正在處理執行特定計算機配置的軟體,此優先順序級別非常重要。
這裡應該注意的是,COM1(序列通訊通道一)通常使用 IRQ-4,而 COM2 使用 IRQ-3,這使得 COM2 在接收資料方面的優先順序高於 COM1。通常,軟體並不關心這一點,但在某些罕見的情況下,您確實需要知道這個事實。
8259 有幾個與 I/O 埠地址相關的“暫存器”。當我們談到 8250 晶片時,我們將更多地瞭解這個概念。對於典型的 PC 計算機系統,以下是與 8259 相關的典型主埠地址。
| 暫存器名稱 | I/O 埠 |
|---|---|
| 主中斷控制器 | $0020 |
| 從中斷控制器 | $00A0 |
此主埠地址是我們在軟體中直接與 8259 晶片通訊時使用的。可以透過這些 I/O 埠地址向該晶片傳送許多命令,但就我們的目的而言,我們實際上並不需要處理它們。其中大部分用於透過計算機的基本輸入輸出系統 (BIOS) 對計算機裝置進行初始設定和配置,除非您從頭開始重寫 BIOS,否則您實際上不必擔心這一點。此外,每臺計算機在您處理此級別裝置時的行為都略有不同,因此這更適合計算機制造商關注,而不是應用程式程式設計師應該處理的事情,這正是 BIOS 軟體編寫的原因。
請記住,這是大多數 PC 相容型計算機系統的“典型”埠 I/O 地址,可能會因製造商試圖實現的目標而異。通常,您不必擔心此級別的相容性問題,但當我們談到序列埠的埠 I/O 地址時,這將成為一個更大的問題。
我將在這裡花點時間來解釋“暫存器”一詞的含義。當您在這一級別處理裝置時,設計裝置的電氣工程師會提到更改裝置配置的暫存器。這可以在幾個抽象級別上發生,因此我想消除一些混淆。
暫存器只是一個供裝置直接操作的小塊 RAM。在像 8086 或奔騰這樣的 CPU 中,這些是用於直接執行數學運算(如將兩個數字加在一起)的記憶體區域。這些通常被稱為 AX、SP 等。典型的 CPU 上只有很少的暫存器,因為對這些暫存器的訪問是直接編碼到基本機器級指令中的。
當我們談論裝置暫存器時,請記住這些不是 CPU 暫存器,而是裝置本身的記憶體區域。這些通常被設計為連線到埠 I/O 記憶體,因此,當您寫入或讀取埠 I/O 地址時,您是在直接訪問裝置暫存器。有時會存在更深一層的抽象,您將擁有一個指示要更改哪個暫存器的埠 I/O 地址,以及另一個包含要傳送到該暫存器的資料的埠 I/O 地址。您處理裝置的方式取決於其複雜程度以及您要執行的操作。
從本質上講,它們是暫存器,但請記住,這些裝置中的每一個通常都可以被視為一個完整的計算機,而您所做的一切只是建立它將如何與主 CPU 通訊。不要在這裡糾結並將其與 CPU 暫存器混淆。
使用中斷控制器時,您必須定期互動的一個方面是通知 8259 PIC 控制器中斷服務例程已完成。當您的軟體執行中斷處理程式時,CPU 沒有自動方法向 8259 晶片發出您已完成的訊號,因此需要設定 PIC 中的特定“暫存器”以允許下一個中斷處理程式訪問計算機系統。完成此操作的典型軟體如下
Port[$20] := $20;
這將傳送名為“中斷結束”的命令,通常簡稱為縮寫“EOI”。可以向此暫存器傳送其他命令,但就我們的目的而言,這是我們唯一需要關注的命令。
現在,這將清除“主”PIC,但如果您使用的是在“從”PIC 上觸發的裝置,您還需要通知該晶片中斷服務已完成。這意味著您需要像這樣向該晶片傳送“EOI”
Port[$A0] := $20; Port[$20] := $20;
您可以做其他事情來使您的計算機系統正常執行,但現在讓我們保持簡單。
在我們離開 8259 PIC 主題之前,我想介紹一下裝置遮蔽的概念。連線到 PIC 的每個裝置都可以從它們如何透過 PIC 晶片中斷 CPU 的角度來看“開啟”或“關閉”。通常作為應用程式開發人員,我們真正關心的是裝置是否已開啟,儘管如果您試圖隔離效能問題,您可能會關閉某些其他裝置。請記住,如果您關閉裝置,中斷將無法工作,直到它重新開啟。這可能包括鍵盤或您操作計算機可能需要的其他關鍵裝置。
設定此遮蔽的暫存器稱為“操作控制字 1”或“OCW1”。它位於 PIC 基地址 + 1,或對於“主”PIC 位於埠 I/O 地址 $21。這是您需要進行位操作的地方,我在這裡不會詳細介紹。下表顯示了為了啟用或停用每個硬體中斷裝置而需要更改的相關位。
| 位 | IRQ 啟用 | 裝置功能 |
|---|---|---|
| 7 | IRQ7 | 並行埠 (LPT1) |
| 6 | IRQ6 | 軟盤控制器 |
| 5 | IRQ5 | 保留/音效卡 |
| 4 | IRQ4 | 序列埠 (COM1) |
| 3 | IRQ3 | 序列埠 (COM2) |
| 2 | IRQ2 | 從 PIC |
| 1 | IRQ1 | 鍵盤 |
| 0 | IRQ0 | 系統定時器 |
| 位 | IRQ 啟用 | 裝置功能 |
|---|---|---|
| 7 | IRQ15 | 保留 |
| 6 | IRQ14 | 硬碟驅動器 |
| 5 | IRQ13 | 數學協處理器 |
| 4 | IRQ12 | PS/2 滑鼠 |
| 3 | IRQ11 | PCI 裝置 |
| 2 | IRQ10 | PCI 裝置 |
| 1 | IRQ9 | 重定向的 IRQ2 裝置 |
| 0 | IRQ8 | 即時時鐘 |
假設我們要開啟 IRQ3(序列埠 COM2 的典型埠),我們將使用以下軟體
Port[$21] := Port[$21] and $F7; {Clearing bit 3 for enabling IRQ3}
要關閉它,我們將使用以下軟體
Port[$21] := Port[$21] or $08; {Setting bit 3 for disabling IRQ3}
如果您在使任何東西正常工作時遇到問題,您只需在軟體中傳送此命令
Port[$21] := 0;
這隻會啟用所有內容。這可能不是一件好事,但您將不得不根據使用情況進行實驗。儘量不要使用這種捷徑,因為這不僅是懶惰程式設計師的標誌,而且還可能產生副作用,導致您的計算機的行為與預期不同。如果您在這一級別處理計算機,目標是儘可能少地更改,以避免對您使用的任何其他軟體造成損害。
現在我們已經瞭解了 8259 晶片,讓我們繼續討論 UART 本身。雖然 PIC 的埠 I/O 地址相當標準,但計算機制造商通常會移動序列埠本身的內容。此外,如果您有作為附加卡的一部分的序列埠裝置(如計算機擴充套件插槽中的 ISA 或 PCI 卡),這些裝置通常會與內建到計算機主機板中的裝置具有不同的設定。可能需要一些時間來找到這些設定,並且在您試圖編寫軟體時瞭解這些值非常重要。通常,這些值可以在計算機的 BIOS 設定螢幕中找到,或者如果您可以在計算機啟動時暫停訊息,它們可以在計算機啟動過程的一部分中找到。
對於“典型”PC 系統,以下是每個序列 COM 埠的埠 I/O 地址和 IRQ。
| COM 埠 | IRQ | 基埠 I/O 地址 |
|---|---|---|
| COM1 | IRQ4 | $3F8 |
| COM2 | IRQ3 | $2F8 |
| COM3 | IRQ4 | $3E8 |
| COM4 | IRQ3 | $2E8 |
如果您注意到這裡有一些有趣的事情,您會發現 COM3 和 COM1 共享同一個中斷。這不是錯誤,而是在編寫中斷服務例程時需要牢記的事情。透過 8259 PIC 晶片提供的 15 箇中斷仍然不足以允許現代計算機上找到的所有裝置都有其獨立的硬體中斷,因此在這種情況下,您需要學習如何與其他裝置共享中斷。當我們進入訪問序列資料埠的實際軟體時,我將更詳細地介紹這一點,但現在請記住不要嚴格為一個裝置編寫軟體。
基埠 I/O 地址對於我們將要討論的下一個主題很重要,即直接訪問 UART 暫存器。
UART 晶片共有 12 個不同的暫存器,對映到 8 個不同的埠 I/O 位置。是的,你沒有看錯,12 個暫存器在 8 個位置。顯然這意味著有多個暫存器使用相同的埠 I/O 位置,並且會影響 UART 的配置方式。實際上,兩個暫存器實際上是同一個,但處於不同的上下文中,因為你用來傳輸要從序列資料埠傳送出去的字元的埠 I/O 地址與你可以從中讀取傳送到計算機的字元的地址相同。另一個 I/O 埠地址在寫入資料時與讀取資料時具有不同的上下文... 並且在寫入資料後與讀取資料時的數字不同。稍後會詳細介紹。
該晶片最初設計時出現的一個問題是,設計人員需要能夠使用 16 位傳送有關序列資料波特率的資訊。這實際上佔用兩個不同的“暫存器”,並由稱為“除數鎖存器訪問位”或“DLAB”的東西切換。當 DLAB 設定為“1”時,可以設定波特率暫存器,而當它設定為“0”時,暫存器具有不同的上下文。
這一切聽起來很混亂嗎?確實可能,但讓我們一次一步地來。以下是典型的 UART 晶片中可以找到的每個暫存器的表格。
| 基本地址 | DLAB | I/O 訪問 | 縮寫 | 暫存器名稱 |
|---|---|---|---|---|
| +0 | 0 | 寫入 | THR | 傳送器保持緩衝區 |
| +0 | 0 | 讀取 | RBR | 接收器緩衝區 |
| +0 | 1 | 讀/寫 | DLL | 除數鎖存器低位元組 |
| +1 | 0 | 讀/寫 | IER | 中斷使能暫存器 |
| +1 | 1 | 讀/寫 | DLH | 除數鎖存器高位元組 |
| +2 | x | 讀取 | IIR | 中斷識別暫存器 |
| +2 | x | 寫入 | FCR | FIFO 控制暫存器 |
| +3 | x | 讀/寫 | LCR | 線路控制暫存器 |
| +4 | x | 讀/寫 | MCR | 調變解調器控制暫存器 |
| +5 | x | 讀取 | LSR | 線路狀態暫存器 |
| +6 | x | 讀取 | MSR | 調變解調器狀態暫存器 |
| +7 | x | 讀/寫 | SR | 暫存暫存器 |
DLAB 列中的“x”表示 DLAB 的狀態對該偏移量範圍內要訪問哪個暫存器沒有影響。還要注意,有些暫存器是隻讀的。如果你嘗試向它們寫入資料,你可能會遇到調變解調器出現問題(最壞情況),或者資料會被簡單地忽略(通常是結果)。如前所述,某些暫存器共享一個埠 I/O 地址,其中一個暫存器將在你向其寫入資料時使用,而另一個暫存器將在從同一地址檢索資料時使用。
每個序列通訊埠都有自己的一組這些暫存器。例如,如果你想訪問 COM1 的線路狀態暫存器 (LSR),並且假設基本 I/O 埠地址為 $3F8,那麼用於獲取此暫存器中資訊的 I/O 埠地址將在 $3F8 + $05 或 $3FD 處找到。一些示例程式碼如下
const COM1_Base = $3F8; COM2_Base = $2F8; LSR_Offset = $05; function LSR_Value: Byte; begin Result := Port[COM1_Base+LSR_Offset]; end;
每個暫存器中都包含大量資訊,以下是每個暫存器的含義及其包含的資訊的解釋。
傳送器保持緩衝區/接收器緩衝區
[edit | edit source]偏移量:+0。傳送和接收緩衝區是相關的,而且通常甚至使用相同的記憶體。這也是 8250 晶片的後續版本有重大影響的領域之一,因為後續型號在資料以序列資料形式傳輸之前,在晶片內部集成了某些資料緩衝。基本 8250 晶片一次只能接收一個位元組,而後續晶片(如 16550 晶片)則會儲存多達 16 個位元組,以便在傳送或接收之前(有時兩者都... 取決於製造商)進行傳輸或接收,這樣你就不必等待字元傳送。這在多工環境中很有用,在這些環境中,計算機執行許多工,並且可能需要幾毫秒才能恢復處理序列資料流。
這些暫存器實際上是序列資料通訊的“核心”,以及資料如何從你的軟體傳輸到另一臺計算機以及如何從其他裝置獲取資料。對這些暫存器的讀寫操作很簡單,只需訪問相應 UART 的埠 I/O 地址即可。
如果接收緩衝區已佔用或 FIFO 已滿,則丟棄傳入資料,並將接收線路狀態中斷寫入 IIR 暫存器。線路狀態暫存器中還會設定溢位錯誤位。
除數鎖存器位元組
[edit | edit source]偏移量:+0 和 +1。除數鎖存器位元組用於控制調變解調器的波特率。顧名思義,它用作除數來確定晶片將要使用的波特率。
實際上,這比這更簡單。這實際上是一個倒計時時鐘,每次 UART 傳輸一個位時都會使用該時鐘。每次傳送一個位時,一個倒計時暫存器都會重置為該值,然後倒計時到零。該時鐘通常以 115.2 kHz 執行。換句話說,每秒 11.5 萬次,一個計數器會下降以確定何時傳送下一個位。在設計過程中的某一時刻,預計可能會使用其他頻率使 UART 正常工作,但在為該晶片編寫的現有軟體數量眾多的情況下,該頻率幾乎成為所有 PC 平臺上使用的 UART 晶片的標準頻率。它們可能在某些部分使用更快的時鐘(例如 1.843 MHz 時鐘),但該頻率的一部分將用於向下擴充套件到 115.2 kHz 時鐘。
關於 UART 時鐘速度的更多資訊(高階內容):對於許多 UART 晶片來說,驅動 UART 的時鐘頻率為 1.8432 MHz。然後將該頻率透過一個分頻器電路,將頻率降低 16 倍,得到上面提到的 115.2 KHz 頻率。如果你正在使用該晶片製作一些定製裝置,國家半導體規格書允許使用 3.072 MHz 時鐘和 18.432 MHz 時鐘。這些更高的頻率將允許你在更高的波特率下進行通訊,但需要主機板上的定製電路,並且通常還需要新的驅動程式來處理這些新的頻率。有趣的是,你仍然可以在這些更高的時鐘頻率下以 50 波特的速度執行,但在原始 IBM-PC/XT 製造時,這不像現在這樣重要,因為現在需要更高的資料吞吐量。
如果你使用以下數學公式,你可以確定需要將哪些數字放入除數鎖存器位元組中
這將為你提供以下表格,該表格可用於確定序列通訊的常見波特率
| 波特率 | 除數(十進位制) | 除數鎖存器高位元組 | 除數鎖存器低位元組 |
|---|---|---|---|
| 50 | 2304 | $09 | $00 |
| 110 | 1047 | $04 | $17 |
| 220 | 524 | $02 | $0C |
| 300 | 384 | $01 | $80 |
| 600 | 192 | $00 | $C0 |
| 1200 | 96 | $00 | $60 |
| 2400 | 48 | $00 | $30 |
| 4800 | 24 | $00 | $18 |
| 9600 | 12 | $00 | $0C |
| 19200 | 6 | $00 | $06 |
| 38400 | 3 | $00 | $03 |
| 57600 | 2 | $00 | $02 |
| 115200 | 1 | $00 | $01 |
需要注意的是,600 及以上的波特率都將除數鎖存器高位元組設定為零。一個馬虎的程式設計師可能會嘗試跳過設定高位元組,假設沒有人會處理這麼低的波特率,但這並非總是可以假設的。良好的程式設計習慣建議你應該仍然嘗試將它設定為零,即使你只是在使用更高的波特率。
還要注意,除了上面列出的標準波特率之外,還有其他可能的波特率。雖然這在典型的應用程式中不鼓勵,但它會是一件很有趣的事情。此外,你還可以嘗試以這種方式與舊裝置進行通訊,在這些裝置中,標準 API 庫可能不允許特定應該相容的波特率。這應該說明了為什麼在這一級別上了解這些晶片仍然非常有用。
在使用這些暫存器時,還要記住,這些是唯一需要將除數鎖存器訪問位設定為“1”的暫存器。這將在下面進一步介紹,但我想要提到,對於應用程式軟體來說,將 DLAB 設定為“1”只為了更改波特率的即時操作,然後在執行任何其他 I/O 訪問調變解調器之前,將 DLAB 立即設定為“0”會很有用。這只是一個良好的工作習慣,並且使你需要為訪問 UART 編寫的其他軟體更加乾淨、更容易。
一句警告:不要將兩個除數鎖存器位元組的值都設定為“0”。雖然這(可能)不會損壞 UART 晶片,但 UART 傳輸序列資料的方式將是不可預測的,並且會從一臺計算機到另一臺計算機,甚至從你啟動計算機的一次到下次都不一樣。這是一個錯誤條件,如果你正在編寫與該級別的波特率設定一起使用的軟體,那麼你應該捕獲除數鎖存器的潛在“0”值。
以下是一些設定和檢索 COM1 的波特率的示例軟體。
const
COM1_Base = $3F8;
COM2_Base = $2F8;
LCR_Offset = $03;
Latch_Low = $00;
Latch_High = $01;
procedure SetBaudRate(NewRate: Word);
var
DivisorLatch: Word;
begin
DivisorLatch := 115200 div NewRate;
Port[COM1_Base + LCR_Offset] := Port[COM1_Base + LCR_Offset] or $80; {Set DLAB}
Port[COM1_Base + Latch_High] := DivisorLatch shr 8;
Port[COM1_Base + Latch_Low] := DivisorLatch and $FF;
Port[COM1_Base + LCR_Offset] := Port[COM1_Base + LCR_Offset] and $7F; {Clear DLAB}
end;
function GetBaudRate: Integer;
var
DivisorLatch: Word;
begin
Port[COM1_Base + LCR_Offset] := Port[COM1_Base + LCR_Offset] or $80; {Set DLAB}
DivisorLatch := (Port[COM1_Base + Latch_High] shl 8) + Port[COM1_Base + Latch_Low];
Port[COM1_Base + LCR_Offset] := Port[COM1_Base + LCR_Offset] and $7F; {Clear DLAB}
Result := 115200 div DivisorLatch;
end;
中斷使能暫存器
[edit | edit source]偏移量:+1。這個暫存器允許你控制 UART 何時以及如何使用與序列 COM 埠關聯的硬體中斷觸發中斷事件。如果使用得當,這可以實現對系統資源的有效利用,並允許你實質上即時地對透過序列資料線傳送的資訊做出反應。關於這一點的更多內容將在後面介紹,但重點是你可以使用 UART 來告訴你何時需要提取一些資料。該暫存器既可以讀也可以寫。
以下表格顯示了該暫存器中的每個位以及它將使能的事件,以允許你檢查該晶片的狀態。
| 位 | 說明 |
|---|---|
| 7 | 保留 |
| 6 | 保留 |
| 5 | 使能低功耗模式 (16750) |
| 4 | 使能休眠模式 (16750) |
| 3 | 使能調變解調器狀態中斷 |
| 2 | 使能接收線路狀態中斷 |
| 1 | 使能傳送器保持暫存器為空中斷 |
| 0 | 使能接收資料可用中斷 |
接收資料中斷是一種讓你知道是否有資料在等待你從 UART 中提取的方法。這可能是你將使用的最常用的一個位,並且具有更多用途。
發射器保持暫存器為空中斷是為了通知你,輸出緩衝區(在晶片的更高階模型中,例如 16550)已完成傳送你放入緩衝區的所有內容。這是一種簡化資料傳輸例程的方法,使其佔用更少的 CPU 時間。
接收器線路狀態中斷指示 LSR 暫存器中的某些內容可能已更改。這通常是錯誤條件,如果你要為 UART 編寫一個有效的錯誤處理程式,該處理程式將為你的應用程式的終端使用者提供純文字描述,那麼你應該考慮這一點。這當然需要更高階的程式設計知識。
調變解調器狀態中斷是為了在與你的計算機連線的外部調變解調器發生變化時通知你。這可能包括電話“鈴聲”(你可以在你的軟體中模擬它)、你已成功連線到另一個調變解調器(載波檢測已開啟)或有人“結束通話”電話(載波檢測已關閉)。它還可以幫助你瞭解外部調變解調器或資料裝置是否可以繼續接收資料(傳送許可)。本質上,它處理 RS-232 標準中的其他線,而不只是嚴格的傳送和接收線。
另外兩種模式是專門針對 16750 晶片的,有助於將晶片置於“低功耗”狀態,以用於筆記型電腦或具有電池等非常有限電源的嵌入式控制器。在早期晶片上,你應該將這些位視為“保留”,並且只將“0”放入其中。
偏移量:+2。此暫存器用於幫助識別你所使用的 UART 晶片的獨特特性。此暫存器有兩種用途。
- 識別 UART 觸發中斷的原因。
- 識別 UART 晶片本身。
其中,識別中斷服務例程被呼叫的原因可能最為重要。
下表解釋了此暫存器的部分詳細資訊,以及其上每一位表示的內容。
| 位 | 說明 | ||||
|---|---|---|---|---|---|
| 7 和 6 | 位 7 | 位 6 | |||
| 0 | 0 | 晶片上沒有 FIFO | |||
| 0 | 1 | 保留條件 | |||
| 1 | 0 | FIFO 已啟用,但未執行 | |||
| 1 | 1 | FIFO 已啟用 | |||
| 5 | 64 位元組 FIFO 已啟用(僅限 16750) | ||||
| 4 | 保留 | ||||
| 3、2 和 1 | 位 3 | 位 2 | 位 1 | 復位方法 | |
| 0 | 0 | 0 | 調變解調器狀態中斷 | 讀取調變解調器狀態暫存器 (MSR) | |
| 0 | 0 | 1 | 發射器保持暫存器為空中斷 | 讀取中斷識別暫存器 (IIR) 或 寫入發射器保持緩衝區 (THR) | |
| 0 | 1 | 0 | 接收資料可用中斷 | 讀取接收緩衝區暫存器 (RBR) | |
| 0 | 1 | 1 | 接收器線路狀態中斷 | 讀取線路狀態暫存器 (LSR) | |
| 1 | 0 | 0 | 保留 | 不適用 | |
| 1 | 0 | 1 | 保留 | 不適用 | |
| 1 | 1 | 0 | 超時中斷待處理(16550 及更高版本) | 讀取接收緩衝區暫存器 (RBR) | |
| 1 | 1 | 1 | 保留 | 不適用 | |
| 0 | 中斷待處理標誌 | ||||
當你在為 8250 晶片(及其更高版本)編寫中斷處理程式時,你需要檢視此暫存器,以確定中斷的具體觸發原因。
如前所述,多個序列通訊裝置可以共享同一個硬體中斷。“位 0”的使用將讓你知道(或確認)這確實是導致中斷的裝置。你需要做的是檢查所有序列裝置(位於單獨的埠 I/O 地址空間),並獲取此暫存器的內容。請記住,至少有可能多個裝置同時觸發中斷,因此,當你執行此序列裝置掃描時,請確保檢查所有裝置,即使第一個裝置實際上需要處理。某些計算機系統可能不需要這樣做,但這是一個良好的程式設計實踐。還有可能,由於你之前處理了 UART,你已經處理了給定中斷的所有 UART。當此位為“0”時,它表示 UART 正在觸發中斷。當它為“1”時,表示中斷已被處理,或者此特定 UART 不是觸發裝置。我知道這對於計算機中使用的典型位標誌來說似乎有點倒退,但這被稱為數字邏輯被斷言為低電平,在電子電路設計中非常常見。但是,這種邏輯模式進入軟體領域則更加不尋常。
位 1、2 和 3 有助於識別 UART 中用於呼叫硬體中斷的具體中斷事件型別。這些與之前使用 IER 暫存器啟用的中斷相同。但是,在這種情況下,每次處理暫存器並處理中斷時,它都是唯一的。如果由於同時發生多件事而導致 UART 發生多個“觸發”,這將透過多個硬體中斷來呼叫。早期的晶片組不使用位 3,但這是這些 UART 系統上的一個保留位,始終設定為邏輯狀態“0”,因此在嘗試破譯已使用哪種中斷時,程式設計邏輯不必不同。
為了解釋 FIFO 超時中斷,這是一種檢查資料包結束或傳入資料流是否已停止的方法。通常,此中斷被觸發的條件必須存在:一些資料需要在傳入 FIFO 中,並且尚未被計算機讀取。透過序列資料鏈路傳送到 UART 的資料傳輸必須已結束,並且沒有新的字元被接收。CPU 處理傳入資料之前,必須從 FIFO 中檢索任何資料,然後超時才會發生。超時通常在傳輸或接收至少 4 個字元所需的時間之後發生。如果你正在談論以 1200 波特率、8 個數據位、2 個停止位、奇校驗傳送的資料,則這將花費大約 40 毫秒,就你的 4 GHz Pentium CPU 可以完成的事情而言,幾乎是一個永恆。
上面列出的“復位方法”描述瞭如何通知 UART 給定中斷已處理。當你訪問復位方法下提到的暫存器時,這將清除該 UART 的中斷條件。如果同一 UART 的多箇中斷已被觸發,要麼它不會清除 CPU 上的中斷訊號(在你完成時觸發新的硬體中斷),要麼如果你檢查回此暫存器 (IIR) 並查詢中斷待處理標誌以檢視是否有更多中斷需要處理,則可以繼續並嘗試使用適當的應用程式程式碼解決你可能需要處理的任何新的中斷問題。
位 5、6 和 7 正在報告用於傳輸和接收字元的 FIFO 緩衝區的當前狀態。在最初發布時,16550 晶片設計中存在一個錯誤,該錯誤在 FIFO 中存在嚴重缺陷,導致 FIFO 報告其正在工作,但實際上並非如此。由於一些軟體已經編寫好與 FIFO 一起工作,因此保留了此位(此暫存器的位 7),但添加了位 6 以確認 FIFO 實際上正常工作,以防一些新的軟體想要忽略早期版本上的硬體 FIFO。 16550 晶片。這種模式在該晶片的未來版本中也一直保留。在 16750 晶片上,已實施了額外的 64 位元組 FIFO,位 5 用於指定此擴充套件緩衝區的存在。這些 FIFO 緩衝區可以使用下面列出的暫存器開啟和關閉。
偏移量:+2。這是一個相對“新的”暫存器,它不是原始 8250 UART 實現的一部分。此暫存器的目的是控制晶片上的先進先出 (FIFO) 緩衝區的工作方式,並幫助你在應用程式中微調其效能。這甚至使你能夠“開啟”或“關閉”FIFO。
請記住,這是一個“只寫”暫存器。嘗試讀取其內容只會為你提供中斷識別暫存器 (IIR),而 IIR 具有完全不同的上下文。
| 位 | 說明 | |||
|---|---|---|---|---|
| 7 & 6 | 位 7 | 位 6 | 中斷觸發級別(16 位元組) | 觸發級別(64 位元組) |
| 0 | 0 | 1 位元組 | 1 位元組 | |
| 0 | 1 | 4 位元組 | 16 位元組 | |
| 1 | 0 | 8 位元組 | 32 位元組 | |
| 1 | 1 | 14 位元組 | 56 位元組 | |
| 5 | 啟用 64 位元組 FIFO(16750) | |||
| 4 | 保留 | |||
| 3 | DMA 模式選擇 | |||
| 2 | 清除發射器 FIFO | |||
| 1 | 清除接收器 FIFO | |||
| 0 | 啟用 FIFO | |||
向位 0 寫入“0”將停用 FIFO,本質上將 UART 轉換為 8250 相容模式。實際上,這也使此暫存器中的其餘設定變得無用。如果你在此處寫入“0”,它還將阻止 FIFO 傳送或接收資料,因此透過序列資料埠傳送的任何資料都可能在此設定更改後被加擾。建議僅在嘗試重置序列通訊協議並清除應用程式軟體中可能存在的任何工作緩衝區時才停用 FIFO。一些文件建議將此位設定為“0”也會清除 FIFO 緩衝區,但我建議使用位 1 和 2 進行顯式緩衝區清除。
位 1 和 2 用於清除內部 FIFO 緩衝區。這在你首次啟動應用程式時非常有用,你可能希望清除之前可能由使用 UART 的先前軟體“遺留”的任何資料,或者如果你想要重置通訊連線。這些位是“自動”重置的,因此,如果你將其中任何一個設定為邏輯“1”狀態,你就不必以後再將它們設定為“0”。傳送一個邏輯“0”只是告訴 UART 不要重置 FIFO 緩衝區,即使要更改 FIFO 控制的其他方面。
位 3 與 DMA(直接記憶體訪問)的工作方式有關,主要是在你嘗試從 FIFO 中檢索資料時。這主要對試圖直接訪問序列資料並將這些資料儲存在內部緩衝區的晶片設計師有用。UART 晶片本身有兩個數字邏輯引腳,分別標記為 RXRDY 和 TXRDY。如果你試圖使用 UART 晶片設計計算機電路,這可能有用甚至很重要,但對於 PC 系統上應用程式開發人員而言,它幾乎沒有用處,你可以放心地忽略它。
位 5 允許 16750 UART 晶片將緩衝區從 16 位元組擴充套件到 64 位元組。這不僅會影響緩衝區的大小,還會控制觸發閾值的大小,如下一節所述。在早期的晶片型別上,這是一個保留位,應保持在邏輯“0”狀態。在 16750 上,它使該 UART 的表現更像只有 16 位元組 FIFO 的 16550。
位 6 和 7 描述觸發閾值。這是在觸發中斷之前儲存在 FIFO 中的字元數量,中斷將通知您應從 FIFO 中刪除資料。如果您預計會有大量資料透過序列資料鏈路傳送,您可能需要增加緩衝區的大小。觸發器最大值小於 FIFO 緩衝區大小的原因是,某些軟體可能需要一些時間才能訪問 UART 並檢索資料。請記住,當 FIFO 充滿時,您將開始丟失 FIFO 中的資料,因此,在達到此閾值後,務必確保您已檢索到資料。如果您在嘗試檢索 UART 資料時遇到軟體計時問題,您可能需要降低閾值。在閾值設定為 1 位元組的極端情況下,它基本上會像基本的 8250 一樣工作,但增加了可靠性,即在您沒有機會立即獲取所有字元的情況下,某些字元可能會被捕獲在緩衝區中。
偏移量:+3。此暫存器有兩個主要用途
- 設定除數鎖存器訪問位 (DLAB),允許您設定除數鎖存器位元組的值。
- 設定用於接收和傳送序列資料的位模式。換句話說,您將使用的序列資料協議(8-1-無、5-2-偶校驗等)。
| 位 | 說明 | |||
|---|---|---|---|---|
| 7 | 除數鎖存器訪問位 | |||
| 6 | 設定斷開使能 | |||
| 3, 4 & 5 | 位 5 | 位 4 | 位 3 | 奇偶校驗選擇 |
| 0 | 0 | 0 | 無奇偶校驗 | |
| 0 | 0 | 1 | 奇校驗 | |
| 0 | 1 | 1 | 偶校驗 | |
| 1 | 0 | 1 | 標記 | |
| 1 | 1 | 1 | 空格 | |
| 2 | 0 | 一位停止位 | ||
| 1 | 1.5 位停止位或 2 位停止位 | |||
| 0 & 1 | 位 1 | 位 0 | 字長 | |
| 0 | 0 | 5 位 | ||
| 0 | 1 | 6 位 | ||
| 1 | 0 | 7 位 | ||
| 1 | 1 | 8 位 | ||
前兩位(位 0 和位 1)控制透過序列協議傳輸的每個資料“字”傳送多少資料位。對於大多數序列資料傳輸,這將是 8 位,但您會發現一些早期協議和舊裝置需要更少的資料位。例如,一些軍事加密裝置每個序列“字”只使用 5 位資料,就像一些電傳裝置一樣。早期的 ASCII 電傳打字機終端只使用 7 位資料,而且事實上,這種傳統在 SMTP 格式中得以保留,SMTP 格式只對電子郵件訊息使用 7 位 ASCII。顯然,這需要在您能夠使用 RS-232 協議成功完成訊息傳輸之前確定。
位 2 控制 UART 傳送到接收裝置的停止位數量。可以選擇一位或兩位停止位,邏輯“0”表示一位停止位,“1”表示兩位停止位。在 5 位資料的情況下,UART 反而會發送“1.5 位停止位”。請記住,在此上下文中,“位”實際上是一個時間間隔:在 50 波特(每秒位數)下,每個位需要 20 毫秒。因此,“1.5 位停止位”將有至少 30 毫秒的字元間間隔。這與“5 位資料”設定相關聯,因為只有使用 5 位 Baudot 而不是 7 位或 8 位 ASCII 的裝置使用“1.5 位停止位”。
另一件需要牢記的事情是,RS-232 標準只規定每個序列資料字結束時至少會保持一位資料位週期為邏輯“1”(換句話說,從起始位、資料位、奇偶校驗位和停止位組成的完整字元)。如果您在兩臺計算機之間遇到計時問題,但通常能夠一次傳送一個字元,您可能需要新增第二位停止位,而不是降低波特率。這會在每個字元的傳輸速度中增加一位的懲罰,而不是透過降低波特率(通常)將傳輸速度降低一半。
位 3、4 和 5 控制每個序列字如何響應奇偶校驗資訊。當位 3 為邏輯“0”時,這會導致不傳送任何奇偶校驗位與序列資料字一起傳送。相反,它會立即移動到停止位,並承認在此級別的奇偶校驗檢查實際上毫無用處。透過包含奇偶校驗位,您仍然可能會獲得一些資料傳輸可靠性的提高,但還有其他更可靠和實用的方法,將在本書的其他章節中討論。如果您想包含奇偶校驗檢查,以下解釋了除“無”奇偶校驗以外的每種奇偶校驗方法
- 奇校驗
- 序列字資料部分中的每一位都作為邏輯“1”位的數量的簡單計數加起來。如果這是一個奇數位,奇偶校驗位將作為邏輯“0”傳送。如果計數為偶數,奇偶校驗位將作為邏輯“1”傳送,以使“1”位的數量為奇數。
- 偶校驗
- 與奇校驗一樣,位加在一起。但是,在這種情況下,如果位的數量最終為奇數,它將作為邏輯“1”傳送,以使“1”位的數量為偶數,這與奇校驗正好相反。
- 標記奇偶校驗
- 在這種情況下,奇偶校驗位始終為邏輯“1”。雖然這可能看起來有點不尋常,但這主要是為了測試和診斷目的。如果您想確保序列連線接收端的軟體能夠正確響應奇偶校驗錯誤,您可以傳送標記或空格奇偶校驗,併發送不符合接收 UART 或裝置對奇偶校驗的期望的字元。此外,對於僅限標記奇偶校驗,您可以將此位用作額外的“停止位”。請記住,RS-232 標準期望邏輯“1”來結束序列資料字,因此接收計算機將無法區分“標記”奇偶校驗位和停止位。本質上,您可以透過使用此設定以及透過適當使用此暫存器的停止位部分來實現 3 位或 2.5 位停止位。這是一種“調整”計算機設定的方法,典型應用程式不允許您這樣做,或者至少可以更深入地瞭解序列資料設定。
- 空格奇偶校驗
- 與標記奇偶校驗一樣,這使得奇偶校驗位“粘性”,因此它不會改變。在這種情況下,它每次傳輸字元時都會輸入邏輯“0”作為奇偶校驗位。這樣做沒有太多實際用途,除了粗略地將每個序列字輸入 9 位資料或用於上述診斷目的。
當位 6 設定為 1 時,會導致 TX 線變為邏輯“0”並保持該狀態,接收 UART 將其解釋為“0”位的長流 - “斷開狀態”。要結束“斷開”,將位 6 恢復為 0。
偏移量:+4。此暫存器允許您在軟體控制下進行“硬體”流控制。或者,更實際地說,它允許直接操作 UART 上的四根不同的線,您可以將這些線設定為任何獨立的邏輯狀態序列,並能夠提供對調變解調器的控制。還應注意,大多數 UART 需要輔助輸出 2 設定為邏輯“1”才能啟用中斷。
| 位 | 說明 |
|---|---|
| 7 | 保留 |
| 6 | 保留 |
| 5 | 自動流控制使能 (16750) |
| 4 | 迴環模式 |
| 3 | 輔助輸出 2 |
| 2 | 輔助輸出 1 |
| 1 | 請求傳送 |
| 0 | 資料終端就緒 |
在典型的 PC 平臺上,這些輸出中只有請求傳送 (RTS) 和資料終端就緒 (DTR) 實際上連線到 DB-9 聯結器上的 PC 輸出。如果您幸運地擁有 DB-25 序列聯結器(在 PC 平臺上更常用於並行通訊),或者如果您在擴充套件卡上擁有自定義 UART,輔助輸出可能會連線到 RS-232 連線。如果您將此晶片用作自定義電路中的一個元件,這將為您提供一些“免費”的額外輸出訊號,您可以在晶片設計中使用這些訊號來發出任何您可能希望由 TTL 輸出觸發的訊號,並且將在軟體控制下。有更簡單的方法可以做到這一點,但在這種情況下,它可能會節省您在佈局上的額外晶片。
“迴環”模式主要是一種測試 UART 以驗證主 CPU 和 UART 之間的電路是否正常工作的方法。終端使用者很少,如果有的話,需要測試這一點,但這對於使用 UART 的某些軟體的一些初始測試可能有用。當將其設定為邏輯狀態“1”時,任何放入傳送暫存器的字元都會立即在 UART 的接收暫存器中找到。其他邏輯訊號,如上面列出的 RTS 和 DTS,將出現在調變解調器狀態暫存器中,就像您在序列通訊埠末端放了一個迴環 RS-232 裝置一樣。簡而言之,這允許您僅使用軟體進行迴環測試。除了這些診斷目的以及使用 UART 的某些早期軟體開發測試之外,這永遠不會使用。
在 16750 上,有一種特殊的模式可以使用調變解調器控制暫存器來呼叫。基本上,這允許 UART 直接控制 RTS 和 DTS 的狀態,以進行硬體字元流控制,具體取決於 FIFO 的當前狀態。這種行為還受 FIFO 控制暫存器 (FCR) 的位 5 的狀態影響。雖然這很有用,並且可以改變您編寫 UART 控制軟體的方式的一些邏輯,但 16750 作為晶片相對較新,並且在許多計算機系統中並不常見。如果您知道您的計算機有一個 16750 UART,那就享受利用此增強功能帶來的樂趣吧。
偏移量:+5。此暫存器主要用於根據已接收的資料向您提供有關 UART 中可能存在的錯誤條件的資訊。請記住,這是一個“只讀”暫存器,寫入此暫存器的任何資料都可能會被忽略,或者更糟糕的是,會導致 UART 的不同行為。此資訊有幾個用途,下面將提供一些有關它如何用於診斷序列資料連線問題的資訊
| 位 | 說明 |
|---|---|
| 7 | 接收 FIFO 中的錯誤 |
| 6 | 資料保持暫存器為空 |
| 5 | 傳送器保持暫存器為空 |
| 4 | 斷開中斷 |
| 3 | 幀錯誤 |
| 2 | 奇偶校驗錯誤 |
| 1 | 溢位錯誤 |
| 0 | 資料就緒 |
位 7 指的是 FIFO 中字元的錯誤。如果當前位於 FIFO 中的任何字元都存在此處列出的其他錯誤訊息之一(例如幀錯誤、奇偶校驗錯誤等),這提醒您需要清除 FIFO,因為 FIFO 中的字元資料不可靠並且存在一個或多個錯誤。在沒有 FIFO 的 UART 晶片上,這是一個保留的位欄位。
位 5 和 6 指的是字元傳送器電路的狀態,可以幫助你識別 UART 是否準備好接收另一個字元。如果所有字元(包括 FIFO,如果已啟用)都已傳送,並且“移位暫存器”也已完成傳送,則位 6 設定為邏輯“1”。此移位暫存器是 UART 內部的記憶體塊,從傳送保持緩衝區 (THB) 或 FIFO 獲取資料,並負責將資料實際轉換為序列格式,一次傳送一個數據位,並將移位暫存器的內容向下移一位以獲取下一個位的數值。位 5 只是告訴你 UART 能夠接收更多字元,包括髮送到 FIFO 中的字元。
當序列資料輸入線在至少與整個序列資料“字”(包括起始位、資料位、奇偶校驗位和停止位)一樣長的時期內接收“0”位時,中斷 (位 4) 將變為邏輯“1”狀態,對於給定的波特率,此時間對應於除數鎖存位元組。 (序列線的正常狀態是在空閒時傳送“1”位,或傳送始終為一個“0”位的起始位,然後傳送可變資料和奇偶校驗位,然後傳送“1”的停止位,如果線路進入空閒狀態,則繼續傳送更多的“1”位。) 而不是正常狀態的“0”位的長序列通常意味著傳送序列資料到你的計算機的裝置由於某種原因停止了。在序列通訊中,這通常是一種正常情況,但這樣你就可以監控另一個裝置是如何工作的。一些序列終端有一個鍵,使它們可以生成這種“中斷條件”作為帶外信令方法。
當最後一個位不是停止位時,就會發生幀錯誤 (位 3)。或者更準確地說,停止位是邏輯“0”。這有幾個原因,包括你可能在兩臺計算機之間的時序不匹配。這通常是由於波特率不匹配造成的,但也可能涉及其他原因,包括裝置之間的物理佈線問題或電纜太長。你甚至可能將資料位的數量弄錯了,因此當遇到此類錯誤時,請仔細檢查序列資料協議,以確保 UART 的所有設定(資料位長度、奇偶校驗和停止位數量)都符合預期。
奇偶校驗錯誤 (位 2) 也可能表明波特率不匹配,就像幀錯誤一樣(特別是如果兩種錯誤同時發生)。當預期(奇數、偶數、標記或空格)的奇偶校驗演算法未找到時,會設定此位。如果你在 UART 設定中使用“無奇偶校驗”,則此位應該始終為邏輯“0”。當沒有發生幀錯誤時,這是一種識別佈線存在一些問題的方法,儘管你可能還需要處理其他問題。
溢位錯誤 (位 1) 是程式設計不當或作業系統沒有給你對 UART 的適當訪問許可權的標誌。當有字元等待讀取,並且傳入的移位暫存器試圖將下一個字元的內容移入接收緩衝區 (RBR) 時,就會發生這種錯誤情況。在具有 FIFO 的 UART 上,這也表示 FIFO 已滿。
可以做一些事情來幫助解決此錯誤,包括檢視訪問 UART 的軟體的效率,特別是監控和讀取傳入資料的部分。在多工作業系統上,你可能需要確保讀取傳入資料的軟體部分位於單獨的執行緒中,並且執行緒優先順序較高或時間關鍵,因為這對使用序列通訊資料的軟體來說是一個非常重要的操作。應用程式的良好軟體實踐還包括新增透過軟體完成的應用程式特定“緩衝區”,這為你的應用程式提供了更多機會來根據需要處理傳入資料,並遠離從 UART 獲取資料的時限敏感子程式。此緩衝區可以小到 1KB,也可以大到 1MB,並且很大程度上取決於你正在處理的資料型別。還有其他更奇特的緩衝技術適用於應用程式開發領域,將在後面的模組中介紹。
如果你使用的是更簡單的作業系統,如 MS-DOS 或即時作業系統,則需要區分輪詢驅動訪問 UART 和中斷驅動軟體。編寫中斷驅動程式效率更高,本書將專門有一部分介紹如何編寫用於 UART 訪問的軟體。
最後,如果你無法解決防止溢位錯誤出現的問題,你可能需要考慮降低序列傳輸的波特率。這並不總是一個選擇,實際上應該是你在軟體中嘗試解決此問題時最後的選擇。為了快速測試基本演算法是否有效,你可以從較低的波特率開始,逐漸提高速度,但這隻應在軟體的初始開發階段進行,而不是在釋出給客戶或作為公開分發的軟體進行。
資料就緒位 (位 0) 實際上是最簡單的部分。這是一種簡單地通知你 UART 有資料可供你的軟體提取的方式。當此位為邏輯“1”時,就該讀取接收緩衝區 (RBR) 了。在具有活動的 FIFO 的 UART 上,此位將保持邏輯“1”狀態,直到你讀取完 FIFO 的所有內容。
調變解調器狀態暫存器
[edit | edit source]偏移量:+6。此暫存器是另一個只讀暫存器,用於通知你的軟體調變解調器的當前狀態。以這種方式訪問的調變解調器可以是外部調變解調器,也可以是使用 UART 作為與計算機介面的內部調變解調器。
| 位 | 說明 |
|---|---|
| 7 | 載波檢測 |
| 6 | 振鈴指示器 |
| 5 | 資料裝置就緒 |
| 4 | 傳送許可 |
| 3 | 載波檢測增量 |
| 2 | 振鈴指示器後沿 |
| 1 | 資料裝置就緒增量 |
| 0 | 傳送許可增量 |
位 7 和 6 與調變解調器活動直接相關。載波檢測將在調變解調器“連線”到另一個調變解調器時保持邏輯“1”狀態。當此位變為邏輯“0”狀態時,你可以假設電話連線已斷開。振鈴指示器位直接與 RS-232 線路(也稱為“RI”或振鈴指示器)相連。通常,當檢測到電話線路上的“振鈴電壓”時,此位會變為邏輯“1”狀態,就像傳統電話會響鈴以通知你有人試圖打電話給你一樣。
當我們進入 AT 調變解調器命令部分時,將有其他方法可以顯示,這些方法可以通知你有關調變解調器狀態的資訊,以及其他資訊,這些資訊將作為正常序列資料流中的字元傳送,而不是特殊線。實際上,這些額外的位幾乎毫無價值,但從一開始就是規範的一部分,並且對於 UART 設計人員來說相對容易實現。但是,這可能是一種有效地傳送一些額外資訊或允許使用 UART 的軟體設計師從其他裝置獲得其他目的的邏輯位訊號的方式。
“資料裝置就緒”和“傳送許可”位(位 4 和 5)直接位於 RS-232 電纜上,並且與“請求傳送”和“資料終端就緒”相匹配的線,這些線與“調變解調器控制暫存器 (MCR)”一起傳送。使用兩個暫存器中的這四個位,你可以執行“硬體流控制”,你可以向另一個裝置發出訊號,指示它何時傳送更多資料,或者指示它在嘗試處理資訊時暫停並停止傳送資料。當我們進入資料流控制時,將在另一個模組中詳細介紹此主題。
關於“增量”位(位 0、1、2 和 3)的說明。在這種情況下,“增量”意味著變化,就像某個位的狀態發生變化一樣。這來自其他科學領域,如火箭科學,其中 delta-vee 表示速度的變化。就本暫存器而言,如果與該位相關的位(如載波檢測增量與載波檢測)自上次訪問此暫存器以來已改變其邏輯狀態,則這些位中的每一個在下一次訪問此調變解調器狀態暫存器時將為邏輯“1”。振鈴指示器後沿與其他位幾乎相同,只是當“振鈴指示器”位從邏輯“1”狀態變為邏輯“0”狀態時,它才會處於邏輯“1”狀態。這些資訊並沒有太多實際用途,但是有一些軟體試圖利用這些位並根據這些位對從 UART 接收的資料進行一些操作。如果你忽略了這 4 位,你仍然可以製作非常健壯的序列通訊軟體。
暫存暫存器
[edit | edit source]偏移量:+7。Scratch 暫存器是一個有趣的謎。為了嘗試將所有其他 I/O 埠地址中的一大堆暫存器擠在一起,設計人員有一個額外的“暫存器”,他們不知道該如何處理它。請記住,在處理計算機體系結構時,處理 2 的冪更容易,因此他們“被迫”必須定址 8 個 I/O 埠。允許另一個裝置使用此額外的 I/O 埠將使主機板設計過於複雜。
在 8250 UART 的某些變體中,寫入此 Scratch 暫存器的任何資料都可以在你讀取此暫存器的 I/O 埠時供軟體使用。實際上,這為你提供了額外的 1 位元組“記憶體”,你可以以任何你認為有用的方式在你的應用程式中使用它。除了病毒作者(也許我不應該提供任何想法),這個暫存器實際上沒有很好的用途。有限的用途是你可以使用此暫存器來識別 UART 的特定變體,因為原始的 8250 不會儲存透過此暫存器傳送給它的資料。由於這種晶片在 PC 設計中幾乎不再使用(這些公司使用更先進的晶片,如 16550),你不會在大多數現代 PC 類平臺中找到這種“錯誤”。下面將詳細介紹如何透過軟體識別你的計算機中使用的 UART 晶片,以及每個序列埠。
UART 的軟體識別
[edit | edit source]就像可以透過軟體例程識別計算機系統中的許多元件一樣,也可以檢測到計算機上使用的 UART 的版本或變體。這是因為 UART 晶片的每個不同版本都具有一些獨特的特性,如果進行排除法,就可以識別出您正在使用的版本。如果您嘗試提高序列 I/O 例程的效能,瞭解是否有可用於傳輸和傳送資訊的緩衝區,以及更好地瞭解 PC 上的裝置,這些資訊可能很有用。
例如,您可以透過判斷 Scratch 暫存器是否工作來確定 UART 的版本。在最初的 8250 和 8250A 晶片中,這些晶片模型的設計存在缺陷,導致 Scratch 暫存器無法正常工作。如果您向該暫存器寫入一些資料,然後發現數據發生改變,則說明您計算機中的 UART 是這兩種晶片模型之一。
另一個需要檢視的地方是 FIFO 控制暫存器。如果將該暫存器的位“0”設定為邏輯 **1**,則您試圖啟用 UART 上的 FIFO,而這些 FIFO 僅存在於該晶片的較新版本中。讀取位“6”和“7”將幫助您確定您使用的是 16550 還是 16550A 晶片。位“5”將幫助您確定晶片是否為 16750。
下面是一個完整的虛擬碼演算法,幫助您確定正在使用的晶片型別
Set the value "0xE7" to the FCR to test the status of the FIFO flags.
Read the value of the IIR to test for what flags actually got set.
If Bit 7 is set Then
If Bit 6 is set Then
If Bit 5 is set Then
UART is 16750
Else
UART is 16550A
End If
Else
UART is 16550
End If
Else you know the chip doesn't use FIFO, so we need to check the scratch register
Set some arbitrary value like 0x2A to the Scratch Register.
You don't want to use 0xFF or 0x00 as those might be returned by the Scratch Register instead for a false postive result.
Read the value of the Scratch Register
If the arbitrary value comes back identical
UART is 16450
Else
UART is 8250
End If
End If
用 Pascal 語言編寫時,上面的演算法最終看起來像這樣
const
COM1_Addr = $3F8;
FCR = 2;
IIR = 2;
SCR = 7;
function IdentifyUART: String;
var
Test: Byte;
begin
Port[COM1_Addr + FCR] := $E7;
Test := Port[COM1_Addr + IIR];
if (Test and $80) > 0 then
if (Test and $40) > 0 then
if (Test and $20) > 0 then
IdentifyUART := '16750'
else
IdentifyUART := '16550A'
else
IdentifyUART := '16550'
else begin
Port[COM1_Addr + SCR] := $2A;
if Port[COM1_Addr + SCR] = $2A then
IdentifyUART := '16450'
else
IdentifyUART := '8250';
end;
end;
我們仍然沒有區分 8250、8250A 或 8250B;但在大多數當前的計算機上,這樣做幾乎毫無意義,因為這些晶片早已過時,不太可能再找到。
可以使用類似的過程來確定計算機的 CPU,但超出了本書的範圍。
雖然 8250 是桌上型電腦中最流行的 UART,但其他流行的 UART 包括
- Atmel AVR 內部的 UART:... 嵌入式系統/Atmel_AVR#Serial_Communication
- Microchip PIC 內部的 UART:"Microchip AN774:使用 PICmicro® USART 的非同步通訊"
- 蘋果 Macintosh 內部的 UART:...
- "位敲擊" UART:... [1]
序列埠程式設計: 簡介和 OSI 網路模型 -- RS-232 線路和連線 -- 典型的 RS232 硬體配置 -- 8250 UART -- DOS -- MAX232 驅動器/接收器系列 -- Windows 中的 TAPI 通訊 -- Linux 和 Unix -- Java -- Hayes 相容調變解調器和 AT 命令 -- 通用序列匯流排 (USB) -- 形成資料包 -- 錯誤校正方法 -- 雙向通訊 -- 資料包恢復方法 -- 序列資料網路 -- 實際應用開發 -- 序列連線上的 IP