如何在逐位操作中选择正确的左移?

时间:2017-12-07 23:01:58

标签: c++ bit-shift bare-metal

我正在学习c ++中的裸机编程,它通常涉及将32位硬件寄存器地址的一部分设置为某种组合。

例如对于IO引脚,我可以将32位地址中的第15位设置为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

3 个答案:

答案 0 :(得分:2)

选择7(基数10),因为它的二进制表示为111(基数为2)。

至于为什么它的位8,9和10设置它是因为你从错误的方向读取。二进制,就像正常的基数10一样,从右到左计数。

(我将此作为评论,但声誉不够高。)

答案 1 :(得分:1)

如果要隔离并更改寄存器中的某些位但不是所有需要理解像和和/或xor这样的按位操作而不是单个位列操作,则每个操作数的第3位用于确定位结果为3,不涉及其他位。所以我有一些用字母表示的二进制位,因为它们都可以是1或0

jklmnopq

你可以查找的操作真值表,任何一个零的东西都是零,任何一个本身就是

   jklmnopq
&  01110001
============
   0klm000q

任何与一个人联合的东西都是一个零的东西。

   jklmnopq
|  01110001
============
   j111nop1

所以如果你想隔离和改变这个变量/寄存器中的两个比特,比如第5和第6位,并将它们改为0b10(十进制的2),常见的方法是和它们一起使用0或者它们与期望值

   76543210

   jklmnopq
&  10011111
============
   j00mnopq

   jklmnopq
|  01000000
============
   j10mnopq

你可以使用1和0位的orred第6位,但是这是特定于你想要将它们更改为的值,一般来说我们认为我想将这些位更改为2,所以要使用你希望将值2归零,然后将2强制为这些位,然后将它们设为零,然后将2或2置于这些位上。通用的。

在c

x = read_register(blah);
x = (x&(~(3<<5)))|(2<<5);
write_register(blah,x);

让我们深入研究(3 <&lt; 5)

00000011
00000110 1
00001100 2
00011000 3
00110000 4
01100000 5

76543210

将两个1置于我们感兴趣的位之上,但是使用该值隔离位并将其他位置混淆,以便将其归零并且不会混淆寄存器中的其他位,我们需要将这些位反转< / p>

使用x = ~x将这些位反转为逻辑非操作。

01100000
10011111

现在我们有了我们想要的掩码和我们的寄存器,如上所示,将有问题的位置归零,而将其他位置单独留下j00mnopq

现在我们需要将比特准备到或(2 <&lt; 5)

00000010
00000100 1
00001000 2
00010000 3
00100000 4
01000000 5

给出我们想要的位模式给j10mnopq,我们写回寄存器。同样,j,m,n,...位是位,它们是一个或零,我们不想改变它们,所以我们做了额外的屏蔽和转移工作。您可能/有时会看到只是write_register(blah,2&lt;&lt; 5);因为他们知道其他位的状态,知道他们没有使用其他位,零是可以/期望的,或者不知道他们在做什么。

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));

如果引脚在编译时无法减少到特定值,编译器可能会做的优化。

但如果每个字段为3位且字段开始与位0对齐

x = (x&(~(0x7<<(pin*3)))) | (value<<(pin*3));

编译器可能会乘以3但可能只是

pinshift =(pinshift&lt;&lt; 1)| pinshift;

得到乘以3。取决于编译器和指令集。

总的来说这被称为读取修改写入,当你读取内容,修改其中一些,然后回写(如果你修改了所有内容,你不需要打扰读取和修改,你会写出全新的值)。人们会说屏蔽并转换为一般性地覆盖变量中的隔离位,或者为了修改目的,或者如果你想阅读/看看上面那两个位是什么,

x = read_register(blah);
x = x >> 5;
x = x & 0x3;

或先掩饰然后移动

x = x & (0x3<<5);
x = x >> 5;
另外六个中的六个,一般都是相同的,一些指令集可能比另一个更有效(或者可能是相等然后移位,或者然后移位)。人们可能会在视觉上对某些人而不是另一个人更有意义。

虽然从技术上讲这是一个endian事情,因为某些处理器位0是最重要的位。在C AFAIK中,位0是最低有效位。如果/当手册显示从左到右排列的位时,您希望左右移位与之匹配,如上所示,我显示76543210以指示记录的位并与jklmnopq相关联,这是重要的从左到右的信息继续关于修改位5和6的对话。一些文档将使用verilog或vhdl样式表示法6:5(意思是位6到5包含,更有意义,比如4:2表示位4,3,2)或[6 downto 5],更有可能只看到带有方框或线条的视觉图片,以显示哪些位是什么字段。

答案 2 :(得分:0)

  

如何选择7

您想要清除三个相邻位。一个字底部的三个相邻位是1 + 2 + 4 = 7.

  

如何选择向左移位的位数

你想清除第21-23位,而不是第1-3位,所以你向左移动了另外20位。

你的例子都错了。要清除15-17,你需要向左移动14,要清除21-23你需要向左移动20。

  

这有8,9和10 ...

不,不。你错了。