Bitwise operations
Many devices have registers. These are a small number of binary numbers stored in the device and used to control the device or read its sensors.
Often, several values are stored in one register. This means we have to do some programming to manipulate the separate values stored together in one register. For example a display may have an adjustable brightness and and adjustable contrast. These may be stored together in one register.
The problem arises when we have primitives to read or write an entire register, but we only want to read or write one of the values stored in the register.
For this we use a binary "mask". The mask indicates with '1' or '0' which bits we want to process. Mostly people use '1' to indicate the bits that should be processed, but either works. You just have to know which you are using.
Before we can explain the use of a mask we have to explain the bitwise boolean operations.
The "and" operation
The boolean bitwise "and" operation is an operation on two binary numbers that gives a result. It is written:
result = left & right
For each position in the binary number, the result is '1' only if both the inputs have '1' in that position. So:
- 0 & 0 == 0
- 0 & 1 == 0
- 1 & 0 == 0
- 1 & 1 == 1
For a single digit binary number the "&" operation is like multiplication, usually written with "*" on computers.
- 0 * 0 == 0
- 0 * 1 == 0
- 1 * 0 == 0
- 1 * 1 == 1
We can use the bitwise "and" operation with a mask value as follows. Let's say we want to change bits 3 to 7 in a value (bits are counted from the right, starting with 0). We can make a mask that has ones in positions 3 to 7. Binary numbers are written with 0b in Toit, and we can insert underscores anywhere we want to improve readability.
MASK := 0b000000000_1111_000
This mask value has zeros everywhere except in the area we are interested in, positions 3 to 7. For every mask there is the opposite mask, with all the bits reversed:
ANTI-MASK ::= 0b111111111_0000_111
If we have one mask, we can make the other one with the tilde (~) operator:
ANTI-MASK ::= ~MASK // This does the same.
Now if we want to change the bits 3-7 in a value to be 1010 regardless of what they were before we start by zeroing bits 3-7. This can be done with the anti-mask:
value 101010101_0101_010 antimask 111111111_0000_111 ------------------ & operation ("and") new value 101010101_0000_010 ^^^^------- the zeroed bits are here.
The masking operation has zeroed the bits from 3-7, while the other bits in the value are untouched.
The "or" operation
To set the bits to a desired value, we use the "or" operation which is the pendant to the "and" operation, and is written with the vertical pipe sign "|".
- 0 | 0 == 0
- 0 | 1 == 1
- 1 | 0 == 1
- 1 | 1 == 1
Let's say we want to set bits 3-7 to 1100. We would use an "or" operation on a number with the bits in that place and zeroes in all other places.
new value 101010101_0000_010 number 000000000_1100_000 ------------------ | operation ("or") new2 value 101010101_1100_010 ^^^^------ the 1100 has been inserted here.
Putting it all together
Perhaps we have a device with an 8-bit register that has two fields. The low 4 bits control brightness, and the high 4 control contrast. There are some constants:
BRIGHTNESS-0 ::= 0b0000 BRIGHTNESS-1 ::= 0b0001 BRIGHTNESS-2 ::= 0b0010 BRIGHTNESS-3 ::= 0b0011 BRIGHTNESS-4 ::= 0b0100 BRIGHTNESS-5 ::= 0b0101 BRIGHTNESS-6 ::= 0b0110 BRIGHTNESS-7 ::= 0b0111 BRIGHTNESS-8 ::= 0b1000 BRIGHTNESS-9 ::= 0b1001 BRIGHTNESS-10 ::= 0b1010 BRIGHTNESS-11 ::= 0b1011 BRIGHTNESS-12 ::= 0b1100 BRIGHTNESS-13 ::= 0b1101 BRIGHTNESS-14 ::= 0b1110 BRIGHTNESS-15 ::= 0b1111 CONTRAST-0 ::= 0b0000_0000 CONTRAST-1 ::= 0b0001_0000 CONTRAST-2 ::= 0b0010_0000 CONTRAST-3 ::= 0b0011_0000 CONTRAST-4 ::= 0b0100_0000 CONTRAST-5 ::= 0b0101_0000 CONTRAST-6 ::= 0b0110_0000 CONTRAST-7 ::= 0b0111_0000 CONTRAST-8 ::= 0b1000_0000 CONTRAST-9 ::= 0b1001_0000 CONTRAST-10 ::= 0b1010_0000 CONTRAST-11 ::= 0b1011_0000 CONTRAST-12 ::= 0b1100_0000 CONTRAST-13 ::= 0b1101_0000 CONTRAST-14 ::= 0b1110_0000 CONTRAST-15 ::= 0b1111_0000
We only have the operations read_register
and write_register
,
but we want to adjust the two values independently:
class MyDevice extends OneRegisterDevice: // Constructor... get-brightness -> int: reg := read-register return reg & BRIGHTNESS-MASK_ get-contrast -> int: reg := read-register return reg & CONTRAST_MASK_ set-brightness brightness/int -> none: reg := read-register // Remove the old brightness with the mask. reg &= ~BRIGHTNESS-MASK_ // Insert the new brightness with the "or" operation. reg |= brightness // Write back the old contrast and the new brightness. write-register reg set-contrast contrast/int -> none: reg := read-register // Remove the old brightness with the mask. reg &= ~CONTRAST-MASK_ // Insert the new contrast with the "or" operation. reg |= contrast // Write back the new contrast and the old brightness. write-register reg BRIGHTNESS-MASK_ ::= 0b0000_1111 CONTRAST-MASK_ ::= 0b1111_0000
Using the shift operations
In the example above, the user of the driver could choose between 16 discrete contrast levels, but perhaps the user wants to do a calculation. In that case they have perhaps calculated number between 0 and 15, and would like to use that. We can do that with the help of the shift operators, "<<" and ">>".
The shift operations move the bits of a binary number around. For example, the ">>" operator moves them to the right.
value := 0b00101000000 value = value >> 3 // Now value == 0b00000101000 value <<= 4 // Now value == 0b01010000000
Using this operation we could rewrite our MyDevice to take values from 0 to 15 instead of the constants:
class MyDevice extends OneRegisterDevice: // Constructors... /// Get the current brightness from 0-15. get-brightness -> int: reg := read-register return (reg & BRIGHTNESS-MASK_) >> BRIGHTNESS-SHIFT_ /// Get the current contrast from 0-15. get-contrast -> int: reg := read-register return (reg & CONTRAST-MASK_) >> CONTRAST-SHIFT_ /// Set the current brightness from 0-15. set-brightness brightness/int -> none: assert: 0 <= brightness <= 15 reg := read-register // Remove the old brightness with the mask. reg &= ~BRIGHTNESS-MASK_ // Insert the new brightness with the "or" operation. reg |= brightness << BRIGHTNESS-SHIFT_ // Write back the old contrast and the new brightness. write-register reg /// Set the current contrast from 0-15. set-contrast contrast/int -> none: assert: 0 <= contrast <= 15 reg := read-register // Remove the old contrast with the mask. reg &= ~CONTRAST-MASK_ // Insert the new contrast with the "or" operation. reg |= contrast << CONTRAST-SHIFT_ // Write back the new contrast and the old brightness. write-register reg // The brightness is stored in the low 4 bits, and the // contrast is stored in the high 4 bits. BRIGHTNESS-MASK_ ::= 0b0000_1111 CONTRAST-MASK_ ::= 0b1111_0000 BRIGHTNESS-SHIFT_ ::= 0 CONTRAST-SHIFT_ ::= 4