如何在位运算中选择正确的左移?
How to choose the correct left shift in bit wise operations?
我正在学习用 C++ 进行裸机编程,它通常涉及将 32 位硬件寄存器地址的一部分设置为某种组合。
例如,对于一个IO引脚,我可以将32位地址中的第15位到第17位设置为001
,以将引脚标记为输出引脚。
我看过执行此操作的代码,根据另一个 SO question.
的解释,我理解了一半
# here ra is a physical address
# the 15th to 17th bits are being
# cleared by AND-ing it with a value that is one everywhere
# except in the 15th to 17th bits
ra&=~(7<<12);
另一个例子是:
# this clears the 21st to 23rd bits of another address
ra&=~(7<<21);
如何选择 7 以及如何选择要左移的位数?
我在 python 中尝试了这个,看看我是否能弄明白
bin((7<<21)).lstrip('-0b').zfill(32)
'00000000111000000000000000000000'
# this has 8, 9 and 10 as the bits which is wrong
How do I choose the 7
您想清除三个相邻位。一个字底部相邻的三个位是1+2+4=7.
and how do I choose the number of bits to shift left
您想清除第 21-23 位,而不是第 1-3 位,因此您再左移 20。
你举的两个例子都不对。清除15-17需要左移14,清除21-23需要左移20
this has 8, 9,and 10 ...
不,不是。你数错了。
选择 7(基数 10)是因为它的二进制表示形式是 111(基数 2 中的 7)。
至于为什么设置了第 8、9 和 10 位,是因为您从错误的方向读取。二进制,就像普通的 10 进制一样,从右到左计数。
(我将此作为评论留下,但声誉不够高。)
如果您想隔离和更改寄存器中的某些位但不是全部,您需要了解按位运算(如 and 和 or 和 xor)而不是对单个位列进行操作,每个操作数的第 3 位用于确定结果的第 3 位,不涉及其他位。所以我有一些用字母表示的二进制位,因为它们每个都可以是 1 或零
jklmnopq
与操作的真相table你可以查查,凡与零相加的都是零凡与一相加的就是它自己
jklmnopq
& 01110001
============
0klm000q
任何与 1 或 orred 的东西都是 1 任何与 0 orred 的东西都是它本身。
jklmnopq
| 01110001
============
j111nop1
所以如果你想隔离并更改此 variable/register 中的两位,比如第 5 位和第 6 位,并将它们更改为 0b10(十进制的 2),常用方法是将它们设为零然后或它们具有所需的值
76543210
jklmnopq
& 10011111
============
j00mnopq
jklmnopq
| 01000000
============
j10mnopq
您可以将第 6 位 orred 设为 1,将第 5 位设为 0,但这是特定于您想要将它们更改为的值,通常我们认为我想将这些位更改为 2,所以要使用该值 2,您需要将这些位清零,然后将 2 强制到这些位上,然后将它们设为零,然后将 2 加到这些位上。通用。
在c
x = read_register(blah);
x = (x&(~(3<<5)))|(2<<5);
write_register(blah,x);
让我们深入研究这个 (3 << 5)
00000011
00000110 1
00001100 2
00011000 3
00110000 4
01100000 5
76543210
将两个 1 放在我们感兴趣的位之上,但是与该值相结合会隔离这些位并弄乱其他位,以便将它们归零而不是弄乱寄存器中的其他位,我们需要反转这些位位
使用 x = ~x 将这些位反转为逻辑非操作。
01100000
10011111
现在我们有了我们想要的掩码和我们的寄存器,如上所示,将有问题的位归零,而让其他位保持不变 j00mnopq
现在我们需要准备 or (2<<5)
00000010
00000100 1
00001000 2
00010000 3
00100000 4
01000000 5
给出我们想要的位模式或给我们写回寄存器的 j10mnopq。同样,j、m、n、... 位是位,它们要么是 1 要么是 0,我们不想更改它们,所以我们进行了额外的屏蔽和移位工作。您 may/will 有时会看到简单的示例 write_register(blah,2<<5);要么因为他们知道其他位的状态,知道他们没有使用其他位并且零是 okay/desired 或者不知道他们在做什么。
x read_register(blah); //bits are jklmnopq
x = (x&(~(3<<5)))|(2<<5);
z = 3
z = z << 5
z = ~z
x = x & z
z = 2
z = z << 5
x = x | z
z = 3
z = 00000011
z = z << 5
z = 01100000
z = ~z
z = 10011111
x = x & z
x = j00mnopq
z = 2
z = 00000010
z = z << 5
z = 01000000
x = x | z
x = j10mnopq
如果您有一个 3 位字段,则二进制为 0b111,十进制为数字 7 或十六进制为 0x7。一个 4 位字段 0b1111,它是十进制 15 或十六进制 0xF,当您超过 7 时,使用十六进制 IMO 会更容易。 6位字段0x3F,7位字段0x7F等等。
您可以进一步尝试使其更通用。如果有一个寄存器控制 gpio 引脚 0 到 15 的某些功能。从位 0 开始。如果您想更改 gpio 引脚 5 的属性,那么这将是位 10 和 11,5*2 = 10 有两个引脚为 10,下一个引脚为 11。但通常你可以:
x = (x&(~(0x3<<(pin*2)))) | (value<<(pin*2));
因为 2 是 2 的幂
x = (x&(~(0x3<<(pin<<1)))) | (value<<(pin<<1));
如果 pin 在编译时不能减少到特定值,编译器可能会进行优化。
但如果它是每个字段 3 位并且字段开始与位零对齐
x = (x&(~(0x7<<(pin*3)))) | (value<<(pin*3));
编译器可能会乘以 3 但也许只是
pinshift = (pinshift<<1)|pinshift;
乘以三。取决于编译器和指令集。
总的来说,这称为读取修改写入,因为您读取了一些内容,修改了其中的一些内容,然后写回(如果您要修改所有内容,则不需要费心进行读取和修改,您会写下整个内容新价值)。人们会说屏蔽和移位以一般地覆盖变量中的隔离位,或者为了修改目的,或者如果你想 read/see 你会
上面的两位
x = read_register(blah);
x = x >> 5;
x = x & 0x3;
或先屏蔽再移位
x = x & (0x3<<5);
x = x >> 5;
六分之六,总的来说两者是相等的,一些指令集一个可能比另一个更有效(或者可能相等然后移位,或者移位然后和)。对于某些人来说,一种可能比另一种在视觉上更有意义。
尽管从技术上讲这是字节序,因为某些处理器的位 0 是最重要的位。在 C AFAIK 中,位 0 是最低有效位。 If/when 手册显示了从左到右排列的位,你希望你的右移和左移与之匹配,所以如上所述,我显示 76543210 以指示记录的位并将其与 jklmnopq 相关联,这是从左到右的信息继续讨论修改第 5 位和第 6 位很重要。一些文档将使用 verilog 或 vhdl 样式表示法 6:5(意思是第 6 位到第 5 位,用 4:2 表示第 4,3 位更有意义,2) 或 [6 下降到 5],更有可能只是看到带有框或线的视觉图片,以显示哪些位是什么字段。
我正在学习用 C++ 进行裸机编程,它通常涉及将 32 位硬件寄存器地址的一部分设置为某种组合。
例如,对于一个IO引脚,我可以将32位地址中的第15位到第17位设置为001
,以将引脚标记为输出引脚。
我看过执行此操作的代码,根据另一个 SO question.
的解释,我理解了一半# here ra is a physical address
# the 15th to 17th bits are being
# cleared by AND-ing it with a value that is one everywhere
# except in the 15th to 17th bits
ra&=~(7<<12);
另一个例子是:
# this clears the 21st to 23rd bits of another address
ra&=~(7<<21);
如何选择 7 以及如何选择要左移的位数?
我在 python 中尝试了这个,看看我是否能弄明白
bin((7<<21)).lstrip('-0b').zfill(32)
'00000000111000000000000000000000'
# this has 8, 9 and 10 as the bits which is wrong
How do I choose the 7
您想清除三个相邻位。一个字底部相邻的三个位是1+2+4=7.
and how do I choose the number of bits to shift left
您想清除第 21-23 位,而不是第 1-3 位,因此您再左移 20。
你举的两个例子都不对。清除15-17需要左移14,清除21-23需要左移20
this has 8, 9,and 10 ...
不,不是。你数错了。
选择 7(基数 10)是因为它的二进制表示形式是 111(基数 2 中的 7)。
至于为什么设置了第 8、9 和 10 位,是因为您从错误的方向读取。二进制,就像普通的 10 进制一样,从右到左计数。
(我将此作为评论留下,但声誉不够高。)
如果您想隔离和更改寄存器中的某些位但不是全部,您需要了解按位运算(如 and 和 or 和 xor)而不是对单个位列进行操作,每个操作数的第 3 位用于确定结果的第 3 位,不涉及其他位。所以我有一些用字母表示的二进制位,因为它们每个都可以是 1 或零
jklmnopq
与操作的真相table你可以查查,凡与零相加的都是零凡与一相加的就是它自己
jklmnopq
& 01110001
============
0klm000q
任何与 1 或 orred 的东西都是 1 任何与 0 orred 的东西都是它本身。
jklmnopq
| 01110001
============
j111nop1
所以如果你想隔离并更改此 variable/register 中的两位,比如第 5 位和第 6 位,并将它们更改为 0b10(十进制的 2),常用方法是将它们设为零然后或它们具有所需的值
76543210
jklmnopq
& 10011111
============
j00mnopq
jklmnopq
| 01000000
============
j10mnopq
您可以将第 6 位 orred 设为 1,将第 5 位设为 0,但这是特定于您想要将它们更改为的值,通常我们认为我想将这些位更改为 2,所以要使用该值 2,您需要将这些位清零,然后将 2 强制到这些位上,然后将它们设为零,然后将 2 加到这些位上。通用。
在c
x = read_register(blah);
x = (x&(~(3<<5)))|(2<<5);
write_register(blah,x);
让我们深入研究这个 (3 << 5)
00000011
00000110 1
00001100 2
00011000 3
00110000 4
01100000 5
76543210
将两个 1 放在我们感兴趣的位之上,但是与该值相结合会隔离这些位并弄乱其他位,以便将它们归零而不是弄乱寄存器中的其他位,我们需要反转这些位位
使用 x = ~x 将这些位反转为逻辑非操作。
01100000
10011111
现在我们有了我们想要的掩码和我们的寄存器,如上所示,将有问题的位归零,而让其他位保持不变 j00mnopq
现在我们需要准备 or (2<<5)
00000010
00000100 1
00001000 2
00010000 3
00100000 4
01000000 5
给出我们想要的位模式或给我们写回寄存器的 j10mnopq。同样,j、m、n、... 位是位,它们要么是 1 要么是 0,我们不想更改它们,所以我们进行了额外的屏蔽和移位工作。您 may/will 有时会看到简单的示例 write_register(blah,2<<5);要么因为他们知道其他位的状态,知道他们没有使用其他位并且零是 okay/desired 或者不知道他们在做什么。
x read_register(blah); //bits are jklmnopq
x = (x&(~(3<<5)))|(2<<5);
z = 3
z = z << 5
z = ~z
x = x & z
z = 2
z = z << 5
x = x | z
z = 3
z = 00000011
z = z << 5
z = 01100000
z = ~z
z = 10011111
x = x & z
x = j00mnopq
z = 2
z = 00000010
z = z << 5
z = 01000000
x = x | z
x = j10mnopq
如果您有一个 3 位字段,则二进制为 0b111,十进制为数字 7 或十六进制为 0x7。一个 4 位字段 0b1111,它是十进制 15 或十六进制 0xF,当您超过 7 时,使用十六进制 IMO 会更容易。 6位字段0x3F,7位字段0x7F等等。
您可以进一步尝试使其更通用。如果有一个寄存器控制 gpio 引脚 0 到 15 的某些功能。从位 0 开始。如果您想更改 gpio 引脚 5 的属性,那么这将是位 10 和 11,5*2 = 10 有两个引脚为 10,下一个引脚为 11。但通常你可以:
x = (x&(~(0x3<<(pin*2)))) | (value<<(pin*2));
因为 2 是 2 的幂
x = (x&(~(0x3<<(pin<<1)))) | (value<<(pin<<1));
如果 pin 在编译时不能减少到特定值,编译器可能会进行优化。
但如果它是每个字段 3 位并且字段开始与位零对齐
x = (x&(~(0x7<<(pin*3)))) | (value<<(pin*3));
编译器可能会乘以 3 但也许只是
pinshift = (pinshift<<1)|pinshift;
乘以三。取决于编译器和指令集。
总的来说,这称为读取修改写入,因为您读取了一些内容,修改了其中的一些内容,然后写回(如果您要修改所有内容,则不需要费心进行读取和修改,您会写下整个内容新价值)。人们会说屏蔽和移位以一般地覆盖变量中的隔离位,或者为了修改目的,或者如果你想 read/see 你会
上面的两位x = read_register(blah);
x = x >> 5;
x = x & 0x3;
或先屏蔽再移位
x = x & (0x3<<5);
x = x >> 5;
六分之六,总的来说两者是相等的,一些指令集一个可能比另一个更有效(或者可能相等然后移位,或者移位然后和)。对于某些人来说,一种可能比另一种在视觉上更有意义。
尽管从技术上讲这是字节序,因为某些处理器的位 0 是最重要的位。在 C AFAIK 中,位 0 是最低有效位。 If/when 手册显示了从左到右排列的位,你希望你的右移和左移与之匹配,所以如上所述,我显示 76543210 以指示记录的位并将其与 jklmnopq 相关联,这是从左到右的信息继续讨论修改第 5 位和第 6 位很重要。一些文档将使用 verilog 或 vhdl 样式表示法 6:5(意思是第 6 位到第 5 位,用 4:2 表示第 4,3 位更有意义,2) 或 [6 下降到 5],更有可能只是看到带有框或线的视觉图片,以显示哪些位是什么字段。