1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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
|
#define MILLIS_PER_MIN 60000 // milliseconds per a minute
// ports utilisés pour contrôler le moteur pas à pas
// si votre moteur tourne dans le sens inverse,
// changez l'ordre en {4, 5, 6, 7} ;
// pour Arduino nano
int port[4] = {7, 6, 5, 4};
// for ESP32 (example)
// int port[4] = {12, 32, 25, 27};
// Paramètres du moteur et de l'horloge
#define STEPS_PER_ROTATION 1536 // steps for a minute
// attendre un seul pas du stepper
int delaytime = 3;
// séquence de la commande du moteur pas à pas
int seq[4][4] = {
{ LOW, LOW, HIGH, LOW},
{ LOW, LOW, LOW, HIGH},
{ HIGH, LOW, LOW, LOW},
{ LOW, HIGH, LOW, LOW}
};
//***********************************
void rotate(int step) {
static int phase = 0;
int i, j;
int delta = (step > 0) ? 1 : 3;
int dt = 20;
step = (step > 0) ? step : -step;
for(j = 0; j < step; j++) {
phase = (phase + delta) % 4;
for(i = 0; i < 4; i++) {
digitalWrite(port[i], seq[phase][i]);
}
delay(dt);
if(dt > delaytime) dt--;
}
// power cut
for(i = 0; i < 4; i++) {
digitalWrite(port[i], LOW);
}
}
// ======================
// Setup
// ======================
void setup() {
Serial.begin(9600);
pinMode(port[0], OUTPUT);
pinMode(port[1], OUTPUT);
pinMode(port[2], OUTPUT);
pinMode(port[3], OUTPUT);
rotate(-10); // pour la course d'approche
rotate(10); // l'approche fonctionne sans charge lourde
rotate(STEPS_PER_ROTATION);
pinMode (LED_BUILTIN, OUTPUT);
// ======================================================================================================================================================
// Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A
// ======================================================================================================================================================
noInterrupts(); // On désactive les interruptions, pour commencer
// On règle les bits WGM10, WGM11, WGM12, WGM13 pour fonctionner en mode "CTC" (c'est à dire, en mode "comparaison timer <-> valeur de référence")
bitClear(TCCR1A, WGM10); // On met le bit WGM10 à 0 (contenu dans le registre TCCR1A)
bitClear(TCCR1A, WGM11); // On met le bit WGM11 à 0 (contenu dans le registre TCCR1A)
bitSet(TCCR1B, WGM12); // On met le bit WGM12 à 1 (contenu dans le registre TCCR1B)
bitClear(TCCR1B, WGM13); // On met le bit WGM13 à 0 (contenu dans le registre TCCR1B)
// Ensuite, on règle les bits CS10, CS11, et CS12 pour que le prédiviseur du timer1 fasse une division par 256
bitClear(TCCR1B, CS10); // On met le bit CS10 à 0 (contenu dans le registre TCCR1B)
bitClear(TCCR1B, CS11); // On met le bit CS11 à 0 (contenu dans le registre TCCR1B)
bitSet(TCCR1B, CS12); // On met le bit CS12 à 1 (contenu dans le registre TCCR1B)
// Puis on active l'interruption du timer1, qui test en permanence s'il y a égalité entre la valeur courant du timer, et la valeur
// stockée dans un registre de comparaison. En pratique, pour ce faire, on va mettre à 1 le bit OCIE1A dans le registre TIMSK1,
// afin qu'une interruption soit générée, à chaque fois que la valeur du timer1 sera égale à la valeur qu'on aura renseigné dans le registre OCR1A
bitSet(TIMSK1, OCIE1A); // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)
/* Pour info, on aura pu simplifier l'écriture des lignes ci-dessus, en entrant directement la valeur de tous les bits à la fois,
* dans leurs registres correspondants. Pour cela, il aurait fallut écrire les lignes suivantes (au lieu de bitClear/bitSet) :
*
* TCCR1A = 0b00000000; // pour WGM11=0 et WGM10=0
* TCCR1B = 0b00001100; // pour WGM12=1 et WGM13=0, puis CS12=1/CS11=0/CS10=0 pour prédiviseur réglé sur division par 256
* TIMSK1 = 0b00000010; // pour OCIE1A=1, afin d'activer l'interruption par comparaison "A" (test d'égalité entre timer et valeur de registre OCIE1A)
*/
// Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
TCNT1 = 0; // Mise du timer1 à zéro
OCR1A = 31250; // Valeur correspondant à 500ms (car 31250 fois 16 µS donne bien 500ms ; pour rappel, ces 16 µS proviennent du calcul 1/(16MHz/256),
// avec les 16 MHz correspondant à la fréquence du quartz de l'ATmega328P, et le 256 au réglage du prédiviseur du timer1 fait précédemment)
interrupts(); // Et, on ré-active les interruptions
}
// ======================
// Loop
// ======================
void loop() {
static long prev_min = 0, prev_pos = 0;
long min;
static long pos;
min = millis() / MILLIS_PER_MIN;
if(prev_min == min)
{
return;
}
prev_min = min;
pos = (STEPS_PER_ROTATION * min);
Serial.println();
rotate(-10); // pour la course d'approche
rotate(10); // l'approche fonctionne sans charge lourde
if(pos - prev_pos > 0) {
rotate(pos - prev_pos);
}
prev_pos = pos;
}
// ======================
// Routine d'interruption
// ======================
ISR(TIMER1_COMPA_vect) {
// À chaque appel d'interruption, on inverse l'état de la LED
// En bref : si elle était allumée, alors on l'éteint, et si elle était éteinte, alors on l'allume !
digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));
} |