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:
1.jpg
2.jpg
 
比如说, 
// this sample code used with nxp lpc2103
#defineRELAY_PIN0x01<<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):
3.jpg
 
难道不是很有趣吗? 现在我们看到了别出心裁的 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” 操作影响. 
 
后记:唔... 发表后. 理解重读了一下,我想这里我误写了"别出心裁的 data latch". 事实上, data latch 不是"别出心裁", 而是 data latch 被 LAT register read 而"别出心裁". 没有重新编辑修改blog, 我just 在评论这里做一下补充.
 
如果让我们再次非常尽兴地, 继续说下去, 关于 GPIO 的用法, 还有细节可以说: 比方说, 很多人都会提到, MICROCHIP MCU 为例, TRISD = 0b00000000; PORTD = 0x00; 会按照您的要求, 在掉电复位后, 执行上述两个语句后, 立即在 PORTD 上输出0 吗? 答案不是, 在两个语句之间, 比方说在 1M 的振荡频率下, 比方说 4us 的时间内, 输出值是不确定的. 因为在方向寄存器定义输出后, 我们不确定 PORTD 复位后的值, 是不是我们需要的输出值, 所以这里有短暂的随时错误输出的可能. 对于这种说法, 我们没法肯定, 因为这应该需要两个 register, 一个是 PORT, 一个是 LAT, 同时掉电复位为随机, 才能够解释. 所以, 可能的一个理解是, 是 DATA LATCH 在掉电复位后的锁存的输出值随机. 我不晓得这个理解对不对. 但是无论如何, 现在我们在 code 的时候, 会多了一个概念, 这就是掉电复位后, 当我们初始化 PIN 输出值的时候, 我们不得不注意到两条语句之间, 可能存在的短暂不确定输出异常. 这个异常存在的时间可能随着你的 MCU 的指令处理输出的速度而定. 在今天的 MCU 世界中, 我们知道应该是 1us 左右. 我们无论是把PORT/LAT 值在 TRISD 之前率先定义, 还是说, 我们根本就不在乎这 1us 的错误输出, 比如说对于驱动 LED 的点亮, 我们不在意 1us 的行为. 但是如果我们在做一些个与初始化脉冲相关的 interface 处理时, 为什么我们不在此留心会呢?
 
随着工程项目经验的持续增加, 现在我们知道了时下最新的 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.
我想再次总结一下, 对于 GPIO 的 RMW 的 ISSUE: (1) 首先如本篇 blog 而言, 我们强调对于比如说 8-bit MCU 处理器世界中, 对 GPIO 操作会出现 RMW 现象. 并指明, 对于某些特别的如 MICROCHIP 的某种型号的 MCU, 会为了避免 RMW 现象, 而单独增加 register 来解决这个现象. (2) 当我们进入到 ARM7 世界中, 比如 NXP 的 LPC 系列为例, 我们应该注意到, RMW 现象应该已经消除(我的假设), 因为我们使用的 IOSET, IOCLR 寄存器都已经是针对 bit-level 的 single cycle 的指令, 这里就已经谈不上出现了这种"RMW的会读现象". 要提及的一点是并行IO处理应使用 IOPIN resigter. (3) 现在随着技术的发展, 我们进入了 cortex world, 谁说硬件的变化比软件慢, 所以硬件工程师不必很快更新学习知识? -- 随着 cortex M 的出现, 比如 Freescale 的 cortex m0+, 我们以 KL25Z 系列为例, 首先, single cycle 的bit-level 的操作特性仍然保留, 我们见到了类似的 PSOR 与 PCOR 寄存器, 其次同样的, 我们并行操作时, 应使用 FGPIO 对应的 registers. (4) 谈完 cortex M0 与 arm7 在 GPIO 操作上的共同点, 我们回到我们说的"bit manipulation engine", 这里并非是对 GPIO 的 single cycle mode 的详解, 而是我们发现, 在 cortex-M0 或者对应的 M3 的 bit-band 现象, 这是一种新特性. 其目的是对 bit 操作提供了一种拓展的, 装饰性的,或者在很多资料上翻译为"别名的" momery 映射带来的原子操作.
永不止步步 发表于09-19 10:52 浏览65535次
分享到:

已有0条评论

暂时还没有回复哟,快来抢沙发吧

添加一条新评论

只有登录用户才能评论,请先登录注册哦!

话题作者

永不止步步
金币:67410个|学分:345327个
立即注册
畅学电子网,带你进入电子开发学习世界
专业电子工程技术学习交流社区,加入畅学一起充电加油吧!

x

畅学电子网订阅号