La terza parte del CtrlJ pen sketch ti mostra come gestire il display OLED ed il menu di configurazione del progetto.
a completamento della descrizione del software, scritto per consentirti l’utilizzo della penna automatica per il controllo dei fluidi, troverai in questo articolo come gestire il display OLED ed i menu di configurazione.
Come nei precedenti articoli dedicati allo sketch ecco il video in cui si alternano le schermate del menu di configurazione:
in cui puoi vedere la sequenza e le impostazioni possibili.
Passiamo allo sketch vero e proprio.
CtrlJ pen sketch – terza parte
Lo sketch è identico a quello già visto nei precedenti due articoli:
in cui trovi la descrizione delle prime centoventi righe circa:
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | #include <Arduino.h> #include <U8g2lib.h> #ifdef U8X8_HAVE_HW_SPI #include <SPI.h> #endif #ifdef U8X8_HAVE_HW_I2C #include <Wire.h> #endif #define Bt1 MISO //11 #define Bt2 MOSI //12 #define Bt3 SCK //13 #define A 14 #define B 9 #define C 10 #define D 11 int TOTAL_OF_STEPS_PER_REV=512; int NUMBER_OF_STEPS_PER_REV=512; int NUMBER_OF_STEPS_PER_PRESS=256; int MICROSECOND_STEP_DELAY=1000; int OLD_MICROSECOND_STEP_DELAY=MICROSECOND_STEP_DELAY; int TOTAL_STEPS=55; int STEPS_EXECUTED=0; boolean arrowPos = false ; // Button Press Count unsigned long keyPrevMillis = 0; const unsigned long keySampleIntervalMs = 25000; byte longKeyPressCountMax = 6; byte longKeyPressCount = 0; byte prevKeyState = HIGH; // button is active low // Mode byte mode = 0; // 0 - normal; 1 - menu boolean isFirstPage = true ; U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE); // Adafruit Feather ESP8266/32u4 Boards + FeatherWing OLED void u8g2_prepare( void ) { u8g2.setFont(u8g2_font_6x10_tf); u8g2.setFontRefHeightExtendedText(); u8g2.setDrawColor(1); u8g2.setFontPosTop(); u8g2.setFontDirection(0); } void setup() { Serial.begin(15200); pinMode(Bt1,INPUT_PULLUP); pinMode(Bt2,INPUT_PULLUP); pinMode(Bt3,INPUT_PULLUP); pinMode(A,OUTPUT); pinMode(B,OUTPUT); pinMode(C,OUTPUT); pinMode(D,OUTPUT); u8g2.begin(); u8g2.clearBuffer(); u8g2_prepare(); homePage(); u8g2.sendBuffer(); delay(500); } void loop() { draw(); Serial.print( digitalRead(Bt1) ); Serial.print( "\t" ); Serial.print( digitalRead(Bt2) ); Serial.print( "\t" ); Serial.print( digitalRead(Bt3) ); Serial.print( "\n" ); // Settings button pressed if (millis() - keyPrevMillis >= keySampleIntervalMs) { keyPrevMillis = millis(); byte currKeyState = digitalRead(Bt3); if ((prevKeyState == HIGH) && (currKeyState == LOW)) { keyPress(); } else if ((prevKeyState == LOW) && (currKeyState == HIGH)) { keyRelease(); } else if (currKeyState == LOW) { longKeyPressCount++; } prevKeyState = currKeyState; } } void shortKeyPress() { Serial.println( "short" ); if ( isFirstPage ) { OLD_MICROSECOND_STEP_DELAY=MICROSECOND_STEP_DELAY; MICROSECOND_STEP_DELAY=900; int i=0; while (i<NUMBER_OF_STEPS_PER_REV*10) { if ( digitalRead(Bt3) == LOW ) { break ; }; backwardStep(); i++; } STEPS_EXECUTED -= (NUMBER_OF_STEPS_PER_REV/(NUMBER_OF_STEPS_PER_PRESS-i)); MICROSECOND_STEP_DELAY=OLD_MICROSECOND_STEP_DELAY; } } void longKeyPress() { Serial.println( "long" ); mode=1; } void keyPress() { Serial.println( "key press" ); longKeyPressCount = 0; } void keyRelease() { Serial.println( "key release" ); if (longKeyPressCount >= longKeyPressCountMax) { longKeyPress(); } else { shortKeyPress();} } void draw( void ) { u8g2.clearBuffer(); u8g2_prepare(); switch (mode) { case 0: fPage(); break ; case 1: m1Page(); break ; case 2: m2Page(); break ; case 3: m3Page(); break ; } u8g2.sendBuffer(); delay(200); } void homePage() { u8g2.setFont(u8g2_font_9x15_tf); u8g2.drawStr( 0, 0, "Solder paste" ); u8g2.drawStr( 0, 20, "dispenser v1" ); } void fPage() { isFirstPage= true ; char str[30]; int i=0; sprintf (str, "Steps for rev.: %d" , NUMBER_OF_STEPS_PER_REV); u8g2.drawStr( 0, 0, str); u8g2.drawFrame(0,12,110,7); u8g2.drawBox((STEPS_EXECUTED*(110/TOTAL_STEPS)),12,110-(STEPS_EXECUTED*(110/TOTAL_STEPS)),7); u8g2.drawStr( 0, 24, "Settings: " ); u8g2.drawFrame(58,28,15,4); u8g2.drawFrame(84,28,15,4); u8g2.drawFrame(113,28,15,4); u8g2.drawBox(62,21,7,8); u8g2.drawBox(88,21,7,8); u8g2.drawBox(117,24,7,5); if ( digitalRead(Bt1) == LOW ) { while (i<NUMBER_OF_STEPS_PER_PRESS) { if ( digitalRead(Bt3) == LOW ) { break ; }; forwardStep(); i++; } STEPS_EXECUTED += (NUMBER_OF_STEPS_PER_REV/(NUMBER_OF_STEPS_PER_PRESS-i)); } if ( digitalRead(Bt2) == LOW ) { while (i<NUMBER_OF_STEPS_PER_PRESS) { if ( digitalRead(Bt3) == LOW ) { break ; }; backwardStep(); i++; } STEPS_EXECUTED -= (NUMBER_OF_STEPS_PER_REV/(NUMBER_OF_STEPS_PER_PRESS-i)); } if (STEPS_EXECUTED > TOTAL_STEPS || STEPS_EXECUTED < 0) STEPS_EXECUTED=0; arrowPos=!arrowPos; if (arrowPos) { /* Up */ u8g2.drawBox(120,9,2,6); u8g2.drawTriangle(118,15,124,15,121,18); } else { /* Down */ u8g2.drawBox(120,12,2,6); u8g2.drawTriangle(118,18,124,18,121,21); } } void menuButton() { u8g2.drawStr( 0, 12, "Down" ); u8g2.drawStr( 32, 12, "Up" ); u8g2.drawStr( 54, 12, "Confirm" ); u8g2.drawFrame( 4,28,15,4); u8g2.drawFrame(30,28,15,4); u8g2.drawFrame(72,28,15,4); u8g2.drawBox( 8,21,7,8); u8g2.drawBox(34,21,7,8); u8g2.drawBox(76,24,7,5); } void m1Page() { isFirstPage= false ; char str[30]; sprintf (str, "Steps per rev.: %d" , NUMBER_OF_STEPS_PER_REV); u8g2.drawStr( 0, 0, str); menuButton(); if ( (digitalRead(Bt1) == LOW ) && NUMBER_OF_STEPS_PER_REV < TOTAL_OF_STEPS_PER_REV) NUMBER_OF_STEPS_PER_REV += 10; if ( (digitalRead(Bt2) == LOW ) && NUMBER_OF_STEPS_PER_REV > 0 ) NUMBER_OF_STEPS_PER_REV--; if ( digitalRead(Bt3) == LOW ) mode = 2; } void m2Page() { isFirstPage= false ; char str[30]; sprintf (str, "Steps per press: %d" , NUMBER_OF_STEPS_PER_PRESS); u8g2.drawStr( 0, 0, str); menuButton(); if ( (digitalRead(Bt1) == LOW ) && NUMBER_OF_STEPS_PER_PRESS < NUMBER_OF_STEPS_PER_REV) NUMBER_OF_STEPS_PER_PRESS += 10; if ( (digitalRead(Bt2) == LOW ) && NUMBER_OF_STEPS_PER_PRESS > 0 ) NUMBER_OF_STEPS_PER_PRESS--; if ( digitalRead(Bt3) == LOW ) mode = 3; } void m3Page() { isFirstPage= false ; char str[30]; sprintf (str, "Delay per fases: %d" , MICROSECOND_STEP_DELAY); u8g2.drawStr( 0, 0, str); menuButton(); if ( (digitalRead(Bt1) == LOW ) && MICROSECOND_STEP_DELAY < 3000) MICROSECOND_STEP_DELAY += 10; if ( (digitalRead(Bt2) == LOW ) && MICROSECOND_STEP_DELAY > 0 ) MICROSECOND_STEP_DELAY--; if ( digitalRead(Bt3) == LOW ) mode = 0; } void forwardStep(){ Serial.println( "forwardStep" ); write(1,0,0,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,0,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,1,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,1,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,1,1,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,1,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(1,1,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(1,0,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); } void backwardStep(){ Serial.println( "backwardStep" ); write(1,0,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(1,1,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,1,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,1,1,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,1,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,1,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,0,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(1,0,0,1); delayMicroseconds(MICROSECOND_STEP_DELAY); } void write( int a, int b, int c, int d){ digitalWrite(A,a); digitalWrite(B,b); digitalWrite(C,c); digitalWrite(D,d); } |
iniziamo quindi dalla linea 122:
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | void draw( void ) { u8g2.clearBuffer(); u8g2_prepare(); switch (mode) { case 0: fPage(); break ; case 1: m1Page(); break ; case 2: m2Page(); break ; case 3: m3Page(); break ; } u8g2.sendBuffer(); delay(200); } |
in cui definisci la funzione darw(), richiamata dalla linea 072 e che ti permette di “disegnare” sul display OLED 0.91″.
le prime operazioni che la funzione draw() esegue ogni volta che viene richiamata puliscono il buffer del display e lo preparano per visualizzare la schermata corrente;
lo switch() a linea 125 valuta il valore della variabile “mode” e in funzione di quest’ultima ti permette di definire quale menu visualizzare;
I menu sono 3 oltre alla schermata principale:
- 0 – prima pagina o pagina principale;
- 1 – menu di impostazione del numero di step per rivoluzione;
- 2 – menu di impostazione del numero di step per ogni pressione del pulsante;
- 3 – menu di impostazione del numero di micro secondi da attendere tra uno step ed il successivo;
tutte queste impostazioni ti consentono di personalizzare la tua CtrlJ pen e di utilizzarla con differenti fluidi e applicazioni d’uso.
Analizzeremo in dettaglio tutte le funzioni richiamate nella draw().
La linea 142 invia il buffer appena composto al display.
Iniziamo dalla homePage() richiamata dalla linea 066 nella setup();
Funzione homePage()
la funzione homePage, come dice il nome stesso, realizza e visualizza la prima pagina visualizzata in fase di setup dello sketch:
146 147 148 149 150 | void homePage() { u8g2.setFont(u8g2_font_9x15_tf); u8g2.drawStr( 0, 0, "Solder paste" ); u8g2.drawStr( 0, 20, "dispenser v1" ); } |
nel complesso è una funzione semplice il cui scopo è solo quello di scrivere sul display il testo:
Solder paste
dispenser v1
con un font 9×15.
Si tratta solo del titolo dello sketch visualizzato e per farlo usi il metodo drawStr della libreria u8g2.
Funzione fPage()
è la funzione che si occuperà di visualizzare la prima pagina o pagina di default sul display:
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | void fPage() { isFirstPage= true ; char str[30]; int i=0; sprintf (str, "Steps for rev.: %d" , NUMBER_OF_STEPS_PER_REV); u8g2.drawStr( 0, 0, str); u8g2.drawFrame(0,12,110,7); u8g2.drawBox((STEPS_EXECUTED*(110/TOTAL_STEPS)),12,110-(STEPS_EXECUTED*(110/TOTAL_STEPS)),7); u8g2.drawStr( 0, 24, "Settings: " ); u8g2.drawFrame(58,28,15,4); u8g2.drawFrame(84,28,15,4); u8g2.drawFrame(113,28,15,4); u8g2.drawBox(62,21,7,8); u8g2.drawBox(88,21,7,8); u8g2.drawBox(117,24,7,5); if ( digitalRead(Bt1) == LOW ) { while (i<NUMBER_OF_STEPS_PER_PRESS) { if ( digitalRead(Bt3) == LOW ) { break ; }; forwardStep(); i++; } STEPS_EXECUTED += (NUMBER_OF_STEPS_PER_REV/(NUMBER_OF_STEPS_PER_PRESS-i)); } if ( digitalRead(Bt2) == LOW ) { while (i<NUMBER_OF_STEPS_PER_PRESS) { if ( digitalRead(Bt3) == LOW ) { break ; }; backwardStep(); i++; } STEPS_EXECUTED -= (NUMBER_OF_STEPS_PER_REV/(NUMBER_OF_STEPS_PER_PRESS-i)); } if (STEPS_EXECUTED > TOTAL_STEPS || STEPS_EXECUTED < 0) STEPS_EXECUTED=0; arrowPos=!arrowPos; if (arrowPos) { /* Up */ u8g2.drawBox(120,9,2,6); u8g2.drawTriangle(118,15,124,15,121,18); } else { /* Down */ u8g2.drawBox(120,12,2,6); u8g2.drawTriangle(118,18,124,18,121,21); } } |
è una delle funzioni più lunghe e complesse del CtrlJ pen sketch in quanto serve a visualizzare molte informazioni:
il numero di step per ciascuna rivoluzione impostati;
il box relativo alla posizione del pistone, in modalità grafica;
la rappresentazione grafica dei pulsanti e la freccia che ti ricorda che pa pressione prolungata del pulsante 3 ti permette di entrare nella modalità menu.
Tale funzione, inoltre, si occupa di gestire la pressione dei pulsanti 1 e 2 in fase di funzionamento della penna automatica.
iniziando con ordine partiamo dalla linea 153 in cui imposti il valore della variabile isFisrtPage a true per indicare alla funzione shortKeyPress() ( descritta nell’articolo CtrlJ pen sketch – seconda parte ) che sei nella prima pagina;
le linee 155-156: imposti le variabili per il corretto funzionamento delle istruzioni successive;
linea 158: usanto la funzione sprintf concateni al testo “Steps for rev.:” il valore di step per ciascuna pressione dei tasti 1 o 2 come impostato nella variabile NUMBER_OF_STEPS_PER_REV;
la linea 159 invia al display la stringa appena composta;
le linee 160-161 si occupano di realizzare un box grafico, un rettangolo, ed il suo riempimento interno in funzione dei passi eseguiti seguendo la formula:
(STEPS_EXECUTED*(110/TOTAL_STEPS))
ossia il numero di step eseguiti moltiplicato la costante 110 divisa il numero totale di step;
avrai notato che 110 è il punto finale del box che non arriva a 128 per lasciare lo spazio di visualizzazione della freccia in movimento:
ho optato per questa soluzione in modo da avere nello stesso tempo sia la barra di avanzamento grafico sia la freccia che ti indica di tener premuto il terzo bottone per entrare nella modalità Settings;
le successive linee 163-170: servono a scrivere sul display il testo “Settings:” e la rappresentazione grafica dei tre pulsanti.
Notice
Avrai notato che il terzo pulsante è più basso dei primi due, questa rappresentazione grafica riprende quella che è l’altezza reale dei pulsanti, come noterai dalle immagini dedicate all’elettronica.
linee 172-187: sono dedicate alla gestione vera e propria del controllo della penna sia in avanzamento del pistone sia in retroazione in base al pulsante che premi, analizziamo nel dettaglio il primo blocco in quanto sono sostanzialmente identici.
Alla linea 172 valuti la pressione del bottone 1, quando rilevi un valore LOW il pulsante è stato premuto;
linea 173: inizia un ciclo while fino al numero di passi definiti nella variabile NUMBER_OF_STEPS_PER_PRESS ;
linea 182: valuta se in un qualsiasi momento del ciclo while venga premuto il pulsante 3, in tal caso esegue il comando break che interrompe il ciclo di step.
L’utilizzo del controllo alla linea 173 ti assicura che in qualsiasi momento puoi fermare l’avanzamento del pistone dopo averlo avviato.
La linea 175 richiama la funzione forwardStep() che fa avanzare il pistone come vedrai nella descrizione della funzione più avanti;
linea 176: incrementa il valore di i ad ogni ciclo while;
infine la linea 178: ricalcola il valore di STEPS_EXECUTED in funzione dei passi eseguiti.
Il blocco di codice successivo è identico a quello appena descritto a meno del tipo di funzione richiamata: backwardStep() ed il modo in cui viene ricalcolato il valore degli step eseguiti.
La linea 190 controlla il valore degli step eseguiti e azzera il contatore quando supera il numero di step totali o scende sotto il valore minimo “zero”;
la linea 191 inverte il valore di arrowPos ad ogni eseguzione della funzione fPage();
le linee 193-194 usano il valore della variabile booleana arrowPos per visualizzare la freccia nella posizione alta (120,9) o bassa (120,12).
Le sole linee 191-194 servono a visualizzare la freccia che si alza ed abbassa per indicarti che la pressione del terzo pulsante ti permette di accedere al menu di impostazioni della penna.
Funzione menuButton()
Questa funzione serve a visualizzare i bottoni nelle diverse schermate dei menu.
le prime tre linee 198-200 scrivono sul display i valori per i tre pulsanti;
le linee 202-208 disegnano i tre pulsanti così come li vedi nelle schermate successive del menu.
Funzione m1Page()
serve a disegnare la prima pagina di impostazioni del menu con cui definisci il valore di step per ciascuna rivoluzione:
come vedi i bottoni, disegnati con la funzione menuButton(), e che ti indicano quali funzioni assumono i singoli bottoni in questa schermata;
passando alle linee di codice che ti permettono di visualizzare le informazioni sul display OLED:
211 212 213 214 215 216 217 218 219 220 | void m1Page() { isFirstPage= false ; char str[30]; sprintf (str, "Steps per rev.: %d" , NUMBER_OF_STEPS_PER_REV); u8g2.drawStr( 0, 0, str); menuButton(); if ( (digitalRead(Bt1) == LOW ) && NUMBER_OF_STEPS_PER_REV < TOTAL_OF_STEPS_PER_REV) NUMBER_OF_STEPS_PER_REV += 10; if ( (digitalRead(Bt2) == LOW ) && NUMBER_OF_STEPS_PER_REV > 0 ) NUMBER_OF_STEPS_PER_REV--; if ( digitalRead(Bt3) == LOW ) mode = 2; } |
alla linea 212 vedi l’impostazione della variabile isFirstPage, già incontrata in precedenza, che in questa funzione imposti a false in quanto non sei nella prima pagina o pagina iniziale;
le linee 213-215 definiscono e inviano al display la prima stringa “Step per rev.:” accompagnata dall’impostazione corrente;
linea 216 richiama la funzione menuButton() per disegnare i bottoni e la loro funzione;
linee 228-229: servono a incrementare e decrementare il valore della variabile NUMBER_OF_STEPS_PER_REV consideranto i limiti superiore ( TOTAL_OF_STEPS_PER_REV ) e inferiore ( 0 );
l’ultima linea la 219 si occupa di gestire il passaggio al menu successivo, quando premuto questo pulsante imposta la variabile mode a 2 che indica alla funzione draw() di passare al menu successivo;
le successive funzioni m2Page() e m3Page() sono identiche alla m1Page() tranne che per il messaggio e le variabili che vengono settate.
Puoi vedere di seguito come si presentano le due successive schermate:
e per i tempi di delay tra le fasi:
Funzione forwardStep()
la funzione forwardStep() serve per muovere in avanti il motore, assumendo come verso in avanti quello che spinge il pistone per erogare il flusso.
244 245 246 247 248 249 250 251 252 253 254 | void forwardStep(){ Serial.println( "forwardStep" ); write(1,0,0,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,0,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,1,1); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,0,1,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,1,1,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(0,1,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(1,1,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); write(1,0,0,0); delayMicroseconds(MICROSECOND_STEP_DELAY); } |
la cui linea 245 ti informa, attraverso il monitor seriale, che sei entrato nella funzione;
le linee 246-253: servono ad inviare i segnali per l’avanzamento di uno step del motore, hai già visto questa funzione nell’articolo dedicato al 28BYJ-48.
allo stesso modo la funzione successiva backwardStep() esegue i passi per far arretrare il pistone della CtrlJ pen;
Funzione write()
E’ l’ultima funzione dello sketch ed ha il compito di eseguire i comandi digitalWrite() così come inviati dalle due precedenti funzioni: backwardStep() e forwardStep():
268 269 270 271 272 273 | void write( int a, int b, int c, int d){ digitalWrite(A,a); digitalWrite(B,b); digitalWrite(C,c); digitalWrite(D,d); } |
Ora conosci tutto lo sketch e le singole funzioni come funzionano e a cosa servono.
Lo sketch descritto è in grado di svolgere tutte le funzioni definite e desiderate che hai letto nel primo articolo dedicato al CtrlJ pen.
Puoi quindi procedere con la realizzazione del tuo progetto.