Arduino Multi-function Shield Bonus Sketches

As bonus sketches become available, they will be published on this page. To use them, you’ll need to first download the Arduino multi-function shield library in the Introduction.

Arduino Metronome

The Arduino metronome application allows music students to work on their timing skills by displaying and sounding a beat, where the BPM rate and time signature are configurable by the user. Button 1 starts/stops the metronome, buttons 2 and 3 configure the BPM and time signature, respectively. The configuration settings are retained when the Arduino is powered off.

#include <MultiFuncShield.h>
#include <avr/eeprom.h>
// Metronome
typedef struct Config {
  char appNameAndVersion[16];
  byte  signature;    // must be between 2 to 8
  unsigned int tempo; // must be between 60 to 240
};

enum MetronomeDisplayModeValues
{
  DISPLAY_TIMER,
  DISPLAY_SIGNATURE,
  DISPLAY_TEMPO
};

enum MetronomeStatusValues
{
  METRO_OFF,
  METRO_ON
};

Config currentConfig;
byte beatCounter;
byte displayMode = DISPLAY_TIMER;
byte statusMode = METRO_OFF;
const byte led[] = {13,12,11,10};


void setup() {
  // put your setup code here, to run once:
  
  loadConfig();
  MFS.initialize();
  MFS.write(beatCounter);
}


void loop() {
  const unsigned int oldTempo = currentConfig.tempo;
  const byte oldSignature = currentConfig.signature;
  const byte btn = MFS.getButton();

  switch (displayMode)
  {
    case DISPLAY_TIMER:
      if (btn == BUTTON_1_PRESSED)
      {
        if (statusMode == METRO_OFF)
        {
          statusMode = METRO_ON;          // Turn metronome on, if off.
        }
        else if (statusMode == METRO_ON)
        {
          statusMode = METRO_OFF;         // Turn metronome off, if on.
          beatCounter = 0;
          MFS.write(beatCounter);
          clearLeds();
        }
      }
      else if (btn == BUTTON_2_PRESSED)
      {
        displayMode = DISPLAY_TEMPO;
        MFS.write((int)currentConfig.tempo);
        MFS.blinkDisplay(DIGIT_ALL, ON);
      }
      else if (btn == BUTTON_3_PRESSED)
      {
        displayMode = DISPLAY_SIGNATURE;
        displaySignature(currentConfig.signature);
        MFS.blinkDisplay(DIGIT_ALL, ON);
      }
      break;
    case DISPLAY_SIGNATURE:
      if (btn == BUTTON_1_PRESSED)
      {
        displayMode = DISPLAY_TIMER;
        MFS.write(beatCounter);
        MFS.blinkDisplay(DIGIT_ALL, OFF);
        saveConfig();                       // Write config to eeprom.
      }
      else if (btn == BUTTON_2_PRESSED)
      {
        currentConfig.signature--;          // Change to lower time signature
        if (currentConfig.signature <2)
        {
           currentConfig.signature = 2;
        }
      }
      else if (btn == BUTTON_3_PRESSED)
      {
        currentConfig.signature++;          // Change to higher time signature
        if (currentConfig.signature > 8)
        {
          currentConfig.signature = 8;
        }
      }

      if (oldSignature != currentConfig.signature)
      {
        displaySignature(currentConfig.signature);
      }
      break;
      
    case DISPLAY_TEMPO:
      if (btn == BUTTON_1_PRESSED)
      {
        displayMode = DISPLAY_TIMER;
        MFS.write(beatCounter);
        MFS.blinkDisplay(DIGIT_ALL, OFF);
        saveConfig();                       // Write config to eeprom.
      }
      else if (btn == BUTTON_2_PRESSED)
      {
        currentConfig.tempo--;              // Reduce tempo by 1
      }
      else if (btn == BUTTON_3_PRESSED)
      {
        currentConfig.tempo++;              // Increase tempo by 1
      }
      else if (btn == BUTTON_2_LONG_PRESSED)
      {
        currentConfig.tempo = ((currentConfig.tempo-2) / 2) * 2;    // Reduce tempo to next even number
      }
      else if (btn == BUTTON_3_LONG_PRESSED)
      {
        currentConfig.tempo = ((currentConfig.tempo+2) / 2) * 2;    // Increase tempo to next even number
      }

      if (oldTempo != currentConfig.tempo)
      {
        if (currentConfig.tempo < 60)         {           currentConfig.tempo = 60;         }         else if (currentConfig.tempo > 240)
        {
          currentConfig.tempo = 240;
        }
        MFS.write((int)currentConfig.tempo);
      }
      break;
  }

  switch (statusMode)
  {
    case METRO_OFF:
      break;
    case METRO_ON:
      // Has millisecond countdown timer reached 0?
      if (MFS.getTimer() == 0)
      {
        // Reset countdown timer.
        MFS.setTimer(240000 / (currentConfig.tempo * (currentConfig.signature <= 4 ? 4 : 8)));
        if (beatCounter < 1 || beatCounter >= currentConfig.signature)
        {
        // Sound beeper every time beat counter is reset.
          MFS.beep(2);
          beatCounter = 1;
        }
        else
        {
          // bump beat counter
          beatCounter++;
        }

        if (displayMode == DISPLAY_TIMER)
        {
          MFS.write(beatCounter);
        }
      }
      break;
  }

  if (beatCounter != 0)
  {
    clearLeds();
    digitalWrite(led[(beatCounter-1) & 3], beatCounter == 1 ? 0 : millis() & 7);  // use crude PWM to change brightness
  }
}


// -------------------------------------------------------------
float displaySignature(byte signature)
{
  float sig = (float)signature;

  if (signature <= 4)
  {
    sig = sig + 0.4;
  }
  else
  {
    sig = sig + 0.8;
  }
  MFS.write(sig);
}


// -------------------------------------------------------------
void clearLeds()
{
  for (int i=0; i < sizeof(led); i++)
  {
    digitalWrite(led[i], 1);
  }
}


// -------------------------------------------------------------
const char appNameAndVersion[] = "Metronome V1.0";

void loadConfig()
{
  // Attempt to load config from EEPROM
  eeprom_read_block(&currentConfig, (void *)0, sizeof (Config));

  if (strcmp(currentConfig.appNameAndVersion, appNameAndVersion) != 0)
  {
    // Config not found in eeprom, so set default values here.
    strcpy(currentConfig.appNameAndVersion, appNameAndVersion);
    currentConfig.signature = 4;
    currentConfig.tempo = 80;
  }
}

void saveConfig()
{
  eeprom_write_block(&currentConfig, (void *)0, sizeof (Config));
}


Download the source code here.

Don’t forget to download and install the shield library before using!

17 thoughts on “Arduino Multi-function Shield Bonus Sketches

  1. Riyanto

    Thank’s this a great work
    but i’m still nube to programe arduino, can u telling me how to display Alpabat & number in the same time,
    Like this “A100” or “100B”.

    Sorry my english so bad.

    tq

    Reply
    1. Kashif Baig Post author

      Hi Riyanto

      You can display alphanumeric values already by doing MFS.write(“A100”); but you will need to do your own string formatting if you want to do something more complex.

      Hope that helps.

      Reply
  2. Riyanto

    Thank’s Kashif

    it’s worked, but i can’t display the value of preset_pot sketch with alphabet on left seven segment,
    what’s i to do now?

    Tq

    Reply
    1. Kashif Baig Post author

      Try changing your loop function to look like this:

      void loop() {

      char buffer[6];
      sprintf(buffer, "A%d", analogRead(POT_PIN));
      MFS.write(buffer);

      delay(100);
      }

      That should work for you.

      Reply
    2. matthew D Murphy

      if the shield is not plugged in just right it acts goofy. remember to cut the extra leads

      Reply
  3. Riyanto

    Thank you again
    What you write is very useful for me, I have a small project for my hobby, maybe one day I’ll show you what I made.
    ” Terima Kasih Banyak ” Indonesian people say

    Reply
    1. Kashif Baig Post author

      Hi Sergio

      Thanks! You may certainly try tone(), although I haven’t tried using it myself, so I can’t say if it will interfere with how some of the library functions work.

      Reply
  4. Herus Munhoz

    Hi, I’m having problems with the code.
    It appears “ser_open (): can not open device” \\. \ COM1 “: The system can not find the file specified.”
    What could be happening? The code is also ignoring the ‘typedef’.

    Reply
    1. N. A. Gribble

      Hi do you have plans for a bonus Sketch for a simple Greenhouse monitor-watering project. Everything seems to be there on the board it just needs the code?

      Reply
  5. CHRISTOPHER M

    This is fantastic, thank you. I want to incorporate this into a realtime sequencer at a division of 480ppq. How would I do that?

    Reply
    1. Kashif Baig Post author

      Hello Christopher. I have another version of the code that generates a midi clock signal, but you’ll need a midi shield that uses midi cables to connect to another musical device. If that’s what you’re looking for, I’ll make the code available for download.

      Reply
    1. Cohesive Computing

      Thanks Roberto. You should be able to find a schematic of the shield to help determine suitability with FPGA. Let us know how it works out.

      Reply
  6. RJC

    Hi Kashif,
    I am working with a research student and we are trying to get the metronome up and running. Do we have to input certain values to make this code work? We are kind of newbies to this board. We had uploaded the provided code and we don’t get anything on the LED display and the buttons are not functional. Please let me know if you can assist. This is the code we are currently using.
    Thanks!

    #include
    #include
    // Metronome
    typedef struct Config {
    char appNameAndVersion[16];
    byte signature; // must be between 2 to 8
    unsigned int tempo; // must be between 60 to 240
    };

    enum MetronomeDisplayModeValues
    {
    DISPLAY_TIMER,
    DISPLAY_SIGNATURE,
    DISPLAY_TEMPO
    };

    enum MetronomeStatusValues
    {
    METRO_OFF,
    METRO_ON
    };

    Config currentConfig;
    byte beatCounter;
    byte displayMode = DISPLAY_TIMER;
    byte statusMode = METRO_OFF;
    const byte led[] = {13,12,11,10};

    void setup() {
    // put your setup code here, to run once:

    loadConfig();
    MFS.initialize();
    MFS.write(beatCounter);
    }

    void loop() {
    const unsigned int oldTempo = currentConfig.tempo;
    const byte oldSignature = currentConfig.signature;
    const byte btn = MFS.getButton();

    switch (displayMode)
    {
    case DISPLAY_TIMER:
    if (btn == BUTTON_1_PRESSED)
    {
    if (statusMode == METRO_OFF)
    {
    statusMode = METRO_ON; // Turn metronome on, if off.
    }
    else if (statusMode == METRO_ON)
    {
    statusMode = METRO_OFF; // Turn metronome off, if on.
    beatCounter = 0;
    MFS.write(beatCounter);
    clearLeds();
    }
    }
    else if (btn == BUTTON_2_PRESSED)
    {
    displayMode = DISPLAY_TEMPO;
    MFS.write((int)currentConfig.tempo);
    MFS.blinkDisplay(DIGIT_ALL, ON);
    }
    else if (btn == BUTTON_3_PRESSED)
    {
    displayMode = DISPLAY_SIGNATURE;
    displaySignature(currentConfig.signature);
    MFS.blinkDisplay(DIGIT_ALL, ON);
    }
    break;
    case DISPLAY_SIGNATURE:
    if (btn == BUTTON_1_PRESSED)
    {
    displayMode = DISPLAY_TIMER;
    MFS.write(beatCounter);
    MFS.blinkDisplay(DIGIT_ALL, OFF);
    saveConfig(); // Write config to eeprom.
    }
    else if (btn == BUTTON_2_PRESSED)
    {
    currentConfig.signature –; // Change to lower time signature
    if (currentConfig.signature 8)
    {
    currentConfig.signature = 8;
    }
    }

    if (oldSignature != currentConfig.signature)
    {
    displaySignature(currentConfig.signature);
    }
    break;

    case DISPLAY_TEMPO:
    if (btn == BUTTON_1_PRESSED)
    {
    displayMode = DISPLAY_TIMER;
    MFS.write(beatCounter);
    MFS.blinkDisplay(DIGIT_ALL, OFF);
    saveConfig(); // Write config to eeprom.
    }
    else if (btn == BUTTON_2_PRESSED)
    {
    currentConfig.tempo–; // Reduce tempo by 1
    }
    else if (btn == BUTTON_3_PRESSED)
    {
    currentConfig.tempo++; // Increase tempo by 1
    }
    else if (btn == BUTTON_2_LONG_PRESSED)
    {
    currentConfig.tempo = ((currentConfig.tempo-2) / 2) * 2; // Reduce tempo to next even number
    }
    else if (btn == BUTTON_3_LONG_PRESSED)
    {
    currentConfig.tempo = ((currentConfig.tempo+2) / 2) * 2; // Increase tempo to next even number
    }

    if (oldTempo != currentConfig.tempo)
    {
    if (currentConfig.tempo 240)
    {
    currentConfig.tempo = 240;
    }
    MFS.write((int)currentConfig.tempo);
    }
    break;
    }

    switch (statusMode)
    {
    case METRO_OFF:
    break;
    case METRO_ON:
    // Has millisecond countdown timer reached 0?
    if (MFS.getTimer() == 0)
    {
    // Reset countdown timer.
    MFS.setTimer(240000 / (currentConfig.tempo * (currentConfig.signature <= 4 ? 4 : 8)));
    if (beatCounter = currentConfig.signature)
    {
    // Sound beeper every time beat counter is reset.
    MFS.beep(2);
    beatCounter = 1;
    }
    else
    {
    // bump beat counter
    beatCounter++;
    }

    if (displayMode == DISPLAY_TIMER)
    {
    MFS.write(beatCounter);
    }
    }
    break;
    }

    if (beatCounter != 0)
    {
    clearLeds();
    digitalWrite(led[(beatCounter-1) & 3], beatCounter == 1 ? 0 : millis() & 7); // use crude PWM to change brightness
    }
    }

    // ————————————————————-
    float displaySignature(byte signature)
    {
    float sig = (float)signature;

    if (signature <= 4)
    {
    sig = sig + 0.4;
    }
    else
    {
    sig = sig + 0.8;
    }
    MFS.write(sig);
    }

    // ————————————————————-
    void clearLeds()
    {
    for (int i=0; i < sizeof(led); i++)
    {
    digitalWrite(led[i], 1);
    }
    }

    // ————————————————————-
    const char appNameAndVersion[] = "Metronome V1.0";

    void loadConfig()
    {
    // Attempt to load config from EEPROM
    eeprom_read_block(&currentConfig, (void *)0, sizeof (Config));

    if (strcmp(currentConfig.appNameAndVersion, appNameAndVersion) != 0)
    {
    // Config not found in eeprom, so set default values here.
    strcpy(currentConfig.appNameAndVersion, appNameAndVersion);
    currentConfig.signature = 4;
    currentConfig.tempo = 80;
    }
    }

    void saveConfig()
    {
    eeprom_write_block(&currentConfig, (void *)0, sizeof (Config));
    }

    Reply
    1. Kashif Baig Post author

      Hi Richard, it’s been a while since I did any work on this.

      Can you confirm if you’ve uploaded the source to an Uno R3 (it wont work on an Uno R4)? Also, are you using the most recent multifunction shield library available from this website? There is a version of this library available using the Arduino IDE by someone else, and this metronome likely wont work with it.

      Can you confirm if you’ve been able to get any of the other examples working with your board?

      Reply
  7. DAVID DEWINE

    I am looking for assistance in controlling relays with the countdown timer. I need relay 1 to trigger at the start of the timer for 10 seconds and then off again, and relay 2 to trigger when the countdown reaches 0 for 10 seconds then off again.

    Reply

Leave a Reply

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