跳转到内容

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

进一步阅读

[编辑 | 编辑源代码]
华夏公益教科书