CtrlJ pen sketch – terza parte

La terza parte del CtrlJ pen sketch ti mostra come gestire il display OLED ed il menu di configurazione del progetto.

CtrlJ pen menu

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:

#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:

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:

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:

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:

CtrlJ pen menu first page

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.

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:

CtrlJ pen menu step per rev

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:

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:

CtrlJ pen menu step per press

e per i tempi di delay tra le fasi:

CtrlJ pen menu delay fases

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.

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():

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.

  • Questo sito ed i suoi contenuti è fornito "così com'è" e Mauro Alfieri non rilascia alcuna dichiarazione o garanzia di alcun tipo, esplicita o implicita, riguardo alla completezza, accuratezza, affidabilità, idoneità o disponibilità del sito o delle informazioni, prodotti, servizi o grafiche correlate contenute sul sito per qualsiasi scopo.
  • Ti chiedo di leggere e rispettare il regolamento del sito prima di utilizzarlo
  • Ti chiedo di leggere i Termini e Condizioni d'uso del sito prima di utilizzarlo
  • In qualità di Affiliato Amazon io ricevo un guadagno dagli acquisti idonei qualora siano presenti link al suddetto sito.

Permalink link a questo articolo: https://www.mauroalfieri.it/elettronica/ctrlj-pen-sketch-terza-parte.html

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.