For playing around with digital and analog output, I built a metronome with an LED. A servo sweeps the needle back and forth at the set tempo. The LED blinks in sync with the servo motion. A potentiometer acts as a dial controlling the tempo, from 40 BPM up to 168 BPM. I wanted to have a power of 2 as the number of possible gradations on the dial, in order to make the math accurate when using int variables. 40 BPM is a common lower bound for a metronome. 40 + 256 would be 296, a very fast tempo that the servo might not keep up with. So I chose 128 gradations, giving a range from 40 to 168.


#include <Servo.h>

const int POT_PIN = A0;
const int SERVO_PIN = 6;
const int LED_PIN = 7;

const int MIN_BPM = 40; // A common lower bound for metronomes
const int MAX_BPM = 168; // Adding a power of two so that the math is accurate (potentiometer input has 1024 possible values)
const int MAX_ANGLE = 61; // Min angle is 1 degree, and I want 60 degrees of precision
const int LED_TICK_MS = 100; // How long (in milliseconds) the lED will flash for

int freq_bpm; // Current metronome frequency (beats per minute)
bool left_side = true; // Whether the servo should tick to the left (true) or to the right (false)

Servo servo;

void setup() {
  pinMode(POT_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

void loop() {
  const unsigned long int loopStart_ms = millis(); // For accuracy of delay(...), track millis spent in executing loop()

  // Read BPM from user
  freq_bpm = MIN_BPM + analogRead(POT_PIN) / 8; // 1024 possible input values, 128 possible bpms.

  // Move servo
  int angleFromCenter = map(freq_bpm, MIN_BPM, MAX_BPM, MAX_ANGLE, 1); // Notice that angle is smaller when bpm is faster
  int angle = left_side ? (90 - angleFromCenter) : (90 + angleFromCenter);
  left_side = !left_side;

  // Flash LED
  digitalWrite(LED_PIN, HIGH);
  digitalWrite(LED_PIN, LOW);
  int interval_ms = 60000 / freq_bpm; // Do the unit analysis, this will be the equation you get.
  delay(interval_ms - (millis() - loopStart_ms)); // I've done the math - given our max BPM, interval_ms will always be greater than LED_TICK_MS

The tricky part of coding was getting the timing right. At first, my metronome would stop after about a minute, because I didn't realize that on the arduino platform, int is 16-bit by default. millis() actually returns an unsigned long integer, but I had been implicitly casting that into a signed 16-bit integer. It would wrap around from ~31000 ms to ~-32000 ms, breaking my delay interval. I'm used to int being 32-bit by default.

Hardware Setup

Setup without housing, while I was debugging the code.