Firmware -- Event and interrupt driven sampling & writing

A project log for Heartbeat Logger

A portable device that logs a snippet of your heart at the push of a button.

Ole Andreas UtstumoOle Andreas Utstumo 05/03/2016 at 20:070 Comments

As the SAMD20 doesn't have DMA, I solved the sampling & writing to the SD card by having a timer time the ADC sampling with an event and using an interrupt on the ADC's result ready to store the result in a double FIFO buffer. The sampling frequency is low enough for it to pose no trouble at all. The vital thing is that the interrupt always must be serviced before the next sample is ready to ensure consistent sampling. Here's a flowchart of how it works:

There are two buffers as below. As one of the buffers fills up, we switch them by assigning a pointer to each of the buffers. Data will always be stored into the same pointer, buffer_current, and data will simultaneously be written to a memory card from the same pointer, buffer_last. It's the buffers being pointed at that will switch place.

char buffer_a[BUFFER_SIZE];
char buffer_b[BUFFER_SIZE];
char *buffer_current;
char *buffer_last;

To keep track of the buffer, we also need something to point the current array position:

uint16_t buffer_counter = 0;

The ADC ISR is set up in the form as a callback using ASF. When each sample is ready after a conversion has been triggered by the event system, the ADC_Handler callback will be called. The BUFFER_SIZE is set to 512, so it will fit 256 samples.

void ADC_Handler(){
	//Result is ready, put it in buffer
	if(buffer_counter < BUFFER_SIZE){
		uint16_t resultat = REG_ADC_RESULT;
		//store the result as little endian
		*(buffer_current + buffer_counter) = resultat;
		*(buffer_current + buffer_counter + 1) = 0xFF & (resultat>>8);
		if(buffer_counter >= BUFFER_SIZE){
			if(battery_check_counter++ > BATTERY_CHECK_TIMER){
				battery_check_counter = 0;	

			flag_buffer_ready = 1;
			buffer_counter = 0;
			char * buffer_temp;
			buffer_temp = buffer_current;
			buffer_current = buffer_last;
			buffer_last = buffer_temp;
		//Buffer overflow
                buffer_counter = 0;

Once the buffer is full and have been switched, the flag_buffer_ready is set, and we can handle the writing in main. The function below is constantly being called as long as the logger is in the sample state.

int8_t write_data(void){

        flag_buffer_ready = 0;   

        //write to sd card        
        if(!sdcard_write(buffer_last, BUFFER_SIZE))
            return -1;    
        return 1;
    return 0;

If a new ADC sample is ready, the ADC_Handler will be called whatever the system is doing, including writing to the SD card.

Here's a tip: By toggling a port pin at a certain point in the code, you can debug the timing of your system by hooking up an oscilloscope:

Here, the code is toggling a pin at every ADC interrupt (red) and raises another pin while writing to the SD card (blue). Rock steady! I'll post the code on GitHub soon enough.