Bits Bytes and Manipulation - Daniel-Git-Hub/ELEC3042 GitHub Wiki

Bits and Bytes and Manipulation

Simply put a bit is one pieace of data which can either be true (commonly called SET, often represented by 1) or false (commonly called clear, often represented by 0) A byte of data is set of 8 bits. In total there is 2^8 (256, from 0 to 255) combinations of bits in a byte.

The 112 common ways to represent numbers

There are 3 commonly used methods for representing data*, binary (base 2), hex (base 16), and decimal (base 10)

These are ways of representing the SAME DATA, there is NO DIFFERENCE between a number represented in one way vs another. i.e 0b1111 = 0xf = 15

A computer will TREAT THEM ALL THE SAME so it DOES NOT MATTER if you type 0xE5 or 0b11100101 or 229

There are 2 key ways of thinking about numbers

  1. They represent the how many object there are in a set
  2. A number can also be thought of representing different unique states that are ordered sequentially. The base of a number is how many states each digit can represent before it needs to carry to the next digit (by increasing that digit by 1). i.e in base 10 each digit can store 10 states, 0, 1, 2, 3, 4, 5, 6, 7, 8, or 9 in base 16 each digit can store 16 states, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, or F in base 2 each digit can store 2 states, 0 or 1

because computers are digital and each little switch inside them can only be on or off they by default count in base 2 (binary). So realistically every number is stored in binary at the core of the computer.

The problem with using binary for everything is 2 fold

  1. Its not how humans think, as children we where taught to count in decimal so it is hard for us to see a binary number and work out how many objects it is representing

*There are also other bases that are used, base64 is often used for storing data and in URLs the characters are 0-9, a-z, A-Z, /, and + some langauges and cultures also use others bases such as base 12 but these are rarely used in programing and western culture

How to convert numbers

Using maths: but this is slow, so don't.

Windows calculator: If you select the hamburger button (☰) and select programmer. From here select HEX, BIN, or DEC and type your number in that format. This auto-converts it to the other formats (OCT is base8)

Online converter: Just google "binary to decimal converter" (or something similar) Here is the one I use https://codepen.io/daniel-github-watson/full/RwozbwJ note that this is quick and dirty and I'm not a UI/UX expert

Binary

Binary numbers are denoted by a prefix of "0b"

Hexadecimal, more often called Hex

Hex is a way of representing numbers with 16 charaters. Therefore it condenses 4 bits of data into a single character. It is represented with the characters 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f (capitalization does not mater) for example here c would represents 12

In this way a byte of data is condensed to 2 characters.

Hex numbers are denoted by a prefix of "0x"

Some examples are 0xff = 0b11111111 = 255 0xe7 = 0b11100111 = 231 0x5 = 0x05 = 0b0101 = 5

Decimal

Is is the number format that humans use it counts from 0 to 9 (then goes to 10, 11, 12)

In C this is represented by having NO prefix and a NON-ZERO first digit* (C will not recognize 080 as 80)

*This is because C will treat a prefix of 0 as a base8 (octal) number

How numbers are stored in C, (in particular AVR coding)

Signed vs Unsigned

A terminolgy that is used is a value can either be signed or unsigned If a number is unsigned then it can ONLY be postive and starts at 0 and can go up to 2^N - 1, where N is the length of that number type If a number is signed then it can be negative OR postive it can range from -(2^(N-1) - 1) to (2^(N-1) - 1)

Registers

Most registers (for example PORTB) on an ATMEGA328P are 8 bits long and unsigned (so they go from 0 to 255), if you set one these registers using a longer number it will ignore any bit expect the 8 LEAST significant bits for example PORTB = 0b1100110010101010; will result in PORT == 0b10101010

16 Bit registers

These registers are special, for example the current count of Timer 1 is stored a a 16 bit register called TCNT1. These registers can be accessed and set in C by calling the register directly. Alternatively you can call the 8 least signficant bits with TCNT1L (where TCNT1 is the register name and L stands for low) and 8 most significant bits with TCNT1H (where TCNT1 is the register name and H stands for high)

IMPORTANT If you are reading/writing from MOST 16 bit registers using the high and low byte you NEED to be careful about which one you read/write to first. The datasheet explains the rules for each register in the register desciption for that register. But the general rule of thumb are (from part 16.3 of datasheet on page 122) For Reading Read the low byte THEN the high byte

For Writing Set the high byte THEN set the low byte.

Note The reason for this complications is that when you read the low byte the high byte is copied into a TEMP register in the same clock cycle, then when you read the high byte you are read is from the TEMP register and not actually from that real register location, (of course it is a lot more complicated and nuanced then this)

Int

these are 16 bits and signed (meaning that the value can go from -(2^15+1) to (2^15 - 1, or -32767 to 327687)

uint8_t

this is 8 bits and unsigned

char

this is IDENTICAL to uint8_t for all intents and purposes (and C can automatically transform one to the other) Nommenly this is used to store a single character (letter or symbol) using the ASCII lookup table

uint16_t

this is 16 bits and signed

Operations

The simple

You can natively do simple arithmetic in C

  1. Addition (+)

  2. Subtraction (-)

  3. Multiplication (*)

  4. Integer division* (/) Integer division is the same as normal division except that the remainder is thrown out (3/2 = 1 and 11/3 = 3) 7 / 5 = 1

  5. modulo (%) simply put it returns the remainder when dividing the left number by the right number 7 % 5 = 2

Each of these have a time complexity and can sometimes be slow, for exact number clock cycles read the instruction set datasheet. You also NEED TO BE CAREFUL whether you are going out of bounds of that number (240 + 80 and both of them are stored as 8 bit numbers will return 79)

You can also natively compare values In C True IS 1 and False IS 0 and all these operations will return either 0 or 1

Also to note that C will count ANY non-zero number as true and zero as false for example

if(5){
  //this code will run
}
if(0){
  //this code will not run
}
  1. greater than (>) example 5 > 3 returns 1 3 > 3 returns 0 3 > 5 returns 0

  2. less than (<) 5 < 3 returns 0 3 < 3 returns 0 3 < 5 returns 1

  3. equal to (==) Double equal signs tells the chip to compare the values and not to set the left value to the right value Double equal signs tells the chip to compare the values and not to set the left value to the right value

5 == 3 returns 0
3 == 3 returns 1
3 == 5 returns 0
  1. does not equal (!=)
5 != 3 returns 1
3 != 3 returns 0
3 != 5 returns 1
  1. greater than or equal to (>=)
5 >= 3 returns 1
3 >= 3 returns 1
3 >= 5 returns 0
  1. less than or equal to (<=)
5 <= 3 returns 1
3 <= 3 returns 1
3 <= 5 returns 0
  1. Logical NOT (!)
!1 returns 0
!5 returns 0
!0 returns 1
  1. Logical AND (&&) will return true if BOTH values are true
1 && 0 returns 0
1 && 1 returns 1
5 && 4 returns 1
0 && 5 returns 0
0 && 0 returns 0
  1. Logical OR (||) (this symbol is called a pipe, it is located on a keyboard below backspace and to use it you need to hold shift and press that key) will return true if EITHER value is true
1 || 0 returns 1
1 || 1 returns 1
5 || 4 returns 1
0 || 5 returns 1
0 || 0 returns 0
  1. Logical XOR Will return true if only ONE value is truth Exclusive or, can be achieved like !(value1) != !(value2) The way it works is forst it converts each value to 0 or 1 (by the NOT operator), then it sees if they are not equal (!=). If your values are guaranteed to be only 0 and 1, the does not equal operator can be used (!=)

Bitwise Operations

The above operations treat the number as a whole, but what if we want to apply those operators bit by bit.

  1. bitwise AND (&) will set the corresponding bit to true if both values are 1
  0b1100
& 0b1010
  =======
  0b1000
  1. bitwise OR (|) will set the corresponding bit to true if one or both values are 1
  0b1100
| 0b1010
  ======
  0b1110
  1. bitwise XOR (^) Exclusive or, will set the corresponding bit if only one value is true
  0b1100
^ 0b1010
  ======
  0b0110
  1. bitwise NOT (~) Will reverse the bits, you need to be careful about the size of a variable as it will change ALL bits, key thing to remeber is that "int" are 16 bits long
~0b1100
 ======
 0b0011
  1. left shift (>>) This is the same as dividing a number by 2^N, this will move all bits to the left by N
0b110010 >> 1 equals 0b011001
0b110010 >> 3 equals 0b000110
0b110010 >> 0 equals b110010
  1. right shift (<<) This is the same as multipling a number by 2^N, this will move all bits to the right by N
0b110010 << 1 equals 0b100100
0b110010 << 3 equals 0b010000
0b110010 << 0 equals 0b110010

Useful formulas

_BV(N) vs (1 << N)

These do the identical thing, both shift 1 by N bits to the left so

1 << 3 == 0b1000
_BV(3) == 0b1000

In fact _BV(N) is translated by the compiler* to (1 << (N)) so it also has ZERO effect on performance of the ATMEGA328p

*This can be seen in by CTRL + left clicking _BV(), which goes to the definition

#define _BV(bit) (1 << (bit))

Where "define" finds all instances of _BV(bit) in the code and replaces it with (1 << (bit)) BEFORE it is converted to assembly

Toggling the Nth bit

Both of these will toggle the Nth bit (remebering that bit indexes start at 0) of PORTB (for example toggling the 2nd bit of 0b11001100 returns 0b11001000)

PORTB = PORTB ^ (1 << N);
PORTB ^= (1 << N);

Setting the Nth bit

Both of these will set the Nth bit of PORTB to 1 (for example toggling the 2nd bit of 0b11001000 returns 0b11001100)

PORTB = PORTB | (1 << N);
PORTB |= (1 << N);

Clearing the Nth bit

This will clear the Nth bit of PORTB by setting it to 0

PORTB = PORTB & (~(1 << N));
PORTB &= ~(1 << N);
⚠️ **GitHub.com Fallback** ⚠️