PIC18 GPIO 的 "Read-modify-write操作"使用特点
[背景]
接触与使用 PIC18 的 GPIO, 是一件饶有兴致的事. 原因就是我们发现, MICROCHIP 把所谓” Read-modify-write Operations” 现象, 小心翼翼地强调提出, 并在 PIC18 中, 专门使用了一个 LAT register 作为解决方法.
这个特点, 是我们在之前的经验中, 未曾遇到的.
在持续的思考这个现象后, 我们假设, 这与 “Read-modify-write” 行为中, 包含有 bit 操作有关, 应该说明的是, 没有作更多资料搜索去证明这个说法, 这只是 Allen 对这个问题的考虑与判断, 请读者自明.
[什么是 “Read-modify-write” 操作?]
随意用google 检索到一个简单解释:
http://www.piclist.com/tecHREF/readmodwrite.htm
When you perform any operation, apart from a MOV on a register ... the PIC {or SX} first reads the register, then it performs the operation on the number it has just read and finally it write the number back to the register.
当我们对某个 register 进行操作时, 我们首先从该 register中读回(read) data, 然后对该 data进行修改(modify)后, 再写回(write)该register. 这就是所谓” Read-modify-write 操作”.
但是对于 GPIO register. ” Read-modify-write 操作”可能会带来两个问题:
(1) PORT 总是读回 PIN 脚上的当前值. ---- 这个值有可能是错的(因为外部电路可能非正常改变它). (2) 对一个 PIN 单独地立即 toggle 操作, 将可能得不到正确的状态响应. ---- 可能在PIN 没有 toggle 改变时, 下一个 read pin 的操作已经到来.
一份中文资料, http://blog.21ic.com/user1/5742/archives/2010/72577.html, 也基本上对上述说法进行了类似的解释.
[可以不进行单个PIN的“Read-modify-write” 操作吗?]
当然. 我们在假设不进行 bit 操作的情况下, 我们可以避免上面的错误的发生, 那么我们只用下面的用法即可(我们总是对整个的 PORT 进行操作即可):
// this sample code used with microchip PIC18F45K20
TRISD = 0x00;
PORTD = 0x01;
但是, 在一个 real world里, 对单个 PORT PIN 的操作是经常的事情, 大多数情况下, 特别是非并行操作的情况下, 我们其实无需对整个 PORT 相关寄存器进行操作.
比方说, 在 8-bit MCU 中, 我们可能不需要对 PORTA(8PINS) 进行操作. 而对于 32-bit MCU, 当然这里 PORTA 往往代表着 32 个PIN脚.
回到上面的例子中, 我们其实仅仅只想将 D0 置1, 而不是想将 D7~D1 同时都置 0.
[NXP 的 LPC21XX (32-bit ARM) 的GPIO 使用特点比较]
让我们看看其他的 MCU 给出的单独操作 PIN 脚的办法吧.
以 NXP 的 LPC21XX 系列进行比较, 这是颗 32-bit ARM7 MCU. 我们注意到, 用于 GPIO 的 register, 往往包括 4 个 register: IOPIN, IOSET, IODIR(方向), IOCLR.
其中, IODIR 是方向寄存器, 那么, 对 PINIO 的操作即是.
(1) RD: 用 IODIR 定义方向, READ IOPIN
(2) WR: 用 IODIR 定义方向后, WRITE IOPIN
对于单独PIN如何处理? 我们注意到, NXP 给出的方式是: 两个registers, IOSET/IOCLR. 两者用于单独控制某个 PIN 脚.
From lpc21xx datatsheet pdf:
比如说,
// this sample code used with nxp lpc2103
#define RELAY_PIN 0x01<<21 // P0.21
IO0DIR = IO0DIR | RELAY_PIN;
IO0CLR = RELAY_PIN;
我们注意到, 在这份代码片段中, 只有被置 1 的 P0.21 被拉低, 而根据 datasheet 对 register 的解释, 被置 0 的bit(代表其他不同的PIN), 不产生任何影响.
这就是 NXP 的单独控制 GPIO 的方案. 完全没有提及所谓” Read-modify-write 操作”.
这里我们随意提到另个 IOSET/IOCLR 的特点, 同时并行输出多个”1”与”0”, 不推荐使用 IOSET/IOCLR. 而应该使用 IOPIN. 理由是前者同时输出可能会出现错误.
[PIC18 的GPIO单独操作解决方案]
在我们注意到 ” Read-modify-write 操作” 的限制后,
如果我们相信电路上 GPIO 技巧被上拉下拉或者限流电阻的存在的可靠性, 或者我们相信我们不需要考虑立即的 toggle, 那么我们可以使用下面的不可靠用法:
// PORTD bit 0 to output (0); bits 7:1 are inputs (1)
TRISD = 0b11111110;
PORTDbits.RD0 = 1;
如果我们不确定呢? 也许我们可能会有一个 back ram 就能解决 “read-modify-write GPIO register” 的烦恼.
但是, 如同我们好奇地发现的, 在 PIC18 系列中, 出现了一个新的 register: LAT.
我们也注意到很多 PIC10 serial 中, 不存在这个 LAT register .
让我们观察 MICROCHIP 的 PIC18 的 GPIO 的原理框图(from datasheet pdf):
难道不是很有趣吗? 现在我们看到了别出心裁的 Data Latch 了. 我们现在有 3 个 registers 处理 GPIO: TRIS(方向), PORT, LAT
(1) RD PORT, 用 TRIS 定义方向, 我们从 I/O 读到了实际的 PIN脚状态.
(2) WD, 无论是写 PORT register, 还是写 LAT 寄存器, 我们都把 data 放到了 Data Latch 中, 然后输出到了 PIN 脚.
(3) RD LAT, 从 DataLatch 中读取了被锁存的 data.
这意味着, 尽管我们用 PORT 和 LAT register 都能输出正确的状态. 但是考虑到 “Read-modify0write”, 我们承担着 PIN 脚上(PORT)上可能被外部电路影响的错误数据的风险(从而导致错误输出), 我们使用 LAT 将不会产生这个担心.
// PORTD bit 0 to output (0); bits 7:1 are inputs (1)
TRISD = 0b11111110;
LATDbits.LATD0 = 1;
[总结]
因此, 对 PIN18, 不意外的, 我们应该使用 LAT register 作 output 操作, 用 PORT 做 read 操作. 在特别的 LAT register 的帮助下, 我们可以使用 LAT 进行"bit相关操作"而不必担心 “Read-modify0write” 操作影响.
Written by Allen Zhan
Realse on EETC
allen_zhan 2023-9-14 17:51
我们将新的修改文章发布在知乎: https://zhuanlan.zhihu.com/p/653415381
主要考虑是, 知乎对于文章中的代码部分, 排版更加合理清晰之故.
allen_zhan_752827529 2013-8-9 14:55
allen_zhan_752827529 2013-5-26 12:09
随着工程项目经验的持续增加, 现在我们知道了时下最新的 cotex m0+ 的某类 MCU, 完全不存在 read-modify-write 现象, 因为对 IO 的操作已经进化为 single cycle mode, 如下解释: Bit manipulation engine reduces code size and cycles for bit oriented perations to peripheral registers eliminating traditional methods where the core would need to perform read-modify-write operations.
用户1602177 2013-2-26 09:41
allen_zhan_752827529 2013-2-8 19:32
allen_zhan_752827529 2013-2-7 15:53