While troubleshooting the DAC on my SAM L21, I tried to set up a pin to monitor one of its event signals. I configured a new channel that triggers off the DAC's EMPTY0 event generator, and piped it to pin PA20 through the PORT's event input. I set the EVACT action to OUT. According to the datasheet:
Output (OUT): I/O pin will be set when the incoming event has a high level ('1') and cleared when the incoming event has a low-level ('0').
I naively thought this would make the pin "follow" the level of the event signal, with a bit of latency in the case of the synchronous path.
I found if the event uses a synchronous path, the output gets clobbered as soon as any other register activity takes place on any PORT (regardless of pin).
Using an asynchronous path produces arguably more "deterministic" results, with the output going high briefly when the event is triggered then going low again (although the Event Detected flag is still high).
Here's a logic trace from the sample program below:
It happens regardless which edge the synchronous event triggers off (including "both"). If I switch the EVACT to TOGGLE, I don't see the clobber behavior.
Does what I'm seeing seem rational, or have I stumbled upon an undocumented errata?
#include "sam.h" // PINS: // PA20 - DAC.EMPTY0 monitor // PB09 - used to trigger a capture on my logic probe // PB10 - hardwired to LED on SAM L22 Xplained Pro board #define ASYNC 0 // whether to use async event mode #define INPUT 0 // whether to enable input buffer on output pin PA20 #define EV_CHANNEL 1 // use event channel 1 to monitor DAC.EMPTY0 signal int main(void) { volatile int in1, in2, flag; // volatile so they don't get optimized away REG_PORT_DIR0 = PORT_PA20; // Set direction on output ports (Note: This will REG_PORT_DIR1 = PORT_PB09 | PORT_PB10; // set them all LOW as OUT regs are 0 by default). #if INPUT PORT->Group[0].PINCFG[PIN_PA20].reg |= PORT_PINCFG_INEN; // enable input sampling #endif // Trigger logic probe to start a capture, with a negative edge on PB09 PORT->Group[1].OUTSET.reg = PORT_PB09; // start the trigger pulse for (int i = 0; i < 1; i++); // make it last a couple cycles PORT->Group[1].OUTCLR.reg = PORT_PB09; // end the trigger pulse // Enable clock GCLK0 for DAC peripheral GCLK->PCHCTRL[DAC_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0; while (!GCLK->PCHCTRL[DAC_GCLK_ID].bit.CHEN); // wait for sync #if !ASYNC // Enable clock on event channel peripheral GCLK->PCHCTRL[EVSYS_GCLK_ID_1].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0; // enable clock while (!GCLK->PCHCTRL[EVSYS_GCLK_ID_1].bit.CHEN); // wait for sync #endif // Event setup order prescribed in 30.6.2.1 of datasheet: // 1. Set "event output enable" bit in generator peripheral (in this case, DAC.EVCTRL.EMPTYEO0) // 2. Select users (EVSYS.USER) // 3. Configure channel and select generator (EVSYS.CHANNEL) // 4. Configure action in user peripherals, if applicable (in this case, PORT.EVCTRL.EVACT1) // 5. Set "event input enable" in user peripherals // Configure DAC // Note: CTRLB, EVCTRL, DACCTRL0 are enable-protected and must be configured before overall DAC // peripheral is enabled (i.e. while CTRLA.ENABLE = 0). DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VDDANA; // use analog input voltage as reference DAC->EVCTRL.reg = DAC_EVCTRL_EMPTYEO0; // output EMPTY event when DAC0 buffer empty DAC->DACCTRL[0].reg = DAC_DACCTRL_ENABLE | DAC_DACCTRL_CCTRL_CC1M; // enable DAC0 and set current level // Select PORT Event Input 1 as user EVSYS->USER[EVSYS_ID_USER_PORT_EV_1].reg = (EV_CHANNEL + 1); // Configure channel EVSYS->CHANNEL[EV_CHANNEL].reg = EVSYS_CHANNEL_ONDEMAND // preferred over "always on"; see errata 14532 | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_DAC_EMPTY_0) // generator is DAC.EMPTY0 #if ASYNC | EVSYS_CHANNEL_PATH_ASYNCHRONOUS; #else | EVSYS_CHANNEL_EDGSEL_RISING_EDGE // "falling" or "both" don't help either | EVSYS_CHANNEL_PATH_SYNCHRONOUS; #endif // Configure PORTA to pipe EMPTY event signal to PA20 PORT->Group[0].EVCTRL.reg = PORT_EVCTRL_PORTEI1 // enable Event Input 1 | PORT_EVCTRL_EVACT1(0) // 0 = OUT, 3 = TOGGLE | PORT_EVCTRL_PID1(PIN_PA20); // tie to PA20 // Wait for all event 1 users to be ready (unavailable in async mode) while (!ASYNC && !EVSYS->CHSTATUS.bit.USRRDY1); // Short LED pulse to indicate we're about to enable the DAC PORT->Group[1].OUTSET.reg = PORT_PB10; PORT->Group[1].OUTCLR.reg = PORT_PB10; // Enable DAC. Note: Enabling it fires off an EMPTY0 event a few clock cycles later, around the // time the READY bit is set. DAC->CTRLA.reg = DAC_CTRLA_ENABLE; // enable overall DAC module while (!DAC->SYNCBUSY.bit.ENABLE); // wait for sync after enabling DAC while (!DAC->STATUS.bit.READY0); // Wait for DAC0 startup. Note: On die revisions earlier than C, // should check INTFLAG.EMPTY instead, see errata 14664 & 14678. for (int i = 0; i < 5; i++); // wait as long as desired to show this isn't a spurious timing issue // At this point, PA20 is HIGH (as set by EMPTY0 event) // If event mode is synchronous, it will go LOW and clobber the event value, as soon as we output to // or read from any pin. #if INPUT in1 = PORT->Group[0].IN.reg & PORT_PA20; // sample PA20; this one reads HIGH for (int i = 0; i < 5; i++); // another brief wait in2 = PORT->Group[0].IN.reg & PORT_PA20; // sample PA20 again; this one reads LOW #else PORT->Group[1].OUTSET.reg = PORT_PB10; // toggle PB10 // PA20 goes LOW when PB10 is changed; even though it's a complete different port and pin #endif flag = EVSYS->INTFLAG.bit.EVD1; // the event flag is still HIGH (void)in1; (void)in2; (void)flag; // suppress "unused variable" warnings while(1); // break here to inspect variables }