Electronic – ATSAMR21 sleep high current draw

atmelembeddedlow-powersamd21

I have an ATSAMR21B18-MZ210PA, which is an ATSAMR21E18 packaged with MX25L2006E flash. The SAMR21 is itself a SAMD21 paired with an AT86RF233 wireless module. So that's a SAMD21 + low power wireless + flash.

The datasheets say I should expect ~4uA current draw from the SAMR21 in standby mode and the AT86RF233 in deep sleep (page 1061 of the second datasheet linked above) and an additional ~5uA from the MX25 flash in deep power down mode (no more than 45uA in regular standby—page 28 of the third datasheet linked above).

However, I cannot get current draw to drop below ~0.55mA. Below I have copied my main entry point. What might I be missing or doing wrong?

#define RTC_TICKS_PER_SECOND 1
#define SECONDS_PER_TICK 1
#define BLINKY_PIN PIN_PA07
#define SLEEP_MODE_DEEP 3
#define AT86RF233_SLP_TR PIN_PA20
#define AT86RF233_RST PIN_PB15
#define MX25_CS PIN_PA27

int main(void)
{
    /* Initializes MCU, drivers and middleware */
    atmel_start_init();

    // Initialize the blinky pin
    initBlinkyPin();

    // Set AT86RF233 into low power state
    gpio_set_pin_direction(AT86RF233_RST, GPIO_DIRECTION_OUT);
    gpio_set_pin_level(AT86RF233_RST, false); // Active low reset
    delay_ms(1); // Hold it there for a little bit
    gpio_set_pin_level(AT86RF233_RST, true); // Release reset
    // Drive SLP_TR high to signal it should enter sleep
    gpio_set_pin_direction(AT86RF233_SLP_TR, GPIO_DIRECTION_OUT);
    gpio_set_pin_level(AT86RF233_SLP_TR, true);

    // Set MX25 flash chip into deep sleep
    struct io_descriptor *io;
    spi_m_sync_get_io_descriptor(&SPI_0, &io);
    spi_m_sync_enable(&SPI_0);
    gpio_set_pin_direction(MX25_CS, GPIO_DIRECTION_OUT);
    gpio_set_pin_level(MX25_CS, true);
    delay_ms(1);
    // Select MX25 flash chip
    gpio_set_pin_level(MX25_CS, false);
    // Read status register until WIP bit is false
    uint8_t readStatusRegisterCommand[] = { 0x05 };
    uint8_t statusRegister[1];
    while (1)
    {
        io_write(io, readStatusRegisterCommand, 1);
        statusRegister[0] = 0xff;
        io_read(io, statusRegister, 1);
        if ((*statusRegister) & 1)
            continue;
        break;
    }
    // Send DP command
    uint8_t deepPowerDownCommand[] = { 0xb9 };
    io_write(io, deepPowerDownCommand, 1);
    gpio_set_pin_level(MX25_CS, true); // Release CS at byte boundary

    /* Replace with your application code */
    while (1) {
        sleep(SLEEP_MODE_DEEP); // Stop until interrupt, power down all clocks except those set to run in standby
        toggleBlinkyPin();
    }
}

I'm not sure what other code I should include—please let me know. I'm quite a novice in the embedded world. I'm using Atmel Studio with Atmel Start to auto generate driver code and wire up clocks for me—I'm still figuring out the files it gives me.

The CPU is clocked at 8MHz from OSC8M. SPI is also driven by that clock. I also have an RTC timer always running from OSCULP32K. The timer is set to generate an interrupt every second.

You can see that the while loop is putting the MCU into standby mode, toggling a pin when it wakes up. That allows me to see how frequently the MCU is waking up. I can confirm that the pin is toggling once every second.

Before the while loop you can see my attempts to put the AT86RF233 and MX25 into their respective low power states. I say "attempts" because I don't see an easy way to confirm the correctness of that code. However, my output pin toggles which means execution at least makes it through that code. If I comment it out then current draw increases only slightly (maybe 0.1mA) so it's probably doing something.

I omitted the initBlinkyPin and toggleBlinkyPin functions for brevity. There's nothing surprising in them… they just set the state of and drive the output of a single pin.


Edit: Unplugging the programmer (duh!) reduces current to ~0.44mA. Better, but still a couple of orders of magnitude higher than I expect.

I should also say now that the programmer is disconnected from the module there are only the power and ground pins connected to anything when I measure current draw. And I think my multimeter is accurate and precise enough.


Edit 2: I programmed a second identical module and measured current in the same way: 0.52mA. So not only orders of magnitude too high, but also very high variance. These are not Chinese knockoffs. What gives?


Edit 3: I'm cross-posting this on community.atmel.com.

Best Answer

Looks like I wasn't correctly disabling the AT86RF and MX25 modules. Disabling those correctly reduced current draw to ~11uA. More details here: https://community.atmel.com/comment/2630026#comment-2630026


Edit: For future readers, I used the libraries in Atmel's ASF3. There I found function calls implemented by Atmel which disable these peripherals correctly. Here's what my main function looked like after that:

#define TRX_STATUS 0x01
#define TRX_STATE 0x02
#define TRX_STATE_P_ON 0x00
#define TRX_STATE_TRX_OFF 0x08
#define TRX_STATE_PREP_DEEP_SLEEP 0x10

#define BOARD SAMR21B18_MODULE

/*
 * Include header files for all drivers that have been imported from
 * Atmel Software Framework (ASF).
 */
/*
 * Support and FAQ: visit <a href="https://www.microchip.com/support/">Microchip Support</a>
 */
#include <asf.h>
#undef ENABLE
#include <mx25l.h>

void trxSendToState(uint8_t state);

void trxSendToState(uint8_t state)
{
    trx_reg_write(TRX_STATE, state);
    while ((trx_reg_read(TRX_STATUS) & 0x1F) != state);
}

void toggleBlinkyPin(void);

void toggleBlinkyPin()
{
    static bool blinkyState = false;
    blinkyState ^= true;
    port_pin_set_output_level(PIN_PA07, blinkyState);
}

int main (void)
{
    delay_init();
    system_init();

    // Set up blinky pin
    struct port_config pin_conf;
    port_get_config_defaults(&pin_conf);
    pin_conf.direction = PORT_PIN_DIR_OUTPUT;
    pin_conf.input_pull = PORT_PIN_PULL_NONE;
    port_pin_set_config(PIN_PA07, &pin_conf);
    port_pin_set_output_level(PIN_PA07, false);

    // Disable voltage references
    system_voltage_reference_disable(SYSTEM_VOLTAGE_REFERENCE_BANDGAP);
    system_voltage_reference_disable(SYSTEM_VOLTAGE_REFERENCE_TEMPSENSE);

    // Tell flash to go into deep power down
    struct mx25l_spi_config mx25Config;
    mx25l_spi_get_config_defaults(&mx25Config);
    if (mx25l_init(&mx25Config) != STATUS_OK)
        while (1) ;
    if (mx25l_enter_deep_powerdown() != STATUS_OK)
        while (1) ;

    // Put TRX into deep sleep
    PhyReset();
    trx_spi_init();
    trxSendToState(TRX_STATE_P_ON); // Sending to P_ON state is NOP. So this is equivalent to waiting for P_ON state
    trxSendToState(TRX_STATE_TRX_OFF);
    trxSendToState(TRX_STATE_PREP_DEEP_SLEEP);
    SLP_TR_HIGH(); // Send to DEEP_SLEEP state

    // Disable BOD33
    while (!(SYSCTRL->PCLKSR.bit.B33SRDY)) ;
    SYSCTRL->BOD33.bit.ENABLE = 0;

    // Prep MCU for standby mode
    system_set_sleepmode(SYSTEM_SLEEPMODE_STANDBY);
    while (1)
    {
        // Deep sleep
        toggleBlinkyPin();
        system_sleep();
    }

    /* Insert application code here, after the board has been initialized. */
}