Arduino 201

Tips for your next project

Todd Lawall and Andrew Adare

December 1, 2016 @ Solid State Depot

The Arduino IDE

Why we

Love it:

  • Very approachable
  • Free and open
  • Community
  • Documentation
  • Diverse HW support

Don't love it:

  • Lousy editor
  • Not-quite-C++
  • Bad examples around
  • Code can be slow
  • analogWrite?


BTW: You didn't miss "Arduino 101" - we skipped it.

outline

A few hardware preliminaries

  • Powering your project
  • Current considerations, large loads
  • Overview of MCU peripherals

outline

Host software

  • Using the Arduino CLI and your own editor
  • Writing custom library classes
  • Communicating externally

outline

Firmware

  • Coding outside loop()
  • When and how to avoid delay()
  • Integer math: choosing datatypes and keeping precision
  • Interrupts
  • timer configuration and gotchas
  • AVR-specific hacks: registers, datasheet, AVR libc
  • Sleeping

Powering your project

  • Voltage is easy to get right
  • Current not so much
  • USB can only source 500mA
  • Use another source if you're powering more than the Arduino
  • Power conflicts can occur - USB port drops off
  • If conflict happens, power project with a battery

Driving large loads

  • Each pin only drives 40mA
  • You cannot drive larger loads with that
  • E.g. An LED draws from 5mA to 20mA
  • You can drive the gate of a transistor
  • Relays usually take more current than this
  • Relays can be driven using a transistor, but also need a shunt diode to protect against back EMF.
  • Larger transistors include MOSFETs and IGBTs
  • Solid State Relays are an alternative

MCU Hardware overview

Our specimen: ATMega 328P (Uno, Nano, Pro Mini, ...)

  • 20 GPIO pins, all with pin-change interrupt support
  • 3 Timer/counter units: 0 (8 bit), 1 (16 bit), 2 (8 bit)
  • 6 PWM channels
  • 1 A/D converter (10 bits, 6 channels)
  • Serial peripherals: I2C, SPI, and USART
  • EEPROM
  • Analog Comparator

Responding to changes

The polling approach

Consider the stateChangeDetection example:

void loop() {
  buttonState = digitalRead(buttonPin);
  if (buttonState != lastButtonState) {
    // Conditional logic here (LOW-HIGH vs. HIGH-LOW)...
  }
  lastButtonState = buttonState;
  // Possibly several other tasks here....
}

Pros:

  • Straightforward
  • No chance of corruption

Cons:

  • Delayed response
  • Inefficient
  • No priority control

Responding to Changes

The Interrupt-driven Approach

#define BUTTON_PIN 2;
#define LED_PIN 13;

volatile uint8_t pinState = LOW;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), toggle, CHANGE);
}

void loop() {
  digitalWrite(LED_PIN, pinState);
}

void toggle() {
  pinState = !pinState;
}

Two kinds of Interrupts

Internally triggered by system events

  • Timers, serial events, A/D complete, etc.
  • Arduino mostly handles these for us (Serial, Wire)

Externally triggered by voltage changes on a pin

  • Pin-change interrupts (available on every GPIO pin)
    • Only fired on change, BYO logic to do more
  • Arduino pins 2, 3 (PD2/INT0, PD3/INT1) are special:
    • They have top priority
    • Dedicated HW to trigger on rise, fall, or change
  • Both types are handled by "callbacks" called ISRs.

Interrupt Programming Guidelines:

  • Keep them short and sweet
  • put sensitive code (e.g. assignments) in a noInterrupts()/interrupts() block
  • (it's like AVR's ATOMIC_BLOCK, the most epic preprocessor definition ever)
  • Declare variables changed in interrupts as volatile so compiler doesn't optimize them away

When to use delay()?

Not often. delay() blocks everything but interrupts. It's especially bad with polling patterns.

Better to do something like this:

unsigned long timeMarker = 0;
const unsigned long dt = 20;
void loop() {
  if (millis() - timeMarker >= dt) {
    timeMarker = millis();
    // Put code here that runs every dt milliseconds
  }
}

Teensyduino has elapsedMillis and elapsedMicros timers, which account for code run time to keep a tight schedule

AVR Programming in Arduino

Why defeat Arduino's hardware abstraction?

Pros:

  • Fine-grained control
  • Significant performance gains
  • Transparency: WYSIWYG bare-metal code

Cons:

  • Non-portable
  • Looks esoteric
  • Mixing APIs

Timers

  • Timers in AVR are more than a clock
  • internal clock source counting
  • external pin change counting
  • capture - timer value is copied at time of an event, like a stopwatch
  • compare - timer throws interrupt when a specific value is reached
  • When they roll over (overflow) they can fire an interrupt.
  • Prescaler/postscaler - these are ways to stretch out the clock

How to read the datasheet

Electrical specs

  • Current limits
  • Voltage limits

Programming aids

  • Peripheral information
  • Timer/counter register tables
  • Interrupts

AVR Glibc avrfreaks

http://www.nongnu.org/avr-libc/

Sleep

  • Sleeping conserves the battery
  • You will need an interrupt source to wake
  • Don't forget the on board regulator

http://playground.arduino.cc/Learning/ArduinoSleepCode
http://www.engblaze.com/hush-little-microprocessor-avr-and-arduino-sleep-mode-basics/

ATMega328 PWM info

PWM pins:

  • 5, 6 (Timer 0)
  • 9, 10 (Timer 1)
  • 3, 11 (Timer 2)

Base frequencies:

  • 31350 Hz for pins 3, 9, 10, and 11
  • ~62.5 kHz for pins 5, 6

Default PWM frequencies:

  • 980 Hz - pins 5, 6
  • ~490 Hz - pins 9, 10, 3, 11

ATMega328 PWM configuration

Let's say you wish to output a PWM frequency other than the default.

Warning: changing timers affects more than one pin, and can silently change built-in functions.

Reconfiguring Timer 1 does not affect millis() or delay() (but borks the Servo library)

Example: Set up Timer 2 for phase-correct (count-up-count-down) PWM mode, with variable PWM frequency. With the WGM22 and COM2A0 bits set, OC2A toggles based on the OCR2A value.

Pin 11 frequency and duty cycle:

  • f = 16MHz / 64 / (2*OCR2A) / 2
  • d = 50% (fixed)

Pin 3:

  • f = 16MHz / 64 / OCR2A / 2
  • d = OCR2B/OCR2A

For a 500 Hz square wave on pin 3 (and 250 Hz on pin 11), use OCR2A = 250 and OCR2B = 125.

Speeding up GPIO

Direct register i/o versus arduino

AVR register R/W takes 1 instruction ($\lt$ 0.1 $\mu$s @ 16MHz)!

digitalRead/Write can take 50 cycles or more (4-5 us)

Timing comparison: toggle pin 4 from the CPU

void loop() {
  digitalWrite(4, !digitalRead(4)); // 56.58 kHz on my scope
}
 // vs.
void loop() {
  PORTD ^= (1 << PD4); // 444.4 kHz, ~8x faster
}

Math tips

  • always use unsigned long for timers
  • Floating-point math is slow and inefficient on AVRs (no hardware FPU).
  • Beware: some Arduino functions (e.g. p map) use integer division internally
  • Scale up your ints to keep precision:
// convert ADC from a turnpot to an angle in degrees
float angle = (float)map(adc, ADC_FULL_LEFT, ADC_FULL_RIGHT, 100*DEG_FULL_LEFT, 100*DEG_FULL_RIGHT)/100;

analogRead and Noise

Low-pass filtering is easily implemented in firmware! Consider the EWMA for time-series ADC measurements $x_{t}$:

$$ S_t = \alpha x_{t−1} + (1−\alpha) S_{t−1}, \quad 0 \lt \alpha \leq 1. $$

Let's implement this with $\alpha = 1/16$ using integer math.

To improve precision, work with $16S_t$ instead of $S_t$:

$$ 16S_t = x_{t−1} + 16S_{t−1} - (16S_{t−1} - 16/2)/16 $$

void ewma(unsigned int x, unsigned int &x16)
{
  x16 = x + x16 - ((x16 - 8) >> 4);
}

When using the measurement, simply divide by 16.

The Arduino CLI

I don't care much for Arduino as an IDE, and mainly just use it as a convenient toolchain. I have these in my .bashrc

alias arduino='/Applications/Arduino.app/Contents/MacOS/Arduino'
alias uno='arduino --board arduino:avr:uno'
alias nano='arduino --board arduino:avr:nano:cpu=atmega328'
alias t3='arduino --board teensy:avr:teensy31'

So I can do

uno --port /dev/cu.usbmodem1234 --upload mysketch.ino

Serial Coms (arduino side)

Setup of the hardware serial port is as simple as:

void setup()
{
    Serial.begin(115200);
}

Reading from it can be done by checking for available chars

if( Serial.available() > 0 )
{
  Serial.readBytesUntil('\r',inputBuffer,127);
  // do something with whatever just came in
}

Serial Coms (arduino side)

Writing to the serial port is as easy as using the print() and println() methods

Serial.print("Hello world!");
Serial.println("Bob's your uncle!");
Serial.print(someInteger);

Serial Coms (arduino side)

Printing multiple values can be tedious, especially if you want a specific format.

Serial.print("Count is ");
Serial.print(myCounter);
Serial.print(" out of ");
Serial.println(total);

Compared to

serialPrintf("Count is %d out of %d", myCounter, total);

Serial printf version

#include 
#include 
#define PRINTF_BUF 80 // define the tmp buffer size (change if desired)
void serialPrintf(const char *format, ...)
{
  char buf[PRINTF_BUF];
  va_list ap;
  va_start(ap, format);
  vsnprintf(buf, sizeof(buf), format, ap);
  for(char *p = &buf[0]; *p; p++) // emulate cooked mode for newlines
  {
    if(*p == '\n')
    {
      Serial.write('\r');
    }
    Serial.write(*p);
  }
  va_end(ap);
}

Serial Coms (host side)

  • For serial coms, I use the venerable screen utility:
    screen /dev/cu.usbmodem 115200
  • If you need to edit the string before sending the characters down, use the Arduino Serial Monitor
  • Python serial module does well for programs interacting with an arduino

Scaling up or down

The standard ATMega328 may not fit your needs.

  • Memory may be too small
  • Physical size may be too big
  • Might not have enough computing power

There are other options that the Arduino environment supports.

ATMega Alternatives

32-bit systems

  • Teensy (ARM based)
  • Feather (ARM M0 based)
  • ChipKIT (PIC32 based)
  • ESP8266 (Tensilica based, with WiFi)

8-bit systems

  • ATTiny85 (smaller AVR, 8 pins)
  • PIC18F2550 (USB built in)

http://playground.arduino.cc/Main/ArduinoOnOtherAtmelChips
https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls

Where to buy

  • SparkFun (one of our favorite places)
  • Adafruit
  • Seeedstudio
  • iTead mall
  • eBay (caveat emptor - faulty parts, long shipping times)
  • AliExpress (caveat emptor - long shipping times)

Takeaways

  • HW abstraction is a great thing...
  • But (judiciously) busting it is useful!
  • Stop thinking of your MCU as just a CPU with i/o pins! Program the timer/counters and interrupt system too.
  • Arduino is not dying out - the latest devices are still targeting the platform.

Thanks!!