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); } }
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); }
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!