Tips for your next project
Todd Lawall and Andrew Adare
December 1, 2016 @ Solid State Depot
Why we
Love it:
Don't love it:
analogWrite
?BTW: You didn't miss "Arduino 101" - we skipped it.
A few hardware preliminaries
Host software
Firmware
Our specimen: ATMega 328P (Uno, Nano, Pro Mini, ...)
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:
Cons:
#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;
}
Internally triggered by system events
Serial, Wire
)Externally triggered by voltage changes on a pin
noInterrupts()/interrupts()
blockvolatile
so compiler doesn't optimize them awayWhen 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
Why defeat Arduino's hardware abstraction?
Pros:
Cons:
Electrical specs
Programming aids
http://www.nongnu.org/avr-libc/
PWM pins:
Base frequencies:
Default PWM frequencies:
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:
Pin 3:
For a 500 Hz square wave on pin 3 (and 250 Hz on pin 11), use OCR2A = 250 and OCR2B = 125.
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
}
unsigned long
for timersmap
) use integer division internally// 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.
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
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
}
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);
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);
}
screen
utility:screen /dev/cu.usbmodem 115200
serial
module does well for programs interacting with an arduinoThe standard ATMega328 may not fit your needs.
There are other options that the Arduino environment supports.
32-bit systems
8-bit systems
Thanks!!