我的一位同事在编写 ATMega 时遇到了一些奇怪的问题,这些问题与访问输入 - 输出端口有关。
经过一些研究后观察问题,我得出结论,如果我们的目标是安全的符合 C 标准的软件,我们应该避免使用可能编译为的操作SBI
或指令访问 SFR。CBI
我正在寻找这个决定是否正确,所以如果我的担忧是有效的。
Atmel 处理器的数据表在这里,它是 ATMega16。我将在下面参考本文档的一些页面。
我将使用本网站上WG14 N1256 链接下的版本来参考 C 标准。
处理器的SBI
和CBI
指令在位级操作,仅访问相关位。所以它们不是真正的读取-修改-写入(RMW)指令,因为据我了解,它们不执行读取(目标 8 位 SFR)。
在上述数据表的第 50 页上,第一句以所有 AVR 端口都具有真正的读取-修改-写入功能......开始,同时它指定这仅适用于技术上不是 RMW的使用SBI
和指令的访问。CBI
数据表没有定义例如PORTx
寄存器应该返回的读数(但是它表明它们是可读的)。所以我假设读取这些 SFR 是未定义的(它们可能会返回最后写在上面的东西或当前的输入状态或其他)。
在第 70 页它列出了一些外部中断标志,这很有趣,因为这是SBI
和CBI
指令的性质变得重要的地方。这些标志在发生中断时设置,并且可以通过将它们写入 1 来清除它们。因此,如果SBI
是真正的 RMW 指令,它将清除所有三个标志,而不管操作码中指定的位如何。
现在让我们进入 C 的问题。
编译器本身确实无关紧要,唯一重要的事实是它可能会在某些情况下使用CBI
andSBI
指令,我认为这会使其不兼容。
在上面提到的 C99 标准中,第5.1.2.3 节程序执行,第 2 点和第 3 点引用了这一点(第 13 页)和6.7.3 类型限定符,第 6 点(第 109 页)。后者提到构成对具有 volatile 限定类型的对象的访问是实现定义的,但是在它之前的几句话要求任何引用此类对象的表达式都应严格按照抽象机的规则进行评估。
另请注意,示例中使用的硬件端口是volatile
在适当的标头中声明的。
例子:
PORTA |= 1U << 6;
众所周知,这可以转换为SBI
. PORTA
这意味着在 volatile ( ) 对象上仅发生写入访问。但是,如果有人会写:
var = 6;
...
PORTA |= 1U << var;
SBI
即使它仍然只会设置一位(因为SBI
在操作码中编码了要设置的位),那也不会转换为。因此,这将扩展为一个真正的 RMW 序列,其结果可能与上述不同(在这种情况下,PORTA
我可以从数据表中推断出未定义的行为)。
根据 C 标准,这种行为可能会或可能不会被允许。在这个术语中也很混乱,这里发生了两件事,它们混合在一起。第一,更明显的是在其中一种情况下缺乏读取访问权限。另一个不太明显的是如何执行写入。
如果编译后的代码省略了读取,它可能无法触发与此类访问相关的硬件行为。但是据我所知,AVR 没有这样的机制,所以它可能会通过标准。
写入更有趣,但它也包含读取。
在 using 的情况下省略读取SBI
意味着受影响的 SFR 必须都像锁存器一样工作(或者任何不这样工作的位都绑定到 0 或 1),因此编译器可以确定如果它会从它们读取什么实际上做了访问。如果不是这种情况,那么编译器至少会出错。顺便说一句,这也与数据表没有定义从PORTx
寄存器读取的内容相冲突。
写入的执行方式也是不一致的来源:根据编译器编译它的方式,结果会有所不同(一个CBI
或SBI
仅影响一个位,一个字节写入影响所有位)。因此,编写代码以清除/设置一位可能“有效”(如不是“意外”清除中断标志),或者如果编译器生成真正的 RMW 序列而不是。
也许这些在技术上是 C 标准允许的(作为“实现定义的”行为,并且编译器推断出这些情况,即对 volatile 对象不需要读取访问权限),但至少我会认为它是一个错误或不一致的实现。
另一个例子:
PORTA = PORTA | (1U << 6);
可以清楚地看到,通常为了符合标准,PORTA
应该先进行读取,然后再进行写入。虽然根据 的行为SBI
,它将缺少读取访问权限,尽管如上所述,这可能会混合实现定义的行为,并且编译器会推断出此处不需要读取。(或者我的假设是错误的?那是假设a |= b
与a = a | b
?)
因此,基于这些,我决定我们应该避免这些类型的代码,因为它(或将来可能)不清楚它们的行为方式,具体取决于编译器是否使用SBI
orCBI
或真正的 RMW 序列。
说实话,我主要是通过各种论坛帖子等来解决这个问题,而不是分析实际的编译器输出。毕竟不是我的项目(现在我不在工作)。我接受它阅读AVRreaks例如,AVR-GCC 会在上述情况下输出这些指令,即使使用我们使用的实际版本,我们也不会观察到这一点,这可能会导致问题。(但是我认为这种情况是我的建议,即使用影子工作变量实现端口访问解决了我同事观察到的问题)
注意:我根据对 C (C99) 标准的一些研究编辑了中间部分。
编辑:阅读AVR Libc FAQ我再次发现与自动使用SBI
or相矛盾的东西CBI
。这是最后一个问题和答案,它明确指出,由于声明了端口,编译器无法根据 C 语言的规则(如它所说的那样)volatile
优化读取访问。
我也明白,这种特定行为(即使用SBI
or CBI
)不太可能直接引入错误,但是通过掩盖“错误”,从长远来看,如果有人在不理解的情况下不小心基于这种行为进行概括,它可能会引入非常讨厌的错误装配级别的 AVR。