Arduino (and microcontrollers) Under the Hood (work in progress)

What goes under the hood in Arduino? How does it do the things we ask it to do? And how do microcontrollers work?

These are the things I will try to explain:

  1. How microcontrollers work
  2. The Arduino build process
  3. How Arduino uses the microcontroller to do what you want
  4. How the bootloader works
  5. Resources used by the built-in libraries
  6. Interactions between the built-in libraries

1. How does a microcontroller work

Disclosure: As you might imagine this is a topic that could take a whole book or a graduate degree,  so this necessarily is greatly simplified.

1.1 Everything is controlled by a switch

And in a microcontroller, each switch is a transistor.

Think back to your first Arduino sketch, blink. The instruction that makes the LED turn on and off is digitalWrite(). digitalWrite(13, HIGH) flips a switch so that  pin 13 is connected to 5V. digitalWrite(13, LOW) flips the switch so that the pin is connected to GND. More correctly, there are two transistors: one that connects the pin to 5V, and a different transistor that connects the pin to GND. (In fact many more transistors are involved, but for the sake of simplicity two transistors will be enough.)

What about the pinMode() command? Remember that in order to use a pin as an output, you must use pinMode(). pinMode() also manipulates transistors, in this case that configure the pin to be used as an input or as an output.

What about delay()? How is that done with switches? In this case we use a bunch of switches to represent the desired delay time, and at some regular interval we subtract one from this desired time until we reach zero. When we reach zero delay() returns and your sketch continues.

But the delay amount (1000) is a big number, and these very simple switches only have two positions – on or off. How can we store a big number like 1000 in simple on and off switches?

1.2 Binary and hexadecimal numbers

The answer is to use a special code called binary. In the binary code each digit has only two possible values (0 or 1), unlike our decimal system in which each digit has 10 possible values (0-9). The binary digits 0 and 1 are easily represented by our simple switch: 0 can be off, and 1 can be on (or the other way around, as long as we are consistent).

Here is the binary code:

DecimalBinary
00
11
210
311
4100
5101
6110
7111
81000
91001
101010
111011
121100

(The binary system is not an arbitrary code. The binary system is simply a number system with a base of 2, compared to our common decimal number system that uses a base of 10. You can learn more about the binary numeral system here, but for our needs, it’s enough to think of it as a code for representing any number as a sequence of ones and zeros that can then be represented by switches (transistors) that are either on or off.)

Now that you understand the binary code, let’s go back to the blink example and the delay() function. In the blink example delay is called with an argument of 1000. The microcontroller represents this number with a bunch of transistors that are either on or off, and every so often it subtracts one from this number and then causes the transistors to represent the new number, until the number reaches zero.

The decimal number 1000, converted to binary, is 1111101000. That’s hard to read correctly and manipulate, so we tend to group binary digits in groups of 4 and use another code for each group of 4:

DecimalBinary Hexadecimal
00 0
11 1
210 2
311 3
4100 4
5101 5
6110 6
7111 7
81000 8
91001 9
101010 A
111011 B
121100 C
131101 D
141110 E
151111 F

The advantage of this code is that it’s relatively easy to remember the codes for these 16 values, especially if you work with them often; it’s much easier to work with large numbers; and it’s easy to convert large numbers to binary. For instance, to convert F0FC72 in hexadecimal to binary, just look up the code for each hexadecimal digit:

F0FC72 (hexadecimal) = 1111 0000 1111 1100 0111 0010 (binary)

Or the other way around, to convert binary to hexadecimal. Start by grouping the binary number in groups of 4 bits, adding leading zeros if necessary

1111101000 (binary) = 0011 1110 1000 (binary) = 3E8 (hexadecimal)

Just as the binary number system is not an arbitrary code, neither is the hexadecimal system. The hexadecimal system is simply a number system with a base of 16. The first 10 digits are the same 10 digits we use in the decimal system: the numerals 0-9. After that we use the letters A-F.

One last word on number systems: If a number has any of the letters A-F in it, it must be a hexadecimal number. But if a number has only the digits 0-9, how can you know if it’s a hexadecimal number or a decimal number? Similarly, if a number is made up only by the digits 0 and 1, we can’t tell if it’s a binary, decimal, or hexadecimal number.

To get around this problem it’s common to precede hexadecimal numbers by the letters 0X. For binary numbers I will follow the Arduino convention as documented here, of preceding a binary number with the letter B. Numbers that have neither a 0x or a B in front of them are decimal:

B1000 is a binary number
0x1000 is a hexadecimal number
1000 is a decimal number

1.3 Bits and bytes

Just as in the decimal system each place represents one decimal digit, in the binary system each one of these switches (or transistors) represents one binary digit.

Definition: A single digit in a binary number is called a bit, which is an abbreviation for binary digit.

As you might imagine, working with individual bits can be very time consuming, so usually microcontrollers group a bunch of them together. Your laptop probably groups bits in bunches of 32 or 64, but the tiny microcontroller in Arduino groups them in bunches of 8.

Definition: 8 bits grouped together are called a byte

Even though all bits might be part of a byte, the individual bits might work together or independently. For instance, 8 bits might represent a number (the largest number you can represent with 8 bits is 255), or, 8 bits might represent digital pins 0-7, where the first bit (always referred to as bit number zero) represents digital pin 0, bit number one represents digital pin 1, etc.

It’s even possible that some bits in a byte work together, while others work alone. For instance, bits 0-1 might select the mode of an analog input pin, and bits 2-7 might do something different. Here is one possible way to select analog input mode with two bits:

Bit 1 Bit 0 Mode
0 0 Analog Input
0 1 Digital Input
1 0 Digital Output
1 1 Unused

(Remember that on the Arduino, all analog input pins can also be used as digital inputs or digital outputs.)

1.4 Registers

Let’s review what we’ve covered:

  1. A microcontroller consists, for the most part, of many transistors behaving as switches.
  2. The binary system is used for counting and for groups of switches
  3. An individual binary digit is a bit
  4. The microcontroller used in Arduino works with groups of 8 bits, which are called a byte
  5. The bits in a byte might work together or they might work independently
  6. By manipulating all these bits and bytes, a microcontroller does what your sketch asks it to do

A microcontroller might have a couple hundred such bytes. How does the microcontroller indicate which one it wants to manipulate?

The solution is to organize all these byes into a nice regular array, and to give each byte an address. You can think of this as a large spreadsheet. Each byte in such a structure is called a register. Traditionally, register arrays are shown with the lowest address at the bottom, and just like bit positions, the address starts with zero:

Register address Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
3
2
1
0

Now we can add the information about what a particular byte does. For instance, let’s pretend that register 1 selects the mode of the analog input pin we discussed above, register 2 is the value of an analogWrite(), and that register 3 controls digital pins 0-7:

Register address Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
3 Digital pin 7 Digital pin 6 Digital pin 5 Digital pin 4 Digital pin 3 Digital pin 2 Digital pin 1 Digital pin 0
2 Value sent to analogWrite()
1 Unused Unused Unused Unused Unused Unused Analog input pin mode bit 1 Analog input pin mode bit 0
0 Unused Unused Unused Unused Unused Unused Unused Unused

When we start looking at the registers and bits in the Arduino microcontroller it’s hard to remember the addresses of all the different registers, so instead they are all assigned names and abbreviations. Similarly the bits within a register might have individual names. Continuing with our example from above, we might have:

Register address Register name Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
3 Port A PORT A bit 7 PORT A bit 6 PORT A bit 5 PORT A bit 4 PORT A bit 3 PORT A bit 2 PORT A bit 1 PORT A bit 0
2 PWM C Value sent to analogWrite()
1 Analog Configuration Unused Unused Unused Unused Unused Unused Analog input pin mode bit 1 Analog input pin mode bit 0
0 Unused Unused Unused Unused Unused Unused Unused Unused

 

1.5 Bit Manipulations

Time for another review:

  1. A microcontroller is made up of a large number of switches (transistors) which control the microcontroller to “run”, or execute, your sketch
  2. These individual switches are organized by function into an array of registers
  3. Each register has a unique address. A name helps us remember what a particular register does.
  4. The bits in a register might work together or they might work independently. Individual bits or groups of bits might have names as well.
  5. Arduino does what your sketch wants it to do by manipulating the values in the registers, causing pins to go high or low, reading inputs, causing delays to happen, etc.

Manipulating bits in a register are an important part of what a microcontroller program does. Review the Bitwise Operators here, here, and here.

Definition: Setting an individual bit to one is called setting that bit

Definition: Setting an individual bit to zero is called clearing that bit

Definition: A bitwise operator operates on the individual bits in a variable or register

Here are some manipulations that will show up as we dig deeper:

1.5.1 Setting a Bit

To set a particular bit in a register without affecting the other bits, perform a bitwise OR of itself with a byte of all zeros except for a one in the position you want to set. for example, suppose A contains some number in which we want to set bit 2 without affecting the other bits:

A = A | B00000100; // set bit 2; don't affect any other bits

Usually you will see this with the shorthand |= operator:

A |= B00101101; // set bits 0, 2, 3, and 5; don't affect other bits

1.5.2  Creating a Bit Mask

To create a byte of all zeros except for one bit at a specific index, shift the number 1 to the left by the index. For example, to set bit 5 in a byte of all zeros:

A = 1 << 5; // this creates a byte of all zeros with only bit 5 set

This is safer than B00100000 because it’s so easy to make a mistake in the latter

Definition: A byte of one(s) in the position(s) we want to manipulate, and zeros in all other positions, is called a bit mask.

1.5.3 Setting a Bit, Improved

Combing these two tricks:

A |= 1 << 5; // set bit 5; don't affect other bits

1.5.4 Clearing a Bit

Clearing a bit is a little tricky. It involves 3 steps:

  1. Create a bit mask of a 1 in the position of the bit you want to clear
  2. Invert that mask (using the NOT operator), so each 1 becomes a 0, and each 0 becomes a 1. Now you have a byte of 1s with a single 0 of the bit you want to clear
  3. Apply a bitwise AND of this byte with the register in question. All bits will remain unchanged, except for the one bit in the position you want to clear

Here it is in individual steps:

temp = 1 << 5; // bitmask of all zeros and a one in bit position 5
temp = ~temp; // inverted mask of all ones and a zero in position 5
A = A & temp; // clear only bit 5 in A

Or, in one step:

A &= ~(1 << 5); // clear only bit 5 in A

Confused about bitwise math? A good resource is here, and you can find many others on the internet.

1.5 Microcontroller Datasheet

The bible of any microcontroller (in fact of most electronic components) is the datasheet. The datasheet is usually provided by the manufacturer of the microcontroler or electronic component. The datasheet for the ATmega328, which is made by Atmel and is used on the Arduino Uno, is here. You will see that it is almost 450 pages long, and the font is quite small, so there is a lot of information in there, and it’s all important, but I’ll point to the sections I think are important:

Read at least once, even if it doesn’t all make sense, for a bit of an overview:

  1. Pin Configurations (page 2)
  2. Pin Descriptions (page 3)
  3. Block Diagram (page 5)

The sections that are most important for understanding Arduino are:

  1. I/O Ports (page 75)
  2. The Timer/Counters (starting at page 94)

which we will now explore in more detail.

1.5.1 Arduino Analog and Digital pins

To explore the datasheet, it’s helpful to start with something we know something about, which for most of us are the digital pins (your first sketch was probably Blink, wasn’t it?). Let’s try to implement Blink without using pinMode and digitalWrite, to see how that’s done.

Let’s start with Arduino pin 13. Note that the Arduino pin number is not the same is the chip pin number. The first step is to figure out which pin this is on the microcontroller. Referring to the Arduino Uno schematic, you can see that Arduino pin 13 is connected to the ATmega328 microcontroller pin 19. Referring to the Pin Configurations on page 2 of the datasheet you can see that pin 19 is named PB5 (SCK/PCINT5) (this is also shown on the Arduino schematic and here). Moving on to the Pin Descriptions on page 3, you can see that PB5 is part of Port B (PB7:0 is shorthand for PB7 through PB0). Now we have to find the description for Port B.

The table of contents (which, for some reason, is at the end of the book, on page 441) tells us that the I/O Ports are discussed in chapter 13, starting on page 75. In this section, at the bottom of page 76, we see “The DDxn bit in the DDRx Register selects the direction of this pin. If DDxn is written logic one, Pxn is configured as an output pin. If DDxn is written logic zero, Pxn is configured as an input pin.” This sounds very much like what pinMode() does, doesn’t it?

Let’s go on to “Register Description” on page 92. There we see the specific registers and the bit names.

First note that we can now figure out the meaning of the letter ‘x’ for example in DDRx: there are three DDR registers, one each for ports B, C, and D. Looking back at the Arduino schematic and at the pin configuration on page 2, we can see that all the Arduino I/O pins are grouped into something called “Ports”, Port B, Port C, and Port D. The letter ‘x’ is to be replaced by the appropriate port letter, so the DDRx Register for Arduino pin 19 will be DDRB.

Back to page 92, section 13.4.3, we see that DDRB stands for Data Direction Register, Port B, and that each bit in the register controls an individual I/O pin. As it says in the quote above from page 76: “If DDxn is written logic one, Pxn is configured as an output pin.”. We know that Arduino pin 13 is atMEGA pin PB5, and so its direction is controlled by DDB5. If DDB5 is logic one, then Arduino pin 13 is configured as an output.

Now we know how to make Arduino pin 13 an output: we have to set DDB5, which is bit number 5 of DDRB to logic one, but we have to be careful not to disturb the other bits in DDRB. Referring to our discussion above  “Setting a Bit”, the answer is:

DDRB |= 1 << 5; // set bit 5; don't disturb other bits
                // DDB5 controls Arduino pin 13 so this 
                // is the same as pinMode(13,OUTPUT);

Now that we’ve figured out the pinMode(), let’s turn our attention to digitalWrite().

At the very top of page 77 we find “If PORTxn is written logic one … the port pin is driven high (one). If PORTxn is written logic zero … the port pin is driven low (zero).” so we simply need to set or clear bit 5 of PORTB:

PORTB |= 1 << 5;        // set bit 5; same as digitalWrite(13, HIGH);
PORTB &= ~(1<<5);       // clear bit 5; same as digitalWrite(13,LOW);

So now we can write Blink:

void setup() {
  DDRB |= 1 << 5; // pinMode(13,OUTPUT);
}
void loop () {
  PORTB |= 1 << 5; // set bit 5; same as digitalWrite(13, HIGH); 
  delay(1000);
  PORTB &= ~(1<<5); // clear bit 5; same as digitalWrite(13,LOW);
  delay(1000);
}

A nice summary of port registers and direct port manipulation can be found here

 


The register summary starts on page 423, but as you can see, the names of the registers are very short abbreviations that are unlikely to be self explanatory, so you need to look up the register   The Arduino build process The build process adds these files to those in the sketch:

          • hardware/arduino/cores/arduino/Arduino.h

 

 

        • hardware/arduino/variants/standard/pins_arduino.h

 

 

        • hardware/arduino/cores/arduino/main.cpp

 

main.cpp, after some minor deletions:

int main(void)
{
  init();
  initVariant();
  setup();
  for (;;) {
    loop();
    if (serialEventRun) serialEventRun();
  }
}

init() comes from hardware/arduino/cores/arduino/wiring.c

  • // the prescaler is set so that timer0 ticks every 64 clock cycles, and the
    // the overflow handler is called every 256 ticks.
    #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
  • Timer 0 overflow ISR
  • millis(), micros(), delay(), and delayMicroseconds()
  • init(), which does
    • set interrupts
    • set timer 0 for fast hardware PWM on ATmega168
    • set timer 0 prescale factor = 64 and enable overflow interrupt
    • set timers 1, 2, and 3 prescale factor to 64 and set to 8 bit phase-correct hardware PWM
    • set timer 4 prescale factor to 64 and
      • phase-correct PWM mode(ATmega1280 and ATmega2560)
      • phase and frequency-correct PWM mode (32U4)
    • set timer 5 prescale factor to 64 and set to 8 bit phase-correct hardware PWM
    • // set a2d prescale factor to 128
      // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
      // XXX: this will not work properly for other clock speeds, and
      // this code should use F_CPU to determine the prescale factor.
    • // the bootloader connects pins 0 and 1 to the USART; disconnect them
      // here so they can be used as normal digital i/o; they will be
      // reconnected in Serial.begin()

Resources:

I used the following resources in writing this tutorial. If you want to dig deeper, you might start here:

  • http://shallowsky.com/software/arduino/arduino-cmdline.html
  • http://www.embeddedrelated.com/showarticle/453.php
  • http://arduino.cc/en/Hacking/LibraryTutorial
  • http://arduino.cc/en/Guide/ArduinoYun
  • https://github.com/arduino/Arduino/wiki/_pages
  • https://code.google.com/p/arduino/wiki/HardwareResourceMap
  • ~/.arduino/preferences.txt
  • http://www.arduino.cc/en/Reference/PortManipulation

These two documents describe the build process:

  1. http://arduino.cc/en/Hacking/BuildProcess
  2. https://code.google.com/p/arduino/wiki/BuildProcess

To do

Combine and update Hacking/BuildProcess and wiki/BuildProcess. The latter seems the more recent.

  1. Remove reference to pre-1.0 IDE
  2. “Arduino.h … found in <ARDUINO>/hardware/cores/<CORE>/” no, it’s in <ARDUINO>/hardware/arduino/cores/<CORE>/ where <CORE> is either “robot” or “arduino”
  3. pins_arduino.h line 71 and everywhere else that 168 is mentioned:
    // ATMEL ATMEGA8 & 168 / ARDUINO
    should be also 328
Print Friendly
Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Leave a Reply

Your email address will not be published. Required fields are marked *