接口¶
考试范围
本部分会考察简单的接口编程方法,因此需要对各接口芯片的端口及其功能有一定的了解
最后一颗数模转换芯片不考察编程,只需了解一些技术指标的计算
这一部分最理想的学习方式是读手册,我会在每个接口的章节开头附上其对应的手册
8086 微处理器¶
手册: Intel 8086/8088 微处理器手册, 一个简化的 Cheat Sheet.
引脚描述¶
8086 的数据总线为 16 位,地址总线为 20 位,对应引脚如下:
- AD0 - AD15: 地址与数据总线 (共用部分)
 - A16 - A19: 地址总线剩余的部分
 
其他引脚功能:
- CLK: 时钟信号
 - INTR: 中断请求线
 - \(\overline{\text{BHE}}\): 总线高字节使能 (Bus High Enable), 指示总线高八位数据有效
 
工作模式¶
Maximum Mode 最大模式: 最大模式适用于系统存在多个微处理器的情况,8086 作为主处理器,而其他处理器作为协助处理器,比如 8087 浮点数协处理器可以为 8086 提供浮点运算功能。此时,8086 的通信都是请求式的,需要等待 8288 总线芯片的仲裁信号,控制信号由总线控制器 8288 产生。
Minimum Mode 最小模式: 最小模式适用于系统中只有 8086 一个微处理器的情况,此时 8086 可以直接发送控制信号,并对内存和外部设备有完全的管理权限。
内存组织方式¶
8086 和 8088 的 bank 以 1 Byte 为宽度来组织内存。8088 的数据总线是 8 位,因此只需要一个 bank 来管理,无需 \(\overline{\text{BHE}}\) 信号。然而,8086 的数据总线是 16 位,内存需要分为奇偶两个 bank,并通过 \(\overline{\text{BHE}}\) 信号来指示高 8 位 (奇 bank) 是否有效。
使用数据线的不同情况:
- 
单字节 & 偶地址: \(D_7-D_0\)
 - 
单字节 & 奇地址 \(D_{15}-D_8\)
 - 
双字节 & 偶地址: \(D_{15}-D_0\)
 - 
双字节 & 奇地址: 此时内存未对齐,访问需要多一个周期。先读 \(D_{15}-D_8\),再读 \(D_7-D_0\)
 
82C55 可编程并行接口¶
手册: Intersil 提供的 82C55A 手册
器件构成¶
82C55 是一个 24 引脚的可编程并行接口,包含 3 个 8 位 I/O 端口,分别为 A, B, C。
这三个端口分为两组:
- Group A 由 A 端口和 C 的高 4 位 (\(\text{PC}_7-\text{PC}_4\)) 构成,可以工作在模式 0, 1, 2
 - Group B 由 B 端口和 C 的低 4 位 (\(\text{PC}_3-\text{PC}_0\)) 构成,可以工作在模式 0, 1
 
82C55 的引脚描述如下:
- \(\overline{\text{CS}}\): 芯片选择信号,低电平有效
 - \(\overline{\text{RD}}\): 读信号,低电平有效,表示读取数据或工作状态
 - \(\overline{\text{WR}}\): 写信号,低电平有效,表示写入数据或控制字
 - \(\text{A}_1-\text{A}_0\): 端口地址线,用于选择 Port A, Port B, Port C
 - \(\text{D}_7-\text{D}_0\): 数据总线
 - \(\text{PA}_7-\text{PA}_0\): Group A 的 8 位端口
 - \(\text{PB}_7-\text{PB}_0\): Group B 的 8 位端口
 - \(\text{PC}_7-\text{PC}_0\): Group C 的 8 位端口
 
端口 A 比端口 B 功能多的原因在于,在 82C55 内部,端口 A 由独占的输入 latch 和输出 latch / buffer, 而端口 B 只有输入 buffer 和共享的输入输出 latch / buffer.
控制字设置¶
82C55 的控制字为 8 位,但是可以控制两个功能,一是设置每个 Group 的工作模式,二是控制 Port C 每个引脚的输入输出方向,这两个控制功能通过最高位 \(D_7\) 来区分.
控制字 A - 模式设置¶
当 \(D_7\) 为 1 时,设置的是 Group A 和 Group B 的工作模式和输入输出方向. 注意只有 Group A 可以工作在模式 2, 因此 Group A 的工作模式需要有 2 位来表示.
控制字 B - Port C 设置¶
当 \(D_7\) 为 0 时,设置的是 Port C 的输出电平. \(D_0\) 表示的是设置 / 重置 (set / reset), 而 \(D_3-D_1\) 表示的是设置的引脚. 注意此时 \(D_6-D_4\)为 don't care.
运行方式¶
模式 0¶
模式 0 无握手信号,只有基本的输入输出,适用于简单的 Always On 的 I/O 操作。比如点亮 LED 灯,只 CPU 需要输出亮灯状态即可。模式 0 时接口功能如下:
- 两个 8 位端口 Port A, Port B 以及两个 4 位端口: Port C 的高 4 位和低 4 位
 - 每个端口均可以独立设置输入和输出
 - 输出有 latch 锁存输出状态,输入没有
 
例子: 控制步进电机
half-step 半步控制:
.stack 64h
.data
datacw  db 0000_0110b ;
        db 0000_0100b ; half-step
        db 0000_0011b ; 顺时针旋转位置
        db 0000_0010b ;
.code
    mov ax, @data
    mov ds, ax
    mov bx, offset datacw
    mov si, 0         ; 步进位置
    mov cx, 16        ; 步进次数
next_step:
    mov al, [bx][si]  ; 加载当前状态步进电机控制状态
    out 7, al         ; 输出到端口
    inc si
    cmp si, 4
    jb next_step
    mov si, 0         ; 重置步进位置
    loop next_step
    hlt
.exit
micro-step 微步控制: 用 cx 的最高位表示方向,后面 15 位表示步进多少步
    PORT EQU 40h ; 端口 A 地址
STEP PROC NEAR USES cx ax
    mov al, pos
    or  cx, cx                   ; 判断当前转子位置
    .IF !ZERO?                   ; 如果转子位置不为 0
        .IF !SIGN?
            .REPEAT
                rol al, 1        ; 向左旋转
                out port al
                call delay       ; 等待 1 ms
            .UNTILCXZ
        .ELSE
            and cx, 7FFFh
            .REPEAT
                ror al, 1        ; 向右旋转
                out port, al
                call delay       ; 等待 1 ms
            .UNTILCXZ
        .ENDIF
    .ENDIF
    mov pos, al
    ret
STEP ENDP
模式 1¶
模式 1 采用选通式握手信号,适用于需要 CPU 和外设之间的数据传输。比如键盘输入,CPU 需要读取键盘的输入。
模式 1 输入的工作时序:
- 设备通过 \(\overline{\text{STB}}\) 信号通知 82C55 数据准备好
 - 82C55 通过 IBF 信号告知设备当前输入缓冲已满,不要再传数据
 - 83C55 通过 INTR 给 CPU 发送中断,请求 CPU 读取数据
 - CPU 通过 \(\overline{\text{RD}}\) 信号读取数据
 
模式 1 输出的工作时序:
- CPU 通过 \(\overline{\text{WR}}\) 信号写入数据
 - 82C55 通过 \(\overline{\text{OBF}_\text{B}}\) 低电平告知设备输入缓冲已满,通知设备读取
 - 设备通过 \(\overline{\text{ACK}}\) 通知 82C55 已经读走数据
 - 82C55 向 CPU 发送中断,告知 CPU 可以继续写入数据
 
例子: 控制并口打印机
串口打印机每次接受 1 Byte ASCII 码,存储在 ah 寄存器中。
使用端口 B 进行并行输出,同时控制端口 C 的 \(\text{PC}_2\) 作为 \(\overline{\text{ACK}}\) 输入引脚,端口 C 的 \(\text{PC}_4\) 作为 \(\overline{\text{DS}}\) 输出引脚,代表数据选通信号:
BIT1  EQU 2
PORTC EQU 62H
PORTB EQU 61H
CMD   EQU 63H
PRINT PROC NEAR
    .REPEAT
        in   al, portc ; 读取 82C55 状态字
        test al, BIT1  ; 检测 OBF 标志位
    .UNTIL !ZERO?      ; 等待直到 OBF = 1,即输出缓冲区满
    mov al, ah         ; 待打印字符位于 ah
    out PORTB, al      ; 发送数据
    mov al, 8          ; 0000_1000, bit reset, 选择 (100)_2 = 4
    out CMD, al        ; 重置 PC4 为 0: 选通信号置 0
    mov al, 9          ; 0000_1001, bit set,   选择 (100)_2 = 4
    out CMD, al        ; 设置 PC4 为 1: 选通信号置 1
    RET
PRINT ENDP
TODO: 这里使用 PC4 的原因
模式 2¶
模式 2 则包含了双向总线操作,可以在不同时间做输入和输出,而模式 0 和 1 的输入输出方向是固定的。
BIT5  EQU 20h
PORTA EQU 62h
PORTC EQU 60h
READ PROC NEAR
    .REPEAT
        in   al, PORTC  ; 读入状态
        test al, BIT5   ; 检查 IBF 标志位
    .UNTIL !ZERO?       ; 一直等待,直到 IBF = 1,即输入缓冲区满
    in al, PORTA        ; 从数据端口读取
    ret
READ ENDP
8254 可编程定时器¶
手册: 8254 可编程定时器手册
器件构成¶
8254 是一个可编程定时器,其内部含有 3 个独立的计数器,每个计数器都是 16 位的,可以工作在不同的工作模式下。不过,8254 的数据总线只有 8 位,因此需要两次读写操作才能完成对 16 位计数器的操作。8254 芯片的内部结构与引脚描述如下:
- \(\overline{\text{CS}}\): 芯片选择信号, 低电平有效
 - \(\text{A}_1-\text{A}_0\): 端口选择,指定要操作的计数器
- 当 \(\text{A}_1-\text{A}_0 = 11\) 时,表示读写控制字
 
 - \(\overline{\text{RD}}\): 读信号, 低电平有效
 - \(\overline{\text{WR}}\): 写信号, 低电平有效
 - \(\text{D}_7-\text{D}_0\): 数据总线
 - \(\text{CLK}\), \(\text{GATE}\), \(\text{OUT}\): 时钟输入, 门控输入, 输出信号
 
编程方式¶
8254 单个计数器的内部结构如下 (控制字寄存器是共用的):
计数器寄存器是 16 位的 CE,其输入是两个 8 位的寄存器初值寄存器 \(\text{CR}_\text{M}\) 和 \(\text{CR}_\text{L}\),其输出又接入了两个 8 位的输出锁存器 \(\text{OL}_\text{M}\) 和 \(\text{OL}_\text{L}\). 这样一来,读计数器的值和写入计数初值的操作都不影响计数器的工作 (不过仍然建议使用门控信号暂停计数再操作).
控制字格式¶
8254 控制字的格式如下:
- \(\text{SC}_1-\text{SC}_0\): 选择计数器 (Select Counter)
- 当 \(\text{SC}_1-\text{SC}_0 = 11\) 时, 表示回读计数器状态
 
 - \(\text{RW}_1-\text{RW}_0\): 读写计数器的方式, 00 表示计数器锁存
 - \(\text{M}_2-\text{M}_0\): 工作模式,模式 0-5
- 000: 模式 0
 - 001: 模式 1
 - X10: 模式 2
 - X11: 模式 3
 - 100: 模式 4
 - 101: 模式 5
 
 - \(\text{BCD}\): 二进制或 BCD 计数方式, 二进制计数范围 0 - 0xFFFF (65535), BCD 计数范围 0 - 9999
 
两阶段初始化¶
读计数器方式¶
读取计数器的值: 使用计数器锁存指令
    mov  al, 10000000b ; 10_00_0000: 计数器 2 锁存
    out  43h, al
    in   al, 42h       ; 读取 LSB
    mov  ah, al
    in   al, 42h       ; 读取 MSB
    xchg ah, al        ; 交换 ax 的高低字节
读取计数器状态: 使用控制字回读指令,当 \(\text{D}_7-\text{D}_6 = 11\) 时,表示回读计数器状态,此时其他位有不同的含义:
一些回读指令的例子:
注意,如果已经锁存了 计数 / 状态,如果没有读走,再次发送锁存指令不会更新锁存的值. 如果计数和状态都锁存了,那么一定先读取状态,再读取计数 (因为计数值可能单字节,也可能双字节,读取与写入设置相同).
状态字含义: (\(\text{D}_5-\text{D}_0\) 与控制字格式相同)
    mov  al, 11000010b ; 11_00_001_0: 回读指令, 锁存计数和状态, 计数器 0
    out  43h, al
    in   al, 40h       ; 读取计数器 0 状态字
    mov  bl, al        ; 状态字保存至 bl
    in   al, 40h       ; 读取计数器 LSB (假设计数器 0 读写双字节)
    mov  ah, al
    in   al, 40h       ; 读取计数器 MSB
    xchg ah, al        ; 交换 ax 的高低字节
运行模式¶
下面用到的一些术语:
- CLK Pulse (CLK 脉冲): 时钟信号 CLK 的一个上升沿 + 一个下降沿,直到下降沿才算一个脉冲.
 - Trigger (门控触发): 门控信号 GATE 的上升沿.
 
模式 0: Interrupt on Terminal Count 计数定时中断¶
模式 0 适合将 \(\text{OUT}\) 与中断控制器相连,用于产生定时中断.
写入控制字后,门控有效开始计时,\(\text{OUT}\) 始终为低电平,直到计数器变为 0,\(\text{OUT}\) 变为高电平并维持.
特点: 计数初值在 CLK 脉冲下降沿加载,直到初值完全加载后才开始计数. 无论是单字节还是双字节初值 N,都只在完全加载后才开始计数. 单字节 N+1 个 CLK 脉冲,双字节 N+2 个 CLK 脉冲.
时序图
Case 1: 正常工作情况. 计数初值在 CLK 脉冲下降沿加载,计数器变为 0 时, OUT 拉高并保持. 计数到 0 继续减 1 会变成 0xFFFF.
Case 2: 门控信号 GATE 拉低,计时暂停.
Case 3: 重新载入计数初值情况.
模式 1: Hardware Retriggerable One-shot 硬件可重触发单脉冲¶
\(\text{OUT}\) 最开始是高电平,当检测到一个 CLK 脉冲和门控触发时,\(\text{OUT}\) 变为低电平,直到计数器变为 0 后,拉高并仅维持. 直到下一次门控触发跟一个 CLK 脉冲,重新开始计数.
计数初值加载 (armed) 后,门控触发 (trigger) 会在下一个 CLK 脉冲下降沿立刻加载计数器,将 \(\text{OUT}\) 拉低,开始单脉冲计数. 在计数期间写入计数初值不影响当前计数,除非又来了一次触发信号.
时序图
Case 1: 正常工作情况. 时钟在门控触发 + CLK 脉冲的下降沿载入,每次 CLK 脉冲计数一次.
Case 2: 多次触发情况. 可以看到计数器在门控触发 + CLK 脉冲的下降沿重新加载.
Case 3: 重新写入初值情况. 重新写入初值不会影响当前计数,直到下一次触发.
模式 2: Rate Generator: 时钟分频¶
模式 2 适用于时钟分频,当计数初值为 N 时,输出频率被分频为 \(\frac{1}{N}\).
\(\text{OUT}\) 绝大多数情况为高电平,直到当计数器减小到 1 时,变为低电平,且只维持一个 CLK 脉冲. 随后计数器重新加载初值,继续计数,循环下去.
门控信号会影响模式 2 的输出. 当门控信号 = 0 (无效) 时,\(\text{OUT}\) 立刻拉高,直到下一次门控有效,重新装入初始值并开始计数.
时序图
Case 1: 正常工作情况. 可以看到 \(\text{OUT}\) 每 N = 3 个周期拉低一次.
Case 2: 门控信号影响情况. 当门控信号 = 0 (无效) 时,\(\text{OUT}\) 立刻拉高,直到下一次门控有效,重新装入初始值并开始计数.
Case 3: 重新载入计数初值情况. 重新写入初值不会影响当前计数,直到下一次计数.
模式 3: Square Wave Mode: 方波¶
模式 3 适用于产生方波信号,\(\text{OUT}\) 会在计数器的一半时拉高,另一半时拉低.
如果输入 N 为偶数,那么占空比为 50%, 如果 N 为奇数,高电平周期比低电平周期多一个,占空比为 \(\frac{N+1}{2N}\).
模式 4: Software Triggered Strobe: 软件触发选通¶
类比模式 0, 不过模式 4 在计数器到达 0 时只产生 1 拍低电平.
模式 5: Hardware Triggered Strobe (Retriggerable): 硬件触发选通¶
与模式 4 类似,但是只要门控信号上升沿就触发计数,即使没有被时钟信号上升沿采样到 (因为硬件产生的信号时异步的).
总结¶
门控信号 \(\text{GATE}\) 对不同模式的影响:
| 模式 | 低电平 / 下降沿 | 上升沿 | 高电平 | 
|---|---|---|---|
| 0 | 暂停计数 | 启动计数 | |
| 1 | 1) 载入计数初值  2) 在下一次 CLK 时重置输出  | 
||
| 2 | 1) 暂停计数 2) 立刻拉高输出  | 
载入计数初值 | 启动计数 | 
| 3 | 1) 暂停计数 2) 立刻拉高输出  | 
载入计数初值 | 启动计数 | 
| 4 | 暂停计数 | 启动计数 | |
| 5 | 载入计数初值 | 
最小和最大计数次数的计数初值设置:
| 模式 | 最小次数 | 最大次数 | 
|---|---|---|
| 0 | 1 | 0 | 
| 1 | 1 | 0 | 
| 2 | 2 | 0 | 
| 3 | 2 | 0 | 
| 4 | 1 | 0 | 
| 5 | 1 | 0 | 
例题: 计数初值计算
- 
输入时钟频率为 8 MHz, 需要一个 100 KHz 的方波和 200 KHz 的持续脉冲,该如何设置 8254 的计数初值?
- 计数器 0: 模式 3, 计数初值为 80 (8 Mhz / 100 Khz = 80)
 - 计数器 1: 模式 2, 计数初值为 40 (8 Mhz / 200 Khz = 40)
 
 - 
如果要产生 100 Hz 的方波,该如何设置 8254 的计数初值?
- 8 Mhz / 100 Hz = 80000 > 65535 超过计数初值上限
 - 需要串联分频: 比如先降到 10 KHz 再降到 100 Hz
- 计数器 0: 模式 2, 计数初值为 800 (8 Mhz / 10 Khz = 800),输出连接到计数器 1 的 CLK 信号
 - 计数器 1: 模式 3, 计数初值为 100 (10 Khz / 100 Hz = 100)
 
 
 
例程¶
例程 1: 8 MHz 时钟,输出 100 KHz 方波与 200 KHz 持续脉冲
TIME PROC NEAR USES ax dx
    mov dx, 706h       ; 计数器控制字端口
    mov al, 00110110b  ; 00_11_011_0: 计数器 0 写入双字节, 工作在模式 3
    out dx, al
    mov al, 01110100b  ; 01_11_010_0: 计数器 1 写入双字节, 工作在模式 2
    out dx, al
    mov dx, 700h       ; 计数器 0 端口
    mov al, 80         ; LSB = 80 (分频系数)
    out dx, al
    mov al, 0          ; MSB = 0
    out dx, al
    mov dx, 702h       ; 计数器 1 端口
    mov al, 40         ; LSB = 40 (分频系数)
    out dx, al
    mov al, 0          ; MSB = 0
    out dx, al
    ret
TIME ENDP
16550 串行通信接口¶
手册: 16550 UART
器件构成¶
- 
\(\text{A}_2-\text{A}_0\): 三根地址线,不过后面利用读写复用,提供了 12 个端口
 - 
CS: 三根片选信号,为了兼容性,实际上只用一个就行,其他的必须 disable (或门)
 - 
RD, WR: 读和写都有两个,也是为了兼容性,另一个必须 enable (与门)
 - 
ADS: 地址锁存信号: 并不是所有的芯片都能保证读写信号有效的情况下,地址仍然有效(比如摩托罗拉的 CPU), Intel 的芯片没有这个问题,也不需要. 只要给 ADS 接低就可以
 - 
XIN, XOUT: 外部时钟输入
 - 
\(\overline{\text{TXRDY}}\), \(\overline{\text{RXRDY}}\): 用于 DMA
 - 
INTR: 中断
 - 
SIN, SOUT: 串行数据的进出,全双工
 - 
\(\overline{\text{BAUDOUT}}\), RCLK: 两个时钟,一个是发送时钟,一个是接收时钟. 前者是内部产生的,后者可以从外部接过来,也可以接 \(\overline{\text{BAUDOUT}}\)
 - 
后面的引脚都是用来接调制解调器的
 
端口读写复用:
控制字格式¶
- PE: 奇偶校验使能
 - P: 奇偶校验类型, 0 为奇校验, 1 为偶校验
 - ST: 粘滞位 (Sticky) 打开时, 校验位与有效载荷无关,固定为 0 (偶校验) / 1 (奇校验) \(= \overline{\text{P}}\)
 - DL: DLAB 位, 发送为 1 的控制字后, 先后写入分频系数的 LSB 和 MSB
 
状态字格式¶
- DE: Data Ready, 1 表示有数据可读
 - TH: Transmitter Holding, 1 表示发送缓冲区为空, 可以发送
 
两阶段编程¶
初始化¶
使用 16550 芯片时, 首先需要计算时钟分频因子, 以及数据帧的格式, 然后清理 FIFO, 完成初始化:
LINE EQU 0F3H
LSB  EQU 0F0H
MSB  EQU 0F1H
FIFO EQU 0F2H
INIT PROC NEAR
    mov al, 10001010b
    out LINE, al
    mov al, 120         ; 9600 波特率的分频系数 (查表)
    out LSB, al
    mov al, 0
    out MSB, al
    mov al, 00001010b   ; 7 数据位, 1 停止位, 奇校验
    out LINE, al
    mov al, 00000111b   ; 启用发送和接受
    out FIFO, al
    ret
INIT ENDP
计算分频系数的方法:
注: 上式中的 16 为过采样倍数.
当然查表才是正解:
收发使能¶
发送数据:
LSTAT EQU 0F5H
DATA  EQU 0F0H
SEND PROC NEAR USES AX
    .REPEAT
        in   al, LSTAT
        test al, 20h    ; 检测 TH 标志位 (Transmitter holding)
    .UNTILL !ZERO?
    mov al, ah
    out DATA, al        ; 发送数据
    ret
SEND ENDP
接收数据
LSTAT EQU 0F5H
DATA  EQU 0F0H
REVC PROC NEAR USES AX
    .REPEAT
        in   al, LSTAT
        test al, 1      ; 检测 DR 标志位 (Data Ready)
    .UNTILL !ZERO?
    test al, 0Eh        ; 检测是否发生错误 (FE, PE, OE 非 0)
    .IF ZERO?
        in  al, DATA    ; 读取数据
    .ELSE
        mov al, '?'     ; 用 '?' 代表接受错误
    .ENDIF
    ret
REVC ENDP
DAC0830 数模转换芯片¶
D/A 转换¶
DAC 技术指标¶
- Resolution / Step size 分辨率: 最小数字量电压变化的值, 对于 \(n\) 位的 DAC, 分辨率计算方法为:
 
其中 \(V_{\text{REF}}\) 为参考电压, \(V_{\text{FS}}\) 为满量程输出电压.
- Linearity 线性度
- 
实际进行转换输出时,会由于种种原因会偏离理想的直线, 比如:
- 电阻发生了变化
 - 运放零点发生了偏移
 - \(V_\text{ref}\) 发生了变化
 
 
 - 
 
- Settling Time 建立时间: 输出电压稳定到理想电压 \(\pm \frac{1}{2}\) 最小数字量电压所需要的时间.
 
例题
- 
一个 4 位 DAC 的满量程输出电压为 15V, 求输入为 \((0110)_2\) 时其输出电压:
- 分辨率: \(\dfrac{15}{2^4-1} =\) 1V / LSB
 - 输出电压: \(6 \times 1 =\) 6 V
 
 - 
一个 4 位 DAC 的参考电压为 15V, 求输入为 \((0110)_2\) 时其输出电压:
- 分辨率: \(\dfrac{15}{2^4} =\) \(\dfrac{15}{16}\) V / LSB
 - 输出电压: \(6 \times \dfrac{15}{16} =\) 5.625 V
 
 
































