maximum frequency interrupt

hello, im tring to make 16 bits music with an arduino due. The samples are played in an interrupt, but in don’t managed to get a frequency sufficiently high.
for the TC_SetRC(TC0, 1, 400); if i write a number under 400 for TIMER_CLOCK3 it doesn’t work. And for each other timer clock, i can’t get under something like 6500 Hz for the music.

Here is the program, just look at the ISR and the timer setup:

#include <SdFat.h>
SdFat sd;
SdFile file;

#define BUF_SIZE 10000//taille des buffers (doit être pair)
uint8_t buffer0[BUF_SIZE];
uint8_t buffer1[BUF_SIZE];
volatile uint32_t var32;//variable 32bits qui contient le nombre 32 bits à écrire sur les registres
volatile boolean num_buff_lec=0;//variable qui dicte le choix du buffer qui est lu
volatile boolean auto_remp_buffer=0;
volatile boolean numerobufferecr=1;
volatile int tetelecture=0;

unsigned int i=0;
  
void setup() 
{

  //TIMER
  /* activation du peripherique TC dans le Power Management Controller */
  PMC->PMC_WPMR = PMC_WPMR_WPKEY_VALUE;//on desactive la write protection des registres en entrant la clé dans le registre PMC_WPMR
  
  PMC->PMC_PCER0 = 1 << 28;//on active le TC_3 dont l'identifiant est 30, il est donc controlé par le PMC_PCER0 au bit 30

  /*on configure le timer*/
  TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_WAVE                 // Waveform mode
                            | TC_CMR_WAVSEL_UP_RC         // UP mode with automatic trigger on RC Compare
                            | TC_CMR_TCCLKS_TIMER_CLOCK3; // MCK/2 = 42 M Hz, clk on rising edge
                            
  TC0->TC_CHANNEL[1].TC_RC = 400;//on choisi la valeur du registre C TC_RC
  TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;//démarre le timer avec le registre TC_CCR
  
  // enable timer interrupts on the timer
  TC0->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;   //IER = interrupt enable register; on écrit sur le registre TC_IER pour autoriser les interuptions liées à la comparaison avec le registre C, ici on active les interruptions du timer pour la comparaison avec le registre C; TC_CHANNEL est un tableau de type TcChannel dans la structure Tc, c'est une structure dans une structure, c'est pourquoi il y a la fleche pour le pointeur puis le point pour l'elements de structure
  
  /* on active le nested vector interrupt controller qui controle toutes les interruptions*/
  NVIC_EnableIRQ(TC1_IRQn);
  

  //SD SPI
  /* Initialisation du port série*/
  //Serial.begin(9600);
  /* Initialisation du port SPI */
  pinMode(10, OUTPUT);
  if (!sd.begin(10, SPI_FULL_SPEED))
  {
    //Serial.println("FAIL CONNEXION");
    for(;;);
  }

  file.open("test.raw", O_READ);//on ouvre le fichier
  file.read(buffer0, BUF_SIZE);
  file.read(buffer1, BUF_SIZE);

  //REGISTRES PIO
  /* activation du peripherique TC dans le Power Management Controller */
  PMC->PMC_PCER0 = 1 << 13;
  PIOC->PIO_PER = 0b00000000000001111111001111111110;
  
  PIOC->PIO_OER = 0b00000000000001111111001111111110;
  PIOC->PIO_OWER = 0b00000000000001111111001111111110;
  PIOC->PIO_OWDR = 0b11111111111110000000110000000001;
}

void loop() 
{
  if(auto_remp_buffer==1)//si on a l'autorisation de remplir le buffer qui vient d'être lu
  {
    if(num_buff_lec==0){file.read(buffer1, BUF_SIZE);}//on rempli le buffer 0
    else{file.read(buffer0, BUF_SIZE);}//on rempli le buffer 1
    auto_remp_buffer=0;
  }

}
void TC1_Handler()//ISR
{ 
  TC0->TC_CHANNEL[1].TC_SR;//on lit la valeur du registre TC_SR (status register), pour qu'il se remette à zero

  if(!num_buff_lec){  var32 = ((uint16_t)buffer0[tetelecture+1]<<8) | (buffer0[tetelecture]);   }
  else{               var32 = ((uint16_t)buffer1[tetelecture+1]<<8) | (buffer1[tetelecture]);   }
  
  PIOC->PIO_ODSR = ((var32 & 0b1111111000000000)<<3) | ((var32 & 0b0000000111111111)<<1);
  tetelecture+=2;
  
  if(tetelecture>=BUF_SIZE)//quand on fini un buffer
  {
    num_buff_lec=!num_buff_lec;//on passe à l'autre buffer
    tetelecture=0;//on reprend au début de l'autre buffer
    auto_remp_buffer=1;//on donne l'autorisation de remplir le buffer qui vient dêtre lu
  }
}

thanks

Here is an example sketch to trigger a TC interrupt handler (1 MHz frequency):

/******************************************************************/
/*       Timer Counter 2 Channel 2, namely TC8, 1 MHz frequency   */
/******************************************************************/
void setup() {

  pinMode(LED_BUILTIN, OUTPUT);
  tc_setup();

}

void loop() {

}

void tc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID35;                      // TC8 power ON : Timer Counter 2 channel 2 IS TC8 - see page 38

  PIOD->PIO_PDR |= PIO_PDR_P7;                            // Set the pin to the peripheral
  PIOD->PIO_ABSR |= PIO_PD7B_TIOA8;                       // Peripheral type B

  TC2->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2 = 42 M Hz, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA2 on RC compare match


  TC2->TC_CHANNEL[2].TC_RC = 42;  //<*********************  Frequency = (Mck/2)/TC_RC  Hz
  TC2->TC_CHANNEL[2].TC_RA = 41;  //<********************   Any Duty cycle in between 1 and TC_RC


  TC2->TC_CHANNEL[2].TC_IER = TC_IER_CPAS;
  NVIC_EnableIRQ(TC8_IRQn);
  TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
}

void TC8_Handler() {

  static uint32_t Count;

  TC2->TC_CHANNEL[2].TC_SR;                               // Read and clear status register
  if (Count++ == 1000000) {
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;                       // Toggle LED every 1 Hz
  }
}

hello newbie, thanks for the response. But I don’t understand your program, why using two register compare ?And why using TIOA with D7? And istill tried it, but it doesn’t work in my application

I think it’s time you read the Timer Counter section of Sam3x datasheet :slight_smile:

The snippet I provided is a more general code, but obviously, a shorter one would do the job as well if you only need to set/clear some bits inside the interrupt Handler:

/******************************************************************/
/*       Timer Counter 2 Channel 2, namely TC8, 1 MHz frequency   */
/******************************************************************/
void setup() {

  pinMode(LED_BUILTIN, OUTPUT);
  tc_setup();

}

void loop() {

}

void tc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID35;                      // TC8 power ON : Timer Counter 2 channel 2 IS TC8 - see page 38

  //PIOD->PIO_PDR |= PIO_PDR_P7;                            // Set the pin to the peripheral
  //PIOD->PIO_ABSR |= PIO_PD7B_TIOA8;                       // Peripheral type B

  TC2->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2 = 42 M Hz, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC;       // UP mode with automatic trigger on RC Compare
                            //  | TC_CMR_ACPA_CLEAR         // Clear TIOA2 on RA compare match
                            //  | TC_CMR_ACPC_SET;          // Set TIOA2 on RC compare match


  TC2->TC_CHANNEL[2].TC_RC = 42;  //<*********************  Frequency = (Mck/2)/TC_RC  Hz
 // TC2->TC_CHANNEL[2].TC_RA = 41;  //<********************   Any Duty cycle in between 1 and TC_RC


  TC2->TC_CHANNEL[2].TC_IER = TC_IER_CPCS;//TC_IER_CPAS;
  NVIC_EnableIRQ(TC8_IRQn);
  TC2->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable
}

void TC8_Handler() {

  static uint32_t Count;

  TC2->TC_CHANNEL[2].TC_SR;                               // Read and clear status register
  if (Count++ == 1000000) {
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;                       // Toggle LED every 1 Hz
  }
}

Have a look at Tc lib from antodom if you don’t understand the above snippet.

hello newbie,

i read it but I didn’t understand all of it lol. But i know the purpose of each instruction in my code, i just didn’t understand what your code was bringing more compared to mine.

But i finally find what’s wrong with my program, i just don’t know how to fix it:
Timer and handler interrupt works fine. The problem is in this instruction in the handler interrupt:

PIOC->PIO_ODSR = ((var32 & 0b1111111000000000)<<3) | ((var32 & 0b0000000111111111)<<1);

that instruction isn’t executed if the routine interrupt goes to a too high frequency.
Writing PIO_ODSR seems to be limited in frequency, i don’t know why

A few thoughts about the code you posted at the beginning of this topic:

Line 24: Comment is wrong

Line 29: You select MCK/32 whith TC_CMR_TCCLKS_TIMER_CLOCK3, so final frequency is 84000000/32/400 = 6562 Hz, is this what you want ?

There is something I guess is wrong in your sketch : Let's say you exit from TC1_Handler() with num_buff_lec = 0 and auto_remp_buffer = 1, then in loop() you enter the first if() condition, then the second if() condition to fill buffer1. Next time when you enter TC1_Handler(),num_buff_lec = 0 so you should work with the previous buffer you filled, namely buffer1 NOT buffer0 !!

At some point, if you increase the timer frequency, you will fill a first buffer slower in loop() than you will leverage the other one in TC_Handler() because of the SD card reading speed.

hello newbie,

ard_newbie:
There is something I guess is wrong in your sketch : Let’s say you exit from TC1_Handler() with num_buff_lec = 0 and auto_remp_buffer = 1, then in loop() you enter the first if() condition, then the second if() condition to fill buffer1. Next time when you enter TC1_Handler(),num_buff_lec = 0 so you should work with the previous buffer you filled, namely buffer1 NOT buffer0 !!

hum, if we exit from TC1_Handler() with num_buff_lec = 0 and auto_remp_buffer = 1, then in loop() we will fill buffer1 (the buffer which has just been readed). And next when we enter TC1_Handler() we read the buffer0 which is already filled, TC1_Handler() can’t wait until the buffer0 is filled.

But no matter, i completly changed the code and now it’s working, 44 100 Hz 16 bits sound from an sd card. I don’t realy know what was wrong with the first program, i get some weird behavior sometimes.

Here is the code, it could help:

#include <SdFat.h>
SdFat sd;
SdFile file;
int state=false;
#define BUF_SIZE 300//Taille des buffers - Deux choses importantes: 1) la taille des buffers doit être pair car on lit les octets 2 par deux - 2) il faut utiliser plusieurs buffers de petite taille plutot que peu de buffer de grosses tailles pour mieux dissiper le temps utliser pour les remplir et évitier les saccades.
uint8_t buffer0[BUF_SIZE];
uint8_t buffer1[BUF_SIZE];
uint8_t buffer2[BUF_SIZE];
uint8_t buffer3[BUF_SIZE];
uint8_t buffer4[BUF_SIZE];
volatile uint32_t var32;//variable 32bits qui contient le nombre 32 bits à écrire sur les registres
volatile unsigned char numerobuffer=0;
volatile unsigned char numerobufferecr=1;
volatile unsigned int tetelecture=0;
boolean tetecriture=0;



void setup() 
{
  pinMode(10, OUTPUT);
  sd.begin(10, SPI_FULL_SPEED);
  /*====================================================================================================================================================================*/
  /*====================================================================================================================================================================*/
  //TIMER
  //TIMER
  //TIMER_CLOCK1: 84Mhz/2 = 42 000 000 Hz
  //TIMER_CLOCK2: 84Mhz/8 = 10 500 000 Hz
  //TIMER_CLOCK3: 84Mhz/32 = 2 625 000 Hz
  //TIMER_CLOCK4: 84Mhz/128 =  656 250 Hz
  //TIMER_CLOCK5: SLCK ( slow clock )
  //activation du peripherique TC dans le Power Management Controller
  PMC->PMC_WPMR = PMC_WPMR_WPKEY_VALUE;//on desactive la write protection des registres en entrant la clé dans le registre PMC_WPMR
  
  PMC->PMC_PCER0 = 1 << 30;//on active le TC_3 dont l'identifiant est 30

  //on configure le timer
  TC1->TC_CHANNEL[0].TC_CMR = TC_CMR_WAVE                 // Waveform mode
                            | TC_CMR_WAVSEL_UP_RC         // UP mode with automatic trigger on RC Compare
                            | TC_CMR_TCCLKS_TIMER_CLOCK4; // MCK/2 = 42 M Hz, clk on rising edge
                            
  TC1->TC_CHANNEL[0].TC_RC = 15;//on choisi la valeur du registre C TC_RC
  TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;//démarre le timer avec le registre TC_CCR
  
  // enable timer interrupts on the timer
  TC1->TC_CHANNEL[0].TC_IER=TC_IER_CPCS;

  NVIC_EnableIRQ(TC3_IRQn);
  /*====================================================================================================================================================================*/
  //SD SPI
  file.open("001.raw", O_READ);//on ouvre le fichier
  file.read(buffer0, BUF_SIZE);//on rempli le buffer 0
  /*====================================================================================================================================================================*/
  //REGISTRES PIO
  /* activation du peripherique TC dans le Power Management Controller */
  PMC->PMC_PCER0 = 1 << 13;//on active PIOC avec le PMC
  PIOC->PIO_PER = 0b00000000000001111111001111111110;//on donne le controle au general purose i/o pour les pins qu'on veut utiliser en écrivant sur le registre PIO_PER, c'est pour le cas ou les pins sont multiplexés
  
  PIOC->PIO_OER = 0b00000000000001111111001111111110;//on place les pins qu'on veut utiliser (ex: 1 << 21 - b21 - (52)) en OUTPUT
  PIOC->PIO_OWER = 0b00000000000001111111001111111110;//on déclare les pins dont on autorise l'écriture par PIO_ODSR
  PIOC->PIO_OWDR = 0b11111111111110000000110000000001;//on déclare les pins dont on interdit l'écriture par PIO_ODSR (toutes les autres)
  //on procède ainsi car sur ces registres seuls les 1 ont un effet. Donc pour, en même temps, autoriser l'écriture par PIO_ODSR sur certaines broches ET interdire l'écriture sur d'autres broches par PIO_ODSR, il faut écrire ET sur PIO_OWDR et sur PIO_OWER. On peut ainsi controler les pins avec PIO_ODSR en écrivant uniquement sur les pins que l'ont veut

  
}

void loop() 
{

  file.open("001.raw", O_READ);
  

  file.read(buffer0, BUF_SIZE);
  file.read(buffer1, BUF_SIZE);
  file.read(buffer2, BUF_SIZE);
  file.read(buffer3, BUF_SIZE);
  file.read(buffer4, BUF_SIZE);

  while(file.available())
  {
    if(numerobufferecr != numerobuffer)
    {
      switch (numerobufferecr)
      {
        case 0:
            file.read(buffer0, BUF_SIZE);
            tetecriture=1;
            break;
        case 1:
            file.read(buffer1, BUF_SIZE);
            tetecriture=1;
            break;
        case 2:
            file.read(buffer2, BUF_SIZE);
            tetecriture=1;
            break;
        case 3:
            file.read(buffer3, BUF_SIZE);
            tetecriture=1;
            break;
        case 4:
            file.read(buffer4, BUF_SIZE);
            tetecriture=1;
            break;
      }
    }
    if(tetecriture==1)
    {
      numerobufferecr++;
      if(numerobufferecr==5){numerobufferecr=0;}
      tetecriture=0;
    }
  }
}
/*====================================================================================================================================================================*/
//FONCTION SUIVANTE : ISR
/*====================================================================================================================================================================*/
void TC3_Handler()//ISR
{ 
  switch (numerobuffer)
  {
    case 0:
        var32 = ((uint16_t)buffer0[tetelecture+1]<<8) | (buffer0[tetelecture]);
        break;
    case 1:
        var32 = ((uint16_t)buffer1[tetelecture+1]<<8) | (buffer1[tetelecture]);
        break;
    case 2:
        var32 = ((uint16_t)buffer2[tetelecture+1]<<8) | (buffer2[tetelecture]);
        break;
    case 3:
        var32 = ((uint16_t)buffer3[tetelecture+1]<<8) | (buffer3[tetelecture]);
        break;
    case 4:
        var32 = ((uint16_t)buffer4[tetelecture+1]<<8) | (buffer4[tetelecture]);
        break;
  }
  PIOC->PIO_ODSR = ((var32 & 0b1111111000000000)<<3) | ((var32 & 0b0000000111111111)<<1);
  tetelecture+=2;
  if(tetelecture>=BUF_SIZE)
  {
    numerobuffer++;
    if(numerobuffer==5){numerobuffer=0;}
    tetelecture=0;
  }
  
  TC1->TC_CHANNEL[0].TC_SR;//on lit la valeur du registre TC_SR (status register), pour qu'il se remette à zero

}

And next when we enter TC1_Handler() we read the buffer0 which is already filled, TC1_Handler() can't wait until the buffer0 is filled

OK, I understand :slight_smile: .

To achieve 44.1 KHz, select TIMER_CLOCK2 (MCK/8) and TC_RC = 238 (84000000/8/238 = 44117 Hz)

Test the time needed to fill entirely a buffer, it should be less than the time needded to read the previous buffer otherwise ther will be an issue whatever the number of buffers you declare. The pseudo code:

t1= micros()

Fill a buffer

t2 = micros()

t3 = t2 - t1

Then test the time needed to read a full buffer and output the result with PIO_ODSR and compare.

BTW, a two dimension buffer would be nice too :slight_smile: e.g. uint8_t buffer[5][256]