GalvaCuckoo

De Wiki LOGre
Aller à : navigation, rechercher


Nouveau projet néo-ringard : le GalvaCuckoo

Genèse

Un supporter du LOG est passé nous donner une tonne de composants et autres vieilleries. Parmi ses cadeaux, un stock de vieux galvanomètres (A, V, KW), de véritables antiquités de chez PEKLY, marque célèbre de galvanomètres du siècle dernier.

Nul doute qu'aucune de nos Arduineries n'aurait utilisé ces cadrans pour un projet standard, avec la confirmation de leurs inaptitudes à la geekerie par la remarque d'un charmant docteur ingénieur du cru : "à quoi ça sert ? comment ça marche ?"

Que peut-on afficher avec des galva en les détournant de leurs fonctions premières ? Donc, après le thermomètre d'extérieur revisité, j'ai eu l'idée de l'horloge galvanique. Mais ce n'était pas assez ringard, donc pourquoi pas un coucou suisse galvanique ? Dont acte !!

P1000131.JPG

Matériel nécessaire

  • un arduino UNO
  • une horloge temps réel (sauvegardée par batterie)
  • un système de lecture de fichiers audio (mp3 par simplicité).
  • quelques micro-switches en DIP, capa, résistances…

Transformation des galvanomètres

Petit rappel grâce à wikipedia:

Un galvanomètre à cadre mobile, est constitué d'une bobine montée sur pivot, baignant dans le champ magnétique d'un aimant fixe. S ur cette bobine est fixée l'aiguille de visualisation et un ressort chargé de rappeler l'équipage mobile dans la position indiquant le zéro. La bobine de faible impédance est branchée en série dans le circuit dans lequel circule le courant à mesurer (ou le courant de commande dans mon cas). Le courant, en traversant la bobine, induit dans celle-ci un champ électromagnétique, ce qui provoque un pivotement par répulsion des champs magnétiques. Plus le courant est intense plus la bobine bascule. Il ne fonctionne qu'en courant continu.

Dans le stock donné, je trouve des voltmètres et ampèremètres, donc il faut les adapter pour les faire fonctionner avec les sorties PWM de l'Arduino.

C'est en fait très simple, il faut trouver la bonne valeur de la résistance interne à insérer avant la bobine pour que la valeur 255 (Vcc) de l'analogWrite soit au maximum de l'échelle. Après essai, une résistance de 47 kohms est bien adaptée.

Restait à imprimer les écrans gradués en H, Mn et Secondes. Merci à Pekly, il y avait des écrans nativement gradués en 15 graduations (donc pour les heures) et en 300 pour les 60 minutes. Avec Pixelmator (photoshop pour Mac), ce fut très simple. Quant à celui des secondes, il était déjà gradué de 0 à 60, il ne restait qu’à imprimer les valeurs et l’unité de mesure !!


Migration d'une horloge de base vers un coucou suisse.

Trouver un chant de coucou suisse sur internet, c'est l'affaire de quelques secondes avec le moteur de recherche. Pour le faire jouer au bon moment, la première idée fut d'acheter via eBay, un micro-lecteur de mp3 à 1,19$ (oui, à ce prix là, livré en France, ça fonctionne).

Ensuite, on explose le boitier pour se câbler directement sur l’électronique et externaliser les boutons qui seront ensuite pilotés par les sorties digitales de l'Arduino. Sauf, que cela n'a pas fonctionné comme prévu. Une fois les fils connectés aux traces sur le circuit imprimé, les fils ont fait antenne, et le fonctionnement fut chaotique. Donc la solution à 1,19$ fut abandonnée.

De retour sur eBay, on trouve un shield mp3 qui peut être livré en France pour 9,07€. Pas de quoi se prendre la tête d'autant plus qu'avec la bonne bibliothèque, on peut choisir la piste mp3 que l'on veut et la jouer sans délai. Le shield geeetech.com à base de VS1053 est un clone de la Sparkfun Mp3shield, bien documentée et supportée par une bibliothèque.

Horloge temps réel

Module RTC à base de DS1307 et fonctionnant sur BUS I2C. Avec l'utilisation de la bibliothèque adaptée (Wire.h) et les exemples du playground Arduino, pas de problèmes particuliers.


Expérimentation

Avec une approche phasée due aux temps d'approvisionnement des modules, j'ai d'abord expérimenté l'horloge RTC avec affichage sur le terminal. Ce fut le plus facile.

Ensuite, modification des galvanomètres; Pas trop compliqué sauf que les aiguilles sont très fragiles. En effet, un galva s'est retrouvé privé d'aiguille. Avec les sorties PWM, pas de problèmes pour afficher l'heure sur les galva. Petit problème de linéarité résolu par un tableau de valeurs horaires.

Par contre, il manque une sortie PWM sur le UNO, car le shield mp3 utilise des sorties digitales qui sont également PWM. Donc j’ai opté pour une solution de SoftPWM trouvé dans un github. Solution élégante qui permet d’utiliser une Entrée Analogique en Sortie PWM. Cette sortie SoftPWM sera affectée au secondes.

Affichage AM/PM

Avec un restant de diodes RGB 2812, j’ai fait une barrette de 5 diodes derrière des cabochons en verre teinté du stock de vieilleries. Réutilisation de vieux cabochons qui étaient associées à des ampoules incandescentes qui peuplaient les automatismes du siècle dernier.

Pour commander les 2812, une seule sortie digitale est nécessaire pour moduler les commandes du protocole 2812 et utilisation de la bibliothèque FastLED (bien plus riche que la NeoPixel d’Adafruit).

Changement de modes en dynamique

J’ai associé un ensemble de micro-switches en DIP aux entrées analogiques restantes pour valider :

  • Le tictac d’horloge comtoise (on / off)
  • Le volume du tictac (fort/réduit)
  • Le chant du coucou suisse (1 coup par heure / n coups par heure, avec n = heure)

Fonctionnement

A la mise sous tension, petit chenillard (va et vient) sur les 5 diodes, le coucou sonne l’heure du moment.

  • A chaque heure, le coucou chante (1 fois ou n fois si le switch 1 "Verbose" est sur ON)
  • A 12 heures, c’est le carillon de Westminster (1 fois)
  • A minuit, les 12 coups de BigBen.
  • Le TicTac de l'horloge comtoise peut être inhibé par le switch 2 "TicTac"
  • Le Volume a deux intensités suivant la position du switch 3 "Volume".

Petite singularité, l’horloge va de 1 AM à 13:59 heures et de 2PM à 24:59 !! C’est ma norme !!

Pour changer les types de sonnerie, il suffit de remplacer les pistes audio existantes (en mp3) par de nouvelles et de les nommer ‘track00x.mp3’ sur la carte microSD.

Schéma de connexion

GalvaCuckoo.png

Code arduino

/* GlavaCuckoo
 * version 0.5
 * by Pja - LOGre - Grenoble
 */
#include "Wire.h"
#include "FastLED.h"    
#include <SoftPWM.h>       // SoftPWM library
#include <SPI.h>           // SPI library
#include <SdFat.h>         // SDFat Library
#include <SdFatUtil.h>     // SDFat Util Library
#include <SFEMP3Shield.h>  // Mp3 Shield Library
 
//#define VERBOSE       // for display date and time on terminal
 
// param for RTC
#define DS1307_I2C_ADDRESS 0x68  // This is the I2C address
#define I2C_WRITE Wire.write 
#define I2C_READ Wire.read
 
// Map Meters to Arduino Pins
#define HOURS    5 // orange wire - top meter
#define MINUTES 10 // blue wire - middle meter
#define SECONDS A0 // red wire - lower meter
 
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year, test, zero, curSec, curHour, Rx_byte;
 
//byte MeterHour[13] =  {0, 1,  2, 3, 4,  5,  6,  7,    8,   9,   10,  11,  12};   Value for displaying hour - with correction
byte MeterHour[13] =    {0, 15,31,46, 62, 78, 96, 113, 128, 144, 161, 174, 192};
//byte MeterMinut[7] =  {0, 10, 20,  30,  40, 50,   60};   Value for displaying minut - with correction
byte MeterMinut[7] =    {0, 43, 87, 130, 173, 215, 255};
 
#ifdef VERBOSE
  byte weekday = 0;
  byte monthday = 0;
  const char* days[] =
  {"","Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  const char* months[] =
  {"","January", "February", "March", "April", "May", "June", "July", "August","September", "October", "November", "December"};
#endif
 
// param for 2812 LEDs
#define LED_DT 4         // Data pin #4 to connect to the strip.
#define COLOR_ORDER RGB   // RGB coding
#define LED_TYPE WS2811   // type of LEDS.addLeds
#define NUM_LEDS  5       // strip with 5 Leds
#define led0AM    4       // led green
#define led6AM    3       // led violet
#define led12AM   2       // led yellow
#define led18PM   1       // led brown
#define led24PM   0       // led red 
struct CRGB leds[NUM_LEDS];    // Initiate 'leds' as a structure.
 
// param for MP3Shield
 
#define cliclac    1  // track001.mp3  tictac from horloge comtoise
#define coucou     3
#define chime      2  //westminster chime
#define cliclac2   4
#define bigben     5
#define bigbenmidnight     6	    // 12 hits of BigBen tower
#define button_silence    A3      // pin A3 for switch#1 silence = 1 (tictac)
#define button_madcuckoo  A2      // pin A2 for switch#2 madcuckoo = 1 (verbose)
#define button_volumetic  A1      // pin A1 for switch#3 for setting volume of tics  (volume)
#define dureeCoucou    1000
#define dureeChime    16000       // 16 secondes 
#define dureeBigBen   54000       // 54 secondes 
int done_once = 0, RingType, value, repeat;
 
SdFat sd; // Create object to handle SD functions
SFEMP3Shield MP3player; // Create Mp3 library object
// These variables are used in the MP3 initialization to set up
 
uint8_t volume = 10;     		// MP3 Player volume 0=max, 255=lowest (off)
uint8_t volumeTic = 50;  		// lower for Tics
const uint16_t monoMode = 1; 	// Mono setting 0=off, 3=max
bool TicTic;    				// true = tictac is ON
bool madCuckoo;         // true = cuckoo strikes as many times as hour is
int  volum;             
long dureeMp3;          // duration of mp3 to be protected from tictac 
 
// init
void setup() {
  delay(1000);
  zero=0x00;
  Wire.begin();
  Serial.begin(9600);
  pinMode(button_silence, INPUT);   // capture noisy tic status swicth each second
  pinMode(button_madcuckoo, INPUT); // capture if cuckoo strikes should be many
  pinMode(button_volumetic, INPUT);
  pinMode(SECONDS, OUTPUT);       // trick to use A0 as PWM for SECONDS/minutes meter
  pinMode(MINUTES, OUTPUT);  
  pinMode(HOURS, OUTPUT);
  analogWrite(HOURS,0);  
  analogWrite(MINUTES,0);      
  SoftPWMBegin();
  SoftPWMSet(SECONDS, 0);   // software PWM on A0
 
  //---
  #ifdef VERBOSE
    Serial.println(F("GalvaCuckoo Patrick version 0.4 "));
  #endif
  LEDS.addLeds<LED_TYPE, LED_DT, COLOR_ORDER>(leds, NUM_LEDS);  // Use this for WS2811
  FastLED.setBrightness(255);
  initSD();               // Initialize SD card
  initMP3Player();        // Initialize MP3 Shield
  curSec=61;              // init
  curHour=25;             // init
  RingType = coucou;      // cuckoo ringtone by default
  titChenille();          // display lights at startup
}
 
void loop() {
   getDateDs1307();           // acquire time several times per second
   if(curSec!=second){        // only once per second
       checkConfig();         // check if DIP switches status changed
       displayTimeQuarter();  // display AM-PM
       displayHourMeter();    // display time on meters
       ticking();             // seconds are ticking if requested
       curSec=second;         // change flag to display once per second
 
        #ifdef VERBOSE
          printHour();      // dispaly each second when verbose
         // getValue();     // used during dev and maintenance
        #endif
     }
}
 
// ------ subroutines ---
 
void checkConfig()
{
 if(analogRead(button_silence)>1000)    // status of silence switch for audible TicTac
           TicTic=TRUE;
       else
           TicTic=FALSE;
 if(analogRead(button_madcuckoo)>1000)  // status of MadCuckoo switch for repeating coucou song
           madCuckoo=TRUE;
       else
           madCuckoo=FALSE;  
 if(analogRead(button_volumetic)>1000)   // status of volume switch for ticTic
           volum=volumeTic;
          else
           volum=volume;
}
 
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ((val/16*10) + (val%16));
}
 
// Gets the date and time from the ds1307
void getDateDs1307()
{
  // Reset the register pointer
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(byte(0));
  Wire.endTransmission();
  Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
  second = bcdToDec(Wire.read());
  minute = bcdToDec(Wire.read());
  hour = bcdToDec(Wire.read());
  #ifdef VERBOSE
    dayOfWeek = bcdToDec(Wire.read());
    dayOfMonth = bcdToDec(Wire.read());
    month = bcdToDec(Wire.read());
    year = bcdToDec(Wire.read());
  #endif
}
 
#ifdef VERBOSE
void printHour()   // sur terminal
{
  if (hour < 10)
    Serial.print("0");
  Serial.print(hour, DEC);
  Serial.print(":");
  if (minute < 10)
    Serial.print("0");
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second < 10)
    Serial.print("0");
  Serial.print(second, DEC);
  Serial.print("  ");
  Serial.print(days[dayOfWeek]);
  Serial.print(", ");
  Serial.print(dayOfMonth, DEC);
  Serial.print(" ");
  Serial.print(months[month]);
  Serial.print(" 20");
  if (year < 10)
    Serial.print("0");
  Serial.println(year, DEC);  
}
#endif
 
void displayHourMeter()
{   // display seconds
    SoftPWMSet(SECONDS,map(second,0,59,0,255));
    // display minutes
    analogWrite(MINUTES,MeterMinut[(int)(minute/10)]+(int)(4.5*(int)(minute-(minute/10)*10)));
    // display hours with 0-13 format
    int temp_hours;
    temp_hours =  hour;
    if(temp_hours > 12 & temp_hours <= 23)
       temp_hours = temp_hours - 12;    // my choice is to display until 13 for 1PM
    if(temp_hours == 0)  // trick for midnight, display 12 instead of 0 !
       temp_hours = 12;
    analogWrite(HOURS,(MeterHour[temp_hours]+(minute*15/60)));  
      #ifdef VERBOSE  
        Serial.println("exit displayHourMeter");   // debug
      #endif
}
 
void ticking()
{
if(TicTic==TRUE){                  // ticking is allowed by switch
      if(second==0&done_once==0)   // cliclac not already started, will start at sec=0
         {trigMp3(cliclac,volum);
          done_once=1;}
      if(second>0&done_once==1)    // reset, ready for next start at sec==0
          done_once=0; 
    }  
}
 
// 5 LEDs to display the 4 quarter of the day (0,6,12,18,24)
// used as AM:PM indicators
// CHSV (60,0,255) is white , full brightness
void displayTimeQuarter()
{
  leds_off();
  dureeMp3 = dureeCoucou;
  switch (hour) {
    case 0:
      //at midnight, both LEDS are ON
     leds[led0AM]  = CHSV( 60, 0, 255);
     leds[led24PM] = CHSV( 60, 0, 255);
     // Big Ben clocks are ringing 12 strikes
     RingType = bigbenmidnight;
	   repeat = 1;
     dureeMp3 = dureeBigBen;
     break;
    case 1:
      leds[led0AM]   = CHSV( 60, 0, 255);
      leds[led6AM]   = CHSV( 60, 0, 15);
	    repeat = 1;
      break;
    case 2:
      leds[led0AM]   = CHSV( 60, 0, 150);
      leds[led6AM]   = CHSV( 60, 0, 55);
	    repeat = 2;
      break;
    case 3:
      leds[led0AM]   = CHSV( 60, 0, 100);
      leds[led6AM]   = CHSV( 60, 0, 100);
	    repeat = 3;
      break;
    case 4:
      leds[led0AM]   = CHSV( 60, 0, 55);
      leds[led6AM]   = CHSV( 60, 0, 150);
	    repeat = 4;
      break;
    case 5:
      leds[led0AM]   = CHSV( 60, 0, 35);
      leds[led6AM]   = CHSV( 60, 0, 200);
	    repeat = 5;
      break;
    case 6:
      leds[led6AM]   = CHSV( 60, 0, 255);
      repeat = 6;
      break;
    case 7:
     leds[led6AM]   = CHSV( 60, 0, 200);
     leds[led12AM]  = CHSV( 60, 0, 35);
	   repeat = 7;
     break;
    case 8:
     leds[led6AM]   = CHSV( 60, 0, 150);
     leds[led12AM]  = CHSV( 60, 0, 55);
	   repeat = 8;
     break;
    case 9:
     leds[led6AM]   = CHSV( 60, 0, 100);
     leds[led12AM]  = CHSV( 60, 0, 100);
	   repeat = 9;
     break;
    case 10:
     leds[led6AM]   = CHSV( 60, 0, 55);
     leds[led12AM]  = CHSV( 60, 0, 150);
	   repeat = 10;
     break;
    case 11:
     leds[led6AM]   = CHSV( 60, 0, 35);
     leds[led12AM]  = CHSV( 60, 0, 200);
	   repeat = 11;
     break;
    case 12:
     leds[led12AM]  = CHSV( 60, 0, 255);
     RingType = chime;  
	   repeat = 1;  // one special Westmister chime
     dureeMp3 = dureeChime;
     break;
    case 13:
     leds[led12AM]  = CHSV( 60, 0, 200);
     leds[led18PM]  = CHSV( 60, 0, 35);
	 repeat = 1;  // back to cuckoo song
     break;
    case 14:
     leds[led12AM]  = CHSV( 60, 0, 150);
     leds[led18PM]  = CHSV( 60, 0, 55);
	   repeat = 2;
     break;
    case 15:
     leds[led12AM]  = CHSV( 60, 0, 100);
     leds[led18PM]  = CHSV( 60, 0, 100);
	   repeat = 3;
     break;
    case 16:
     leds[led12AM]  = CHSV( 60, 0, 55);
     leds[led18PM]  = CHSV( 60, 0, 150);
	 repeat = 4;
      break;
    case 17:
     leds[led12AM]  = CHSV( 60, 0, 35);
     leds[led18PM]  = CHSV( 60, 0, 200);
	   repeat = 5;
     break;
    case 18:
     leds[led18PM]  = CHSV( 60, 0, 255);
	 repeat = 6;
      break;
    case 19:
     leds[led18PM]  = CHSV( 60, 0, 200);
     leds[led24PM]  = CHSV( 60, 0, 35);
	   repeat = 7;
     break;
    case 20:
     leds[led18PM]  = CHSV( 60, 0, 150);
     leds[led24PM]  = CHSV( 60, 0, 55);
	   repeat = 8;
     break;
    case 21:
     leds[led18PM]  = CHSV( 60, 0, 100);
     leds[led24PM]  = CHSV( 60, 0, 100);
	   repeat = 9;
     break;
    case 22:
     leds[led18PM]  = CHSV( 60, 0, 55);
     leds[led24PM]  = CHSV( 60, 0, 150);
	   repeat = 10;
     break;
    case 23:
     leds[led18PM]  = CHSV( 60, 0, 85);
     leds[led24PM]  = CHSV( 60, 0, 200);
	   repeat = 11;
     break; 
    default: 
      // if nothing else matches, do the default
      leds[led18PM] = CHSV( 0, 0, 0);
  }
  FastLED.show();                 // once each LED's color is set, then it's time to show ip
  dingDong(RingType,repeat);      // cuckoo for each hour or bigben or chime
  RingType = coucou;              // back to 'cuckoo' after BigBen or Westminter chime
  #ifdef VERBOSE      // debug
     Serial.println("EXIT displayQuarter");
  #endif
}
 
void leds_off()
 {for(int i=0;i<5;i++){
      leds[i] = CRGB( 0, 0, 0);
      }
 }
 
void titChenille()
{int i ;
for(i=0;i<5;i++){
      leds[i] = CRGB( 255, 255, 255);
      FastLED.show(); 
      delay(50);
      leds[i-1] = CRGB( 0, 0, 0);
      }
      leds[i] = CRGB( 0, 0, 0);
 for(int i=4;i>0;i--){
      leds[i] = CRGB( 255, 255, 255);
      FastLED.show(); 
      delay(50);
      leds[i] = CRGB( 0, 0, 0);
      }
}
 
void dingDong(int whichRing, int iter)
{ int volum = volume;
 if(madCuckoo==FALSE) iter=1;    // if cuckoo should strike only once, iter is forced to 1
 if(curHour!=hour)
 {   // in order to ring only once
	for(int xfois=0;xfois<iter;xfois++)
	    {
      trigMp3(whichRing,volum);  // start reading track00x refenced by whichRing
	    delay(dureeMp3);  // delay to let the cuckoo track finishing before next one
	    }  
  curHour=hour;
  }
}
 
// initSD() initializes the SD card and checks for an error.
void initSD()
{
  //Initialize the SdCard.
  if(!sd.begin(SD_SEL, SPI_HALF_SPEED)) 
    sd.initErrorHalt();
  if(!sd.chdir("/")) 
    sd.errorHalt("sd.chdir");
}
 
void initMP3Player()
{
  MP3player.begin();        // init the mp3 player shield
  MP3player.setVolume(volume, volume);
  MP3player.setMonoMode(monoMode);
}
 
void trigMp3(int PlayTrack, int vol)
{
  if (MP3player.isPlaying())
        MP3player.stopTrack();
  MP3player.setVolume(vol, vol);
  MP3player.playTrack(PlayTrack);
}