Low Power Use of the Arduino Nano Matter
Really smart, creative engineers, makers, and hobbyists create incredible devices and clever code. I love reading about this stuff and have tremendous respect for the efforts of these folks. However, lurking there at the back of the workbench is a power supply. You can’t bring your widget outside. Your air quality sniffer needs to be near a wall plug. The Wi-Fi cat water level monitor, if deployed as designed, would last 5 days on battery. What about real-world deployment and power consumption?
Certainly, some of my best projects have not worried about power consumption. I love working with the Teensy, but it typically draws 100 mA. A great board when I’m plugged into the wall socket with my GPS clock, but not my first pick for a trail counter sitting out in the middle of nowhere.
So, power consumption and management have become a consideration for me in component choice and design. For several reasons, I chose the Arduino Nano Matter for a project and ended up taking a deep dive into its power consumption. I want to share what I learned.
The Arduino Nano Matter (ANM) is a low-power, Nano-form-factor MCU board from Arduino, marketed as an effective way to use the Matter ecosystem. Its capabilities as a Matter board aside, it is a robust little MCU that is attractive for numerous reasons, among them well-exposed sleep and low-power capabilities. I use this board in my most recent iteration of my low-power trail counter. The counter does not interact with the Matter universe, but it does take advantage of both the board's sleep and low-power features. While I ultimately found the board to be a great choice, several documentation issues created initial obstacles. I will review my findings here in the hope I can save others time and frustration. Please note I used only the deepSleep() and related functions. If you want to idle() or sleep(), you are on your own. Note that these modes (idle() and sleep()) do not natively support external interrupts for wakeup.
Let’s start with the hardware. Board Fritzing pictures are at the beginning of the user manual. The back side of the board contains a jumper, which can be cut to disable the power LED. This works as documented and intended. There is also a 3.3V power jumper. The diagram includes the following: “Cut the jumper to disable the 3V3 rail that powers the whole board.”
I don’t think most people want to cut that jumper. If I understand the schematic correctly, the board's power configuration is complicated, with two voltage regulators: one for the MCU and the other for the USB interface. Cutting the jumper isolates the 3.3V MCU rail from all of the USB power and renders the MCU dependent on a 3.3V source connected to the 3.3V pin. If the user chooses to attach the board to a USB to program or debug it, a second power source is required for the MCU. Additionally, the circuit is configured so that applying power to the board via the 3.3V pin does not power the USB components, so concerns about USB power consumption when using external 3.3V power are irrelevant. I think most hobbyists (and even professional developers) don’t usually achieve an absolute final program (maybe ever?), and retaining the ability to easily switch between USB and external power is desirable.
OK, so what about external power? Let’s flip the board to the front. There are (in addition to ground) three relevant pins, +5V, Vin, and 3V3.
+5V is Vusb. Documentation indicates that you can power the board by applying 5V to this pin. This is correct. However, the manual includes the following statement: “To power the board through the VIN pin you need to close the jumper pads with solder. The maximum voltage supported is +5 VDC.” This is not correct. Later board versions include a Schottky diode preinstalled on the referenced jumper pads. An additional statement indicates that a voltage of 6-21 V can/should be applied to the Vin pin. This statement contradicts the 5V limit. According to the schematic, Vin (as opposed to Vusb) lacks reverse-polarity protection. The regulator connected to both Vusb and Vin is an MP2322GQH, which handles up to 22V. FWIW, the regulator is low-dropout, and I was able to power the board by connecting a fresh 18560 battery to Vin.
Why use Vin at all? The MP2322GQH regulator is robust and can supply up to 1A. With the 3.3V jumper intact (closed) and power applied to Vin, the 3.3V pin can supply power to peripherals, obviating the need for an external 3.3V power rail. Comprehension of the above piece of schematic is left as an exercise for the reader…
Alternatively, if power is not applied via a USB cable, Vusb, or Vin, the 3.3V pin can accept power. Power applied to the 3V3 pin does not power the USB-related circuitry, saving a fair bit of power. I power my trail counter board with an external buck-boost regulator from Pololu (Pololu 3.3V Step-Up/Step-Down Voltage Regulator S7V8F3), which has a quiescent current of 0.1 mA and over 80% efficiency. This is the most power-efficient configuration for me. In the final analysis, I can’t see the value of cutting the 3V3 jumper. I welcome other opinions and would be interested in them.
The software side of this is much, much less confusing, but has a couple of gotchas. Again, I’m limiting myself to deepSleep().
deepSleep() puts the MCU into a very low-power mode. From the GitHub page: “Puts the MCU in deep sleep mode. The deep sleep mode allows the device to enter an extremely low power consumption state (EM4). Every peripheral is stopped except for a few - like the back-up RTC and back-up RAM. The CPU can only wake up using the RTC or a pulse on a wake-up interrupt capable pin. In this mode the CPU is completely halted and RAM contents are lost. Upon waking up the device starts from the beginning of the sketch.”
The MCU can be put to sleep using either LowPower.deepSleep() or LowPower.deepSleep(milliseconds). The latter call is self-explanatory, waking from deep sleep every x milliseconds. The interrupt-driven wakeup requires more work and explanation.
Deep sleep puts the MCU into power-consumption state EM4. Although almost all ANM pins can be used for conventional interrupts, not all pins are capable of waking the MCU from EM4. Limited experimentation identified D7 as working. Once I found this pin, I didn’t look further. It has no other functions, such as SPI, I2C, etc., so it was perfectly good for my purposes. LowPower.attachInterruptWakeup() is used to link the pin. Although a callback is specified in the documentation, it will not be executed because it doesn’t exist—remember: the sketch starts at setup(). LowPower.attachInterruptWakeup() must be called each time before putting the MCU back to sleep.
There are a couple of neat additional calls. LowPower.wokeUpFromDeepSleep() allows you to distinguish between the MCU being powered up and waking from sleep. Use cases include resource provisioning on a network or (as in my project) reading a configuration file for run-time parameters.
But isn’t RAM clobbered by deepSleep()? Yup. That’s why there’s a small chunk of protected RAM that survives deepSleep(). You can write to the RAM using LowPower.deepSleepMemoryWrite(address, data) and read the information using LowPower.deepSleepMemoryRead(address). The ANM has 128 bytes of protected RAM. Each read/write function reads or writes 4 bytes, and the address is in 4-byte increments starting at 0. (LowPower.deepSleepMemoryRead(0) returns bytes 0-3 and LowPower.deepSleepMemoryRead(1) returns bytes 4-7. I found a long to be a convenient variable type here.)
Putting this all together, in setup(), I include a check to determine whether this run of setup() is a power-up event or a wake-up. If it is a power-up, I read my config file and store the relevant parameters in the protected RAM. If it is a wakeup run, I read the protected RAM into the appropriate variables.
The last interesting function is LowPower.deepSleepMemorySize(). Different chips have different-sized protected memories. I ran this on the ANM, so you don’t need to; it’s where I got that 128-byte number.
The final issue in using the ANM in deep sleep mode is programming. Writing code to run after waking from deep sleep is not the same as conventional setup() and loop() programming. In many situations, all work might be done in setup(), with the final setup() call a return to deep sleep. In that situation, the loop() function would be empty and present just to satisfy the compiler (Maybe it doesn’t even need to be there? I’ve never checked). Setup() may also need to include an if{} branch to handle power-up vs wakeup conditions. As with my trail counter, I continue to use the loop() functionality because my device performs some work while it stays awake. There’s a learning curve, but it’s not particularly steep or difficult (if I can do it, you can do it.)
The final issue with programming concerns physically programming the unit. If the device is in deep sleep, it needs a special escape routine to be programmed. The ANM can be forced to accept programming by pushing and holding the user button and then pushing the reset button. You can then release the user button. A red LED will come on when the ANM is ready to be programmed. Preserving access to the board’s push buttons had a significant impact on the physical design of the current iteration of my trail counter and may affect your designs as well; I can’t stack boards with the ANM under another board with 15 mm clearance.
I hope this is useful to someone other than me. I would welcome comment. If I’ve made an error in my analysis, particularly of the 3.3V jumper stuff (I’m pretty confident about the other observations), I’d like to know and would be happy to revise this project
Discussion (0 commentaire(s))