序列程式設計/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 個引腳專門用於與晶片通訊。確切的細節因晶片設計和其他因素而異,這些因素過於詳細,不適合目前的討論,但總體理論相當簡單。
8 個引腳代表一個 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 埠“對映”到什麼裝置。我們將在稍後深入瞭解如何識別用於序列通訊的 I/O 埠的更多細節。最後,我們開始編寫一些軟體,還有更多內容。
x86 埠 I/O 擴充套件
[edit | edit source]8088 CPU 和 8086 之間存在一些差異。對軟體開發影響最大的一個區別是,8086 可以訪問 65536 個不同的 I/O 埠,而不是隻有 256 個埠 I/O 地址。但是,計算機配置可能使用少於 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 代表中斷請求。總共有 15 個不同的硬體中斷。在你認為我不知道如何計數或做數學運算之前,我們在這裡需要上一節歷史課,我們將在繼續討論 8259 晶片時完成。當原始的 IBM-PC 構建時,它只有 8 個 IRQ,分別標記為 IRQ 0 到 IRQ 7。當時人們認為這足以滿足幾乎所有將要安裝在 PC 上的東西,但很快人們意識到這遠遠不夠滿足當時正在新增的所有東西。當 IBM-PC/AT 製造出來時(第一個搭載 80286 CPU 的計算機,以及許多如今在 PC 上常見的增強功能),人們決定使用兩個相同的晶片而不是一個 8259 晶片,並將它們“連結”在一起,以便將中斷數量從 8 個擴充套件到 15 個。為了完成這項任務,必須犧牲一個 IRQ,那就是 IRQ 2。
重點是,如果一個裝置想要通知 CPU 它有一些資料已準備好供 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 暫存器
[edit | edit source]8259 有幾個與 I/O 埠地址相關的“暫存器”。當我們接觸到 8250 晶片時,我們將進一步瞭解這個概念。對於典型的 PC 計算機系統,以下是與 8259 相關的典型主要埠地址
| 暫存器名稱 | I/O 埠 |
|---|---|
| 主中斷控制器 | $0020 |
| 從中斷控制器 | $00A0 |
這個主要埠地址是我們將在軟體中用來直接與 8259 晶片通訊的。可以透過這些 I/O 埠地址向該晶片傳送許多命令,但就我們的目的而言,我們實際上不需要處理它們。大多數這些命令用於由計算機的基本輸入輸出系統 (BIOS) 完成計算機裝置的初始設定和配置,除非您從頭開始重寫 BIOS,否則您真的不必擔心這一點。此外,當您在此級別處理裝置時,每臺計算機的行為都略有不同,因此這是計算機制造商更應該擔心的事情,而不是應用程式程式設計師應該處理的事情,這正是 BIOS 軟體被編寫的根本原因。
請記住,這是大多數 PC 相容型計算機系統的“典型”埠 I/O 地址,並且可能會因製造商試圖實現的目標而異。通常您不必擔心此級別的相容性問題,但當我們接觸到序列埠的埠 I/O 地址時,這將成為一個更大的問題。
裝置暫存器
[edit | edit source]我將在這裡花一點時間來解釋“暫存器”一詞的含義。當您在此級別處理裝置時,設計裝置的電氣工程師會提到改變裝置配置的暫存器。這可以在幾個抽象級別發生,因此我想消除一些混淆。
暫存器只是一個小的 RAM 部分,裝置可以直接操作。在像 8086 或奔騰這樣的 CPU 中,這些是用於直接執行數學運算(例如將兩個數字加在一起)的記憶體區域。這些通常被稱為 AX、SP 等。典型的 CPU 上只有很少的暫存器,因為對這些暫存器的訪問直接編碼到基本的機器級指令中。
當我們談論裝置暫存器時,請記住這些不是 CPU 暫存器,而是裝置本身的記憶體區域。這些通常被設計成連線到埠 I/O 記憶體,因此當您寫入或讀取埠 I/O 地址時,您實際上是在直接訪問裝置暫存器。有時會有進一步的抽象級別,您將有一個埠 I/O 地址指示您要更改哪個暫存器,另一個埠 I/O 地址將包含您要傳送到該暫存器的資料。您如何處理裝置取決於它有多複雜以及您將做什麼。
從某種意義上說,它們是暫存器,但請記住,這些裝置中的每一個都可以被認為是一個完整的計算機,而您所做的只是建立它將如何與主 CPU 通訊。不要在這裡停滯不前,不要把這些與 CPU 暫存器混淆。
ISR 清理
[edit | edit source]在使用中斷控制器時,您必須定期進行互動的一個領域是告知 8259 PIC 控制器中斷服務例程已完成。當您的軟體執行中斷處理程式時,CPU 沒有自動的方法向 8259 晶片發出已完成的訊號,因此 PIC 中的特定“暫存器”需要被設定為讓下一個中斷處理程式能夠訪問計算機系統。完成此操作的典型軟體如下所示
Port[$20] := $20;
這將傳送一個名為“中斷結束”的命令,通常簡寫為“EOI”。還有其他命令可以傳送到此暫存器,但就我們的目的而言,這是我們唯一需要關心的命令。
現在這將清除“主”PIC,但是如果您使用的是在“從”PIC 上觸發的裝置,您還需要告知該晶片中斷服務已完成。這意味著您還需要像這樣向該晶片傳送“EOI”
Port[$A0] := $20; Port[$20] := $20;
您可以做其他事情來使您的計算機系統順利執行,但現在讓我們保持簡單。
PIC 裝置遮蔽
[edit | edit source]在我們離開 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;
這隻會啟用所有內容。這可能不是一件好事,但您將不得不根據您正在使用的東西進行實驗。儘量不要使用這種捷徑,因為這不僅是懶惰程式設計師的標誌,而且還會產生副作用,您的計算機可能表現出與您預期不同的行為。如果您在此級別處理計算機,目標是儘可能少地更改,以免對您使用的任何其他軟體造成損害。
序列 COM 埠記憶體和 I/O 分配
[edit | edit source]現在我們已經瞭解了 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 暫存器
[edit | edit source]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 的頻率執行。換句話說,每秒 115 千次,一個計數器向下計數以確定何時傳送下一個位。在設計過程中的某個時候,人們預計可能會使用其他頻率來使 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 訪問調變解調器之前將其恢復為“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”。
中斷識別暫存器
[edit | edit source]偏移量:+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 緩衝區可以使用下面列出的暫存器開啟和關閉。
FIFO 控制暫存器
[edit | edit source]偏移量:+2。這是一個相對“新的”暫存器,它不是原始 8250 UART 實現的一部分。此暫存器的目的是控制晶片上先進先出 (FIFO) 緩衝區的工作方式,並幫助您微調應用程式中的效能。這甚至可以讓您“開啟”或“關閉”FIFO。
請記住,這是一個“只寫”暫存器。試圖讀取內容只會得到中斷識別暫存器 (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 | 啟用 FIFOs | |||
將“0”寫入位 0 將停用 FIFOs,本質上將 UART 轉換為 8250 相容模式。實際上,這也使該暫存器中其餘設定變得無用。如果您在此處寫入“0”,它還會阻止 FIFOs 傳送或接收資料,因此透過序列資料埠傳送的任何資料在此設定更改後都可能被加擾。僅當您嘗試重置序列通訊協議並清除應用程式軟體中可能存在的任何工作緩衝區時,才建議停用 FIFOs。一些文件建議將此位設定為“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 的行為更像 16550,只有一個 16 位元組 FIFO。
位 6 和 7 描述觸發閾值。這是儲存在 FIFO 中的字元數,在觸發中斷之前,該中斷將讓您知道應從 FIFO 中刪除資料。如果您預計將透過序列資料鏈路傳送大量資料,您可能需要增加緩衝區的大小。FIFO 緩衝區大小的觸發值最大值較小的原因是,一些軟體可能需要一段時間才能訪問 UART 並檢索資料。請記住,當 FIFO 充滿時,您將開始丟失來自 FIFO 的資料,因此務必確保在達到此閾值後已檢索到資料。如果您在嘗試檢索 UART 資料時遇到軟體計時問題,您可能需要降低閾值。在閾值設定為 1 位元組的最極端情況下,它將基本像基本的 8250 一樣工作,但增加了在您沒有機會立即獲取所有字元的情況下,一些字元可能會被捕獲到緩衝區中的可靠性。
線路控制暫存器
[edit | edit source]偏移量:+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 位資料,一些 TELEX 裝置也是如此。早期的 ASCII 電傳打字機終端只使用 7 位資料,實際上,這種傳統一直保留在 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。
調變解調器控制暫存器
[edit | edit source]偏移量:+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 根據 FIFO 的當前狀態直接控制 RTS 和 DTS 的狀態,以進行硬體字元流控制。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 的字元。
中斷斷開(第 4 位)在序列資料輸入線在至少與整個序列資料“字”一樣長的時間內接收“0”位時變為邏輯狀態“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 的所有內容為止。
偏移量:+6。該暫存器是另一個只讀暫存器,用於向您的軟體通告調變解調器的當前狀態。以這種方式訪問的調變解調器可以是外部調變解調器,也可以是使用 UART 作為與計算機介面的內部調變解調器。
| 位 | 備註 |
|---|---|
| 7 | 載波檢測 |
| 6 | 振鈴指示 |
| 5 | 資料裝置準備就緒 |
| 4 | 清除傳送 |
| 3 | 增量資料載波檢測 |
| 2 | 後沿振鈴指示 |
| 1 | 增量資料裝置準備就緒 |
| 0 | 增量清除傳送 |
第 7 位和第 6 位與調變解調器活動直接相關。當調變解調器“連線”到另一個調變解調器時,載波檢測將保持邏輯狀態“1”。當它變為邏輯狀態“0”時,您可以假設電話連線已斷開。振鈴指示位直接連線到 RS-232 電線,該電線也標記為“RI”或振鈴指示。通常,當檢測到電話線上的“振鈴電壓”時,此位將變為邏輯狀態“1”,就像傳統電話會響起以通知您有人在嘗試呼叫您一樣。
當我們進入 AT 調變解調器命令部分時,將顯示其他方法來通知您有關此資訊以及有關調變解調器狀態的其他資訊,並且這些資訊將作為正常序列資料流中的字元傳送,而不是特殊電線。實際上,這些額外的位沒什麼用,但從一開始就是規範的一部分,並且調變解調器設計人員比較容易實現。但是,它可能是一種有效地傳送一些附加資訊或允許使用 UART 的軟體設計人員從其他裝置獲取一些邏輯位訊號以用於其他目的的方式。
“資料裝置準備就緒”和“清除傳送”位(第 4 位和第 5 位)直接位於 RS-232 電纜上,並且與“請求傳送”和“資料終端準備就緒”匹配的電線透過“調變解調器控制暫存器 (MCR) 傳輸。使用兩個暫存器中的這四個位,您可以執行“硬體流控制”,您可以在其中向另一個裝置發出訊號,指示它何時傳送更多資料,或在您嘗試處理資訊時暫停傳送資料。在後面的模組中,當我們進入資料流控制時,將對該主題進行更多介紹。
關於“delta”位(位 0、1、2 和 3)的說明。在本例中,“delta”表示變化,如某個位的狀態變化。這源於其他科學領域,例如火箭科學,其中 delta-vee 表示速度變化。就本暫存器而言,如果與之關聯的位(例如 Delta 資料載波檢測與載波檢測)從您上次訪問該調變解調器狀態暫存器時更改了邏輯狀態,則這些位中的每一個在您下次訪問該暫存器時將為邏輯“1”。尾隨邊沿振鈴指示器與其他位大體相同,只是當“振鈴指示器”位從邏輯“1”變為邏輯“0”狀態時,它才處於邏輯“1”狀態。這些知識實際上並沒有什麼實際用處,但有一些軟體試圖利用這些位,並根據這些位對從 UART 接收到的資料進行一些操作。如果您忽略這 4 位,您仍然可以製作非常健壯的序列通訊軟體。
偏移量:+7。Scratch Register 是一個有趣的謎題。設計人員為了將大量暫存器擠進所有其他 I/O 埠地址,而留了一個額外的“暫存器”,他們不知道該怎麼用。請記住,在處理計算機體系結構時,處理 2 的冪會更容易,因此他們“被迫”要定址 8 個 I/O 埠。允許另一個裝置使用這個額外的 I/O 埠會使主機板設計過於複雜。
在 8250 UART 的某些變體中,寫入此 scratch register 的任何資料都可以在您讀取此暫存器的 I/O 埠時供軟體使用。實際上,這為您提供了一個額外的位元組“記憶體”,您可以在應用程式中以任何您認為有用的方式使用它。除了病毒作者(也許我不應該給出任何想法),這個暫存器實際上沒有很好的用途。有限的用途是,您可以使用這個暫存器來識別 UART 的特定變體,因為原始的 8250 不會儲存透過這個暫存器傳送給它的資料。由於該晶片在 PC 設計中幾乎不再使用(這些公司正在使用更先進的晶片,如 16550),因此您不會在大多數現代 PC 型別平臺中發現這個“bug”。下面將詳細介紹如何透過軟體識別計算機中使用的 UART 晶片,以及每個序列埠。
就像可以透過軟體例程識別計算機系統中的許多元件一樣,也可以檢測到計算機上找到的 UART 的版本或變體。這是因為 UART 晶片的每個不同版本都有一些獨特的特性,如果您進行一個排除過程,您就可以識別出您正在處理的版本。如果您試圖提高序列 I/O 例程的效能,瞭解是否有可用於傳輸和傳送資訊的緩衝區,以及更好地瞭解 PC 上的裝置,這些資訊可能很有用。
您可以確定 UART 版本的一個例子是 Scratch Register 是否工作。在第一個 8250 和 8250A 晶片中,這些晶片模型的設計存在一個缺陷,導致 Scratch Register 不工作。如果您向這個暫存器寫入一些資料,並且它返回的是更改後的資料,那麼您就知道計算機中的 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,但這超出了本書的範圍。
- 中斷程式設計的歷史
- 8259 晶片資訊,解釋了其他暫存器 (失效連結?)
- 序列/RS232 埠的介面
雖然 8250 是臺式計算機上最流行的 UART,但其他流行的 UART 包括
- Atmel AVR 中的 UART:... 嵌入式系統/Atmel_AVR#Serial_Communication
- Microchip PIC 中的 UART:"Microchip AN774:使用 PICmicro® USART 進行非同步通訊"
- Apple Macintosh 中的 UART:...
- "位邦定" UART:... [1]
序列程式設計: 簡介和 OSI 網路模型 -- RS-232 接線和連線 -- 典型的 RS232 硬體配置 -- 8250 UART -- DOS -- MAX232 驅動器/接收器系列 -- Windows 中的 TAPI 通訊 -- Linux 和 Unix -- Java -- Hayes 相容調變解調器和 AT 命令 -- 通用序列匯流排 (USB) -- 形成資料包 -- 糾錯方法 -- 雙向通訊 -- 資料包恢復方法 -- 序列資料網路 -- 實用應用開發 -- 序列連線上的 IP