我正在学习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
答案 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 ...
不,不。你错了。