我的应用程序要求将值存储在16位计数器中,但由于pcb问题,它要求将计数器的低8位反转(01001110至01110010)。该代码以C(GCC)编写,并且计数器寄存器为“ int”类型(16位)。我的应用程序使用Atmel ATtiny 8位MCU。我知道,如果我将计数器寄存器声明为“ int”类型,则编译器将分配2个RAM单元。我是否只是使用掩码提取低位字节,然后重新排列位,然后将它们粘贴回类似的位置?
counter = counter & 0x00 clear lower byte value
counter = counter + (register with the reversed 8 bits)
// Then, Replace lower byte value with new value
这行得通吗? 谢谢
答案 0 :(得分:5)
您对该过程的单词描述是正确的,但是您的伪代码插图不准确且不完整。
在清除counter
的LSB之前,您需要复制;否则,您将丢失需要反转的位。您需要正确清除LSB,然后可以将LSB位直接反转回计数器LSB,如下所示:
// Copy counter LSB
uint8_t lsb = (uint8_t)(counter & 0xFFu) ;
// Clear counter LSB
counter &= 0xff00u ;
// Reverse LSB bits and mask into counter LSB
for( uint8_t mask = 0x80u;
mask != 0;
lsb >>= 1, mask >>= 1 )
{
counter |= ((lsb & 0x01u) != 0) ? mask : 0 ;
}
对于此操作,您还应该使用类型uint16_t
和uint8_t
的stdint.h,而不要依赖int
的任何特定大小-它将使代码更可移植且可测试int
不是16位的系统。通常,在执行按位运算时,应使用无符号类型。
一种更快的方法,尽管可能需要更多的ROM空间,但它是使用查找表。生成一个256字节的查找表非常麻烦,并且在ATtiny上,在内存使用方面相当高。相反,使用16字节查找几乎可以高效地完成它,如下所示:
// Copy counter LSB
uint8_t lsb = (uint8_t)(counter & 0xFFu) ;
// Clear counter LSB
counter &= 0xff00u ;
static const uint8_t lookup[] = { 0x0, 0x8, 0x4, 0xC,
0x2, 0xA, 0x6, 0xE,
0x1, 0x9, 0x5, 0xD,
0x3, 0xB, 0x7, 0xF } ;
counter |= lookup[lsb & 0xf] << 4 | lookup[lsb >> 4] ;
您甚至可以打包查找表并仅使用8个字节(0x80、0xC4等):
static const uint8_t lookup[] = { 0x80, 0xC4,
0xA2, 0xE6,
0x91, 0xD5,
0xB3, 0xF7 } ;
uint8_t msnib = ( lsb & 0x01 ) ? lookup[(lsb & 0xf) >> 1] >> 4 :
lookup[(lsb & 0xf) >> 1] & 0xf ;
uint8_t lsnib = ( lsb & 0x10 ) ? lookup[(lsb & 0xf0) >> 5] >> 4 :
lookup[(lsb & 0xf0) >> 5] & 0xf ;
counter |= (lsnib | msnib << 4) ;
但是通过增加额外的位操作的代码大小来增加查询表大小的可能性是不合理的-只是有点“太聪明了”-花了一段时间才把它弄对了!
第一种方法的优点是可以应用于任意数量的位。两种查找表解决方案都可以扩展为4位倍数的任何字长,而无需更改查找表大小,因此扩展性很好。
基准化
我使用三种不同的优化设置,在设置为AVR GCC 4.6.4的https://godbolt.org/测试了每种实现。指令计数不包括为使其可编译而添加的函数进入/退出代码,仅表示此答案中从源代码生成的指令。
| | Instruction Count | |
|Algorithm | No Opt | -O3 | -Os | + Data (bytes)|
|----------|:------:|:---:|:---:|:-------------:|
| Loop | 38 | 88 | 23 | 0 |
| LookUp16 | 59 | 38 | 37 | 16 |
| LookUp8 | 137 | 65 | 62 | 8 |
该测试几乎没有说明执行时间,但是如果代码大小至关重要,那么具有空间优化(-Os
)的循环算法可能是最佳选择。
毫无疑问,无论优化级别如何,查找表都更快,并且具有任一优化的16字节查找表可能是一个合理的平衡。对于-O3
,它比88指令展开循环总体上更小,更快。它还具有明显的优势,即通过优化设置,代码大小的可变性要小得多,这可以最大程度地减少在调试和发行版本之间切换时的意外情况。
8字节查找没有什么好处,也许还很有趣。
答案 1 :(得分:1)
您有错字:
counter = counter & 0x00 clear lower byte value
应该是
counter = counter & 0xFF00;
或
counter &= 0xFF00;
清除低字节。您可以通过一次将位旋转到另一个变量来反转位。如果计时很关键,则需要在组装中执行此操作,因为C没有旋转运算符,并且必须模拟该功能,例如
new_byte = 0;
if (orig_byte & 0x80)
new_byte |= 0x01;
if (orig_byte & 0x40)
new_byte |= 0x02;
...
等可能是C语言中最快的方法之一。 或者如果您可以保留256个字节的Flash,那么只需使用一个表,例如
__flash unsigned char rotated_bytes[] = { 0x00, 0x80, 0x40, 0xC0, 0x20, ... };
new_byte = rotated_byte[orig_byte];
(用编译器的扩展关键字替换__flash表示“程序存储器”)
答案 2 :(得分:1)
这是我的方法:
uint16_t Flipper(uint8_t hi, uint8_t reversed_lo)
{
uint8_t lo=0;
if (reversed_lo & 0x01) lo |= 0x80;
if (reversed_lo & 0x02) lo |= 0x40;
if (reversed_lo & 0x04) lo |= 0x20;
if (reversed_lo & 0x08) lo |= 0x10;
if (reversed_lo & 0x10) lo |= 0x08;
if (reversed_lo & 0x20) lo |= 0x04;
if (reversed_lo & 0x40) lo |= 0x02;
if (reversed_lo & 0x80) lo |= 0x01;
return (hi<<8) | lo;
}
我的编译器为此功能生成了25条指令,但花费了50个字节:
;reversed_lo sits in R22
;hi sits in R21
;lo goes to R18
000007DF 60.fd SBRC R22,0 Skip if bit in register cleared
000007E0 02.c0 RJMP PC+0x0003 Relative jump
000007E1 20.e0 LDI R18,0x00 Load immediate
000007E2 01.c0 RJMP PC+0x0002 Relative jump
000007E3 20.e8 LDI R18,0x80 Load immediate
000007E4 61.fd SBRC R22,1 Skip if bit in register cleared
000007E5 20.64 ORI R18,0x40 Logical OR with immediate
000007E6 62.fd SBRC R22,2 Skip if bit in register cleared
000007E7 20.62 ORI R18,0x20 Logical OR with immediate
000007E8 63.fd SBRC R22,3 Skip if bit in register cleared
000007E9 20.61 ORI R18,0x10 Logical OR with immediate
000007EA 64.fd SBRC R22,4 Skip if bit in register cleared
000007EB 28.60 ORI R18,0x08 Logical OR with immediate
000007EC 65.fd SBRC R22,5 Skip if bit in register cleared
000007ED 24.60 ORI R18,0x04 Logical OR with immediate
000007EE 66.fd SBRC R22,6 Skip if bit in register cleared
000007EF 22.60 ORI R18,0x02 Logical OR with immediate
000007F0 66.23 TST R22 Test for Zero or Minus
000007F1 0c.f4 BRGE PC+0x02 Branch if greater or equal, signed
000007F2 21.60 ORI R18,0x01 Logical OR with immediate
000007F3 30.e0 LDI R19,0x00 Load immediate
000007F4 a9.01 MOVW R20,R18 Copy register pair
000007F5 58.2b OR R21,R24 Logical OR
000007F6 ca.01 MOVW R24,R20 Copy register pair
000007F7 08.95 RET Subroutine return
答案 3 :(得分:0)
反转字节中的位的简单方法是使用联合和位字段,如下所示。
> struct ST_BYTE {
unsigned char b0 : 1;
unsigned char b1 : 1;
unsigned char b2 : 1;
unsigned char b3 : 1;
unsigned char b4 : 1;
unsigned char b5 : 1;
unsigned char b6 : 1;
unsigned char b7 : 1;
} ;
union U_BYTE
{
struct ST_BYTE byteflag;
unsigned char charflag;
};
unsigned char Reverse_Bits_in_Byte(U_BYTE local_byte)
{
U_BYTE Byte_Var2;
Byte_Var2.byteflag.b0 =local_byte.byteflag.b7;
Byte_Var2.byteflag.b1 =local_byte.byteflag.b6;
Byte_Var2.byteflag.b2 =local_byte.byteflag.b5;
Byte_Var2.byteflag.b3 =local_byte.byteflag.b4;
Byte_Var2.byteflag.b4 =local_byte.byteflag.b3;
Byte_Var2.byteflag.b5 =local_byte.byteflag.b2;
Byte_Var2.byteflag.b6 =local_byte.byteflag.b1;
Byte_Var2.byteflag.b7 =local_byte.byteflag.b0;
return (Byte_Var2.charflag);
}
void main()
{
int i;
for(i=0;i<8;i++)
{
Byte_Var1.charflag = pow(2,i);
printf("\nBefore Reverse %02X\n", Byte_Var1.charflag);
Byte_Var1.charflag = Reverse_Bits_in_Byte(Byte_Var1);
printf("\nAfter Reverse %02X\n", Byte_Var1.charflag);
}
}
请注意,尽管很简单,但也不建议自己使用reasons。
是否采用它是程序员的选择。