我是 AVR 编程的新手。我想控制一个变量(uint8_t received_msg
),如果它等于0xFF
。这样做是否正确:
if (!(received_msg ^ 0xFF))
还是我需要一点一点比较
uint8_t test = 0;
test = received_msg ^ 0xFF
for (i =0; i<8; i++){
test = 0 & (1<<received_msg)
}
if(test==0)
如果您想知道变量是否等于0xff
,只需测试是否相等:
if (received_message == 0xff)
您的问题与 AVR 几乎没有关系,而是关于编译器和微控制器如何工作的一些错误想法。这并不是抱怨这是一个坏问题——任何有助于你学习的问题都是好的!
(TLDR:“使用按位运算符”仅与 AVR 特定的东西形成对比,请随意使用所有正常操作。)
首先,你已经用英语表达了你想做的事情——平等测试。像 C 这样的编程语言的全部意义在于允许您以相当易读的方式表达计算操作,因此请使用最明显(因此也是清晰)的翻译received_msg == 0xFF
- 编译器的工作是将其转换为特定计算机的代码(AVR),即使它做得很糟糕,它也不会浪费超过几微秒的时间。(它没有,但如果你使代码足够复杂,它可能无法完成出色的工作。)
其次,您尝试以其他两种方式表达相同的操作 - 将每一位与设定值进行比较,并收集结果以查看它们是否都相等。正如第二个版本中的错误所示,这在读取和写入方面都变得棘手,但更重要的是,第二个版本显示了对 C 的位运算符的误解。这里的按位表示值的每个位都独立于其他位进行处理;它们仍在处理中。因此,不需要将其拆分为循环,只会使程序员和编译器的工作变得更加困难。用于使按位运算符仅影响单个位而不与它们操作的位相混淆的技术称为掩码;它依赖于“0 or n = n”、“1 and n = n”和“
我也觉得这是基于这样的想法,即像 AVR 这样的微控制器将一直在单个位上工作。这是极其罕见的,但经常被 PLC 模拟。我们所做的是使单比特工作成本低于通用 CPU 的操作。例如,考虑“PORTB |= 1<<3”。这可以理解为一些基本操作:
v0 := 1 // load immediate
v1 := 3
v2 := v0 shiftleft v1 // shift left
v3 := PORTB // load I/O register
v4 := v3 or v2
PORTB := v4 // store back to I/O register
这种解释将是一个极其简化的指令集,其中加载和存储永远不会与诸如移位和或之类的 ALU 操作结合使用。如果你要求它根本不优化,你甚至可以从编译器中得到这样的代码。但由于它对于微控制器来说是如此常见的操作,因此 AVR 有一条指令来执行此操作,而无需花费寄存器来保存 v0-v4:
SBI PORTB, 3 // (set bit in I/O register)
这使我们从需要两个寄存器(从重用不再需要的 vN)和六条指令到零寄存器和一条指令。进一步的收益是可能的,因为一旦它是一条指令,就可以使用跳过而不是分支。但它依赖于一些已知的事情,例如 1<<3 仅设置一个固定位,并且 PORTB 位于最低的 32 个 I/O 寄存器中。如果编译器不知道这些事情,它就永远无法使用 SBI 指令,而且有这样的时间。这就是为什么我们有“使用位运算符”的建议——你不再需要编写sbi(PORTB,PB3);
,这对于不了解 AVR 指令集但现在可以编写的人来说是不明显的PORTB |= 1<<3;
这是标准C,因此更清晰,同时同样有效。可以说更好的宏命名也可能使代码更具可读性,但是这些宏中的许多都是以键入简写形式出现的——例如_BV(x)
等于1<<x
.
遗憾的是,一些标准的 C 公式变得相当棘手,例如清除位 N:port &= ~(1<<N);
它为“clear_bit(port, bit)”宏提供了一个很好的案例,例如 Arduino 的 digitalWrite。一些微控制器(如 8051)为单个位工作提供特定地址,一些编译器提供语法扩展,如 port.3。我有时想知道为什么 AVR Libc 不为位操作声明位域。原谅吐槽。还有一些编译器不知道的优化,例如转换PORTB ^= x;
为PINB = x;
(这看起来很奇怪 - PIN 寄存器不可写,因此他们将该操作用于另一个函数)。
另请参阅AVR Libc 手册中有关位操作的部分,特别是“使用已弃用的 sbi/cbi 宏的移植程序”。
您还可以尝试有用的 switch(){ case } 语句,例如:
#define OTHER_CONST_VALUE 0x19
switch(received_msg){
case 0xff:
do_this();
break;
case 0x0f:
do_that();
break;
case OTHER_CONST_VALUE:
do_other_thing();
break;
case 1:
case 2:
received_1_or_2();
break;
default:
received_somethig_else();
break;
}
此代码将根据received_msg的值执行命令,重要的是在case word之后放置常量值,并注意它告诉何时从{ }块跳转的break语句。
我不确定received_msg
将代表什么。如果它是一个数值,那么一定要使用 switch-case、if-else 或其他比较结构;不需要位掩码。
但是,如果received_msg
包含二进制数据并且您只想查看某些元素并排除其他元素,则位掩码将是合适的方法。