跳轉到內容

Mizar32/UART

來自華夏公益教科書,開放的書籍,開放的世界

通用非同步收發器電路或簡稱為 UART(發音為“you art”)是用於序列通訊的更常見的介面之一。某些計算機,例如 IBM PC,使用稱為 UART 的積體電路將字元轉換為非同步序列形式,反之亦然。所謂“序列”,是指資料一次傳輸一位。

該電路是推薦標準 - 232(簡稱 RS-232)的基礎,該標準還定義了 IBM PC 中的物理外部 COM 埠。RS-232 的最新修訂版是 EIA RS-232,該版本於 1997 年完成。

近年來,PC 已經不再使用 RS-232 埠(可能只在外部),取而代之的是 USB,但 RS-232 仍在廣泛使用,包括許多變體,從工業中使用的 RS-485 到空間站中使用的 SpaceWire。在更簡單的形式中,它目前在嵌入式行業中被廣泛採用。

為了更好地瞭解 UART 電路和 RS-232,讓我們回顧一下過去。序列傳輸的歷史非常悠久,它起源於第一臺電傳打字機(TTY)。電傳打字機從 1914 年開始使用,直到最近才停止使用,最後一家制造 TTY 的公司於 1990 年倒閉,而它們最後一次使用據報道是在最近幾年用於航空公司公告,大多數術語都來自 TTY 世界。例如,Mark 和 Space 是描述電傳打字機電路中邏輯電平的術語。波特率或符號率是序列連線的速度,它基於機電電傳打字機的速率的倍數。

如今,儘管它在個人計算機中變得越來越少見,但它仍然是微控制器 (MCU) 上最常見的外設之一,用於與外部裝置和系統通訊,與板載序列裝置通訊或在板之間、盒之間或嵌入式板之間以及具有 RS-232 埠的 PC 之間建立連線。

RS-232 全球範圍內指定

  • 佈線
  • 訊號電壓
  • 訊號功能
  • 訊號時序
  • 資訊交換協議
  • UART 配置

從微控制器角度來看,我們將現在考察以上所有要點。

UART 是一個全雙工通訊通道,在非同步模式下,每條線路在主要功能方面都獨立於其他線路。RX 引腳可以接收資料,而不管 TX 引腳的活動如何,反之亦然。

通常,來自微控制器的 UART 線路在 3 線配置(未實現流控制)中標記為 TX、RX、GND(分別代表傳送、接收和接地),以及 5 線 TX、RX、GND、RTS、CTS,其中 RTS 代表請求傳送,CTS 代表清除傳送。

該訊號被描述為正電壓,用於傳達邏輯值 0,稱為“Mark”,負電壓用於傳達邏輯值 1,稱為“Space”。

這些訊號的電流和電壓通常太弱,無法從微控制器中輸出,標準規定應使用 ±5V 到 ±15V 的電壓,並且電線長度為 10 米,那麼如何與輸出 ±3V 且電流非常低的 UART 線路進行介面?

Mizar32 的序列 UART 附加板上的 MAX232 晶片是一個 TTL 到 RS-232 電平轉換器,它將電壓從 ±3V 提升到 ±15V。

訊號時序以波特率來衡量,在二進位制通訊中,每一位波特率對應於每秒一位,因此在 9600 波特率下,我們有 9600 位/秒,描述每一位的時間範圍為 104 μs 1/9600。通常,時鐘的頻率將是波特率的 16 倍,以便接收器可以進行中心取樣。

資料交換協議非常簡單。

資料包以起始位開頭,該起始位是邏輯 0,首先被髮送/接收。在軟體方面,該位很重要,因為我們可以輪詢 RX 引腳以查詢該位,以指示正在接收一個數據包。資料位或有效載荷可能包含奇偶校驗位,也可能不包含奇偶校驗位,然後資料包以邏輯 1 結尾,即一個或兩個停止位。

0 start)
XXXXXXX (7 或 8 位資料)
X 1 位可選奇偶校驗位
1 一個或兩個停止位

請注意,位元組的最低有效位首先發送,而我們通常將 LSB 寫在右側,因此我們應該從右到左讀取。

關於配置引數,常見配置(通常儲存在暫存器中)是 9600/8n1,這意味著對於序列埠:9600 波特率,8 位資料,無奇偶校驗位,1 個停止位。顯然,兩個 UART 應以相同的方式配置才能進行通訊。

硬體檢視

[編輯 | 編輯原始碼]

Mizar32 在匯流排聯結器上有兩個可用的序列埠,UART0 在右側總線上,UART1 在左側總線上。UART0 只有資料 (TXD, RXD) 和硬體流控制 (CTS, RTS) 訊號,而 UART1 還具有調變解調器控制訊號 (DSR, DTR, DCD, RI)。

在 Atmel 文件中,這些被稱為“USART”,因為它們也可以程式設計為同步模式以用作額外的 SPI 埠。

匯流排引腳
訊號 GPIO 匯流排引腳 eLua 名稱 PicoLisp
UART0_RX PA0 BUS4 引腳 3 pio.PA_0 'PA_0
UART0_TX PA1 BUS4 引腳 4 pio.PA_1 'PA_1
UART0_RTS PA3 BUS4 引腳 5 pio.PA_3 'PA_3
UART0_CTS PA4 BUS4 引腳 6 pio.PA_4 'PA_4
UART1_RX PA5 BUS3 引腳 3 pio.PA_5 'PA_5
UART1_TX PA6 BUS3 引腳 4 pio.PA_6 'PA_6
UART1_DCD PB23 BUS3 引腳 5 pio.PB_23 'PB_23
UART1_DSR PB24 BUS3 引腳 6 pio.PB_24 'PB_24
UART1_DTR PB25 BUS3 引腳 7 pio.PB_25 'PB_25
UART1_RI PB26 BUS3 引腳 8 pio.PB_26 'PB_26
UART1_CTS PA9 BUS3 引腳 9 pio.PA_9 'PA_9
UART1_RTS PA8 BUS3 引腳 10 pio.PA_8 'PA_8

RS232/RS485 序列附加板

[編輯 | 編輯原始碼]

附加的串行板有兩個組開關,DIP1 和 DIP2,用於選擇 RS232 和 RS485 模式。

RS232 模式

[編輯 | 編輯原始碼]

如果 DIP1 的所有開關都向上,DIP2 的所有開關都向下,它將匯流排訊號轉換為其母 DB9 聯結器 J7 上的 RS232 電平。該聯結器被配置為 DCE 裝置,這與 PC 序列埠相反,因此與 PC 通訊的電纜應在兩端連線相同的引腳;不需要空閒調變解調器電纜。將其他 DCE 裝置(如調變解調器或 GPS 接收器)連線到它,需要交換 TX 和 RX,例如使用空閒調變解調器電纜。

請注意,在序列埠的 1.1.1 版本中,CTS 和 RTS 引腳錯誤地交換了位置,因此要在這裡獲得正確的連線,您需要修改板或電纜。但是,eLua 中的硬體流控制尚不可用,因此它沒有區別;請參閱 問題 #29

訊號 匯流排引腳 UART 模組
rev. 1.0
DB-9F 引腳
UART 模組
rev. 1.1.1
DB-9F 引腳
UART0_RX P5 引腳 3 引腳 3 (輸入) 引腳 3 (輸入)
UART0_TX P5 引腳 4 引腳 2 (輸出) 引腳 2 (輸出)
UART0_RTS P5 引腳 5 引腳 8 (輸出) 引腳 7 (輸出)
UART0_CTS P5 引腳 6 引腳 7 (輸入) 引腳 8 (輸入)
GND 各種 引腳 5 引腳 5

RS485 模式

[編輯 | 編輯原始碼]

如果 DIP1 的所有開關都向下,DIP2 的所有開關都向上,則板的 DB9 聯結器將被停用,並且 RS485 訊號將出現在四個螺絲端子上。

此介面允許最多 32 個 RS485 裝置連線到同一條電線上,電纜長度最長可達 1200 米,速率為 100 kbit/秒。

目前,Alcor6L 不支援 RS485 模式;請參閱 問題 #77

軟體檢視

[編輯 | 編輯原始碼]

簡單 I/O

[編輯 | 編輯原始碼]

取決於您使用的韌體,UART0 可能用於 Lua 控制檯(配置為 115200 波特率,8 個數據位,1 個停止位,無奇偶校驗),並且 Lua 的預設輸入和輸出檔案是控制檯,因此像“print()”和“io.write()”這樣的函式可用於在序列埠上輸出字元(分別帶和不帶尾隨 CR-LF 換行符)。

在 eLua 中

-- Greet the user
io.write( "What's your name? " )     -- Issue a prompt (with no trailing newline)
name = io.read()                     -- Read a line of input and store it in "name"
print( "Hello, " .. name .. "!" )    -- Salute them

在 PicoLisp 中

# Greet the user
(prinl "What's your name? ")
(prinl "Hello, " (setq name (read)) "!")

低階 I/O

[編輯 | 編輯原始碼]

UART0 也可以使用更低階的 uart Alcor6L 模組訪問(而 UART1 必須使用該模組訪問),該模組可以更深入地控制 UART 的行為。

以下示例在 UART0 上設定不同的波特率,並在收到回覆字元之前以每秒兩次的頻率吐出一個提示字元。為此,它使用了 setup 函式和 getchar 函式的可選超時引數。

在 eLua 中

-- Prompt a 9600 baud serial device until we receive a character in reply
uartid = 0          -- Which UART should we be talking on?
timeout = 500000    -- Prompt once every half second
timerid = 0         -- Use timer 0 to measure the timeout
prompt = "U"        -- The prompt character (0x55 : binary 01010101)

uart.setup( uartid, 9600, 8, 0, 1 )    -- Configure the UART
repeat
  uart.write( uartid, prompt )
  reply = uart.getchar( uartid, timeout, timerid )
until reply ~= ""

在 PicoLisp 中

# Prompt a 9600 baud serial device until we receive
# a character in reply

(setq
   uartid 0       # Which UART should we be talking on?
   timeout 500000 # Prompt once every half second
   timerid 0      # Use timer 0 to measure the timeout
   prompt "U" )   # The prompt character (0x55 : binary 01010101)

(de get-char-uart ()
   (uart-getchar uartid timeout timerid) )

# Get a character from UART
(setq reply (get-char-uart))

(until (= "" reply)
  (uart-write uartid prompt)
  (setq reply (get-char-uart)) )

請注意:您也可以從我們在 github 上的示例儲存庫中下載上述程式碼 uart-io.l

硬體流控制

[編輯 | 編輯原始碼]

請注意,使用

語言 程式碼
eLua uart.set_flow_control(uartid, uart.FLOW_RTS + uart.FLOW_CTS)
PicoLisp (uart-set-flow-control uartid (+ *uart-flow-rts* *uart-flow-cts*) )

啟用硬體流控制目前還無法實現。請參閱 問題 #29

輸入緩衝區

[編輯 | 編輯原始碼]

當 UART 接收一個字元時,它會記住該字元,直到您使用 getchar 請求它的值。但是,如果在您讀取第一個字元之前第二個字元到達,第一個字元將被遺忘。

您可以透過啟用 UART 緩衝區來解決這個問題,例如

語言 程式碼
eLua uart.setup(1, 115200, 8, 0, 1); uart.set_buffer(1, 1024);
PicoLisp (uart-setup 1 115200 8 0 1) (uart-set-buffer 1 1024)

上述程式碼配置了 UART 1 併為其提供了一個輸入緩衝區。這將允許 UART 接收多達 1024 個字元並記住所有這些字元,即使您尚未讀取第一個字元(第 1025 個字元將引發錯誤訊息並被遺忘)。

UART 緩衝區大小必須是 2 的冪,即 1、2、4、8、16 等等,最大為 32768 個字元。

一些韌體將 UART0 用作 Alcor6L 控制檯。在這種情況下,該 UART 上始終啟用緩衝區。

USB CDC 序列埠

[編輯 | 編輯原始碼]

從 2013 年釋出版開始,較新的韌體包含在 USB 介面上模擬另一個序列埠的軟體。您將 Mizar32 連線到您的 PC 上,PC 上就會出現一個新的序列埠。在 Linux 下,它被稱為 /dev/ttyACM0,而在 Windows 下,它顯示為一個新的“USB 序列埠”。

通常,這個虛擬序列埠用作 eLua 控制檯,傳送 eLua 輸出和錯誤訊息,並接收來自使用者的鍵盤輸入。但是,您可以透過在 http://builder.simplemachines.it 編譯自己的韌體來將控制檯輸出傳送到其他地方,並且您可以透過將序列埠號指定為 176 來與之對話。 eLua 的低階 uart.*() 函式。

它與物理序列埠略有不同,因為它

  • 比最快的 RS232 序列埠快十倍以上;
  • 它始終實現流控制,確保您永遠不會因溢位而丟失任何輸出或輸入,但是如果您的程式產生輸出而沒有 PC 連線到 USB 埠,您的程式將在輸出 1 到 2 KB 後凍結;
  • 一些設定(如波特率和停止位)沒有區別,因為訊號不會透過 RS232 線路傳輸;
  • 我不知道 Lua 中斷是否在 USB 序列埠上工作。

請注意:PicoLisp 目前不支援中斷處理。請參閱 問題 #12。但是,您可以在 eLua 中使用中斷。

當在 UART 上啟用輸入緩衝區時,每當接收一個字元時都會生成一箇中斷。該中斷會將字元儲存在您請求的緩衝區中,直到您的程式準備好讀取它。

如果您使用具有 Lua 中斷的韌體(包含在 20120123 elua 0.8 版本的 Mizar A 和 B 韌體中),您也可以安排自己的程式碼段在每次接收字元時被呼叫。

以下示例程式碼在每次接收字元時快速閃爍板載 LED

-- Test UART interrupts handled in Lua.
-- Should flash the onboard LED each time a character is received.

led = pio.PB_29         -- Which PIO pin is the LED connected to?

function uart_handler( resnum )
  -- flash the onboard LED
  pio.pin.setlow( led )
  for i=1,10000 do end  -- for about 1/100th of a second
  pio.pin.sethigh( led )
end

pio.pin.sethigh( led )    -- off
pio.pin.setdir( pio.OUTPUT, led )

uart.setup( 0, 115200, 8, uart.PAR_NONE, 1 )
uart.set_buffer( 0, 1024 )  -- buffer must be enabled for UART IRQs to happen

-- tell eLua which function it should call every time the UART receives
cpu.set_int_handler( cpu.INT_UART_RX, uart_handler )
-- and enable that Lua interrupt
cpu.sei( cpu.INT_UART_RX, 0 )

-- Wait for about ten seconds while the test runs
for i=1,10000000 do end

-- disable the Lua interrupt
cpu.cli( cpu.INT_UART_RX, 0 )
-- and remove our handler function
cpu.set_int_handler( cpu.INT_UART_RX, nil )

字元在 Lua 中斷例程被呼叫之前從 UART 接收並放置到緩衝區中,因此您可以在 Lua 中斷例程中使用 uart.getchar( 0, 0 ) 讀取它,並立即對其進行操作。

波特率精度

[編輯 | 編輯原始碼]

下表列出了 eLua 為最常用的波特率設定的實際波特率

想要 獲取 錯誤
300 300 0%
600 600 0%
1200 1200 0%
2400 2400 0%
4800 4799 -0.02%
9600 9604 +0.04%
19200 19186 -0.07%
31250 31250 0%
38400 38372 -0.07%
57600 57692 +0.07%
115200 114583 -0.5%

劫持串行板的 TX LED

[編輯 | 編輯原始碼]

如果 UART0 未使用,則可以透過將 pio.PA_1 用作通用的 Mizar32/PIO 輸出,來切換串行板上的 LED:低輸出值會關閉該 LED,而高輸出值會開啟該 LED。

在 eLua 中

-- Turn the serial board's TX LED on (a low output lights the LED)
txled = pio.PA_1
pio.pin.setlow( txled )                -- Prepare "off" as the output value
pio.pin.setdir( pio.OUTPUT, txled )    -- Make the pin a GPIO output, disabling serial port 0

在 PicoLisp 中

# Turn the serial board's TX LED on (a low output lights the LED)
(setq txled 'PA_1)
(pio-pin-setlow txled)              # Prepare "off" as the output value
(pio-pin-setdir *pio-output* txled) # Make the pin a GPIO output, disabling serial port 0

進一步閱讀

[編輯 | 編輯原始碼]
華夏公益教科書