Reading/writing a SAML21 pin clobbers synchronous event output to all other pins

1 post / 0 new
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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).

  PORT->Group[0].PINCFG[PIN_PA20].reg |= PORT_PINCFG_INEN; // enable input sampling

  // 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
  while (!GCLK->PCHCTRL[DAC_GCLK_ID].bit.CHEN);     // wait for sync

#if !ASYNC
  // Enable clock on event channel peripheral
  while (!GCLK->PCHCTRL[EVSYS_GCLK_ID_1].bit.CHEN);                                 // wait for sync

  // Event setup order prescribed in 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

  // Configure channel
      EVSYS_CHANNEL_ONDEMAND                        // preferred over "always on"; see errata 14532
    | EVSYS_CHANNEL_EDGSEL_RISING_EDGE              // "falling" or "both" don't help either

  // 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.

  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
  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

  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



Last Edited: Mon. Feb 10, 2020 - 08:05 PM