Arduino Basics

Summary

Explains basic terminology, tools and development environment for Arduino.

Arduino Basics

The overall process has several components and several steps:

  • There are source file(s) that contain your code
  • That code is compiled into a binary image which is another file in hex format that has internal commands recognized by the microprocessor e.g. atmega328p.
  • The binary image is transmitted to the microprocessor (via avrdude) and stored in permanent memory. That memory is persistent across reboots, power on and offs.
  • When the microprocessor is rebooted, it jumps to a well-defined address in that memory and starts executing whatever is there i.e. your code.
  • It continues to process that memory until it is shut off.

But...

  • If your code does something crazy, the processor continues to run. You may not be able to see what it is doing, but it is executing... something.
  • If it looks like it is just sitting there, it isn't. It is always executing... something.
  • Typically, it is looping i.e. repeating the same code over and over again, waiting for some condition to occur and when it does, it performs the code you wrote to handle that condition.
  • Those conditions, on embedded applications, tend to be either a pin changed value or an interrupt occurred.
  • A pin is a physical "leg" on the microprocessor chip. If you apply a voltage to it, it changes value from a "0" (i.e. 0 volts) to a "1" (i.e. the supply voltage 3.3V or 5V)
  • An interrupt is when the internal microprocessor hardware detects certain events and stops (i.e. interrupts) whatever is being executed and jumps to a special chunk of code that handled that particular kind of interrupt.

It's key therefore to...

  • know what all the pins do on the arduino
  • know what all the interrupt capabilities are
  • to write code so that it didn't do something crazy. It's always responding to whatever your app needs it to.

And it's also key to some common microprocessor architectures:

  • have the processor only do one thing
  • use interrupts to set a bunch of flags and have the main loop handle each of those flags in the simplest/easiest sequence
  • have the main loop be constantly checking everything and handling them in the simplest/easiest/quickest sequence

Tools

avrdude

avrdude is used to transmit and program the arduino board. For example, the cmake process generates the appropriate command to program your board. See cmake-build-debug/build.ninja. Look for "arduino-serial-input-upload" i.e. your cmake target name with "-upload" suffix:

build CMakeFiles/arduino-serial-input-upload: <snip>
  COMMAND = cd <snip>/cmake-build-debug && 
      /usr/share/arduino/hardware/tools/avrdude 
      -C/usr/share/arduino/hardware/tools/avrdude.conf
      -patmega328p -carduino -b57600 -P/dev/ttyUSB0 -D -V
     -Uflash:w:<snip>/cmake-build-debug/arduino-serial-input.hex:i 
     -Ueeprom:w:<snip>/cmake-build-debug/arduino-serial-input.eep:i

The values for this command comes from your settings in cmake and the default values for that particular board.

avr-size

When you do a build (i.e. make) it generates a few lines at the end to tell you the size of the image:

Calculating image size
Firmware Size:  [Program: 3028 bytes (9.2%)]  [Data: 232 bytes (11.3%)] on atmega328p
EEPROM   Size:  [Program: 0 bytes (0.0%)]  [Data: 0 bytes (0.0%)] on atmega328p

You can also get similar numbers from avr-size:

$ avr-size -t cmake-build-debug/arduino-serial-input.elf 
   text    data     bss     dec     hex filename
   2976      52     180    3208     c88 cmake-build-debug/arduino-serial-input.elf
   2976      52     180    3208     c88 (TOTALS)

Note the 52 + 180 = 232 which is the value reported in the Data section in the build output. And the 2976 + 52 = 3028 which is what is reported in the Program section in the build output.

Software Files

setup() vs loop()

Arduinos follow a simple architecture.

There is a setup() function that is invoked once when the processor is reset or restarted. Its job is to configure whatever is necessary for the rest of the application to do. Since it is only invoked once, it is a good place to put all initialization code, all setting of program parameters, setting up global variables and so on.

Then the loop() function is called. It is called repeatedly and as quickly as the processor can invoke it over and over again.

While it is possible to put an infinite loop (e.g. while (true)) inside the loop() function that may cause problems since there is other code in main() that the arduino architecture calls.

What's hidden?

You can find main() in main.cpp in the arduino library. On Ubuntu that is in /usr/share/arduino/hardware/arduino/cores/arduino. The basic include (.h) files are also in the same directory.

main.cpp is very simple:

#include <Arduino.h>

int main(void)
{
    init();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }

    return 0;
}
  • init() is used by arduino to initialize the hardware and other bits they need
  • setup() is your code, and you can see it is executed only once.
  • for(;;) is the infinite loop that is executed.
  • loop() is your code, and you can see it is executed continually.
  • the serialEventRun() is part of the arduino code in HardwareSerial.cpp(). It is used to handle incoming serial communication requests. This is why you can reprogram the arduino even when it is executing some other code - it interrupts that loop and the serialEventRun() function handles it.

Delays

Since the microprocessor never stops executing, there has to be a way to synchronize with the outside world. For example, you can wait for a pin to change state or blindly wait for a certain amount time. The delay() function is used for that purpose.

It's used to wait for the given number of milliseconds. A millisecond is a thousandth of a second, i.e. there are 1000 milliseconds in 1 second. To delay for 2 seconds, you would call it delay(2000);

You can find delay() in wiring.c.

void delay(unsigned long ms)
{
    uint16_t start = (uint16_t)micros();

    while (ms > 0) {
        if (((uint16_t)micros() - start) >= 1000) {
            ms--;
            start += 1000;
        }
    }
}

The process never stops executing, it just continually checks the current number of microseconds that have gone by. When that value hits your request in milliseconds, it returns back to your code. BTW A microsecond is a millionth of a second.

Pins & GPIO

The arduino has many pins on the outside of the chip. Some of those are dedicated to specific functions:

  • GND - electrical ground
  • VCC - +3.3V
  • RESET - causes the processor to reset

And some are General Purpose Input/Output (GPIO) pins. These can be used as to signal (OUTPUT) to an external device or to read an incoming signal (INPUT) from an external device. The electronics of the process require you to declare which mode it's in: INPUT/OUTPUT. For example:

pinMode(13, OUTPUT);

This sets pin 13 to be an output. That pin is connect to the LED on the arduino and so it be used to blink that LED.

pinMode(13, HIGH);  # turn on the LED
pinMode(13, LOW);   # turn off the LED

The best way to figure out which pin number to use is to:

  • find the pin on the physical board
  • use the "Dnn" or "Ann" number etched on the board
  • to double-check, download the schematic for your board and confirm it's what you need

Note that the physical pin on the microcontroller does NOT match with the "Dnn" number. For example pin D13 is physical pin 17 on the chip.

- John Arrizza