1

I am learning bare metal programming in c++ and it often involves setting a portion of a 32 bit hardware register address to some combination.

For example for an IO pin, I can set the 15th to 17th bit in a 32 bit address to 001 to mark the pin as an output pin.

I have seen code that does this and I half understand it based on an explanation of another 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);

Another example is:

# this clears the 21st to 23rd bits of another address
ra&=~(7<<21);

How do I choose the 7 and how do I choose the number of bits to shift left?

I tried this out in python to see if I can figure it out

bin((7<<21)).lstrip('-0b').zfill(32)
'00000000111000000000000000000000'
# this has 8, 9 and 10 as the bits which is wrong
Sam Hammamy
  • 10,819
  • 10
  • 56
  • 94
  • Is your goal to output `00000000000000000100000000000000`? – BHawk Dec 07 '17 at 23:07
  • I don't really get what exactly you asking here. You can shift any pattern in any direction (signed caveats aside), there are no a priory good patterns or good shifts you can choose. Do you have some specific question, like "How can I toggle the 23th bit?"? – Baum mit Augen Dec 07 '17 at 23:08
  • @BaummitAugen Please see the chosen answer – Sam Hammamy Dec 07 '17 at 23:31

3 Answers3

2

The 7 (base 10) is chosen as its binary representation is 111 (7 in base 2).

As for why it's bits 8, 9 and 10 set it's because you're reading from the wrong direction. Binary, just as normal base 10, counts right to left.

(I'd left this as a comment but reputation isn't high enough.)

Pzc
  • 88
  • 1
  • 3
  • 8
1

If you want to isolate and change some bits in a register but not all you need to understand the bitwise operations like and and or and xor and not operate on a single bit column, bit 3 of each operand is used to determine bit 3 of the result, no other bits are involved. So I have some bits in binary represented by letters since they can each either be a 1 or zero

jklmnopq

The and operation truth table you can look up, anything anded with zero is a zero anything anded with one is itself

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

anything orred with one is a one anything orred with zero is itself.

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

so if you want to isolate and change two bits in this variable/register say bits 5 and 6 and change them to be a 0b10 (a 2 in decimal), the common method is to and them with zero then or them with the desired value

   76543210

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

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

you could have orred bit 6 with a 1 and anded bit 5 with a zero, but that is specific to the value you wanted to change them to, generically we think I want to change those bits to a 2, so to use that value 2 you want to zero the bits then force the 2 onto those bits, and them to make them zero then orr the 2 onto the bits. generic.

In c

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

lets dig into this (3 << 5)

00000011
00000110 1
00001100 2
00011000 3
00110000 4
01100000 5

76543210

that puts two ones on top of the bits we are interested in but anding with that value isolates the bits and messes up the others so to zero those and not mess with the other bits in the register we need to invert those bits

using x = ~x inverts those bits a logical not operation.

01100000
10011111

Now we have the mask we want to and with our register as shown way above, zeroing the bits in question while leaving the others alone j00mnopq

Now we need to prep the bits to or (2<<5)

00000010
00000100 1
00001000 2
00010000 3
00100000 4
01000000 5

Giving the bit pattern we want to orr in giving j10mnopq which we write back to the register. Again the j, m, n, ... bits are bits they are either a one or a zero and we dont want to change them so we do this extra masking and shifting work. You may/will sometimes see examples that simply write_register(blah,2<<5); either because they know the state of the other bits, know they are not using those other bits and zero is okay/desired or dont know what they are doing.

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

if you have a 3 bit field then the binary is 0b111 which in decimal is the number 7 or hex 0x7. a 4 bit field 0b1111 which is decimal 15 or hex 0xF, as you get past 7 it is easier to use hex IMO. 6 bit field 0x3F, 7 bit field 0x7F and so on.

You can take this further in a way to try to be more generic. If there is a register that controls some function for gpio pins 0 through say 15. starting with bit 0. If you wanted to change the properties for gpio pin 5 then that would be bits 10 and 11, 5*2 = 10 there are two pins so 10 and the next one 11. But generically you could:

x = (x&(~(0x3<<(pin*2)))) | (value<<(pin*2));

since 2 is a power of 2

x = (x&(~(0x3<<(pin<<1)))) | (value<<(pin<<1));

an optimization the compiler might do for if pin cannot be reduced to a specific value at compile time.

but if it were 3 bits per field and the fields start aligned with bit zero

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

which the compiler might do a multiply by 3 but maybe instead just

pinshift = (pinshift<<1)|pinshift;

to get the multiply by three. depends on the compiler and instruction set.

overall this is called a read modify write as you read something, modify some of it, then write back (if you were modifying all of it you wouldnt need to bother with a read and a modify you would write the whole new value). And folks will say masking and shifting to generically cover isolating bits in a variable either for modification purposes or if you wanted to read/see what those two bits above were you would

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

or mask first then shift

x = x & (0x3<<5);
x = x >> 5;

six of one half a dozen of another, both are equal in general, some instruction sets one might be more efficient than another (or might be equal and then shift, or shift then and). One might make more sense visually to some folks rather than the other.

Although technically this is an endian thing as some processors bit 0 is the most significant bit. In C AFAIK bit 0 is the least significant bit. If/when a manual shows the bits laid out left to right you want your right and left shifts to match that, so as above I showed 76543210 to indicate the documented bits and associated that with jklmnopq and that was the left to right information that mattered to continue the conversation about modifying bits 5 and 6. some documents will use verilog or vhdl style notation 6:5 (meaning bits 6 to 5 inclusive, makes more sense with say 4:2 meaning bits 4,3,2) or [6 downto 5], more likely to just see a visual picture with boxes or lines to show you what bits are what field.

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • Thanks again old_timer! The part about making it generic is exactly what I was missing. For `pin 13` on the `Pi's`, it's actually pin `3` of one address, and has `3` bits so I use `7`. As in `0x7<<(pin*3)` where `pin==3`. Looking at the manual that's what's needed! – Sam Hammamy Dec 10 '17 at 20:03
0

How do I choose the 7

You want to clear three adjacent bits. Three adjacent bits at the bottom of a word is 1+2+4=7.

and how do I choose the number of bits to shift left

You want to clear bits 21-23, not bits 1-3, so you shift left another 20.

Both your examples are wrong. To clear 15-17 you need to shift left 14, and to clear 21-23 you need to shift left 20.

this has 8, 9,and 10 ...

No it doesn't. You're counting from the wrong end.

user207421
  • 305,947
  • 44
  • 307
  • 483