Go Down

Topic: Help on Sleepwalking with SAMD21 (Read 1 time) previous topic - next topic

forcetronics

Hello,

One great feature on the SAMD21 (MCU on Zero) is its sleep walking capability. I am trying to setup the Zero to sleep walk by monitoring an ADC pin while a sleep and then waking up when the value on the pin goes below 1.65V. To do this I am using the RTC, ADC, and event system. As per the data sheet, the event system allows you to trigger an ADC reading from the RTC without involving the CPU clock so you can stay in sleep mode. The SAMD21 only wakes up if the ADC value is below the value stated earlier. But I can't seem to get all the pieces to work (below is my code). Individually I have tested the RTC and ADC the two of them function fine individually, but when I try to put them together with the event system it doesn't work so that makes me think I am setting up the event system wrong, but I really don't know. Any help or suggestions would be appreciated.

void setup() {
  PM->APBAMASK.reg |= PM_APBAMASK_RTC; // turn on RTC
  config32kOSC(); //setup exeternal 32kHz clock for RTC and ADC

   // Setup clock GCLK2 with OSC32K divided by 32
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2)| GCLK_GENDIV_DIV(4);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); 

  //configure the generator of the generic clock
  GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_DIVSEL );
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);

  //confugure and enable the gen clock
  GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos)));
  while (GCLK->STATUS.bit.SYNCBUSY);
 
  RTC->MODE0.CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE; // disable RTC
  while (RTCisSyncing());

   RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_SWRST; // software reset
  while (RTCisSyncing());

  //configure RTC
  RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_PRESCALER_DIV1 | RTC_MODE0_CTRL_MODE_COUNT32 | RTC_MODE0_CTRL_MATCHCLR;
  while (RTCisSyncing());

  RTC->MODE0.COMP[0].reg = RTC_MODE0_COMP_COMP(1000); //setup compare match at 1000 or 1 sec
 
  RTC->MODE0.EVCTRL.reg |= RTC_MODE0_EVCTRL_CMPEO0; //create event on compare match
  while (RTCisSyncing());

  RTCenable(); //enables RTC
  RTCresetRemove(); //remove reset

  //Setup event system
  EVSYS->USER.reg = (uint16_t)(EVSYS_USER_CHANNEL(0x01) | EVSYS_USER_USER(0x17)); //set for user channel 1 and for user 17 which is ADC. Data sheet says to write as 16bit variable
  EVSYS->CHANNEL.reg = (uint32_t)(EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | EVSYS_CHANNEL_PATH_ASYNCHRONOUS | EVSYS_CHANNEL_EVGEN(0x01) | EVSYS_CHANNEL_CHANNEL(0x01)); // the 0x01 selects RTC as the generator and 1 is the channel (same as user)

  // Input pin for ADC Arduino A0/PA02
REG_PORT_DIRCLR1 = PORT_PA02;

// Enable multiplexing on PA02_AIN0 PA03/ADC_VREFA
PORT->Group[0].PINCFG[2].bit.PMUXEN = 1;
PORT->Group[0].PINCFG[3].bit.PMUXEN = 1;
PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;

// Enable the APBC clock for the ADC
REG_PM_APBCMASK |= PM_APBCMASK_ADC;

  //setup generic clock for ADC
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(2);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);

  //use same generic clock as RTC and enable it
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);

// Select reference
REG_ADC_REFCTRL = ADC_REFCTRL_REFSEL_INTVCC1; //set vref for ADC

// Average control 1 sample, no right-shift
REG_ADC_AVGCTRL = ADC_AVGCTRL_ADJRES(0) | ADC_AVGCTRL_SAMPLENUM_1;

// Sampling time, no extra sampling half clock-cycles
REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);

// Input control and input scan
REG_ADC_INPUTCTRL = ADC_INPUTCTRL_GAIN_DIV2 | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
//REG_ADC_INPUTCTRL = ADC_INPUTCTRL_GAIN_DIV2 | ADC_INPUTCTRL_MUXNEG_IOGND | ADC_INPUTCTRL_MUXPOS_PIN0;
// Wait for synchronization
while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  REG_ADC_CTRLB = ADC_CTRLB_RESSEL_12BIT | ADC_CTRLB_PRESCALER_DIV16; // Single shot
// Wait for synchronization
while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI; //start ADC when event occurs
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_RUNSTDBY; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->WINCTRL.reg |= ADC_WINCTRL_WINMODE_MODE2; //set ADC for window mode to detect certain voltage level
  while (ADC->STATUS.bit.SYNCBUSY);

   ADC->WINUT.reg = 2048; //set windo level
   while (ADC->STATUS.bit.SYNCBUSY);

   ADC->INTENSET.reg |= ADC_INTENSET_WINMON; // enable alarm interrupt
   while (ADC->STATUS.bit.SYNCBUSY);

   NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupt
   NVIC_SetPriority(ADC_IRQn, 0x00);

   // Enable ADC
   REG_ADC_CTRLA = GCLK_GENCTRL_RUNSTDBY | ADC_CTRLA_ENABLE;

  //SYSCTRL->VREG.reg |= SYSCTRL_VREG_RUNSTDBY;
   //samSleep();
}

void loop() {
  //don't need to do anything here
}


//function to show how to put the
void samSleep()
{
  // Set the sleep mode to standby
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
  // SAMD sleep
  __WFI();
}

//This ISR is called at the end or TOP of each PWM cycle
void ADC_Handler() {
    digitalWrite(LED_BUILTIN, HIGH); //turn LED off
    ADC->INTFLAG.reg = ADC_INTFLAG_WINMON; //Need to reset interrupt
}

inline bool RTCisSyncing()
{
  return (RTC->MODE0.STATUS.bit.SYNCBUSY);
}

/* Configure the 32768Hz Oscillator */
void config32kOSC()
{
  SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND |
                         SYSCTRL_XOSC32K_RUNSTDBY |
                         SYSCTRL_XOSC32K_EN32K |
                         SYSCTRL_XOSC32K_XTALEN |
                         SYSCTRL_XOSC32K_STARTUP(6) |
                         SYSCTRL_XOSC32K_ENABLE;
}

void RTCenable()
{
  RTC->MODE2.CTRL.reg |= RTC_MODE2_CTRL_ENABLE; // enable RTC
  while (RTCisSyncing());
}

void RTCresetRemove()
{
  RTC->MODE2.CTRL.reg &= ~RTC_MODE2_CTRL_SWRST; // software reset remove
  while (RTCisSyncing());
}

WiltonHelm

I know this is a very old post, but just in case the issue is active or someone else is looking at it, there are some mistakes in it.  I am working on a similar issue, and haven't totally resolved it, but these comments may be useful.
1. The ADC Event register does not need synchronizing.  That can be skipped
2. The ADC PM is on by default so is not needed
3. The big one is that the Event channel in the Event User needs to be n+1 because 0 is none. If it is 1 in the channel register it should be 2 in the user register.

Go Up