Coder un Tap Tempo en AVR

      Quand j'ai commencé à coder en AVR, je voulais faire un bon Tap Tempo. A ce moment la, je n'ai pas trouvé de ressource en AVR pour en faire un.

Alors aujourd'hui, nous allons fair clignoter une LED en rythme que vous taperez. Cela peut parraitre un peu basique, mais c'est une étape importante dans la creation d'un Tap Tempo, et c'est aussi un exercice de code sympa.

Pourquoi l'AVR? A vrai dire, je ne sais pas vraiment. Cela m'a semblé un peu plus accessible que le PIC. Et avec la popularité d'Arduino il y a beaucoup d'aide sur internet et j'aimais l'idée d'apprendre le C.

Rappelez vous que ceci n'est pas un projet pour Arduino, même si c'est surement possible de faire marcher ce code sur Arduino. A mon avis, coder une puce AVR sans Arduino permet une meilleure compréhension du micro-controleur ainsi qu'un controle plus fin.

Le Circuit

Comme vous pouvez le voir le circuit est très simple. Vous pouvez connecter la resistance et l'interrupteur à n'importe quel entrée/sortie générale.

#include <avr/io.h>

#define TAP_PIN PINB0
#define LED_PIN PINB1

int main(void)
{
	
    while (1) 
    {
    }
}
#include <avr/io.h>

#define TAP_PIN PINB0
#define LED_PIN PINB1

int main(void)
{
    DDRB |= (1<<LED_PIN); // la broche LED comme sortie
    PORTB |= (1<<TAP_PIN); //+5V à l'interrupteur

    while (1)  //Boucle infinie
    {
    }
}

//Pour écrire 1 dans le bit:
PORTA |= (1<<PINA0);

//Pour ecrire 0 dans le bit:
PORTA &= ~(1<<PINA0);

//Pour commuter la valeur du bit:
PORTA ^= (1<<PINA0);
#include <avr/io.h>
#define F_CPU 1000000UL		//indique la fréquence de l'horloge
#include <util/delay.h>		//inclue le header de delay
#include <avr/io.h>
#define F_CPU 1000000UL		//indique la fréquence de l'horloge
#include <util/delay.h>		//inclue la bibliothèque de delay

#define TAP_PIN PINB0
#define LED_PIN PINB1
#define DEBOUNCE_TIME 1000	//définie le temps de debounce en us

uint8_t debounce(void)		//declaration de la fonction de debounce
{
	if (bit_is_clear(PINB,0))	//si bouton appuyé
	{
		_delay_us(DEBOUNCE_TIME); //attendre 1ms
		if (bit_is_clear(PINB,0))    //si le bouton est toujours appuyé
		{
			return(1);	//la fonction retourne 1
		}
		else		//si le bouton n'est plus appuyé à un moment retourne 0
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
}            //fin de la fonction de debounce

int main(void)
{
	DDRB |= (1<<LED_PIN); //broche de LED comme sortie
	PORTB |= (1<<TAP_PIN); //+5V à l'interrupteur
    while (1) //boucle infinie
    {
    }
}

Configuration du compteur

Maintenant que nous pouvons détecter le footswitch, commencons par compter les millisecondes entre les taps. Ceci se fait avec un timer/counter.

La pluspart des AVR ont un compteur 8-bits, souvent le Timer 0. Nous voulons que ce timer compte constemment et revienne à 0 au bout d'une milliseconde.

Notre horloge interne est calibrée à 1MHz, alors logiquement nous devrons compter 1000 cycles. Le problème est que un compteur 8-bits ne compte que jusqu'à 255. Cependant nous pouvons éviter ce problème en utilisant un prescaler de 8. Nous aurons besoin de compter que 1000/8 = 125 cycles. Pour selectionner ce prescaler nous devons ecrire CS01 dans le registre TCCR0B.

Pour avoir un compteur qui compte constemment jusqu'à 124, nous allons utiliser le mode Fast PWM avec une valeur TOP choisie (le TOP est 255 par default). Le nouveau TOP sera défini par OCR0A. Ce table de la datasheet (p.81)nous montre quel Waveform Generation Mode nous devons utiliser. Nous changerons les bits WGMXX dans TCCR0A & TCCR0B pour sélectionner le mode dont nous avons besoin.

Pour compter les ms qui sont passées nous devons incrémenter une variable quand le compteur atteint sa valeur la plus haute (TOP). Pour cela nous devons activer les interruptions. Ceci ce fait avec 3 lignes. D'abord on #include "interrupts.h". Puis on active l'interuption d'overflow du timer0, et enfin on active globalement les interruptions avec sei().

Notre compteur est maintenant configuré, il nous reste à écrire notre interruption. Nous créons une variable globale volatile qui sera incrémenté à chaque execution de l'interruption appelée "ms'. Le vexteur de notre interruption est "TIM0_OVF_vect".

Notre code ressemble maintenant à ça:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#include <avr/io.h>
#define F_CPU 1000000UL	//indique la fréquence d'horloge
#include <util/delay.h>	//inclue la bibliothèque de delay
#include <avr/sfr_defs.h> //inclue les special feature registers
#include <avr/interrupt.h> //iinclue les interruptions

#define TAP_PIN PINB0
#define LED_PIN PINB1
//on définie le temps de débounce en µs
#define DEBOUNCE_TIME 500 

volatile uint16_t ms = 0; //declaring 

//declaration de la fonction de debounce
uint8_t debounce(void)
{
	if (bit_is_clear(PINB,0))	//si bouton appuyé
	{
                        //attendre temps de debounce
		_delay_us(DEBOUNCE_TIME); 
                        //si bouton toujours appuyé
		if (bit_is_clear(PINB,0))    
		{
			return(1);    //fonction donne 1
		}
//si bouton relaché a un moment, fonction donne 0
		else
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
}

int main(void)
{
	DDRB |= (1<<LED_PIN); //broche LED sortie
	PORTB |= (1<<TAP_PIN); //+5V broche interrupteur
	
	/*timer0 setup*/
	OCR0A = 124;		//max du compteur
	TCNT0 = 0;		//Counter start
            //Fast PWM mode OCR1A en TOP
	TCCR0A |= (1<<WGM00) | (1<<WGM01);
            //interruption d'Overflow activée	
	TIMSK0 |= (1<<TOIE0);
             //Fast PWM mode, pas de prescaler
	TCCR0B |=  (1<<WGM02) | (1<<CS01); 
	
	sei(); //active les interruptions
    while (1) 
    {
    }
}

ISR(TIM0_OVF_vect) //interruption d'overflow du compteur
{
	ms++;	//incremente ms toutes les ms
}

Le Tap Tempo

 

Nos périphériques sont configurés, codons maintenant ce Tap Tempo!

Nous devons faire attention à quelques paramètre, créons une variable pour chacun d'eux :

 

- Est-ce que le bouton à été relaché ?: si le programme ne prend pas cela en compte, il contabilisera des centaines de tap si on garde le bouton appuyé. Ainsi nous créons une variable uint8_t appellée buttonstate. Elle sera de 1 si le bouton est appuyé et 0 sinon. Nous l'initialiserons a 0.

 

- Combien de fois à t'on tapé ? Nous ne voulons pas faire la même chose pour le premier tap et les suivants. Alors autant compter le nombre de taps dans une séquence. Nous créons donc une variable uint8_t appellée nbtap. Elle est initialisée a 0.

 

- Quel est le tempo maximum autorisé?: Si nous ne limitons pas le tempo maximum l'AVR n'arretera pas de compter les ms, et cela posera problème entre les séquences de tap. Créons donc une variable uint16_t appellée maxtempo. Elle sera initialisée a 2000µs.

 

- Quel tempo je viens de taper? : La variable la plus évidente, le tempo à suivre pour la LED. Créons une variable uint16_t appelée tempo.

 

On déclare ces variable dans la fonction main avant la boucle infinie:

 

 

 

 

 

 

Maintenant commencons par le début. Que faire lors du premier tap? Et bien nous devons commencer à compter dans l'attente d'un éventuel second tap. Nous devons aussi indiqué que la bouton à été préssé et pas encore relaché.

Nous ferons cela dans un "if" placé dans la boucle infinie:

 

 

 

 

 

 

 

Notons aussi quand le bouton est relaché avec un autre "if":

 

 

 

 

 

 

Ajoutons un autre "if" pour le cas où nous depassons le tempo maximum. Nous aurons besoin de réinitialiser nbtap pour que la prochaine séquence soit normale.

 

 

 

 

 

Et enfin, quand le tap n'est pas le premier. Nous écrivons le nouveau tempo depuis les ms comptées entre les deux taps. Sans oublier de modifier nbtap, buttonstate et de mettre le compteur à zéro pour la prochain tap.

 

 

 

 

 

 

 

 

 

 

 

 

int main(void)
{
    uint8_t buttonstate;    //bouton relaché?
    uint8_t nbtap = 0;     //nombre de tap en une séquence
    uint16_t maxtempo = 2000;    //tempo max autorisé en ms
    uint16_t tempo;    //le tempo actuel
if (debounce() == 1 && nbtap == 0 && buttonstate == 0) //premier tap
{
	TCNT0 = 0;	//commence à compter
	ms = 0;
	nbtap++;    //bouton appuyé une fois
	buttonstate = 1; //bouton pas encore relaché
}
if (debounce() == 0 && buttonstate == 1)	//si le bouton est relaché
		{
			buttonstate = 0;
		}
if (ms >= maxtempo)	// si ms dépasse tempo maximum
		{
			nbtap = 0;  //réinitialise nombre de tap
		}
if (debounce() == 1 && nbtap != 0 && buttonstate == 0) //pas le premier tap
{
	tempo = ms;		//nouveau tempo
	nbtap++;
	buttonstate = 1;
	TCNT0 = 0;		//réinitialise le compteur
	ms = 0;
}

Faire clignoter la LED

 

Tout est fait, il ne reste plus qu'à faire clignoter la LED. Ce que je propose, c'est d'ajouter un nouveau compteur de ms pour la LED. De cette façon la LED est indépendante du processus du Tap Tempo.

 

Nous ajoutons une variable globale volatile "ledms" Nous la déclarons de la même façon que "ms". Cette variable est incrémentée dans l'interruption tout comme "ms".

 

 

 

 

 

 

 

 

Ajoutons maintenant un "if" dan la boucle de la fonction main. h an "if" statement in the main loop.

 

 

 

 

 

 

 

Avec ceci la LED est allumée sur le temp, reste allumée 8ms puis s'éteint jusqu'au prochain temps.

volatile uint16_t ms = 0; //declaring ms counting variables
volatile uint16_t ledms = 0;

ISR(TIM0_OVF_vect) //Timer overflow interrupt
{
	ms++;	//increment every ms
	ledms++;
}
if (ledms >= tempo)
{
	ledms = 0;    //reinitialise le compteur LED
	PORTB |= (1<<LED_PIN);    //Allume la LED
	_delay_ms(8);   //attend 8ms
	PORTB &= ~(1<<LED_PIN);    //Eteint la LED
}
#include <avr/io.h>
#define F_CPU 1000000UL		//indique la fréquence d'horloge
#include <util/delay.h>		//inclue la bibliothèque de delay
#include <avr/interrupt.h>	//inclue les interruptions

#define TAP_PIN PINB0
#define LED_PIN PINB1
#define DEBOUNCE_TIME 500	//definie un temps de debounce en us

volatile uint16_t ms = 0; //declare les variable qui compte les ms
volatile uint16_t ledms = 0;

uint8_t debounce(void)		//declare la fonction de debounce
{
	if (bit_is_clear(PINB,0))	//si la broche du boutton est basse
	{
		_delay_us(DEBOUNCE_TIME); //attendre 1ms
		if (bit_is_clear(PINB,0))	//si broche toujours basse (boutton appuyé)
		{
			return(1);	//la fonction retourne 1
		}
		else	//si la broche n'est pas basse a un moment retourne 0
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
}

int main(void)
{
	DDRB |= (1<<LED_PIN); //LED comme sortie
	PORTB |= (1<<TAP_PIN); //+5v à l'interrupteur
	
	/*timer0 setup*/
	OCR0A = 124;		//valeur max du compteur 
	TCNT0 = 0;		//debut du compteur
	TCCR0A |= (1<<WGM00) | (1<<WGM01);	//Fast PWM mode OCR1A est le TOP
	TIMSK0 |= (1<<TOIE0);	//active l'interrution d'overflow
	TCCR0B |=  (1<<WGM02) | (1<<CS01); //Fast PWM mode, prescaler de 8
	
	uint8_t buttonstate;	//bouton relaché?
	uint8_t nbtap;			// Nombre de taps dans un séquence
	uint16_t maxtempo = 2000;	//tempo max autorisé
	uint16_t tempo;
	
	sei(); //Activate interrupts
	
    while (1) 
    {
		if (debounce() == 1 && nbtap == 0 && buttonstate == 0) //premier tap
		{
			TCNT0 = 0;	//commence à compter
			ms = 0;
			nbtap++;
			buttonstate = 1;
		}
		
		if (debounce() == 0 && buttonstate == 1)	//si bouton relaché
		{
			buttonstate = 0;
		}
		
		if (ms >= maxtempo)		//si tempo max dépassé
		{
			nbtap = 0;			//réinitialise la séquence
		}
		
		if (debounce() == 1 && nbtap != 0 && buttonstate == 0) //pas le premier tap
		{
			tempo = ms;		//nouvelle valeur de tempo
			nbtap++;
			buttonstate = 1;
			TCNT0 = 0;		//compteur à 0 pour la fois d'après
			ms = 0;
		}
		
		if (ledms >= tempo)    //si on est sur le temps
		{
			ledms = 0;    //remet compteur pour LED à zéro
			PORTB |= (1<<LED_PIN);    //allume la led
			_delay_ms(8);    //attend 8ms
			PORTB &= ~(1<<LED_PIN);    //eteint la LED
		}
    }
}

ISR(TIM0_OVF_vect) //interruption d'overflow
{
	ms++;	//incremente les millisecondes
	ledms++;
}

© 2019 Electric Canary | Tous droits réservés

Logo Facebook Logo Instagram Logo Twitter

Logo Facebook Logo Instagram Logo Twitter
Logo Facebook Logo Instagram Logo Twitter Logo Github

Logo Facebook Logo Instagram Logo Twitter

Logo Github

Logo Facebook Logo Instagram Logo Twitter Logo Github