Using the buzzer on PortuxG20 rev. 2 and NanosG20

Introduction

The PortuxG20 rev. 2 and the NanosG20 contain a buzzer connected to pin PA26 (TIOA0) of the AT91SAM9G20 microcontroller. It can be used by programming the Timer Counter controller.

Requirements

Make sure the kernel does not use the Timer Counter block as a clock source. If it does, the example code will crash the device. By default, PortuxG20 rev. 2 does not use them and the example works out of the box. NanosG20 has them enabled, you therefore have to disable them. To do this, disable the following last two options in the kernel config (if you disable the second last, the last becomes unavailable):

Device Drivers ->
  Misc devices ->
    Atmel AT32/AT91 Timer/Counter Library
      TC Block Clocksource

It might be ok to only disable the last option.

Example code

Compile the following example code and run it on the devices. It should play the first notes of "Für Elise"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>

#define BIT(nr) ((unsigned long) 1 << nr)


#define MAP_SYSTEM_SIZE 0x1000UL
#define MAP_USER_SIZE   0x1000UL

#define USER    0xfffa0000UL
#define SYSTEM  0xfffff000UL

#define PIOA_BASE  0x0400
#define PIOB_BASE  0x0600
#define PIOC_BASE  0x0800
#define PIOD_BASE  0x0a00
#define PMC_BASE   0x0c00
#define TC0_BASE   0x0000
#define TC1_BASE   0x0040
#define TC2_BASE   0x0080

/* PIO */

#define PIO_PDR  0x0004
#define PIO_ASR  0x0070

/* PMC */

#define PMC_PCER  0x0010

/* Timer Counter */

#define TC_CCR  0x00
#define TC_CMR  0x04
#define TC_CV   0x10
#define TC_RA   0x14
#define TC_RB   0x18
#define TC_RC   0x1C
#define TC_SR   0x20
#define TC_IER  0x24
#define TC_IDR  0x28
#define TC_IMR  0x2C

void *sys_base;
void *usr_base;
int fd;

#define MAP_PAGES_FAILED -1

int map_pages(void)
{
	if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
		return MAP_PAGES_FAILED;

	/* Map Sys-Page */
	sys_base = mmap(0, MAP_SYSTEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, SYSTEM  & ~(MAP_SYSTEM_SIZE-1));
	if(sys_base == (void *) -1)
		return MAP_PAGES_FAILED;

	/* Map Usr-Page */
	usr_base = mmap(0, MAP_USER_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, USER & ~(MAP_USER_SIZE-1));
	if(usr_base == (void *) -1)
		return MAP_PAGES_FAILED;

	return 0;
}

void unmap_pages(void)
{
	munmap(sys_base, MAP_SYSTEM_SIZE);
	munmap(usr_base, MAP_USER_SIZE);
	close(fd);
}


void init_buzzer(void)
{
	if(!sys_base || !usr_base)
		return;

	/* mux PA26 as perpherial B (TIOA0) */
	*((unsigned int *) (sys_base + PIOA_BASE + PIO_PDR)) = BIT(26); /* disable io */
	*((unsigned int *) (sys_base + PIOA_BASE + PIO_ASR)) = BIT(26); /* peripheral B select */
	
	/* enable pmc for TC0 with peripheral ID 17 */
	*((unsigned int *) (sys_base + PMC_BASE + PMC_PCER)) = BIT(17);
	return;
}

void start_buzzer(void)
{
	if(!sys_base || !usr_base)
		return;

	/* configure mode register of TC0 as waveselect and TIOA as output
	   wavesel = 11, timer clock 2 = 132 MHz/8, with ra and rc compare */
	*((unsigned int *) (usr_base + TC0_BASE + TC_CMR)) = 0x7c401;

	/* disable interrupts */
	*((unsigned int *) (usr_base + TC0_BASE + TC_IDR)) = 0xff;

	/* enable timer and start clock */
	*((unsigned int *) (usr_base + TC0_BASE + TC_CCR)) = 0x0005;
	return;

}

void set_freq(float freq) {
	int rc;

	if (freq == 0)
		rc = 0;
	else
		rc = (132000000/8)/freq;

	*((unsigned int *) (usr_base + TC0_BASE + TC_RA)) = rc / 2;
	*((unsigned int *) (usr_base + TC0_BASE + TC_RC)) = rc;
}

void stop_buzzer(void)
{
	if(!sys_base || !usr_base)
		return;

	/* disable clock */
	*((unsigned int *) (usr_base + TC0_BASE + TC_CCR)) = 0x0002;
}

/*------------------------------------------------------------------------*/

#define C4  261.626
#define D4  293.665
#define E4  329.628
#define F4  349.228
#define G4  391.995
#define A4  440
#define H4  493.883
#define C5  523.251
#define D5  587.330
#define DIS5 622.254
#define E5  659.255
#define F5  698.456
#define G5  783.991
#define A5  880
#define H5  987.767

#define DF 1600000
#define DH 800000
#define DQ 400000
#define DO 200000


/* Für Elise */
float notes[]={
	E5  , DO,
	DIS5, DO,
	E5  , DO,
	DIS5, DO,
	E5  , DO,
	H4  , DO,
	D5  , DO,
	C5  , DO,
	A4  , DQ,
	0   , DO,
	C4  , DO,
	E4  , DO,
	A4  , DO,
	H4  , DQ,
	0   , DO,
	D4  , DO,
	F4  , DO,
	H4  , DO,
	C5  , DQ,
};

void sig_handler(int signr)
{
	stop_buzzer();
	exit(1);
}

int main(int argc, char **argv)
{
	int i;
	if(map_pages()!= MAP_PAGES_FAILED) {
		init_buzzer();
		signal(SIGTERM, sig_handler);
		signal(SIGINT, sig_handler);
		start_buzzer();

		for(i=0; i < sizeof(notes)/sizeof(notes[0])/2; i++) {
			set_freq(notes[(i*2)%(sizeof(notes)/sizeof(notes[0]))]);
			usleep(notes[(i*2+1)%(sizeof(notes)/sizeof(notes[0]))]);
		}

		stop_buzzer();
		unmap_pages();
	}
		exit(0);
	return 0;
}