l’amico [francesco] mi ha messo in mano un attiny85 e mi ha insegnato come programmarlo e come farlo suonare. poi mi ha insegnato anche che cos’è il charlieplexing.

c’è voluto un po’ di tempo, ma alla fine ho inventato un progettino che comprendesse quanto avevo imparato.

è quasi natale, allora ho pensato di aggiungere luci e suoni a un addobbo natalizio, ne ho realizzati un po’ e li attaccherò ai pacchetti dei regali per i parenti. le forme sono diverse: una stellina, tre palline collegate, una pallina singola; il contenuto invece è sempre lo stesso.

quando si scuote la stellina parte una musichetta natalizia e si accendono i led nelle punte; ci sono 20 diverse musiche, possono essere riprodotte a caso oppure in sequenza, cambiando una riga nel codice. anche l’ordine di accensione dei led può essere impostato come fisso o casuale dal codice.

le musiche sono codificate in formato rtttl, usato per le suonerie dei vecchi cellulari; la ram dell’attiny è poca, quindi le musiche sono tenute nella memoria del programma grazie alla parola chiave PROGMEM e vengono caricate in ram appena prima di suonarle. la funzione che suona le note si occupa anche di accendere uno dei led che quindi si accendono a tempo.

i led si possono accendere casualmente o secondo una sequenza fissa attivando o disattivando la direttiva alla riga 18, #define RANDOMLED. anche le musiche possono essere scelte casualmente o secondo una sequenza fissa anche in questo caso con la direttiva alla riga 19, #define RANDOMSONG. ho scelto di usare le direttive del preprocessore perché in questo modo viene compilato solo il codice che serve mentre le altre parti vengono ignorate, risparmiando preziosa memoria sull’attiny

grazie al charlieplexing si possono montare fino a 6 led, ma si può anche scegliere di usare un led rgb collegando gli anodi ai piedi 5, 6 e 7 dell’integrato. in entrambi i casi, anche se nello schema sono indicate delle resistenze, io non le ho messe e i led non sono bruciati; il circuito è alimentato a 3 V e la corrente in uscita dall’attiny85 è limitata a 40 mA.

ho trovato qui le funzioni per leggere e riprodurre musica in formato rtttl, e qui un modo comodo per gestire i led in charlieplexing

[francesco] con lo stesso sistema ha costruito un alberello di natale da appendere alla porta dell’ufficio

il circuito comprende:

  • U1: ATtiny85
  • S1: sensore di vibrazioni
  • J1: speaker piezoelettrico
  • R1-3: resistenze 150 Ω, 250 mW
  • LED1-6: led colorati
  • BAT1: portabatteria per CR2016/CR2032

lo schema:

schema

e la breadboard:

breadboard

il codice per arduino:

// inizio implementazione assert con output in compilazione
#define CONCAT_TOKENS( TokenA, TokenB )       TokenA ## TokenB
#define EXPAND_THEN_CONCAT( TokenA, TokenB )  CONCAT_TOKENS( TokenA, TokenB )
#define ASSERT( Expression )                  enum{ EXPAND_THEN_CONCAT( ASSERT_line_, __LINE__ ) = 1 / !!( Expression ) }
#define ASSERTM( Expression, Message )        enum{ EXPAND_THEN_CONCAT( Message ## _ASSERT_line_, __LINE__ ) = 1 / !!( Expression ) }
// fine implementazione assert con output in compilazione

#define ISDIGIT(n) (n >= '0' && n <= '9')
#define NUM_OF_PINS (sizeof(pins) / sizeof(char))

#define DEBOUNCETIME  125
#define SPEAKER_PIN   4
#define BUTTON_PIN    3

#define NUM_OF_SONGS  21
#define NUM_OF_LEDS   6

#define RANDOMLED
#define RANDOMSONG

const int notes[] = { 0,
 262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494, // o=4
 523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988, // o=5
1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, // o=6
2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, // o=7
};


const char song_1[] PROGMEM  = "Silent:d=4,o=5,b=150:f#.,8g#,f#,d#.,p,f#.,8g#,f#,d#.,p,2c#6,c#6,a#.,p,2b,b,f#.,p,2g#,g#,b.,8a#,g#,f#.,8g#,f#,d#.,p,2g#,g#,b.,8a#,g#,f#.,8g#,f#,d#.,p,2c#6,c#6,e.6,8c#6,a#,2b.,2d#6,p,b,f#,d#,f#.,8e,c#,b.4";
const char song_2[] PROGMEM  = "DeckTheHalls:d=4,o=5,b=140:g.6,8f6,e6,d6,c6,d6,e6,c6,8d6,8e6,8f6,8d6,e.6,8d6,c6,b,2c6,g.6,8f6,e6,d6,c6,d6,8e6,8b,8c6,8g,8d6,8e6,8f6,8d6,e6,8c6,8d6,8c6,8a,8b,8f,2c6";
const char song_3[] PROGMEM  = "JingleBells:d=4,o=5,b=180:8e.,8e.,e.,8e.,8e.,e.,8e.,8g.,c,16d.,2e.,8f.,8f.,f,16f.,8f.,8e.,8e.,16e.,16e.,8e.,8d.,8d.,8e.,d.,g.,8e.,8e.,e.,8e.,8e.,e.,8e.,8g.,c,16d.,2e.,8f.,8f.,f,16f.,8f.,8e.,8e.,16e.,16e.,8g.,8g.,8f.,8d.,c.";
const char song_4[] PROGMEM  = "LetItSnow:d=8,o=5,b=120:4c,c6,c6,4a#,4a,4g,4f,2c,c.,16c,4g.,f,4g.,f,4e,2c,4d,d6,d6,4c6,4a#,4a,2g.,e.6,16d6,4c6,c.6,16a#,4a,a.,16g,2f.";
const char song_5[] PROGMEM  = "WeWish:d=8,o=5,b=140:4d,4g,g,a,g,f#,4e,4c,4e,4a,a,b,a,g,4f#,4d,4f#,4b,b,c6,b,a,4g,4e,4d,4e,4a,4f#,2g";
const char song_6[] PROGMEM  = "SantaClausIsComingTonight:d=4,o=5,b=170:g,8e,8f,g,g.,8g,8a,8b,c6,2c6,8e,8f,g,g,g,8a,8g,f,2f,e,g,c,e,d,2f,b4,1c,p,g,8e,8f,g,g.,8g,8a,8b,c6,2c6,8e,8f,g,g,g,8a,8g,f,f,e,g,c,e,d,2f,b4,1c";
const char song_7[] PROGMEM  = "OhTannenbaum:d=4,o=5,b=85:d,8g,16g,g,a,8b,32b,b,16b.,8a,8b,c6,f#,a,g,16d.6,16d.6,8b,e.6,16d.6,8d6,16c.6,c.6,16c.6,8c6,16a.,d.6,8c6,8c6,16b.,b.,d,8g,16g,g,a,8b,32b,b,16b.,8a,8b,c6,f#,a,g.";
const char song_8[] PROGMEM  = "Rudolph:d=4,o=5,b=125:8g.,16a,8p,16g.,e,c6,a,1g,8g.,16a,8g.,16a,g,c6,1b,8f.,16g,8p,16f.,d,b,a,1g,8g.,16a,8g.,16a,g,d6,2c.6";
const char song_9[] PROGMEM  = "TuScendi:d=4,o=5,b=77:8g,g,8a,g,8f,8f,e,8d,8e,8f,8g,8g,8f,8e,d.,8e,d,8e,f,8e,d.,a.,8g,8a,8g,8f,8e,8d,e.,8d,d,8e,f,8e,d.,a.,8g,8a,8g,8f,8e,8d,2c";
const char song_10[] PROGMEM = "FirstDayOfChristmas:d=4,o=5,b=160:8c,8c,f,8f,8f,f,8f,8f,8g,8a,8a#,8g,a.,8p,c,8g,8a,8a#,8g,8c,8c,8g,8a,8a#,8g,c,d,c.,8p,8c,8a#,8a,8g,f,8g,8a,a#,d,d,c,8f,8g,8a,8a#,a,g,2f";
const char song_11[] PROGMEM = "MorgenKommtDerWeihnachtsmann:d=4,o=5,b=70:8g,8g,8d6,8d6,8e6,8e6,d6,8c6,8c6,8b,8b,a,g,8d6,8d6,8c6,8c6,8b,8b,a,8d6,8d6,8c6,8c6,8b,8b,a,8g,8g,8d6,8d6,8e6,8e6,d6,8c6,8c6,8b,8b,a,g";
const char song_12[] PROGMEM = "DingDongMerrilyOnHigh:d=4,o=5,b=230:g6,g6,8a6,8g6,8f#6,8e6,2d.6,d6,e6,g6,g6,f#6,2g6,2g6,g6,g6,8a6,8g6,8f#6,8e6,2d.6,d6,e6,g6,g6,f#6,2g6,2g6,d.7,8c7,8b6,8c7,8d7,8b6,c.7,8b6,8a6,8b6,8c7,8a6,b.6,8a6,8g6,8a6,8b6,8g6,a.6,8g6,f#6,d6,e6,g6,g6,f#6,2g6,2g6";
const char song_13[] PROGMEM = "FelizNavidad:d=4,o=5,b=140:8a,d6,8c#6,8d6,2b.,p,8b,e6,8d6,8b,2a.,p,8a,d6,8c#6,8d6,b.,8g,b,b,8a,8a,8b,8a,g,8g,2f#.";
const char song_14[] PROGMEM = "JingleBellRock:d=4,o=5,b=100:8a.,8c.6,16d.6,16d6,8c.6,8g#,16g#,16c6,16p,d.6,8p,8c6,16a,8b,16a,8g.,2c.6,8p,8c6,16c6,8c.6,16p,8b,16b,8b.,8a,16b,8a,e.,8p,8a,16b,8a,8e.,8g,16p,8a,16b,8a,f.,8p,8d,8e.,16f,16g,8p,8a,16g,8d,16e,16f,16p,2g";
const char song_15[] PROGMEM = "AuldLangSign:d=4,o=5,b=125:a.4,d.,8d,d,f#,e.,8d,e,8f#,8e,d.,8d,f#,a,2b.,b,a.,8f#,f#,d,e.,8d,e,8f#,8e,d.,8b4,b4,a4,2d";
const char song_16[] PROGMEM = "JoyToTheWorld:d=4,o=5,b=112:d6,8c#.6,16b,a.,8g,f#,e,d,8p,8a,b,8p,8b,c#6,8p,8c#6,2d.6,8p,8d6,8d6,8c#6,8b,8a,8a.,16g,8f#,8d6,8d6,8c#6,8b,8a,8a.,16g,8f#,8f#,8f#,8f#,8f#,16f#,16g,a.,16g,16f#,8e,8e,8e,16e,16f#,g,8p,16f#,16e,8d,8d6,8p,8b,8a.,16g,8f#,8g,f#,e,2d";
const char song_17[] PROGMEM = "SoThisIsChristmas:d=8,o=5,b=76:a,b,c#6,a,e.,8p.,e,a,b,c#6,4b,5p,16f#,b,c#6,d6,c#6,4b,16e,16e,c#6,e6,16c#6,16b,4a";
const char song_18[] PROGMEM = "Fr:d=4,o=5,b=140:2g,e,8p,8f,g,2c6,8b,8c6,d6,c6,b,a,2g,p,8b,8c6,d6,c6,b,8a,8a,8g,c6,e.,8f,8a,g,f,e,f,2g,2p,2g,e,8p,8f,g,2c6,8b,8c6,d6,c6,b,a,2g,p,8b,8c6,d6,c6,b,8a,8a,8g,c6,e.,8g,8a,g,f,e,d,2c,p,c,a,a,c6,c6,b,a,g,e,f,a,g,f,2e,p,e,d,d,g,g,b,b,d6,8d6,8b,d6,c6,b,a,g,p,2g";
const char song_19[] PROGMEM = "LastChristmas:d=4,o=5,b=112:e.6,e6,8d6,8p,8a,8e6,8e6,8f#6,d.6,8b,8b,8e6,8e6,f#6,d.6,8b,8c#6,8d6,8c#6,2b.,16e6,f#6,8p,e.6,8p,8b,8f#6,8g6,8f#6,2e6,8d6,8c#6,8d6,8c#6,c#6,8d6,8p,8c#6,8p,a";
const char song_20[] PROGMEM = "White:d=8,o=5,b=56:4f#,g,f#,f,f#,4g,g#,a,p,b,c#6,d6,e6,d6,c#6,b,a,p,d,e,4f#,4f#,b,4a,a,4d6,p,d,e,4f#,4f#,b,b,c#,16c#.,4d";
const char song_21[] PROGMEM = "12DaysofChristmas:d=4,o=5,b=140:8d,8d,d,8g,8g,g,8f#,8g,8a,8b,8c6,8a,b,8p,8c6,d6,8e6,8c6,8b,8g,a,2g,8d6,8d6,d6,8g6,8g6,g6,8f#6,8g6,8a6,8b6,8c7,8a6,2b6,d7,8a,8b,c6,8b6,8c7,d7,8e7,8c7,8b6,8g6,a6,2g.6";//,8f#,8f#,f#,8b,8b,b";

const char* const mySongs[NUM_OF_SONGS] PROGMEM = {
  song_1,  // ok
  song_2,  // ok
  song_3,  // ok
  song_4,  // ok
  song_5,  // ok
  song_6,  // ok
  song_7,  // ok
  song_8,  // ok
  song_9,  // ok
  song_10, // uguale a 21
  song_11, // ok
  song_12, // ok
  song_13, // ok
  song_14, // JingleBellRock:d=4,o=5,b=112:8a.,8c,16d,16d6,8c,8g#,16g#,8c6,2d6,16a,8b,16a,8g.,1c6, 8c6,16c6,c6,8b,16b,8b.,8a,16b,8a,2e,8a,16b,8a,8e,8g.,8a,16b,8a,2f,8d,8e.,16f,8g,8a, 16g,8d,16e,8f,2g
  song_15, // ok
  song_16, // ok
  song_17, // ok
  song_18, // ok
  song_19, // ok
  song_20, // ok
  song_21, // uguale a 10
};

const char pins[] = {0, 1, 2};

const char ledPins[NUM_OF_LEDS][2] = {
  {0, 2},
  {0, 1},
  {1, 2},
  {2, 1},
  {1, 0},
  {2, 0},
};

#ifndef RANDOMSONG
byte giro = 0;
#endif

#ifndef RANDOMLED
byte j = 0;
#endif

ASSERT(NUM_OF_LEDS <= (NUM_OF_PINS * (NUM_OF_PINS-1)));

void setup(void) {
  pinMode(SPEAKER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop(void) {
  alloff();
  if ( digitalRead(BUTTON_PIN) == HIGH ) {
    delay(DEBOUNCETIME);
    if ( digitalRead(BUTTON_PIN) == HIGH ) {
      char songs[270];
#ifdef RANDOMSONG
      strcpy_P(songs, (char*)pgm_read_word(&(mySongs[random(0, NUM_OF_SONGS)])));
#else
      strcpy_P(songs, (char*)pgm_read_word(&(mySongs[giro])));
      giro = (giro < (NUM_OF_SONGS - 1)) ? giro + 1 : 0;
#endif
      play_rtttl(songs);
    }
  }
}

void play_rtttl(char *p) { // http://www.interactiondesign.se/wiki/attiny
  // Absolutely no error checking in here
  byte default_dur = 4;
  byte default_oct = 6;
  int bpm = 63;
  int num;
  long wholenote;
  long duration;
  byte note;
  byte scale;

  // format: d=N,o=N,b=NNN:
  // find the start (skip name, etc)

  // legge il titolo
  while(*p != ':') p++;    // ignore name
  p++;                     // skip ':'

  // get default duration
  if(*p == 'd') {
    p++; p++;              // skip "d="
    num = 0;
    while(ISDIGIT(*p)) {
      num = (num * 10) + (*p++ - '0');
    }
    if(num > 0) default_dur = num;
    p++;                   // skip comma
  }

  // get default octave
  if(*p == 'o') {
    p++; p++;              // skip "o="
    num = *p++ - '0';
    if(num >= 3 && num <=7) default_oct = num;
    p++;                   // skip comma
  }

  // get BPM
  if(*p == 'b') {
    p++; p++;              // skip "b="
    num = 0;
    while(ISDIGIT(*p)) {
      num = (num * 10) + (*p++ - '0');
    }
    bpm = num;
    p++;                   // skip colon
  }

  // bpm = quante note da un quarto stanno in un minuto
  wholenote = (60 * 1000L / bpm) * 4  ;  // this is the time for whole note (in milliseconds)

  // now begin note loop
  while(*p) {
    // first, get note duration, if available
    num = 0;
    while(ISDIGIT(*p)) {
      num = (num * 10) + (*p++ - '0');
    }
    
    if (num) duration = wholenote / num;
    else duration = wholenote / default_dur;  // we will need to check if we are a dotted note after

    // now get the note
    note = 0;

    switch(*p) {
      case 'c':
        note = 1;
        break;
      case 'd':
        note = 3;
        break;
      case 'e':
        note = 5;
        break;
      case 'f':
        note = 6;
        break;
      case 'g':
        note = 8;
        break;
      case 'a':
        note = 10;
        break;
      case 'b':
        note = 12;
        break;
      case 'p':
      default:
        note = 0;
    }
    p++;

    // now, get optional '#' sharp
    if(*p == '#') {
      note++;
      p++;
    }

    // now, get optional '.' dotted note
    if(*p == '.') {
      duration += duration/2;
      p++;
    }
  
    // now, get scale
    if(ISDIGIT(*p)) {
      scale = *p - '0';
      p++;
    } else {
      scale = default_oct;
    }

    if(*p == ',')
      p++;       // skip comma for next note (or we may be at the end)

    // now play the note
    if(note) {
      freqout(notes[(scale - 4) * 12 + note], duration);
    } else {
      delay(duration);
    }
  }
}

void freqout(int freq, int t) {
  freqout(freq, t, SPEAKER_PIN);
}

void freqout(int freq, int t, byte speaker) { // http://www.arduino.cc/playground/Main/Freqout
#ifdef RANDOMLED
  turnon(random(0, NUM_OF_LEDS - 1));
#else
  turnon(j);
#endif
  const int hperiod = 500000 / freq - 7;              // subtract 7 us to make up for digitalWrite overhead
  const long cycles = ((long)freq * (long)t) / 1000;  // calculate cycles

  pinMode(SPEAKER_PIN, OUTPUT);                       // turn on output pin
  for (long i = 0; i <= cycles; i++) {                // play note for t ms  - SOFTWARE PWM?
    digitalWrite(SPEAKER_PIN, HIGH);
    delayMicroseconds(hperiod);
    digitalWrite(SPEAKER_PIN, LOW);
    delayMicroseconds(hperiod - 1);                   // - 1 to make up for digitaWrite overhead
  }
  pinMode(SPEAKER_PIN, INPUT);                        // shut off pin to avoid noise from other operations
#ifndef RANDOMLED
  alloff();
  j = (j < (NUM_OF_LEDS - 1)) ? j + 1 : 0;
#endif
}

void turnon(int led) { // http://www.instructables.com/id/CharliePlexed-LED-string-for-the-Arduino/
  // This turns on a certian led from the list of leds
  int pospin = ledPins[led][0];
  int negpin = ledPins[led][1];
  pinMode (pospin, OUTPUT);
  digitalWrite (pospin, HIGH);
  pinMode (negpin, OUTPUT);
  digitalWrite (negpin, LOW);
}

void alloff() { // http://www.instructables.com/id/CharliePlexed-LED-string-for-the-Arduino/
  // This turns all the LED's off
  for(int i = 0; i < NUM_OF_PINS; i++)
    pinMode (pins[i], INPUT);
}