Arduino (3)


MySensors smartSleep

How to support the MySensors smartSleep function?

FYI my development board looks like this. Parts include Arduino Pro Mini, MCP1703 (+v.e to 3.3v), 8 channel relay board, nRF24L01+ radio module.

I want to be able to reliably send commands to this MySensors node but I want the node to be sleeping most of the time when the relays are not activated.

When you code up a MySensors node you can save power by sleeping. This works fine until you want to send the node a command. The node won’t receive the command because it is asleep. I initially thought that I could get around this by simply sending the command with a qos status of 1.  This doesn’t work!

Recall that a MySensors NODE communicates with a MySensors GATEWAY. The gateway is responsible for sending and receiving messages to an MQTT BROKER.

If you send a command with a qos status of 1 this guarantees delivery to the GATEWAY but it does not guarantee delivery to the node if the node is sleeping. The gateway isn’t clever enough to buffer messages for nodes.

The MySensors library has a built in function called smartSleep. When you call this function the following happens:

  1. The Node sends a message (I_PRE_SLEEP_NOTIFICATION) to the server with a payload containing the number of milliseconds before it sleeps
  2. The Node waits for this amount of time
  3. The Node sleeps

Sending a command to a sleeping node then becomes a lot easier.

The following algorithm can be used when a command is received.

  • MqttCogs receives a set command
  • Check whether node is sleeping
  • If sleeping buffer message to new table
  • If not sleeping or it doesn’t look like a MySensor packet then issue the set command

The following algorithm can be used when a incoming I_PRE_SLEEP_NOTIFICATION message is received by MqttCogs

  • Check buffer for messages for this node (extract in datetime order)
  • Send messages to Node

I’ve had an alpha stab at implementing this. The following will submit a set command to node 10, sensor 6. This is a relay, the value is the number of minutes the relay should open.

[ mqttcogs_set topic='mysensors_in/10/6/1/0/2' text='Add' label_text='Enter a new value here:' input_type='number' input_min='0' input_max='6' input_title='Please enter a number between 0 and 6' qos='1'/ ]

And heres a history of what happened. When you set the value the graph won’t change. The node is only not sleeping for 500ms every minute unless the relay is activated….

For reference, my arduino loop looks something like the code below. I added in a couple of useful safety features.

  • A relay can only be activated for a fixed time only.
  • When the arduino restarts the relays are set to OFF
void loop()
 {
 
 if (sensor_active) {
     //check if sensor should still be on
     if ((millis() - sensor_start)> sensor_ms) {
        digitalWrite(sensor_active-1 + RELAY_PIN, RELAY_OFF);
        saveState(sensor_active, false);
        send(msg.setSensor(sensor_active).set(false));
        sensor_active = 0;
        sensor_start = 0;
        sensor_ms = 0;
     }
     //don't sleep while relay is on
     return;
   }
   smartSleep(60000);
 }

So, power saving?

It’s good. While sleeping the board (without modifications) consumes 1.5mA (mainly due to the led). I removed the on LED and the board now consumes 60uA when sleeping (should be less so need to look at why).

When relay is on it uses *a lot* because the relay has to be energised.

I’ve coded things so that only one relay can be on at once. This reduces the suck through the MCP1703. The MCP1703 only supports 250mA current draw. More than 2 relays on at once would break it!




Connect Serial Flash to Arduino

How to Connect Serial Flash to Arduino to Save Power

I chose a SPI Flash or Serial Flash module for 2 reasons. Firstly, it uses a lot less power than an SD card and secondly it has a much smaller form factor. An SPI Flash module will use <10mA for writing whereas an SD card will use up to 150mA.

So my thought was ‘I can drive the Serial Flash module from one of the Arduino digital IO pins’.

I bought one of these https://www.ebay.co.uk/itm/W25Q-Winbond-Serial-Flash-Memory-Module-SPI-W25Q128B-BIOS-25Q64BVSIG-EEPROM-/181873964697. This contains a winbond 128Mbit Serial Flash chip.

The board includes pull up resistors (more about these later) and labelled pins. The board is 3.3v volts. I did all my testing for this post using a Arduino Mini Pro at 3.3v so I didn’t have to do any logic level conversion.

Why 128Mbits?

One of my previous posts included code to write a serial buffer to EEPROM. This allowed chunks of data up to 255bytes to be written sequentially to the EEPROM. On startup the code determined the next ‘free page’ to write to and only overwrote one 4K block once the log started to loop.

So 128Mbits = 16777216 bytes

So 16777216 bytes = 65536 lots of 255byte records

So if I write a single log record every minute that is 45 days of logging

Wiring the Winbond W25Q128B module to the Arduino

Wiring the SPI Flash Module
Arduino Pin SPIFlash Pin
Pin 6 (your choice) VCC
GND GND
VCC WP/IO2
VCC HOLD/IO3
MISO DO/IO1
MOSI DI/IO0
SCK CLK
Pin 10 (SS) CS

Enable and Disable

I firstly tried to the SPIMemory library for sleeping. There are functions for powerUp() and powerDown(). I rejected these. A powerDown and Arduino reset causes the SPI Flash module to lock up and the Arduino wouldn’t restart.

This took a bit of trial and error but I eventually came up with the following function. I’ve attached VCC of the Serial Flash to Pin 6 of

void flashToggle(bool yes) {
  if (yes) {
      pinMode(6, OUTPUT); 
      digitalWrite(6, HIGH); 
      flash.begin();
  }
  else {
      pinMode(6, INPUT); 
     digitalWrite(6, LOW); 
  }
}

Switching the pin to INPUT and setting low seemed to stop all power from the pin. I tried all other options.

Power  Use

These were measured on the VCC line with a cheap multimeter.

Continuous write ~ 5mA
after flashToggle(false) – 0uA (err, yes really. Nothing)

Conclusion

For me, a result. The module uses a tiny about of power and I can switch it off by simply toggling the DIO pin. I need to see if the same is true when it is attached to a 5v Arduino through a logic level converted.




Arduino Circular Buffer to Serial Flash

Writing an Arduino Circular Buffer to Serial Flash.

Data is arranged in BLOCKS of 4K, each block contains PAGES of length 256 bytes. Pages are written sequentially to flash.

When we get to the end of the flash we then start at the beginning. Serial Flash must be erased a BLOCK at a time. We erase only one BLOCK ahead of where we are currently writing.

On startup we determine what is the next free page in the flash so that we can continue writing. We keep an address pointer in memory after this.

The first byte of each page is used to flag teh following:

11111111 – not written

01111111 – writing

00111111 – written

The following 255 bytes contain the page payload. We CANNOT write more than 255 bytes to a page.

#define BLOCK_SIZE 4096  //4K
#define PAGE_SIZE 256    //

#define PAGE_WRITING 0x7F //01111111
#define PAGE_WRITTEN 0x3F //00111111

SPIFlash flash;
uint32_t addr;
uint32_t capacity;

void setup() {
  Serial.begin(BAUD_RATE);
   flash.begin();
   capacity = flash.getCapacity(); 
   addr = getNextAddress();
}


/**
  Writes a variable length packet of no more than 255
  bytes to the rolling log

  @param data_buffer[] the bytes to write
  @param bufferSize the size of data_buffer
  @return the next available address
*/
uint32_t writePacket(byte data_buffer[], int bufferSize) {
  if (addr%BLOCK_SIZE==0) {
     Serial.print("ERASE:");
     Serial.println(addr);
    flash.eraseSector( (addr + BLOCK_SIZE)%capacity);
  }
  flash.writeByte(addr, 0x7F); //flag as started
  flash.writeByteArray(addr+1, data_buffer, bufferSize);
  flash.writeByte(addr, 0x3F); //flag as written
  return (addr + PAGE_SIZE) % capacity;  
}


/**
  Examines the SerialFlash finds the next empty page. Should
  be called on startup. A reference can then be kept to
  log position.

  @return the next available address
*/
uint32_t getNextAddress() {
    uint32_t ad = search(0,capacity, BLOCK_SIZE, 0x3F);
    return (search(ad, BLOCK_SIZE, PAGE_SIZE, 0x3F)+ PAGE_SIZE)%capacity;
}

/**
  Examines the first byte in a chunk of flash to determine if 
  page has been written.

  @param from the from address
  @param to the to address
  @param ss the step size, normally BLOCK or PAGE
  @param headermask mask to apply to first byte of PAGE
  @return the next available address
*/
uint32_t search(uint32_t from,uint32_t to, uint32_t  ss, byte headermask) {
  byte lb = 255;//isnt written
  uint32_t pt;
  
  for ( pt = from; pt<=to;pt=pt+ss) {
    byte b = flash.readByte(pt);   
    if ((b==255) && (~lb & ~headermask)) {
      return pt-ss;
    }
    lb = b;
  }
  //got to the end
  return pt;
}