/*
Programme de contrôle de servo-moteurs.
Les commandes sont données par un PC connecté via la liaison série (9600,8,N,1).
Huit servos peuvent être contrôlés, mais 2 sont utilisés ici, connectés
aux pins PD2 et PD3.
Cible : ATMega8
Quartz : 8MHz
Compilateur : avr-gcc (WInAVR)
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
// nos fonctions de gestion de l'USART, pour la communication avec le PC
#include "uart.h"
// durées typiques (en µs)
#define PULSE_MIN_WIDTH 1000
#define PULSE_MAX_WIDTH 2000
#define PULSE_MED_WIDTH 1500
// nombre de servos gérés
#define SERVO_COUNT 8
// largeurs des pulses des servos, en fonction de la position demandée
volatile unsigned int pulse_widths[SERVO_COUNT] ;
// id du servo courant (dont on génère le créneau)
volatile unsigned char cur_servo = 0 ;
/*
Interrupt handler pour l'Ouput Compare A du timer 1
Le passage à cette valeur déclenche le rebouclage du compteur, et
correspond au début du créneau.
*/
SIGNAL (SIG_OUTPUT_COMPARE1A) {
// On monte donc la sortie du servo en cours de traitement
// (en gérant la distribution des sorties sur les ports C et D)
if (cur_servo < 2) {
PORTC |= (1 << cur_servo) ;
} else {
PORTD |= (1 << cur_servo) ;
}
// On définit le comparateur B en fonction de la durée de l'impulsion à générer
OCR1B = pulse_widths[cur_servo] ;
}
/*
Interrupt handler pour l'Ouput Compare B du timer 1
Le passage à cette valeur correspond à la fin du créneau pour
le servo en cours.
*/
SIGNAL (SIG_OUTPUT_COMPARE1B) {
// on descend la sortie du servo en cours de traitement
if (cur_servo < 2) {
PORTC &= ~(1 << cur_servo) ;
} else {
PORTD &= ~(1 << cur_servo) ;
}
// on passe au servo suivant, en rebouclant en fin de série
if (++cur_servo == SERVO_COUNT) cur_servo = 0 ;
}
/*
Initialisation des ports
*/
void port_init(void) {
// configuration du port D
// - PD2..PD7 en sortie
DDRD = 0xFC ;
// - toutes les sorties inactives
PORTD = 0x00 ;
// configuration du port C
// - PC0 et PC1 en sortie
DDRC = DDRC | 0x03 ;
// - toutes les sorties inactives (en laissant /RESET à 1)
PORTC = 0x40 ;
}
/*
Initialisation des périphériques
*/
void init_devices(void) {
cli(); // inhibition des interruptions (pour avoir la paix)
port_init(); // initialisation de prts
usart_init(UBRR_8_9600) ; // initialisation de l'UART
// MCU Control Register
// - interruptions externes sur niveau bas de la ligne
// - sleep mode désactivé
MCUCR = 0x00;
// General Interrupt Control
// - INT0 et INT1 inactives
GICR = 0x00;
// Initialisation du Timer/Counter 1
/*
On utilise un prescale de 8, ce qui pour un quartz à 8 MHz donne donc 1000 clocks
par ms. Ainsi, les valeurs des compteurs seront directement les timings voulus,
exprimés en µs.
Le créneau maximal de la commande du servo étant de 2ms, on va configurer le timer
pour qu'il déclenche une interruption et reboucle automatiquement en atteignant
cette valeur (mode CTC), grâce au premier comparateur (OCR1A). Cette interruption nous
servira à déclencher le font montant du créneau de commande.
L'instant du front descendant sera signalé par l'interruption du deuxième comparateur
(OCR1B) dont la valeur sera variable et traduira la position qu'on veut donner au servo.
*/
// Configuration du timer 1
// - mode CTC avec TOP indiqué par registre OCR1A
// => seul TCCR1B est à définir, le reste de la config donnant 0 pour TCCR1A
// (voir pages 95 et suivantes du datasheet)
TCCR1B = (1 << WGM12) // Waveform Generation Mode = CTC (TOP = OCR1A)
| (1 << CS11) // prescale à 8
;
// Définition du TOP à la largeur d'impulsion max + epsilon
/*
(pour éviter d'éventuels pbs aux limites pour la génération d'un créneau de 2ms,
situation dans laquelle OCR1A et OCR1B auraient les mêmes valeurs, et donc les
interruptions correspondantes pourraient arriver dans la mauvaise séquence)
*/
OCR1A = PULSE_MAX_WIDTH + 10 ;
// initialisation de OCR1B à la valeur de mi-course des servo
OCR1B = PULSE_MED_WIDTH ;
// initialisation du point de départ du compteur juste après cette valeur,
// pour démarrer le processus dans la bonne phase
TCNT1 = OCR1B + 10 ;
// Activation des interuptions des comparateurs A et B
TIMSK = (1 << OCIE1A)
| (1 << OCIE1B)
;
sei(); // autorisation des interruptions
}
// inputs utilisateur
unsigned char servo_id ; // id du servo à positionner
unsigned char servo_pos ; // position
char* msgInvalidInput = "*** Invalid input. Please re-enter ***\r\n" ;
int main(void) {
// initialisation de tous les servos à mi-course
for (int i = 0 ; i < SERVO_COUNT ; i++) {
pulse_widths[i] = 1500 ;
}
init_devices();
// A partir de maintenant, nous sommes en train de générer des pulses en permanence
// sur chacune des sorties des servos (PC0, PC1, PD2,..,PD7)
usart_puts("\fServo test application started...\r\n") ;
while (1) {
// Saisie de l'id du servo
while (1) {
usart_puts("Servo id [0..7] : ") ;
servo_id = usart_getc() ;
usart_putc(servo_id) ;
usart_puts("\r\n") ;
if ((servo_id >= '0') && (servo_id <= '7')) {
servo_id -= '0' ;
break ;
}
usart_puts(msgInvalidInput) ;
}
// arrivé ici, servo_id contient la valeur numérique de l'id du servo (de 0 à 7)
// Saisie de la valeur de la position
while (1) {
usart_puts("Position [0..9, A] : ") ;
servo_pos = usart_getc() ;
usart_putc(servo_pos) ;
usart_puts("\r\n") ;
if ((servo_pos >= '0') && (servo_pos <= '9')) {
servo_pos -= '0' ;
break ;
}
if ((servo_pos == 'a') || (servo_pos == 'A')) {
servo_pos = 10 ;
break ;
}
usart_puts(msgInvalidInput) ;
}
// arrivé ici, servo_pos contient la valeur numérique de la position du servo (de 0 à 10)
// Calcul de la largeur d'impulsion :
/*
- 0 correspond à la position mini, soit une créneau de 1ms, soit une valeur du comparateur B
de 1000 (cf initialisation du timer 1)
- 10 correspond au maxi, soit un créneau de 2ms, soit un comparateur à 2000
*/
pulse_widths[servo_id] = 1000 + 100 * servo_pos ;
}
}