SAMD21 DAC events and TCx

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm trying to avoid timer ISRs/ interrupt handlers in a library so that they are free for a library user and reduce the chance of clashes (i.e. if user code configures a timer differently than the library). I know there is a way to code around library clashes but I would like to explore the Events system as an alternative. We would still need to use an interrupt but not a timer interrupt. I'm using the DAC (PA02) as a small piezo driver and from tests using non-Event system code, it seems capable of supplying sufficient current to drive the piezo.

 

The idea is to use the event generated by e.g. TC5 to then trigger a DAC conversion (as the user) on an events system channel. Then, use the DATABUF empty event generated by the DAC to trigger an interrupt that refills the DAC DATABUF with the next value to be played. I have generated some test code below (from several examples across this forum and the web for different applications) but can't seem to verify the events are happening, using the events interrupt handler. Please could you give some ideas on that specific issue and how to best set up the DAC to act as a piezo speaker driver with minimal reliance on interrupts?

 

void setup() {
  // Configure PA15 (D5 on Arduino Zero) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA02;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA02;      // Set pin to low

  // Enable the port multiplexer for PA02
  PORT->Group[PORTA].PINCFG[2].reg |= PORT_PINCFG_PMUXEN;
  //Connect DAC to PA02. Function B is DAC for PA02.
  PORT->Group[PORTA].PMUX[2 >> 1].reg |= PORT_PMUX_PMUXE_B;
  // GPIO_pmuxen(DAC_OUT, DAC_OUT_PMUX); //PMUX B for PA02 pin DAC

  PM->APBCMASK.reg |= PM_APBCMASK_DAC | PM_APBCMASK_TC5;    // Enable peripheral clock for DAC and TC5
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID (DAC_GCLK_ID) | // DAC share same GCLK
                      GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN (0);

  DAC->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL_AVCC; //Enable the DAC output on Vout pin. Enable VDDANAL REF.
  DAC->EVCTRL.reg = DAC_EVCTRL_EMPTYEO | DAC_EVCTRL_STARTEI; //Enable the DAC buffer empty event input. Enable DAC start conversion event output.


  // set up timer TC5
  // Enable GCLK for TCC2 and TC5 (timer counter input clock)
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
  while (GCLK->STATUS.bit.SYNCBUSY);


  TC5->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16  /*// Set Timer counter Mode to 16 bits*/
                           | TC_CTRLA_WAVEGEN_MFRQ /*// Set TC5 mode as match frequency*/
                           | TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE; /* //set prescaler and enable TC5. you can use different prescaler divisons here like TC_CTRLA_PRESCALER_DIV1 to get different ranges of frequencies
  //set TC5 timer counter based off of the system clock and the user defined sample rate or waveform*/
  TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / 1000 - 1);
  while ( TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
  // set event generators ALL on for testing which one matches the rest of the TC5 config (which is triggered in MODE=match freq?)
  TC5->COUNT16.EVCTRL.reg = TC_EVCTRL_OVFEO /* overflow event generator*/;
    while ( TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);


  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
  // EVSYS GCLK is needed for interrupts and sync path (so not now)
  //GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
  //while (GCLK->STATUS.bit.SYNCBUSY);
  REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_DAC_START) | EVSYS_USER_CHANNEL(3); // Channel n-1 selected so 2 here
  while (!EVSYS->CHSTATUS.bit.USRRDY2);

  // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC5_OVF) | EVSYS_CHANNEL_CHANNEL(2);
  while (EVSYS->CHSTATUS.bit.CHBUSY2);


  // EVSYS GCLK is needed for interrupts and sync path (so not now)
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
  while (GCLK->STATUS.bit.SYNCBUSY);
  REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_DAC_START) | EVSYS_USER_CHANNEL(2); // Channel n-1 selected so 1 here
  while (!EVSYS->CHSTATUS.bit.USRRDY1);

  // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_SYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_DAC_EMPTY) | EVSYS_CHANNEL_CHANNEL(2);
  while (EVSYS->CHSTATUS.bit.CHBUSY1);
  // Below interrupt is for testing the event (not possible with PATH_ASYNCHRONOUS)
  EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
  NVIC_EnableIRQ(EVSYS_IRQn);

  pinMode(5, OUTPUT);
}

void loop() {

}

void EVSYS_Handler(void) {
  static boolean logic;
  digitalWrite(5, logic);
  logic = !logic;
  EVSYS->INTFLAG.bit.EVD1 = 1; // clear interrupt
}

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The channel usage looks confused:

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC5_OVF) | EVSYS_CHANNEL_CHANNEL(2);

and

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_SYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_DAC_EMPTY) | EVSYS_CHANNEL_CHANNEL(2);

can't  both be active.

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lajon wrote:
can't  both be active.
Quite right! I also realised when combing back through the code that I was changing a few registers with = instead of |= for e.g. the second event configuration, which may have wiped out the first event I configured. Was probably an issue with the clocks for DAC and the synchronous event. Here's my updated code to reflect these changes but I'm afraid there's still no output on the pin to signal that the interrupt is firing.

 

void setup() {
  // Configure PA15 (D5 on Arduino Zero) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA02;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA02;      // Set pin to low

  // Enable the port multiplexer for PA02
  PORT->Group[PORTA].PINCFG[2].reg |= PORT_PINCFG_PMUXEN;
  //Connect DAC to PA02. Function B is DAC for PA02.
  PORT->Group[PORTA].PMUX[2 >> 1].reg |= PORT_PMUX_PMUXE_B;
  // GPIO_pmuxen(DAC_OUT, DAC_OUT_PMUX); //PMUX B for PA02 pin DAC

  PM->APBCMASK.reg |= PM_APBCMASK_DAC | PM_APBCMASK_TC5;    // Enable peripheral clock for DAC and TC5
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID (DAC_GCLK_ID) | // DAC share same GCLK
                      GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN (0);

  DAC->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL_AVCC; //Enable the DAC output on Vout pin. Enable VDDANAL REF.
  DAC->EVCTRL.reg = DAC_EVCTRL_EMPTYEO | DAC_EVCTRL_STARTEI; //Enable the DAC buffer empty event input. Enable DAC start conversion event output.


  // set up timer TC5
  // Enable GCLK for TCC2 and TC5 (timer counter input clock)
  GCLK->CLKCTRL.reg |= (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
  while (GCLK->STATUS.bit.SYNCBUSY);


  TC5->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16  /*// Set Timer counter Mode to 16 bits*/
                           | TC_CTRLA_WAVEGEN_MFRQ /*// Set TC5 mode as match frequency*/
                           | TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE; /* //set prescaler and enable TC5. you can use different prescaler divisons here like TC_CTRLA_PRESCALER_DIV1 to get different ranges of frequencies
  //set TC5 timer counter based off of the system clock and the user defined sample rate or waveform*/
  TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / 1000 - 1);
  while ( TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
  // set event generators ALL on for testing which one matches the rest of the TC5 config (which is triggered in MODE=match freq?)
  TC5->COUNT16.EVCTRL.reg = TC_EVCTRL_OVFEO /* overflow event generator*/;
    while ( TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);


  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
  // EVSYS GCLK is needed for interrupts and sync path (so not now)
  //GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
  //while (GCLK->STATUS.bit.SYNCBUSY);
  REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_DAC_START) | EVSYS_USER_CHANNEL(3); // Channel n-1 selected so 2 here
  while (!EVSYS->CHSTATUS.bit.USRRDY2);

  // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC5_OVF) | EVSYS_CHANNEL_CHANNEL(2);
  while (EVSYS->CHSTATUS.bit.CHBUSY2);


  // EVSYS GCLK is needed for interrupts and sync path (so not now)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);
  while (GCLK->STATUS.bit.SYNCBUSY);
  REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_DAC_START) | EVSYS_USER_CHANNEL(2); // Channel n-1 selected so 1 here
  while (!EVSYS->CHSTATUS.bit.USRRDY1);

  // EVSYS_CHANNEL_EDGSEL_BOTH_EDGES // not usable with  PATH_ASYNCHRONOUS
  REG_EVSYS_CHANNEL |= EVSYS_CHANNEL_PATH_SYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_DAC_EMPTY) | EVSYS_CHANNEL_CHANNEL(1);
  while (EVSYS->CHSTATUS.bit.CHBUSY1);
  // Below interrupt is for testing the event (not possible with PATH_ASYNCHRONOUS)
  EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
  NVIC_EnableIRQ(EVSYS_IRQn);

  pinMode(5, OUTPUT);
}

void loop() {

}

void EVSYS_Handler(void) {
  static boolean logic;
  digitalWrite(5, logic);
  logic = !logic;
  EVSYS->INTFLAG.bit.EVD1 = 1;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The EVSYS registers are kind of "special", '|=' is not ok, just write the config you want (for each channel/user).

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lajon wrote:
just write the config you want

Huh, OK thanks! Just when I thought I was getting better at embedded programming :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I isolated sections of code. First, this was missing:

  DAC->CTRLA.reg = DAC_CTRLA_ENABLE; // enable DAC
  while (1 == DAC->STATUS.bit.SYNCBUSY);

But even without tinkering with the events system I can't get the timer to run. If I leave timer code in, comment out anything to do with an interrupt or events I can compile with both DAC and timer code in place but get no output from the DAC. When I then also comment out the timer initialisation and related code, leaving the DAC, I can get a nice output on the DAC pin. Any suggestions please?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I notice you have '|=' also for GCLK registers, use plain '='.

 

Is 

GCM_TC4_TC5

from Arduino maybe? I would use TC5_GCLK_ID.

 

Some TC registers are enable protected, don't enable the TC until they are set.

 

This line has a problem other than the '|=' , GCLK_CLKCTRL_ID_EVSYS_1 is a already a bit mask so remove the GCLK_CLKCTRL_ID():

  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_EVSYS_1);

 

You need to select an edge when the channel is synchronous:

  REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_SYNCHRONOUS | EVSYS_CHANNEL_EDGSEL_RISING_EDGE | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_DAC_EMPTY) | EVSYS_CHANNEL_CHANNEL(1);

 

Asynchronous users can't be checked for "ready", remove

  while (!EVSYS->CHSTATUS.bit.USRRDY2);

/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lars, just quickly to say thank you so much. I haven't managed to get the DAC and TC5 running together yet but all your comments are so helpful.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@smerrett79 Did you ever manage to get the DAC and TC5 to play nice together?  I'm having similar troubles on the SAM L21 over here https://community.atmel.com/forum/demystify-dac-empty-event-sam-l21 and would love to see some working sample code (even if it is for a different flavour of SAM).

Last Edited: Tue. Feb 11, 2020 - 08:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

rkagerer wrote:
Did you ever manage to get the DAC and TC5 to play nice together?

I'm afraid not, I moved on to different problems and pushed that one to the back of the pile!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This works, at least I get the expected voltage ramp updating every ms.

#include "sam.h"
#define PORTA 0

void EVSYS_Handler(void)
{
    static int cnt = 0;
    if (++cnt > 31) {
        cnt = 0;
    }
    EVSYS->INTFLAG.reg = EVSYS_INTFLAG_EVD1;
    DAC->DATABUF.reg = cnt * 32;
}


int main(void)
{
    SYSCTRL->OSC8M.bit.PRESC = 0;
    // Enable the port multiplexer for PA02
    PORT->Group[PORTA].PINCFG[2].reg |= PORT_PINCFG_PMUXEN;
    //Connect DAC to PA02. Function B is DAC for PA02.
    PORT->Group[PORTA].PMUX[2 >> 1].reg |= PORT_PMUX_PMUXE_B;

    PM->APBCMASK.reg |= PM_APBCMASK_DAC | PM_APBCMASK_TC5;    // Enable peripheral clock for DAC and TC5
  
    GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID (DAC_GCLK_ID) | // DAC share same GCLK
                        GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN (0);

    DAC->CTRLB.reg = DAC_CTRLB_EOEN | DAC_CTRLB_REFSEL_AVCC; // Enable the DAC output on Vout pin. Enable VDDANAL REF.
    DAC->EVCTRL.reg = DAC_EVCTRL_EMPTYEO | DAC_EVCTRL_STARTEI; // Enable the DAC buffer empty output event. Enable the DAC start conversion input event.
    DAC->CTRLA.reg |= DAC_CTRLA_ENABLE; // enable DAC
    while (DAC->STATUS.bit.SYNCBUSY);
  
    // set up timer TC5
    // Enable GCLK for TCC2 and TC5 (timer counter input clock)
    GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(TC5_GCLK_ID);
    while (GCLK->STATUS.bit.SYNCBUSY);

    TC5->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16  // Set Timer counter Mode to 16 bits
                            | TC_CTRLA_WAVEGEN_MFRQ // Set TC5 mode as match frequency
                            | TC_CTRLA_PRESCALER_DIV1; // Set prescaler and enable TC5.
    //set TC5 timer counter based off of the system clock and the user defined sample rate or waveform
    TC5->COUNT16.CC[0].reg = (uint16_t) (8000000 / 1000 - 1);
    while ( TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
    TC5->COUNT16.EVCTRL.reg = TC_EVCTRL_OVFEO; // overflow event generator
    while ( TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
    TC5->COUNT16.CTRLA.bit.ENABLE = 1;

    PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;
    REG_EVSYS_USER = EVSYS_USER_USER(EVSYS_ID_USER_DAC_START) | EVSYS_USER_CHANNEL(3); // Channel n-1 selected so 2 here
    REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC5_OVF) | EVSYS_CHANNEL_CHANNEL(2);
    while (EVSYS->CHSTATUS.bit.CHBUSY2);

    // EVSYS GCLK is needed for the interrupt and sync path
    GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_EVSYS_1;
    while (GCLK->STATUS.bit.SYNCBUSY);
    REG_EVSYS_CHANNEL = EVSYS_CHANNEL_PATH_SYNCHRONOUS | EVSYS_CHANNEL_EDGSEL_RISING_EDGE | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_DAC_EMPTY) | EVSYS_CHANNEL_CHANNEL(1);
    while (EVSYS->CHSTATUS.bit.CHBUSY1);
    // Below interrupt is for testing the event
    EVSYS->INTENSET.reg = EVSYS_INTENSET_EVD1;
    NVIC_EnableIRQ(EVSYS_IRQn);

    volatile int tick = 0;
    while (1) {
        tick++;
    }
}

/Lars