Coding an AVR Tap Tempo

When I first started AVR coding I wanted to make a nice Tap Tempo. At that time, I didn't find an easy to understand AVR code for it.

So today, we are going to make a LED blink in sync with the rhythm you tapped. It may not seem like much, but it's an important step in the making of a Tap Tempo, and a cool coding exercise.

Why AVR? I don't really know, to me it seemed a bit more friendly than PIC. With the popularity of Arduino it has a lot of support and I liked the idea of knowing a bit of C.

Keep in mind this isn't an Arduino project (even though it will probably work on an Arduino), coding an AVR chip without Arduino, allows a deeper understanding and control of the micro-controller in my opinion.

The Circuit

 

 

 

 

 

As you can see, the circuit is really simple. You can connect the resistor and switch to any pin with General Input/Output capacities.

 

#include <avr/io.h>

#define TAP_PIN PINB0
#define LED_PIN PINB1

int main(void)
{
	
    while (1) 
    {
    }
}
//Setting a bit in a register
// is done this way:
PORTA |= (1<<PINA0);

//Clearing a bit in a register 
//is done this way:
PORTA &= ~(1<<PINA0);

//Toggling a bit in a register
// is done this way:
PORTA ^= (1<<PINA0);
#include <avr/io.h>

#define TAP_PIN PINB0
#define LED_PIN PINB1

int main(void)
{
     //Setting LED PIN as an output
    DDRB |= (1<<LED_PIN);
     //Setting TAP PIN high
    PORTB |= (1<<TAP_PIN);

    while (1)  //Infinite Loop
    {
    }
}

 

#include <avr/io.h>
//indicate the CPU clock frequency
#define F_CPU 1000000UL
//including the delay header
#include <util/delay.h>
//including special function register
#include <avr/sfr_defs.h>

#define TAP_PIN PINB0
#define LED_PIN PINB1
//define debounce time in µs
#define DEBOUNCE_TIME 1000

uint8_t debounce(void)  //declaring a debounce function
{
            //if TAP_PIN to ground
	if (bit_is_clear(PINB,0))
	{
                        //wait for debounce time
		_delay_us(DEBOUNCE_TIME);
                        //if TAP_PIN still to ground
		if (bit_is_clear(PINB,0))
		{
                                    //function returns 1
			return(1);
		}
//if TAP_PIN not to ground at any time, function returns 0
		else
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
}            //end of debounce function

int main(void)
{
             //Setting LED PIN as an output
	DDRB |= (1<<LED_PIN);
	PORTB |= (1<<TAP_PIN); //Setting TAP PIN high
    while (1) //infinite loop
    {
    }
}
#include <avr/io.h>
//indicate the CPU clock frequency
#define F_CPU 1000000UL
//including the delay header
#include <util/delay.h>

Timer Setup

Now that we can sense the footswitch, let's start counting time between those taps. This is done by setting up a ms timer.

Most AVR chip have a 8bit timer/counter, usually it's Timer 0. We want that timer to count up constantly and to return to 0 when a ms has passed.

Our internal clock is set to 1MHz, so logically we need to count 1000 clock ticks. The problem with that is that a 8bit counter only counts up to 255. We can use a prescaler to avoid that problem, if we use a prescaler of 8 we only need to count 1000/8 = 125 ticks. To select the 8 prescaler we need to set CS01 in the TCCR0B register.

In order to have a counter that counts constantly up to 124 we need to use Fast PWM with a custom TOP value (TOP is 255 by default). The new TOP value will be OCR0A in the mode we choose. This table from the datasheet (p.81) shows us which Waveform Generation Mode to use. We'll change WGMXX bits in TCCR0A and TCCR0B in order to select the WGM mode we need.

 

 

 

To count the ms that has passed we need to update a variable every time that the counter reaches its TOP value. To do that we need to enable interrupts. This is done with 3 lines. First, we #include the "interrupts.h" header. Then we enable the timer 0 overflow interrupt, and finally we enable global interrupts with the sei() command.

Our counter is now set up, the last thing to do is to write what to do in our overflow interrupt. Let's create a volatile global variable "ms" to increment every time the overflow interrupt is executed. The vector for our interrupt is "TIM0_OVF_vect".

Our code now looks like that:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#include <avr/io.h>
//indicate the CPU clock frequency
#define F_CPU 1000000UL
//including the delay header
#include <util/delay.h> 
//including special feature registers
#include <avr/sfr_defs.h>
//including interrupts
#include <avr/interrupt.h>

#define TAP_PIN PINB0
#define LED_PIN PINB1
//define debounce time in µs
#define DEBOUNCE_TIME 500

volatile uint16_t ms = 0; //declaring ms global variable

uint8_t debounce(void)  //declaring a debounce function
{
            //if TAP_PIN to ground
	if (bit_is_clear(PINB,0))
	{
                        //wait for debounce time
		_delay_us(DEBOUNCE_TIME);
                        //if TAP_PIN still to ground
		if (bit_is_clear(PINB,0))
		{
                                    //function returns 1
			return(1);
		}
//if TAP_PIN not to ground at any time function returns 0
		else
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
}

int main(void)
{
            //Setting LED PIN as an output
	DDRB |= (1<<LED_PIN);
            //Setting TAP PIN high
	PORTB |= (1<<TAP_PIN);
	
	/*timer0 setup*/
	OCR0A = 124;  //counter max
	TCNT0 = 0;  //Counter start
            //Fast PWM mode OCR1A as TOP
	TCCR0A |= (1<<WGM00) | (1<<WGM01);
            //Overflow interrupt enable
	TIMSK0 |= (1<<TOIE0);
            //Fast PWM mode, No prescaler
	TCCR0B |=  (1<<WGM02) | (1<<CS01);
	
	sei(); //Activate interrupts
    while (1) 
    {
    }
}

ISR(TIM0_OVF_vect) //Timer overflow interrupt
{
	ms++;	//increment ms every ms
}

The Tap Tempo

 

Now that peripherals are set up, let's code that Tap Tempo!

We'll need to keep track of some things, so let's create variables for each one :

 

- Has the button been released ?: if the program doesn't know it will count hundreds of taps if we keep the button pressed. So we create a uint8_t variable buttonstate. 1 if pressed 0 if released. It is initialised as 0.

 

- How many taps did we do ?: you don't want to do the same thing for the first and 30th tap, so lets keep track of that. We create a uint8_t variable called nbtap.

 

- What's the maximum delay allowed ?: If we don't limit the maximum tempo the AVR will never stop counting and that can be a problem between tap sequences. Let's create a uint16_t variable called maxtempo. We'll choose 2000 ms.

 

- What tempo did we just tap? : The most obious variable needed, the tempo we want the LED to blink to. Let's create a uint16_t variable called tempo. With an initial tempo of 120bpm (500ms).

 

We need to declare these in the main function :

 

 

 

 

 

 

 

 

 

 

Now let's start at the beginning, what do we do at the first press of the button?

Well, we need to start counting the ms in case of a second tap. We also need to indicate that the button has been pressed once, and not released yet.

The code will be an "if" statement in the main loop:

 

 

 

 

 

 

 

 

Now let's notify the program when the button is released :

 

 

 

 

 

 

Let's add another "if" for the case if we exceed the maximum tempo. We'll need to reset  the nbtap variables in order to have a normal tap sequence next time.

 

 

 

 

 

And finally, when we tap more than once, let's update the tempo with the ms counted between the two taps. Not forgatting to update, buttonstate and nbtap variable. We will also reset the  counter to count for next tap too.

 

 

 

 

 

 

 

 

int main(void)
{
    //Was the button released?
    uint8_t buttonstate = 0;
    //Number of taps in sequence
    uint8_t nbtap = 0; 
    //Maximum tempo allowed in ms
    uint16_t maxtempo = 2000;
    //The current tempo
    uint16_t tempo = 500;
//first tap
if (debounce()==1 && nbtap==0 && buttonstate==0)
{
	TCNT0 = 0;  //starts counting
	ms = 0;
	nbtap++;    //the button was tapped once
            //the button isn't released yet
	buttonstate = 1;
}
if (debounce() == 0 && buttonstate == 1)
// if button released
{
	buttonstate = 0;
}
if (ms >= maxtempo)// ms exceed maximum tempo
{
	nbtap = 0;  //reset tap sequence
}
//not first tap
if (debounce()==1 && nbtap!=0 && buttonstate==0)
{
	tempo = ms;	//update tempo
	nbtap++;
	buttonstate = 1;
	TCNT0 = 0;	//reset counter
	ms = 0;
}

Making the LED Blink

 

Everything has been taken care of, we just need to make the LED blink now. What I propose to do is to add another ms counter for the LED. This way, the LED blinking is independant of the Tap tempo ms count.

 

So we add a uint16_t volatile general variable, called "ledms". We need to declare it the same way than "ms". This variable will be incremented in the timer overflow interrupt, just like "ms".

 

 

 

 

 

 

 

 

Now that our led variable is set up, let's make the LED blink with an "if" statement in the main loop.

 

 

 

 

 

 

 

This way the LED wil be on on the downbeat and stay on for 8ms, then turn off until the next downbeat.

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

ISR(TIM0_OVF_vect) //Timer overflow interrupt
{
	ms++;	//increment every ms
	ledms++;
}
if (ledms >= tempo)
{
	ledms = 0;
	PORTB |= (1<<LED_PIN);
	_delay_ms(8);
	PORTB &= ~(1<<LED_PIN);
}
#include <avr/io.h>
#define F_CPU 1000000UL //indicate the CPU clock frequency
#include <util/delay.h> //including the delay header
#include <avr/interrupt.h> //including interrupts

#define TAP_PIN PINB0
#define LED_PIN PINB1
#define DEBOUNCE_TIME 500 //define debounce time in us

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

uint8_t debounce(void) //declaring a debounce function
{
	if (bit_is_clear(PINB,0)) //if TAP_PIN to ground
	{
                        //wait for debounce time
		_delay_us(DEBOUNCE_TIME);
                //if TAP_PIN still to ground (switch still pressed)
		if (bit_is_clear(PINB,0))
		{
			return(1); //function returns 1
		}
//if TAP_PIN not to ground at any time function returns 0
		else 
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
}

int main(void)
{
	DDRB |= (1<<LED_PIN); //Setting LED PIN as an output
	PORTB |= (1<<TAP_PIN); //Setting TAP PIN high
	
	/*timer0 setup*/
	OCR0A = 124; //counter max
	TCNT0 = 0; //Counter start
            //Fast PWM mode OCR1A as TOP
	TCCR0A |= (1<<WGM00) | (1<<WGM01); 
	TIMSK0 |= (1<<TOIE0); //Overflow interrupt enable
             //Fast PWM mode, 8 prescaler
	TCCR0B |=  (1<<WGM02) | (1<<CS01);
	
	uint8_t buttonstate = 0;	//Was the button released?
	uint8_t nbtap; // Number of Taps in sequence
	uint16_t maxtempo = 2000; //Maximum tempo allowed in ms
	uint16_t tempo = 500;
	
	sei(); //Activate interrupts
	
    while (1) 
    {
                        //first tap
		if (debounce()==1 && nbtap==0 && buttonstate==0) 
		{
			TCNT0 = 0; //starts counting
			ms = 0;
			nbtap++;
			buttonstate = 1;
		}
		
                        // if button released
		if (debounce() == 0 && buttonstate == 1) 
		{
			buttonstate = 0;
		}
		
		if (ms >= maxtempo) //if maxtempo exeeded
		{
			nbtap = 0; //reset tap sequence
		}
		
                        //not first tap
		if (debounce()==1 && nbtap!=0 && buttonstate==0) 
		{
			tempo = ms; //update tempo
			nbtap++;
			buttonstate = 1;
			TCNT0 = 0; //reset counter
			ms = 0;
		}
		
		if (ledms >= tempo)
		{
			ledms = 0;
			PORTB |= (1<<LED_PIN);
			_delay_ms(8);
			PORTB &= ~(1<<LED_PIN);
		}
    }
}

ISR(TIM0_OVF_vect) //Timer overflow interrupt
{
	ms++; //increment ms every ms
	ledms++;
}

Legal

About