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
|
附加的串行板有两个组开关,DIP1 和 DIP2,用于选择 RS232 和 RS485 模式。
如果 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 |
如果 DIP1 的所有开关都向下,DIP2 的所有开关都向上,则板的 DB9 连接器将被禁用,并且 RS485 信号将出现在四个螺丝端子上。
此接口允许最多 32 个 RS485 设备连接到同一条电线上,电缆长度最长可达 1200 米,速率为 100 kbit/秒。
目前,Alcor6L 不支持 RS485 模式;请参阅 问题 #77。
取决于您使用的固件,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)) "!")
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 上始终启用缓冲区。
从 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% |
如果 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
- 有关 Atmel AT32UC3A 数据手册 第 26 章:通用同步/异步接收器/发送器 (USART) 的信息。
- 有关所有
uart.*()
函数的详细信息,请参阅 eLua 手册的 UART 部分。 - 串行编程