Hi,
Have been developing smart soil sensors using different i2c sensors and RAK3172 module as brains of everything.
I know this question is not related to TTN, but still, maybe someone can point me in the right direction.
This is my first time working with MBedOs firmware.
I have written code based on mbedOs LoRa example, that reads multiple I2C sensors, encodes data and sends it to TTN.
Things that don’t allow me to sleep:
- ADC readings are all over the place. First ADC reading is nonsense, then it is ok and stable, then after the first uplink, it shows something different again. I really don’t know the inner workings of mbedOS AnalogIn. This is not crucial but still annoying.
- Managed to get node to sleep ~8uA average but then randomly after some uplink it doesn’t go to deep sleep and stays at ~3.1mA. This is something I really don’t know how to debug and find the cause of it.
I have attached the code of main.cpp below.
#include "events/EventQueue.h"
#include "lorawan/LoRaWANInterface.h"
#include "lorawan/system/lorawan_data_structures.h"
#include "mbed.h"
#include <cstdint>
#include <stdio.h>
// Application helpers
// #include "trace_helper.h"
#include "helpers.h"
#define DEBUGGING false
// create I2C instance
I2C i2c(PA_11, PA_12);
// I2C slave addreses
bool nodeOk = true;
const uint8_t tempSensorAddr[6] = {0x1A, 0x1C, 0x1B, 0x19, 0x18, 0x1D};
const uint8_t tempSensType[6] = {0x00, 0x00, 0x00, 0x00,
0x00, 0x01}; // 0x00 - soil tmep 0x01 - air
DigitalOut sensorEN(PA_7);
DigitalOut I2CExpanderEN(PB_3);
/**
values for reading battery voltage (5-3.3V)
*/
DigitalOut batVoltageADCEN(PB_5);
PinName batADCPin = PB_4;
// calibration is done to find out 2 coefficients
// vBat = ADCk*ADCValue*1000 + ADCb
const float ADCk = 0.0083;
const float ADCb = -0.2801;
#include "STM32WL_LoRaRadio.h"
STM32WL_LoRaRadio radio;
using namespace events;
// Max payload size can be LORAMAC_PHY_MAXPAYLOAD.
// This example only communicates with much shorter messages (<30 bytes).
// If longer messages are used, these buffers must be changed accordingly.
uint8_t tx_buffer[30];
uint8_t txBufferPointer = 0;
uint8_t rx_buffer[30];
uint32_t sleepIntervalMs = 30000; // default sleep time 30s
/**
* Maximum number of events for the event queue.
* 10 is the safe number for the stack events, however, if application
* also uses the queue for whatever purposes, this number should be increased.
*/
#define MAX_NUMBER_OF_EVENTS 15
/**
* Maximum number of retries for CONFIRMED messages before giving up
*/
#define CONFIRMED_MSG_RETRY_COUNTER 3
/**
* This event queue is the global event queue for both the
* application and stack. To conserve memory, the stack is designed to run
* in the same thread as the application and the application is responsible for
* providing an event queue to the stack that will be used for ISR deferment as
* well as application information event queuing.
*/
static EventQueue ev_queue(MAX_NUMBER_OF_EVENTS *EVENTS_EVENT_SIZE);
// /**
// * Event handler.
// *
// * This will be passed to the LoRaWAN stack to queue events for the
// * application which in turn drive the application.
// */
static void lora_event_handler(lorawan_event_t event);
// /**
// * Constructing Mbed LoRaWANInterface and passing it the radio object from
// lora_radio_helper.
// */
static LoRaWANInterface lorawan(radio);
// /**
// * Application specific callbacks
// */
static lorawan_app_callbacks_t callbacks;
// I2C low level functions
static uint8_t readReg(uint8_t addr, uint8_t reg) {
char regAddr[1] = {reg};
char data[1];
i2c.write(addr, regAddr, 1, true);
i2c.read(addr, data, 1, false);
i2c.stop();
return data[0];
}
static uint16_t readReg16(uint8_t addr, uint8_t reg) {
char regAddr[1] = {reg};
char data[2];
i2c.write(addr, regAddr, 1, true);
i2c.read(addr, data, 2, false);
i2c.stop();
return (data[0] << 8 | data[1]);
}
static bool writeReg(uint8_t addr, uint8_t reg, uint8_t value) {
char msg[2] = {reg, value};
return i2c.write(addr, msg, 2, false);
}
static bool writeReg16(uint8_t addr, uint8_t reg, uint16_t value) {
uint8_t MSB = value >> 8;
uint8_t LSB = value & 0xFF;
char msg[3] = {reg, MSB, LSB};
return i2c.write(addr, msg, 3, false);
}
// MCP9808 processing
/**
sets mode of the sensor
@param state true - enter shutdown mode
false - sampling mode
*/
static void sensorSleep(uint8_t addr, bool state) {
const uint8_t placeSHDN = 0x08;
const uint8_t regCONFIG = 0x01;
uint16_t currentCONFIG = readReg16(addr, regCONFIG);
// printf("Current Config: "BYTE_TO_BINARY_PATTERN "
// "BYTE_TO_BINARY_PATTERN"\n",
// BYTE_TO_BINARY(currentCONFIG>>8),BYTE_TO_BINARY(currentCONFIG));
uint16_t CONFIG =
(currentCONFIG & ~(1UL << placeSHDN)) | (state << placeSHDN);
// printf("Set CONFIG: "BYTE_TO_BINARY_PATTERN " "BYTE_TO_BINARY_PATTERN"\n",
// BYTE_TO_BINARY(CONFIG>>8),BYTE_TO_BINARY(CONFIG));
writeReg16(addr, regCONFIG, CONFIG);
}
static void setResolution(uint8_t addr, uint8_t resolution) {
const uint8_t regRES = 0x08;
// no need to read resolution because it is the only setting in register
writeReg(addr, regRES, resolution);
}
static float getTemp(uint8_t addr) {
const uint8_t regAMBTEMP = 0x05;
uint16_t regValue = readReg16(addr, regAMBTEMP);
#if DEBUGGING
printf("ABIENT TEMP reg: " BYTE_TO_BINARY_PATTERN " " BYTE_TO_BINARY_PATTERN
"\n",
BYTE_TO_BINARY(regValue >> 8), BYTE_TO_BINARY(regValue));
#endif
/**
temperature registr contains 3 sections
[0:11] - Ambient Temp Bits
[12] - Sign bit
[13-15] - Alert cause
*/
uint16_t maskALRTBits = 0xE000;
uint16_t maskNegativeBit = 0x1000;
uint16_t maskTempBits = 0xFFF;
uint8_t alerCauseBits = regValue & maskALRTBits;
bool negativeTemp = regValue & maskNegativeBit;
// remove alert cause bits and negative temp bit
uint16_t tempBits = regValue & maskTempBits;
// printf("tempBits: "BYTE_TO_BINARY_PATTERN " "BYTE_TO_BINARY_PATTERN"\n",
// BYTE_TO_BINARY(tempBits>>8),BYTE_TO_BINARY(tempBits));
uint8_t tempUpper = tempBits >> 8;
// printf("upper: "BYTE_TO_BINARY_PATTERN"\n", BYTE_TO_BINARY(tempUpper));
uint8_t tempLower = tempBits & 0xFF;
// printf("lower: "BYTE_TO_BINARY_PATTERN"\n", BYTE_TO_BINARY(tempLower));
float temp;
if (negativeTemp) {
temp = (256 - (tempUpper * 16 + ((float)tempLower / 16)));
} else {
temp = (tempUpper * 16 + ((float)tempLower / 16));
}
return temp;
}
float readBatteryVoltage() {
batVoltageADCEN = 1;
AnalogIn batVoltageADC(batADCPin);
batVoltageADC.set_reference_voltage(3.3);
batVoltageADC.read(); // first read is nonsense
// thread_sleep_for(100); //this did not help
float adcValRaw = batVoltageADC.read();
int adcVal = (int)(adcValRaw * 1000);
float vBat = adcVal * ADCk + ADCb;
batVoltageADCEN = 0;
#if DEBUGGING
printf("adcraw: %1.3f\t", adcValRaw);
printf("adcVal: %d \t", adcVal);
printf("adcVoltage %1.4f \r\n", vBat);
#endif
return vBat;
}
/**
* Entry point for application
*/
int main(void) {
// as per low-power suggestion
mbed_file_handle(STDIN_FILENO)->enable_input(false);
// stores the status of a call to LoRaWAN protocol
lorawan_status_t retcode;
// Initialize LoRaWAN stack
if (lorawan.initialize(&ev_queue) != LORAWAN_STATUS_OK) {
printf("\r\n LoRa initialization failed! \r\n");
return -1;
}
printf("\r\n Mbed LoRaWANStack initialized \r\n");
// prepare application callbacks
callbacks.events = mbed::callback(lora_event_handler);
lorawan.add_app_callbacks(&callbacks);
// Set number of retries in case of CONFIRMED messages
if (lorawan.set_confirmed_msg_retries(CONFIRMED_MSG_RETRY_COUNTER) !=
LORAWAN_STATUS_OK) {
printf("\r\n set_confirmed_msg_retries failed! \r\n\r\n");
return -1;
}
printf("\r\n CONFIRMED message retries : %d \r\n",
CONFIRMED_MSG_RETRY_COUNTER);
// Enable adaptive data rate
if (lorawan.enable_adaptive_datarate() != LORAWAN_STATUS_OK) {
printf("\r\n enable_adaptive_datarate failed! \r\n");
return -1;
}
printf("\r\n Adaptive data rate (ADR) - Enabled \r\n");
retcode = lorawan.connect();
if (retcode == LORAWAN_STATUS_OK ||
retcode == LORAWAN_STATUS_CONNECT_IN_PROGRESS) {
} else {
printf("\r\n Connection error, code = %d \r\n", retcode);
return -1;
}
printf("\r\n Connection - In Progress ...\r\n");
// make your event queue dispatching events forever
ev_queue.dispatch_forever();
return 0;
}
// /**
// * Sends a message to the Network Server
// */
static void send_message() {
I2CExpanderEN = 1; // enable I2C expander
sensorEN = 1; // enable sensor power
i2c.init(); // init I2C
// read all sensors
for (uint8_t sensId = 0; sensId < sizeof(tempSensorAddr); sensId++) {
uint8_t addr8Bit = tempSensorAddr[sensId] << 1;
sensorSleep(addr8Bit, false);
thread_sleep_for(300);
float temp = getTemp(addr8Bit);
sensorSleep(addr8Bit, true);
// this is so that we never have negative values and use as much bits as
// necessary, not more
uint8_t tempOffset = 40; //(273.15-233.15);
uint16_t tempOffseted = (temp + tempOffset) * 100; //*100 to have 2 decimals
uint8_t tempDataUpper = tempOffseted >> 8;
uint8_t tempDataLower = tempOffseted & 0xFF;
uint8_t idByte = (sensId << 4) | tempSensType[sensId];
#if DEBUGGING
printf("Sensor: %d \n", (tempSensorAddr[sensId]));
printf("%2.4f \r\n", temp);
printf("%d \r\n", tempOffseted);
printf("id Byte " BYTE_TO_BINARY_PATTERN "\r\n", BYTE_TO_BINARY(idByte));
printf("temp data " BYTE_TO_BINARY_PATTERN " " BYTE_TO_BINARY_PATTERN
"\r\n",
BYTE_TO_BINARY(tempDataUpper), BYTE_TO_BINARY(tempDataLower));
#endif
// fill tx buffer
tx_buffer[txBufferPointer] = idByte;
tx_buffer[txBufferPointer + 1] = tempDataUpper;
tx_buffer[txBufferPointer + 2] = tempDataLower;
txBufferPointer = txBufferPointer + 3; // set ready for the next player.
}
i2c.free(); // release I2C
sensorEN = 0; // disable sensor power
I2CExpanderEN = 0; // disable I2C expander
// read battery voltage
float voltageOffset = 3.3;
uint8_t batVValue = (readBatteryVoltage() - voltageOffset) * 100;
uint8_t idByte = (0x00 | 0x0A); // probe level is left empty and type value is
// added to the 0:3 bits
// fill tx buffer
tx_buffer[txBufferPointer] = idByte;
tx_buffer[txBufferPointer + 1] = batVValue;
txBufferPointer = txBufferPointer + 2; // set ready for the next player.
// /**
// NOTES for encoding
// each sensor node data start with idByte
// bit 0:4 - data type:
// 0x00 - soil temperature
// 0x01 - air temperature
// 0x02 -
// 0x03 -
// 0x04 -
// 0x05 -
// 0x06 -
// 0x07 -
// 0x08 -
// 0x09 -
// 0x0A - battery voltage
// 0x0B -
// 0x0C -
// 0x0D -
// 0x0E -
// 0x0F - error
// bit 5:8 - sensor level
// 0x00 - closes to the bottom
// 0xF0 - closes to the top
// always start by using 0x00 and then move to the top
// as different sensor can be easly added above the surface.
// **/
int16_t retcode = lorawan.send(MBED_CONF_LORA_APP_PORT, tx_buffer,
txBufferPointer, MSG_UNCONFIRMED_FLAG);
if (retcode < 0) {
retcode == LORAWAN_STATUS_WOULD_BLOCK
? printf("send - WOULD BLOCK\r\n")
: printf("\r\n send() - Error code %d \r\n", retcode);
if (retcode == LORAWAN_STATUS_WOULD_BLOCK) {
// retry in 3 seconds
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
ev_queue.call_in(3000, send_message);
}
}
return;
}
// printf("\r\n %d bytes scheduled for transmission \r\n", retcode);
memset(tx_buffer, 0, sizeof(tx_buffer)); // clear buffer
txBufferPointer = 0;
}
/**
*Send error message that something is worong and node is not functional
*/
void send_error_message() {
tx_buffer[txBufferPointer] = 0x0F; // error message
txBufferPointer = txBufferPointer + 1;
int16_t retcode = lorawan.send(MBED_CONF_LORA_APP_PORT, tx_buffer,
txBufferPointer, MSG_UNCONFIRMED_FLAG);
memset(tx_buffer, 0, sizeof(tx_buffer)); // clear buffer
txBufferPointer = 0;
}
/**
* Receive a message from the Network Server
*/
static void receive_message() {
uint8_t port;
int flags;
int16_t retcode = lorawan.receive(rx_buffer, sizeof(rx_buffer), port, flags);
if (retcode < 0) {
printf("\r\n receive() - Error code %d \r\n", retcode);
return;
}
#if DEBUGGING
printf(" RX Data on port %u (%d bytes): ", port, retcode);
for (uint8_t i = 0; i < retcode; i++) {
printf("%02x ", rx_buffer[i]);
}
printf("\r\n");
#endif
// extract command byte
uint8_t cmdByte = rx_buffer[0] >> 4;
uint8_t additionalInfo = rx_buffer[0] & 0x0F;
switch (cmdByte) {
case 0x05: {
#if DEBUGGING
printf("setting uplink interval \r\n");
#endif
uint16_t receivedInterval = (rx_buffer[1] << 8) | rx_buffer[2];
if (additionalInfo & 0x01) {
// time sent in minutes
sleepIntervalMs = receivedInterval * 60 * 1000;
} else {
sleepIntervalMs = receivedInterval * 1000;
}
break;
}
default: {
#if DEBUGGING
printf(" unrecognised command: %02x", cmdByte);
printf("\r\n");
#endif
}
};
memset(rx_buffer, 0, sizeof(rx_buffer)); // clear RX buffer
}
/**
* Event handler
*/
static void lora_event_handler(lorawan_event_t event) {
switch (event) {
case CONNECTED: {
printf("\r\n Connection - Successful \r\n");
// verfy that we have connected all the sensors.
I2CExpanderEN = 1; // enable I2C expander
sensorEN = 1; // enable power for sensors
for (uint8_t sensorAddr : tempSensorAddr) {
printf("Sensor: %d \t", (sensorAddr));
uint8_t addr8Bit = sensorAddr << 1;
int i2cResposne = i2c.write(addr8Bit);
printf("response: %d \r\n", i2cResposne);
if (i2cResposne == 0) {
nodeOk = false;
printf("failed \r\n");
}
}
i2c.free(); // release I2C for deep sleep
I2CExpanderEN = 0; // disable I2C expander
sensorEN = 0; // disable power for sensors
// we connected, send our first sensor reading or error message
if (nodeOk) {
send_message();
} else {
send_error_message();
}
break;
}
case DISCONNECTED:
ev_queue.break_dispatch();
printf("\r\n Disconnected Successfully \r\n");
break;
case TX_DONE:
// this is where we land after TX is done
// printf("\r\n Message Sent to Network Server \r\n");
// enter sleep
//TODO: we should call this message only after RX window
if (nodeOk) {
ev_queue.call_in(sleepIntervalMs, send_message);
} else {
while (true) {
//Nothing happens!
}
}
break;
case TX_TIMEOUT:
case TX_ERROR:
case TX_CRYPTO_ERROR:
case TX_SCHEDULING_ERROR:
printf("\r\n Transmission Error - EventCode = %d \r\n", event);
// try again
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
send_message();
}
break;
case RX_DONE:
// printf("\r\n Received message from Network Server \r\n");
receive_message();
break;
case RX_TIMEOUT:
printf("RX TIMEOUT");
break;
case RX_ERROR:
printf("\r\n Error in reception - Code = %d \r\n", event);
break;
case JOIN_FAILURE:
printf("\r\n OTAA Failed - Check Keys \r\n");
break;
case UPLINK_REQUIRED:
printf("\r\n Uplink required by NS \r\n");
if (MBED_CONF_LORA_DUTY_CYCLE_ON) {
send_message();
}
break;
default:
MBED_ASSERT("Unknown Event");
}
}
// EOF
Really thanks for everyone’s time who looks into this.