Timer1 è l’utilizzo degli interrups per controllare uno stepper motor

Ciao a tutti,

Ho scaricato dalla rete un programma per controllare un motorino bipolare passo passo in modalità "full-step", usando arduino 1 collegato ad un LCD keypad shield e la scheda Polulo A4988.
Nel video seguente si può vedere come funziona il programma.

Il programma permette di raggiungere una velocità massima di 70 rpm , con incrementi di velocità di 5 rpm , però, per il mio progetto, ho bisogno di superare la velocità di 70 rpm .

#include <LiquidCrystal.h>
#include <TimerOne.h>

// buttons code 
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

// directions
#define FORWARD   HIGH
#define BACKWARD  LOW

// debounce time (milliseconds)
#define DEBOUNCE_TIME  200

// PINs for Pololu controller
#define PIN_STEP  2
#define PIN_DIR   3

// lookup table speed - ticks (interrupts)
const int speed_ticks[] = {-1, 600, 300, 200, 150, 120, 100, 86, 75, 67, 60, 55, 50, 46, 43};

// global variables
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int actual_speed;
int actual_direction;

int ticks;
int tick_count;

int button;
boolean debounce;
int previous_time;

// custom LCD square symbol for progress bar
byte square_symbol[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
};

// string constants
char forward_arrow[] = "-->";
char backward_arrow[] = "<--";

void setup() {

  // init the timer1, interrupt every 0.1ms
  Timer1.initialize(100);
  Timer1.attachInterrupt(timerIsr);
  
  // init LCD and custom symbol  
  lcd.begin(16, 2);
  lcd.setCursor(0,0);
  lcd.createChar(0, square_symbol);
  
  // pins direction
  pinMode(PIN_STEP, OUTPUT);
  pinMode(PIN_DIR, OUTPUT);
  
  // initial values
  actual_speed = 0;
  actual_direction = FORWARD;
  tick_count = 0;
  ticks = -1;
  debounce = false;

  digitalWrite(PIN_DIR, actual_direction);  
  updateLCD();
}
  
void loop() {
  
  // check if debounce active
  if(debounce) {
    button = btnNONE;
    if(millis() > previous_time + DEBOUNCE_TIME) debounce = false;
  } else button = read_buttons();
  
  // if a button is pressed, start debounce time
  if(button != btnNONE) {
    
    previous_time = millis();
    debounce = true;  
  }
    
  // check which button was pressed
  switch(button) {
    
    case btnUP:
      increase_speed();
      break;
    case btnDOWN:
      decrease_speed();
      break;
    case btnLEFT:
      change_direction(BACKWARD);
      break;
    case btnRIGHT:
      change_direction(FORWARD);
      break;
    case btnSELECT:
      emergency_stop();
      break;
  }
  
  // finally update the LCD
  updateLCD();
}

// increase speed if it's below the max (70)
void increase_speed() {
  
  if(actual_speed < 70) {
    actual_speed += 5;
    tick_count = 0;
    ticks = speed_ticks[actual_speed / 5];
  }
}

// decrease speed if it's above the min (0)
void decrease_speed() {
  
  if(actual_speed > 0) {
    actual_speed -= 5;
    tick_count = 0;
    ticks = speed_ticks[actual_speed / 5];
  }
}

// change direction if needed
void change_direction(int new_direction) {
  
  if(actual_direction != new_direction) {
    actual_direction = new_direction;
    digitalWrite(PIN_DIR, actual_direction);
  }
}

// emergency stop: speed 0
void emergency_stop() {
  actual_speed = 0;
  tick_count = 0;
  ticks = speed_ticks[actual_speed / 5];
}

// update LCD
void updateLCD() {
  
  // print first line:
  // Speed: xxxRPM --> (or <--)
  lcd.setCursor(0,0);
  lcd.print("Speed: ");
  lcd.print(actual_speed);
  lcd.print("RPM ");

  lcd.setCursor(13,0);
  if(actual_direction == FORWARD) lcd.print(forward_arrow);
  else lcd.print(backward_arrow);
  
  // print second line:
  // progress bar [#####         ]
  // 15 speed steps: 0 - 5 - 10 - ... - 70
  lcd.setCursor(0,1);
  lcd.print("[");
  
  for(int i = 1; i <= 14; i++) {
    
    if(actual_speed > (5 * i) - 1) lcd.write(byte(0));
    else lcd.print(" ");
  }
  
  lcd.print("]");
}

// timer1 interrupt function
void timerIsr() {

  if(actual_speed == 0) return;
  
  tick_count++;
  
  if(tick_count == ticks) {  
    
    // make a step
    digitalWrite(PIN_STEP, HIGH);
    digitalWrite(PIN_STEP, LOW);
    
    tick_count = 0;
  }
}

// read buttons connected to a single analog pin
int read_buttons() {
  
 int adc_key_in = analogRead(0);
 
 if (adc_key_in > 1000) return btnNONE;
 if (adc_key_in < 50)   return btnRIGHT;  
 if (adc_key_in < 195)  return btnUP; 
 if (adc_key_in < 380)  return btnDOWN; 
 if (adc_key_in < 555)  return btnLEFT; 
 if (adc_key_in < 790)  return btnSELECT;   
}

Qualcuno mi potrebbe spiegare come si ricavano i numeri tra le parentesi graffe nella seguente funzione:

const int speed_ticks[] = { -1, 600, 300, 200, 150, 120, 100, 86, 75, 67, 60, 55, 50, 46, 43};.

Ho capito che si tratta di una serie di interrupts , ma non ho capito come siano stati determinati.
allego anche il link con la spiegazione del programmatore

http://www.lucadentella.it/2013/05/30/allegro-a4988-e-arduino-3/

Quella funzione interrupt aumenta una variabile tick_count fino al valore di ticks. Se lo raggiunge lo ri-azzera e fa un passo con le due digitalWrite. Quindi è una "specie" di delay. Il valore minimo che puoi mettere perciò è 1. Prova a sostituire quell'ultimo 43 con 1 e vedi se va alla massima velocità.
Il ticks viene letto da quell'array.

tick_count = 0;
ticks = speed_ticks[actual_speed / 5];

Il Timer1 viene programmato per generare un interrupt ogni 0.1 msec, quindi 10 interrupt ogni msec.

Nel suo esempio usa un motore da 200 passi/giro e che per una velocità di 60RPM deve ricevere (60*200)/60 = 200 comandi/sec ovvero un comando ogni 5ms.

Come spiega nelle righe successive : "La distanza temporale tra un comando di step e il successivo dipende dalla velocità attuale (nell’esempio sopra per 60RPM tale distanza è di 5ms). Visto che le velocità possibili sono soltanto 15 (da 0 a 70RPM con passo 5RPM), ho calcolato tale distanza (in ticks di 0.1ms) per le varie velocità e l’ho inserita in un array" quindi lui crea un array per avere le seguenti velocità (in giri/minuto):

0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70

a cui corrispondono N impulsi di interrupt (ricorda che ne hai uno ogni 0.1 msec) :

-1, 600, 300, 200, 150, 120, 100, 86, 75, 67, 60, 55, 50, 46, 43 (impulsi da 0.1 msec in 1 msec)

che, in msec corrispondono a quanto a detto poco sopra :

-1. 60, 30, 20, 15, 12, 10, 8.6, 7.5, 6.7, 6.0, 5.5, 5, 4.6, 4.3 (msec equivalenti)

I valori sono calcolati facendo semplicemente il calcolo che ha descritto ...
... ovvero: 1 / ((numero_giri_minuto * 200) / 60) = millisecondi per ogni step :wink:

Guglielmo

Grazie alla spiegazione chiara di gpb01 sono finalmente riuscito a capire come Dentella ha determinato la sequenza di interrupts; il guaio è che la velocità massima del motorino non puó superare i 70 rpm.

Redwin:
il guaio è che la velocità massima del motorino non puó superare i 70 rpm.

... perdona, non conosco il tuo motore ... c'è qualche specifico motivo o per cosa ?

Guglielmo

gpb01:
Il Timer1 viene programmato per generare un interrupt ogni 0.1 msec, quindi 10 interrupt ogni msec.

Ovvero queste 2 linee di codice:

  // init the timer1, interrupt every 0.1ms
  Timer1.initialize(100);
  Timer1.attachInterrupt(timerIsr);

Banalmente, non sarebbe meglio mettere quelle due righe come ultime nella setup() ? La setup() esegue altre istruzioni, alcune credo anche "lunghe" tipo inizializzare l'lcd. Non stà già scattando l'interrupt inutilmente?

nid69ita:
Non stà già scattando l'interrupt inutilmente?

:astonished: :astonished: :astonished: in che senso ? Anche di base, solo quando in Arduino scrivi solamente :

void setup() {

}

void loop() {

}

... c'è una serie di interrupt che scattano in continuazione, solo che forse tu non lo sai ... perché magari non ti sei andato a leggere il core XD XD XD

Guglielmo

gpb01:

nid69ita:
Non stà già scattando l'interrupt inutilmente?

:astonished: :astonished: :astonished: in che senso ? Anche di base, solo quando in Arduino scrivi solamente :

void setup() {}

void loop() {}



... c'è una serie di interrupt che scattano in continuazione, solo che forse tu non lo sai ... perché magari non ti sei andato a leggere il core XD XD XD
Guglielmo

No Guglielmo, questo lo sò.
Mi riferisco a quel preciso interrupt. Lo stiamo facendo scattare inutilmente, già da inizio setup() e dentro la ISR c'e' un if che lo fa uscire perchè a inizio current_speed è 0. Probabilmente non ci sono problemi di sorta, era solo una ottimizzazione che mi è venuta in mente guardando il codice. Converrai che c'e' una parte del codice che scatta da subito inutilmente (ma non da nessun problema)

Si, ok, se si vuole essere precisi sarebbe bene infatti attivarlo quando tutto è pronto, i pinMode() fatti, ecc. ecc.

Non tanto perché ... "scatta inutilmente" ... che non da fastidio, ma quanto perché, se all'inizio current_speed fosse diversa da 0, ci potrebbero essere problemi :wink:

Guglielmo