samedi 19 août 2017

Manipuler: pour s'entrainer à la manipulation

C'est un projet commun avec F5RUJ et moi même.
Nous avons imaginé et élaboré un jeu qui consiste à manipuler correctement les lettres à la pioche.


Alain F5RUJ a fait le plus gros du travail dans la programmation de l'arduino.
Le principe est simple: on manipule une pioche et les lettres s'affichent sur une matrice à led.

Un article est disponible dans l'un des numéro de La PIOCHE de l'UFT en 2017

Comme je vais utiliser cet équipement à l'extérieur, pour des raisons de visibilité, j'ai préféré faire l'affichage sur un écran LCD Alphanumérique. La tonalité sort du haut-parleur.









Le circuit s'alimente avec une pile 9V, via un régulateur 7805. C'est un arduino mini-Pro que j'utilise et des écrans LCD de récupérations. L'audio sort par un GPIO de l'arduino, un signal carré à 700Hz. Une résistance série avec le GPIO, et potentiomètre de 10kOhm logarithmique atténue ce signal carré. C'est un module audio à base de LM386 CMS qu'on trouve sur EBAY qui fait un amplificateur pour le HP de sortie de récupération.
Au fur et à mesure de la manipulation, les caractères ALPHA NUMERIQUES s'affiche sur un écran LCD 2x16 caractères.
Pour optimiser l'affichage, il n'y a pas d'espaces entre les mots.



C'est un prototype qui a des bugs:
- Quand le contact de la pioche est trop longue, le logiciel plante. Il faut attendre un peu, sans manipuler. ça se remet tout seul.

- La fermeture du boitier n'est pas vraiment optimisée, j'ai fini par coller le couvercle pour m'éviter des vis qui dépasses. Mais c'est pas pratique en cas de mise à jour

Sauf si je trouve à l'utilisation que ces bugs sont très gênant, je ne compte pas les corriger.

En tout cas, ça fait un bel atelier de démonstration et de jeu pour les plus jeunes.

Je vais utiliser ces appareils dans des ateliers Radioscoutisme.
Ces ateliers seront agrémenté d'un support disponible ici:

http://www.radioscoutisme.org/phocadownload/ft4codes.pdf









 Code Arduino du décodeur de clef morse (8 aout 2021)

#include <LiquidCrystal.h>

/*
Le rôle initial de ce programme Inspiré du travail de Budd Churchward  - WB7FHC est de transformer et d'afficher une lettre manipulée en Morse
avec une pioche ou un manipulateur double contact suivi d'un keyer.
L'affichage doit se faire sur une matrice de LEDs de 8x8 ou un afficheur à plusieurs lignes.
L'appareil est destiné à l'apprentissage de la manipulation correcte, quelle que soit la vitesse. On peut envisager de le transformer en décodeur.
Il constitue également un buzzer en lui adjoignant un amplificateur BF.
A partir du logiciel d'Alain F5RUJ, modifié pour un affichage LCD.

Aduino PIN 0 serial RX
Aduino PIN 1 serial TX

Arduino PIN A0 Efface écran

Utilise un écran 16 char x 2 lignes
// cablage LCD sur l'arduino
GND           - LCD PIN 1 GND
+5V           - LCD PIN 2 VCC
Rvar          - LCD PIN 3 Vo contrast
Aduino PIN 5  - LCD PIN 4 RS
GND           - LCD PIN 5 R/W
Aduino PIN 6  - LCD PIN 6 Enable
Aduino PIN 7  - LCD PIN 11 D4
Aduino PIN 8  - LCD PIN 12 D5
Aduino PIN 9  - LCD PIN 13 D6
Aduino PIN 10 - LCD PIN 14 D7
R             - LCD PIN 15 +5V Anode Retroéclairage
GND           - LCD PIN 16 GND Cathode "

CW OUTPUT
Aduino PIN 12 Audio OUT HPsortie
Aduino PIN 13 sortie 0/1 vers LED ou autre (buzzer actif)
Arduino PIN 2 Entrée Manip Morse
Arduino PIN 2 et 3 Sorties vers relais
*/

// ....................................................................
// .............. Zone des directives de compilation ..................
// ....................................................................

#define LEDPIN 13 // Allume une LED
#define BUZPIN 12 // Délivre du 700 Hz quand on appuie
#define PIOCHE 2  // Broche sur laquelle on branche le manipulateur (Si LCD)
#define CLS A0    // Efface écran
#define REL1 3    // Relais 1
#define REL2 4    // Relais 2
#define DEBUG     // Pour les essais

// ....................................................................
// .......... Zone des variables globales fonctionnelles ..............
// ....................................................................

LiquidCrystal aff(5,6,7,8,9,10); // ................................... L'afficheur
int pic = 1;      //................................................... Stocke la valeur lue sur la broche
int PosXaff = 0;
int PosYaff = 0;
boolean clr = HIGH; //................................................. Bouton effacement écran
//..................................................................... Caractères spéciaux
char smiley[8] = {
    B00000,
    B01010,
    B00000,
    B00000,
    B00000,
    B01110,
    B10001,
    B00000
};
char aaccent[8] = {
    B01000,
    B00100,
    B00100,
    B01010,
    B10001,
    B11111,
    B10001,
    B10001
    };
char eaigu[8] = {
        B00010,
        B00100,
        B11111,
        B10000,
        B11100,
        B10000,
        B10000,
        B11111
        };
char egrave[8] = {
        B01000,
        B00100,
        B11111,
        B10000,
        B11100,
        B10000,
        B10000,
        B11111
        };
char cedille[8] = {
        B01111,
        B10000,
        B10000,
        B10000,
        B10000,
        B10000,
        B01111,
        B00110
        };
/*
 La table suivante correspond à l'ordre des lettres transformées en binaire à partir de leur image en Morse, avec dit = 1 et dah = 0.
On ajoute à gauche un bit de start, pour ne pas avoir par exemple 2 zéros (M) qui ne serait pas différenciable avec un seul zéro (T).
De cette façon T = 0B10 (2) et M = 0B100 (4); Quant à E il devient 0B11 (3).
*/
char tablecar[100]="##TEMNAIOGKDWRUS[#QZYCXBJP#L#FVH09#8###7#(#ç#/=61#à##+è#2##é3#45";
char ligne[]="                "; //.................................... Contiendra les caractères de la ligne 2 pour le scroll up
char cartrouve = ' '; // .............................................. Stocke le caractère décodé
int nombre = 0; // .................................................... Stocke le nombre binaire créé par dit/dah successifs
boolean justefait = true; // .......................................... Trois drapeaux utiles à la programmation
boolean carfait = true;
boolean ditoudah = true;

// .................................................................... Variables de durée pour la reconnaissance des points et traits, espaces etc.
int dahmoyen = 240;
int dit = 80;
int ditvrai = dit;
int picdelai = 2; // .................................................. Passage d'une position à l'autre de la pioche
long tempsbas = 0; // ................................................. Durée du son quand la pioche est basse
long tempshaut = 0; // ................................................ Durée du silence quand la pioche est haute
long toptempsbas = 0; // .............................................. Début chrono pioche appuyée
long toptempshaut = 0; // ............................................. Début chrono pioche relevée

// ....................................................................
// .............. Zone des procédures majeures ........................
// ....................................................................

void setup() {
// ..................................................................... Version 2.0 : Setup affichage LCD
aff.begin(16,2);
aff.createChar(5, smiley);
aff.createChar(1, aaccent);
aff.createChar(2, eaigu);
aff.createChar(3, egrave);
aff.createChar(4, cedille);
pinMode(PIOCHE, INPUT_PULLUP); // ...................................... Pullup évite une résistance de "tirage" vers le 5V
pinMode(CLS,INPUT_PULLUP);
pinMode(LEDPIN,OUTPUT); pinMode (BUZPIN,OUTPUT);
digitalWrite(LEDPIN,HIGH); digitalWrite(BUZPIN,LOW);
pinMode(REL1,OUTPUT); pinMode(REL2,OUTPUT); //.......................... Futures applications
digitalWrite(REL1,HIGH); digitalWrite(REL2,HIGH); //..................... Futures applications
/*
Les relais ARDUINO sont actifs an niveau LOW.
La pin REL1 pourra commander directement un premier relais pouractionner electro-aimant
La pin REL2 commandera un autre relais pour mettre en marche le moteur
*/
#ifdef DEBUG
Serial.begin(9600); Serial.println("Manipuler LCD F5RUJ F4EGX");
#endif
tone(BUZPIN,1750,20); delay(20); noTone(BUZPIN);
aff.setCursor(0,0); aff.print(" Manipuler LCD");
aff.setCursor(2,1);
aff.print("F5RUJ F4EGX"); delay(250);
aff.clear(); aff.print(">"); PosXaff=0; PosYaff = 0;

}

/*
 La boucle principale, très courte, teste en permanence la position de la pioche et envoie vers l'une ou l'autre des procédures correspondantes.
 Les drapeaux ci-dessus permettront de déterminer où l'on en est dans le décodage de la lettre. La lettre sera terminée quand le temps haut
 sera supérieur à un dah de longueur moyenne.
 */
 
void loop()
{
 pic = digitalRead(PIOCHE);
 if (!pic)  clebasse();
 if (pic)   clehaute();
 clr = digitalRead(CLS); //.............................................. Teste l'appui sur le bouton d'effacement de l'écran
 if (clr == LOW) {aff.clear(); while (clr == LOW) clr = digitalRead(CLS); aff.print('>'); PosXaff=0; PosYaff = 0; aff.setCursor(0,0);}
}

// ....................................................................
// ....... Zone des procédures de décodage des signaux morse ..........
// ....................................................................

void clebasse() // .................................................... Clef appuyée
{
digitalWrite(LEDPIN,LOW); tone (BUZPIN, 700); // ........................ Allume et couine
digitalWrite(REL1,LOW); digitalWrite(REL2,LOW); // .......................Lève le bras et met le moteur en marche

if (toptempshaut > 0) toptempshaut = 0; // ............................ Remet à 0 le départ du temps clef relevée
if (toptempsbas == 0) toptempsbas = millis(); // ...................... Initialise le temps clef appuyée
carfait = false; ditoudah = false; delay (picdelai); // ............... Caractère non terminé et on ne sait pas encore si c'est dit ou dah
if (nombre == 0) nombre = 1; // ....................................... On ajoute le bit de départ si le caractère est égal à 0 (nouveau caractère)
}

void clehaute() // .................................................... Clef relevée
// C'est la procédure essentielle pour décoder la lettre. En raison de la durée de la position appuyée (tempsbas) on saura si c'est un dit ou un dah
{
digitalWrite(LEDPIN,HIGH); noTone(BUZPIN); // ............................. Eteint la LED et fait silence
digitalWrite(REL1,HIGH); // ............................................... Abaisse le bras
if (toptempshaut == 0) toptempshaut = millis();
tempshaut = millis() - toptempshaut; if (tempshaut < 10) return; // .... Mesure du temps position haute
// ............................................. Si la durée en position haute est longue, le caractère est terminé et on peut imprimer un espace
if (tempshaut > (dahmoyen * 4)) espace();
if (tempshaut > 3000) digitalWrite(REL2,HIGH); // ........................ Arrête le moteur au bout de 3 secondes avec le levier haut

if (toptempsbas>0) {tempsbas = millis() - toptempsbas; toptempsbas=0;}
// ..................................................................... Mesure du temps position basse pour déterminer si c'est dit ou dah
if (!ditoudah) shiftbits(); // ......................................... On compose le caractère bit par bit en ajoutant 1 ou 0 et en décalant à gauche
if(!carfait)
  {
   if (tempshaut >= (dahmoyen)) {printcaractere(); carfait = true; nombre=0;}
   tempsbas = 0;
  }
}

void shiftbits() // ..................................................... On décale les bits vers la gauche
{
if (tempsbas < ditvrai/2) return; // .................................... C'est une erreur
nombre = nombre << 1; // ................................................ On décale vers la gauche
if (tempsbas < dit) // .................................................. Si c'est un dit on ajoute 1, sinon on ne fait rien
   {nombre++; ditvrai = (tempsbas+ditvrai) / 2;} // ..................... On réajuste la durée dit et dah
else {dahmoyen = (tempsbas + dahmoyen) / 2;  // ....... On fait la moyenne entre le dernier dah et le dah moyen, puis on calcule le dit moyen
     dit = dahmoyen / 3; ditvrai = dit; dit = dit* 2; // .. On allonge la variable dit pour que la comparaison soit pertinente avec tempsbas.
     }
ditoudah = true; // ..................................................... On sait si c'est l'un ou l' autre.
}

// ....................................................................
//.......... Zone des procédures d'affichage sur LCD ..................
// ....................................................................

void printcaractere()
{
if (nombre > 63) {printponctuation(); return;}
switch (nombre)
{
case 16:
//aff.setCursor(0,0); aff.clear();  break; // 4 traits = efface écran
case 43:
 cartrouve = '%'; break; //ç
case 45:
 cartrouve='/'; break;
case 46:
  cartrouve='='; break;
case 47:
 cartrouve='6'; break;
case 48:
 cartrouve='1'; break;
case 50:
 cartrouve ='>'; break; //à
case 53:
 cartrouve='+'; break;
case 54:
 cartrouve ='['; break; //è
case 55:
 cartrouve='&'; break;
case 56:
 cartrouve='2'; break;
case 59:
 cartrouve='<'; break; //é
case 60:
 cartrouve='3'; break;
case 62:
 cartrouve='4'; break;
case 63:
 cartrouve='5'; break;
default:
 cartrouve = tablecar[nombre]; break;
}

afficher(); // ......................................................... On affiche le caractère trouvé sur le LCD
justefait=false; // .................................................... On recommence un nouveau caractère
}

void printponctuation()
/* On fait de cette façon pour éviter d'avoir une table de caractères trop longue et pleine de cases vides,
la ponctuation étant codée avec de nombreux points et traits. On pourrait l'étendre aux lettres accentuées*/
{
#ifdef DEBUG
Serial.print(nombre);Serial.print(" - ");Serial.print(tablecar[nombre]); Serial.print(" - ");
#endif  
  switch (nombre) {
    case 71:
      cartrouve = ':'; break;
    case 76:
      cartrouve = ','; break;
    case 82:
      cartrouve = ')'; break;
    case 84:
      cartrouve = '!'; break;
    case 85:
      cartrouve = ';'; break;
    case 94:
      cartrouve = '-'; break;
    case 97:
      cartrouve = 39;  break; // Apostrophe    
    case 101:
      cartrouve = '@'; break;
    case 106:
      cartrouve = '.'; break;
    case 109:
      cartrouve = '"'; break;
    case 114:
      cartrouve= '_';  break;
    case 115:
      cartrouve = '?'; break;
    case 122:
      cartrouve = '*'; break;  
    case 246:
      cartrouve = '$'; break;
    case 426:
      cartrouve = 'µ'; break;
    default:
      cartrouve = '#' ;    //............................................ On ne sait pas ce que c'est ou n'est pas imprimable.
                           //............................................ Il n'y a pas de # en CW
      break;
  }
  afficher();// ......................................................... On affiche le caractère trouvé
  justefait=false;
}

void espace() //......................................................... Afficher un espace si l'on n'est pas en début de ligne
{
//if(justefait) return; // ................................................ Si on a déjà imprimé un espace on ne fait rien
//justefait = true; // .................................................... De cette façon on n'imprimera plus d'espace
//if (PosXaff == 0) return; // ............................................ Pas d'espace en début de ligne
//cartrouve = ' '; afficher();
}

void afficher () //...................................................... Procédure d'affichage sur LCD
{
 aff.setCursor(PosXaff,PosYaff);
 switch (cartrouve)
 {
 case '#': //............................................................ On affiche un smiley
  aff.write(5); break;
 case '%':
  aff.write(4); break;
 case '>':
  aff.write(1); break;
 case '<':
  aff.write(2); break;
 case'[':
  aff.write(3); break;
 default:
  aff.print(cartrouve);  break;
 }
if (PosYaff == 1) ligne[PosXaff] = cartrouve;
  PosXaff++;
if(PosXaff>15) {PosXaff=0; //............................................ Fin de ligne
if(PosYaff==0)PosYaff=1; //.............................................. Si ligne 1 on passe en ligne 2 première position
else scrollup();} //..................................................... Si ligne 2 on la copie en ligne 1 et on écrit en début de ligne 2
#ifdef DEBUG
Serial.print(nombre);Serial.print(" - ");Serial.print(tablecar[nombre]); Serial.println(" - ");
#endif
}

void scrollup() //....................................................... Remonte le texte d'une ligne en fin de ligne 2
{
int n = 0;
aff.clear(); aff.print(ligne);
PosXaff=0; PosYaff = 1; aff.setCursor(PosXaff,PosYaff);
for (n=0;n<16;n++) ligne[n] = " "; //.................................... Réinitialise la variable "ligne"
}











Code OpenSCAD pour le boitier du décodeur de clef morse:
//manipDecode
// boitier pour le manip avec décodage LCD
$fn=50;

couvercle=1;
evider=1;
bvide=52;


difference(){


        //boite
        hull(){
            if(couvercle==1){
            translate([0,0,-26])cylinder(r=55/2, h=35);
            translate([138,-55/2,-26])cube([2,55,35]);
            }
        }
union(){
        translate([30,0,0])LCD();
        HP();
        translate([133,0,2])potar();
        translate([0,-bvide/2,-22])cube([135,bvide,22]);
        translate([-20,0,-30])cylinder(r=3/2, h=30, center=true);  //vis fixation HP
        translate([-20,0,-19.5])cylinder(r=3.5, h=2, center=true);  //vis fixation HP
       translate([22,-20,0])cylinder(r=3/2, h=80, center=true);  //vis fixation gauche
        translate([22,20,0])cylinder(r=3/2, h=80, center=true);  //vis fixation gauche
        translate([135,-20,0])cylinder(r=3/2, h=80, center=true);  //vis fixation droite
        translate([135,20,0])cylinder(r=3/2, h=80, center=true);  //vis fixation droite
        translate([135,15,-12])rotate([0,90,0])cylinder(r=3/2, h=80, center=true);  //passage cable manip
        translate([135,-5,-12])rotate([0,90,0])cylinder(r=6/2, h=80, center=true);  //passage cable pile
        translate([135,-15,-12])rotate([0,90,0])cylinder(r=6/2, h=80, center=true);  //passage cable pile

        }
translate([-45,-100/2,-22])cube([200,100,0.1]);       
}

module LCD(){
    translate([0,-18.5,0]){
    color("green", 1.0) translate([-1,-0.4,-5.5+1.75])cube([82,37,5.5]);
    color("grey", 1.0) translate([6,31,-15])cube([42,4,15]);
    color("blue", 1.0) translate([2.5,5.5,0])cube([75,26,10]);
    color("grey", 1.0) translate([-1,5.5,0])cube([82,26,6]);
    }
}


module HP(){
   translate([0,0,7])rotate([0,180,0]){
        cylinder(r=bvide/2, h=7);
        cylinder(r=33/2, h=16);
         if(evider==1){color("LemonChiffon"){
            cylinder(r=bvide/2, h=26);

            for (transl = [8 : 5 : 25])
            for (angl = [0 : 30 : 360])  rotate([0,0,(angl)])translate([transl,0,-15])cylinder(r=3/2, h=20);
            }
  }
   }
}

module potar()
{translate([0,9,-5])rotate([0,0,180])color("DarkSlateGray"){
        cube([18,27,10]);
        translate([9,9,0])cylinder(r=10.5/2, h=20);
        translate([9,9,0])cylinder(r=6.4/2, h=45);
       
       
    }
   // if(evider==1){color("LemonChiffon"){translate([-9,0,15])cylinder(r=25/2, h=20, center=true);}}
}

Aucun commentaire:

Enregistrer un commentaire