Reducing SAM3X power consumption in WAIT or SLEEP mode?

Discussion in 'Troubleshooting' started by fetcher, Feb 11, 2015.

  1. fetcher

    fetcher Member

    Joined:
    Mar 9, 2014
    Messages:
    166
    Likes Received:
    20
    I have an application where I need to get the SAM3X Arduino CPU's power consumption down as low as possible (i.MX6 will be shut down for long periods, to be awakened by the SAM3X when certain pin signals are detected).

    According to the Atmel SAM3X datasheet (doc11057.pdf page 26), putting the SAM chip into WAIT mode should drop its consumption to mere microamps-- 26.6uA is specified when using the Atmel's internal 1.8V regulator as Udoo does, which would be less than 0.1 milliwatts at 3.3V.

    For some reason, though, despite the chip apparently going into WAIT (or sleep) mode as intended during tests, I haven' t been able to get its power use below about 200mW (60mA @ 3.3V)-- well down from 450mW when running typical Arduino busy-wait code at full speed, yet still many times what it should be.

    The 200mW reading is with clock reduced from 84MHz -> 21MHz, nearly all peripheral clocks turned off, all pins except Serial Tx set as INPUT with pullups disabled, and the chip itself in WAIT state, which should stop its main clock as well. See test code below, which simply returns some ADC measurements via serial when awakened from sleep/wait by main serial RX, or one GPIO pin going low.

    I'm measuring power consumption of the entire Udoo (plus some attached hardware) using an ina219b I2C current meter and shunt in the +12V positive line, and isolating the SAM3X's contribution to this total by forcing it into RESET state (echo 0 > /sys/class/gpio/gpio0/value), then bringing it back online, taking the difference of the two readings.

    Even dropping the main SAM3X clock speed all the way down to 2.625MHz as a test (not that I'd want to run it that low) only saves 10-15mW of the mysterious 200mW excess. Based on this consumption going away when the SAM3X is held in reset, I figure it has to be some module within that chip responsible, not automatically disabled by Sleep or Wait state and still sucking power.

    Any ideas on what I might be missing? I should probably ask on the Arduino forums also (or at Atmel), but they will probably be quick to blame Udoo vs. Due hardware differences.

    here is my power-saving testbed code. Printing millis() on either side of the WAIT mode transition is to verify that SysTick interrupt is in fact disabled-- it doesn't increment at all until the wakeup-trigger condition is met. Disabling all those peripheral clocks shouldn't even be necessary, since WAIT is supposed to do that automatically, but it does get me down to the same 200mW or so if I do pmc_enable_sleepmode(0) instead of WAIT, where (0) means WFI rather than WFE.

    Code:
    // WAIT-mode version
    
    // 2 at 84MHz -> 1 at 21MHz or below
    #define DLY 1
    
    void setup() {
      unsigned int i;
    
      pmc_set_writeprotect(false);
      pmc_mck_set_prescaler(48);   // 21 MHz
    
       // 12MHz / 64 * 14 = 2.625MHz //  96 = 110 << 4 = /64
       // 12MHz / 32 * 14 = 5.25 MHz //  80 = 101 << 4 = /32
       // 12MHz / 16 * 14 = 10.5 MHz //  64 = 100 << 4 = /16
       // 12MHz /  8 * 14 = 21   MHz //  48 = 011 << 4 = /8
       // 12MHz /  4 * 14 = 42   MHz //  32 = 010 << 4 = /4
       // 12MHz /  2 * 14 = 84   MHz //  16 = 001 << 4 = /2 (default)
    
    // all pins to INPUT, except main serial TX/RX on 0/1:
      for (i=2; i<=78; i++) pinMode(i, INPUT);
    
    // disable unused peripheral clocks to save some power
    // per p. 47-48 of SAM3X manual PDF
      pmc_disable_periph_clk(2);   // real-time clock
      pmc_disable_periph_clk(3);   // real-time timer
      pmc_disable_periph_clk(4);   // watchdog timer
      // 5 = PMC power mgmt ctrl
    
      pmc_disable_periph_clk(6);   // EEFC0  flash ctrl
      pmc_disable_periph_clk(7);   // EEFC1  flash ctrl
      // 8 = main UART
    
      pmc_disable_periph_clk(9);   // SMC_SDRAMC
      pmc_disable_periph_clk(10);  // SDRAMC
    
      pmc_disable_periph_clk(11);  // PIO A 
      pmc_disable_periph_clk(12);  // PIO B 
      pmc_disable_periph_clk(13);  // PIO C 
      pmc_disable_periph_clk(14);  // PIO D 
      pmc_disable_periph_clk(15);  // PIO E 
      pmc_disable_periph_clk(16);  // PIO F 
    
      pmc_disable_periph_clk(17);  // USART0
      pmc_disable_periph_clk(18);  // USART1
      pmc_disable_periph_clk(19);  // USART2
      pmc_disable_periph_clk(20);  // USART3 - using pins for GPIO
    
      pmc_disable_periph_clk(21);  // HSMCI (SD/MMC ctrl, N/C)
      pmc_disable_periph_clk(22);  // TWI/I2C bus 0 (i.MX6 controlling)
      pmc_disable_periph_clk(23);  // TWI/I2C bus 1
      pmc_disable_periph_clk(24);  // SPI0
      pmc_disable_periph_clk(25);  // SPI1
      pmc_disable_periph_clk(26);  // SSC (I2S digital audio, N/C)
    
      pmc_disable_periph_clk(27);  // timer/counter 0
      pmc_disable_periph_clk(28);  // timer/counter 1
      pmc_disable_periph_clk(29);  // timer/counter 2
      pmc_disable_periph_clk(30);  // timer/counter 3
      pmc_disable_periph_clk(31);  // timer/counter 4
      pmc_disable_periph_clk(32);  // timer/counter 5
      pmc_disable_periph_clk(33);  // timer/counter 6
      pmc_disable_periph_clk(34);  // timer/counter 7
      pmc_disable_periph_clk(35);  // timer/counter 8
      pmc_disable_periph_clk(36);  // PWM
      // 37 = ADC, in use
    
      pmc_disable_periph_clk(38);  // DAC ctrl
      pmc_disable_periph_clk(39);  // DMA ctrl
      pmc_disable_periph_clk(40);  // USB OTG high-speed ctrl
      pmc_disable_periph_clk(41);  // random number generator
      pmc_disable_periph_clk(42);  // ethernet MAC - N/C
      pmc_disable_periph_clk(43);  // CAN controller 0
      pmc_disable_periph_clk(44);  // CAN controller 1
    
      analogReadResolution(10);    // was 12-bit
      
      // pmc_set_fast_startup_input( (1 << 4) + 1);
    
      i = ( (1 << 4 ) + 1);
      PMC->PMC_FSMR = i;
      PMC->PMC_FSPR = 0;              // active low
      
      SUPC->SUPC_WUIR = (i << 16);    // enable same sources, hi->low transition
      SUPC->SUPC_WUMR = 0;              //  no debouncing; 1 << 12 for 3-cycle
     
      Serial.begin(460800);     // 115200 * 4 (84MHz/21MHz)   = 460800
    }
    
    void loop() {
      int a, adc10, adc9, adc8, adc7;
      float v, c, f;
    
      unsigned int ctrl;
    
      Serial.print("-=");
      Serial.println(millis());
    
      ctrl = SysTick->CTRL;
      SysTick->CTRL = (ctrl & ~3);  // clear TICKINT & ENABLE
    
      pmc_enable_waitmode();
    
      SysTick->CTRL = (ctrl | 3);  // re-set TICKINT & ENABLE
    
      Serial.print("+=");
      Serial.println(millis());
    
      if(Serial.available())
      {
        a=Serial.read();
    
        delay(DLY);
        adc7 = analogRead(A7); delay(DLY);  // dummy - 1st read is less accurate
        adc8 = analogRead(A8); delay(DLY);
        adc9 = analogRead(A9); delay(DLY);
        adc10 = analogRead(A10); delay(DLY);
    
        Serial.print(" A8 = ");
        Serial.print(adc8);
        Serial.print(" A9 = ");
        Serial.print(adc9);
        Serial.print(" A10 = ");
        Serial.println(adc10);
      }
      else {
        Serial.print("X ");
        delay(50*DLY);
      }
    
    }
    
     
  2. fetcher

    fetcher Member

    Joined:
    Mar 9, 2014
    Messages:
    166
    Likes Received:
    20
    I never was able to get the WAIT-mode power use down, perhaps due to the way multiple SAM3X power rails are tied together on the Udoo board. The deepest sleep BACKUP mode, though, does work as intended, cutting current draw to essentially zero at idle (same as with SAM3X in RESET, or at least less than the ~1mA resolution of my inline ammeter).

    BACKUP does a nearly-complete reset of the Atmel chip between every wake event, though, with only eight registers preserved across the deep sleep. It can be awakened by RTC or RTT timers, FWUP pin (not accessible on Udoo), or one of 16 WKUP signals, wihch map out as follows:

    Code:
            pin     periphA         periphB         extra fn        arduino-due
    
    WKUP0*  PA1     CANRX0          PCK0                      -->   + canrx0
    WKUP1   PA3     TIOB1           PWMFI1          AD1             * ad6 (A6)/D60
    WKUP2   PA5     TIOA2           PWMFI0                          -NC: eth extint
    WKUP3*  PA7     TCLK2           /NCS1/                          * pin31 <spiSS2
    WKUP4*  PA8     URXD            PWMH0                     -->   RX (main UART)
    WKUP5   PA10    RXD0            DATRG                           RXD2 (RX1)
    WKUP6   PA11    TXD0            ADTRG                           TXD2 (TX1)
    WKUP7   PA12    RXD1            PWML1                           RXD1 (RX2)
    WKUP8   PA15    CTS1            TF (ssc?)                       * pin24 <MXpwm1
    WKUP9   PA18    TWCK0           /A20/                           i2c SCL1
    WKUP10  PA27    SPI0_SPCK       /A20/                           spi SPCK
    WKUP11  PA28    SPI0_NPCS0      PCK2                            X SS0/PWM10<bkt
    WKUP12  PB15    CANRX1          PWMH3           DAC0            ~ DAC0(CANRX1)
    WKUP13  PB21    RXD2            SPI0_NPCS2      AD14            X D52 <spi2SCLK
    WKUP14  PB23    CTS2            SPI0_NPCS3                      -NC?: SS3
    WKUP15* PB26    CTS0            TCLK0                           X pin22 <optPSU
    
    Contrary to what some documentation implies, I found that it isn't possible to maintain a SAM3X pin in OUTPUT state during backup-- all are forced to high-impedance inputs. So, obviously for a lot of applications this mode won't be practical.

    I haven't yet updated the board that needs to wake its i.MX periodiclaly, but here is another sample sketch showing how to use the Backup mode. Note that everything happens in setup(), after a wakeup-reset -- it goes back to sleep/backup before loop() is ever executed, then starts again in setup() on the next cycle.

    This particular sketch basically just monitors RS232 from the i.MX6 for a sensor-value request (dumping three analog values on wakeup), as well as some keypad buttons and an infrared remote receiver. A lot of the code is for an unrelated IR sending function.

    Code:
    // imp7.ino - muxed analog in / keypad / IR xmit/rcv for Udoo
    // BACKUP-mode version w/active-high keypad scan
    
    // using library from https://github.com/enternoescape/Arduino-IRremote-Due :
    //
    #include "/opt/arduino-1.5.4/libraries/IRremote/IRremote2.h"
    
    // 4-key matrix readback pins w/10k pulldowns
    #define ROW0 42
    #define ROW1 38
    
    // 4-key matrix driven pins (+WKUP)
    #define COL0 68
    #define COL1 31
    
    #define DLY 2
    
    // 10s of ms to wait for IR decode: 160ms
    #define IRWAIT 16
    
    #define RECV_PIN 22
    // TX pin = 34, set in IR lib's IRremoteInt2.h
    
    // object imports from IR library
    IRrecv irrecv(RECV_PIN);
    IRsend irsend;
    
    decode_results results;
    
    // IR: Storage for recorded code
    int codeType = -1;             // The type of code
    unsigned long codeValue;       // The code value if not raw
    int codeLen;                   // The length of the code
    int toggle = 0;                // The RC5/6 toggle state
    
    #define MARK_EXC_TICKS  MARK_EXCESS/USECPERTICK
    
    void dumpCode(decode_results *results) {
      codeType = results->decode_type;
      int count = results->rawlen;
    //if (codeType == UNKNOWN || codeType == SANYO ) {   // sanyo == 9
      if (codeType == UNKNOWN ) {
        // Serial.println(F("R:unknown IR code, treating as raw"));
        codeLen = results->rawlen - 1;
        // To store raw codes:
        // Drop first value (gap)
        // Convert from ticks to microseconds
        // Tweak marks shorter, and spaces longer to cancel out IR receiver distortion
        Serial.print(F("I:RAW:"));
        Serial.print(codeType);
        Serial.write(':');
        Serial.print(codeLen);
        Serial.write(':');
        for (int i = 1; i <= codeLen; i++) {
          if (i % 2) {
            // Mark
            //rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK - MARK_EXCESS;
            results->rawbuf[i] -= MARK_EXC_TICKS;  // jnh
            Serial.write(' ');
            // Serial.print(F(" m"));
          }
          else {
            // Space
            //rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK + MARK_EXCESS;
            results->rawbuf[i] += MARK_EXC_TICKS;  // jnh
            // Serial.write(' ');
          }
          //Serial.print(rawCodes[i - 1], DEC);
          Serial.print(((results->rawbuf[i] & 0x10) >> 4), HEX);
          Serial.print((results->rawbuf[i] & 0x0F), HEX);
        }
        Serial.println("");
      }
      else {
        if (codeType == NEC) {
          Serial.print(F("I:NEC: "));
          if (results->value == REPEAT) {
            // Don't record a NEC repeat value as that's useless.
            Serial.println("repeat; ignoring.");
            return;
          }
        }
        else if (codeType == SONY) {
          Serial.print(F("I:SONY: "));
        }
        else if (codeType == RC5) {
          Serial.print(F("I:RC5: "));
        }
        else if (codeType == RC6) {
          Serial.print(F("I:RC6: "));
        }
        else if (codeType == DISH) {
          Serial.print(F("I:DISH: "));
        }
        else if (codeType == SHARP) {
          Serial.print(F("I:Sharp: "));
        }
        else if (codeType == JVC) {
          Serial.print(F("I:JVC: "));
        }
        else if (codeType == SAMSUNG) {
          Serial.print(F("I:Samsung: "));
        }
        else {
          Serial.print(F("I:Unknown codeType "));
          Serial.print(codeType, DEC);
          Serial.print(F(": "));
        }
        Serial.print(results->value, HEX);
        codeValue = results->value;
        codeLen = results->bits;
        Serial.print(F(" bits: "));
        Serial.println(codeLen, DEC);
      }
    }
    
    
    int keydown() {
    
      int key;
    
      //  CANRX = D68  i.mx6 gpio7 (col0)
      //  D31 = gpio137 (col1)
      //  D38 = gpio54  (row1)
      //  D42 = gpio34  (row0)
      // # front to back, red - black - blue - brown
      // echo "$G54_7 $G34_137 $G34_7 $G54_137
      //    k0 (1)       k1 (2)     k2 (4)    k3 (8)
      //  ROW1<-COL0 ROW0<-COL1 ROW0<-COL0 ROW1<-COL1
    
      key = 0;
    
      // reverse row/column sensing after wakeup
      pinMode(ROW0, INPUT);
      pinMode(ROW1, INPUT);
      pinMode(COL0, OUTPUT);
      pinMode(COL1, OUTPUT);
    
      digitalWrite(COL0, HIGH);
      digitalWrite(COL1, LOW);
    
      key |= (digitalRead(ROW1) ? 1 : 0);
      key |= (digitalRead(ROW0) ? 4 : 0);
      
      digitalWrite(COL0, LOW);
      digitalWrite(COL1, HIGH);
    
      key |= (digitalRead(ROW1) ? 8 : 0);
      key |= (digitalRead(ROW0) ? 2 : 0);
    
      digitalWrite(COL0, HIGH);
      digitalWrite(COL1, HIGH);
    
      pinMode(COL0, INPUT_PULLUP);
      pinMode(COL1, INPUT_PULLUP);
    
      return key;
    }
    
    
    void setup() {
      unsigned int tmp, count, sr, len, repeat, bytes, copies, intergap;
      unsigned int key_wkup_mask=(( (1 << 3) | 1) << 16);
      unsigned int ser_wkup_mask=(( (1 << 4) ) << 16);
      unsigned int irc_wkup_mask=(( (1 << 15 ) ) << 16);
      int adc10, adc9, adc8, adc7;
    
      char buf[128];
    
    // CANTX -- override default output-state!
      pinMode(69, INPUT);
    
      Serial.begin(115200);
    
      sr=SUPC->SUPC_SR;
    
      if ((sr & key_wkup_mask) != 0) {
        count=1;
        tmp=keydown();
        if (tmp == 0) {   // debounce
         count++;
         tmp=keydown();
        }
        if (tmp == 0) {
         count++;
         tmp=keydown();
        }
        if (tmp != 0) {
         Serial.print(F("K:"));
         Serial.print(tmp);
         Serial.write(',');
         Serial.println(count);
        }
      }
    
    //  if ((sr & ser_wkup_mask) != 0) {
    //    Serial.println("woke on rs232");
    //  }
    
      if ((sr & irc_wkup_mask) != 0) {
        irrecv.enableIRIn();
        //  Serial.println(F("R:irrecv_begin"));
        count=0;
        while (count < IRWAIT) {
          if (irrecv.decode(&results)) {
            irsend.space(0);       // disable timer IRQ
            // Serial.println(F("R:decode complete!"));
            dumpCode(&results);
            // irrecv.resume(); // resume receiver
            break;
          }
          else {
            count++;
            delay(10);
          }
        }
        // if (count == IRWAIT) Serial.println(F("R:decode timed out"));
        irsend.space(0);       // disable timer IRQ
        delay(DLY*4);
      }
    
      delay(DLY*4);
    
      tmp=gpbr_read(GPBR1);
    
      adc7 = analogRead(A7); delay(DLY << 2);  // 1st read is less accurate
      adc8 = analogRead(A8); delay(DLY);
      adc9 = analogRead(A9); delay(DLY);
      adc9 = analogRead(A9); delay(DLY);
      adc10 = analogRead(A10); delay(DLY);
    
      Serial.print(F("A:"));
      Serial.print(adc8);
      Serial.write(',');
      Serial.print(adc9);
      Serial.write(',');
      Serial.print(adc10);
      Serial.write(',');
      Serial.print(tmp);
      Serial.write(',');
      Serial.println((sr >> 16),HEX);
    
      gpbr_write(GPBR1,tmp+1);
    
    // X <bits> <copies> <intergap-millisec> <type> <freq|repeat> b0 b1 b2 b4 ...
    
      if(len=Serial.available()) {
        switch (tmp=Serial.read()) {
         case 'X':
           len--;
           bytes=Serial.readBytes(buf,((len > 127) ? 127 : len));
           buf[bytes]=0;
           codeLen=buf[0];
           copies=buf[1];
           intergap=buf[2];
           bytes = bytes-3;
           if (bytes < 3 || codeLen < 8) {
             Serial.print(F("X: Short IR Send code! 3/8 bytes/bits min, got "));
             Serial.print(bytes);
             Serial.print(F(", codeLen "));
             Serial.println(codeLen);
             break;
           }
           if (buf[3] == 'R') {
             codeType=-1;
             // sendRawCompact expects 50us tick-count at each position
             while (copies > 0) {
               irsend.sendRawCompact(((unsigned char*)buf+6), codeLen, buf[4]);  // buf[4]=frequency in kHz
               copies--;
               if (copies > 0) delay(intergap);
             }
             Serial.print(F("X: Sent raw-compact, copies: "));
             Serial.println(copies);
           }
           else {
             codeType=buf[3];
             repeat=buf[4];
             codeValue=buf[5];
             count = (bytes - 3);  tmp=6;
             while (count > 0) {
                codeValue = (codeValue << 8) + buf[tmp];
                tmp++;
                count--;
             }
             while (copies > 0) {
               switch (codeType) {
                 case NEC:           // 1 
                   if (repeat) {
                     irsend.sendNEC(REPEAT, codeLen);
                     Serial.println(F("X: Sent NEC repeat"));
                   }
                   else {
                     irsend.sendNEC(codeValue, codeLen);
                     Serial.print(F("X: Sent NEC "));
                     Serial.println(codeValue, HEX);
                   }
                   break;
                 case SONY:          // 2
                   irsend.sendSony(codeValue, codeLen);
                   Serial.print(F("X: Sent Sony "));
                   Serial.println(codeValue, HEX);
                   break;
                 case RC5:           // 3
                 case RC6:           // 4
                   if (repeat) toggle = 1; else toggle = 0;
                   // -jnh- rely on sending host to track toggle state
                   // Put the toggle bit into the code to send
                   codeValue = codeValue & ~(1 << (codeLen - 1));
                   codeValue = codeValue | (toggle << (codeLen - 1));
                   if (codeType == RC5) {
                     Serial.print(F("X: Sent RC5 "));
                     Serial.println(codeValue, HEX);
                     irsend.sendRC5(codeValue, codeLen);
                   }
                   else {
                     irsend.sendRC6(codeValue, codeLen);
                     Serial.print(F("X: Sent RC6 "));
                     Serial.println(codeValue, HEX);
                   }
                   break;
                 case DISH:          // 5
                   irsend.sendDISH(codeValue, codeLen);
                   Serial.print(F("X: Sent DISH "));
                   Serial.println(codeValue, HEX);
                   break;
                 case SHARP:         // 6
                   irsend.sendSharp(codeValue, codeLen);
                   Serial.print(F("X: Sent Sharp "));
                   Serial.println(codeValue, HEX);
                   break;
    //           case PANASONIC:     // 7
    //             Serial.print(F("X: no Panasonic xmit support (addr/value)! "));
    //             break;
                 case JVC:           // 8
                   irsend.sendJVC(codeValue, codeLen, repeat);
                   Serial.print(F("X: Sent JVC "));
                   Serial.println(codeValue, HEX);
                   break;
    //           case SANYO:         // 9
    //             break;
    //           case MITSUBISHI:    // 10
    //             break;
                 case SAMSUNG:       // 11
                   irsend.sendSamsung(codeValue, codeLen);
                   Serial.print(F("X: Sent Samsung "));
                   Serial.println(codeValue, HEX);
                   break;
    //           case SAMSUNG2:      // 12
    //             Serial.print(F("X: no Samsung2 xmit support (addr/value)! "));
    //             break;
                 default:
                   Serial.print(F("X: Unsupported IR code type "));
                   Serial.println(codeType, HEX);
                   break;
               }
             copies--;
             if (copies > 0) delay(intergap);
             }
           }
    //       Serial.println(buf);
           break;
         case 'Z':
           len--;
           Serial.print(F("Z:"));        // echo back for testing
           tmp=Serial.readBytes(buf,((len > 127) ? 127 : len));
           buf[tmp]=0;
           Serial.println(buf);
           break;
         default:
           Serial.print(F("?:unknown serial cmd: "));
           Serial.println(tmp,HEX);
        }
      }
    
      Serial.println('#');      // end of transmission mark
    
      Serial.flush();
      delay(DLY);
    
      // trying to kill serial noise pulse on shutdown (always 0xFE / 254)
      pinMode(0, INPUT_PULLUP);
      pinMode(1, INPUT_PULLUP);
    
      delay(DLY);
    
    // WKUP0(CANRX0) + WKUP3(pin31) + WKUP4(uart-rx) + WKUP15(pin22=IR-RX)
    
      tmp = ( (1 << 15) | (1 << 4 ) | (1 << 3) | 1);
      SUPC->SUPC_WUIR = tmp;          // above sources, hi->low transition
      SUPC->SUPC_WUMR = 0;            // try no debouncing; 1 << 12 for 3 cycle
    
      pmc_enable_backupmode();
    
    }
    
    void loop() {
      Serial.println (F("in loop() - I should not be here!"));
      delay(500);
    }
    
     
  3. Andrea Rovai

    Andrea Rovai Well-Known Member

    Joined:
    Oct 27, 2014
    Messages:
    1,703
    Likes Received:
    240
    Hi there fetcher,
    due to SAM3X and iMX6 using the same pins, the SAM3X can't turn on the A9.
    You will be able to do it with UDOO Neo :)
    Cheers!
     

Share This Page