diff --git a/devkit-cayenne/README.md b/devkit-cayenne/README.md new file mode 100644 index 0000000..deb975d --- /dev/null +++ b/devkit-cayenne/README.md @@ -0,0 +1,61 @@ +# Helium Developer Kit - Cayenne Example + +This example demonstrates sending motion and environmental sensor data in [CayenneLPP](https://developers.mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload) format, using a B-L072Z-LRWAN1 development board with a X-NUCLEO-IKS01A3 expansion shield, to the myDevices Cayenne dashboard. For more information on adding your device to the Helium network, visit our quickstart guide [here](https://developer.helium.com/console/quickstart). For more information on adding your device to myDevices Cayenne, visit our guide [here](https://developer.helium.com/console/integrations/mydevices-cayenne-integration). + +## Required Arduino Libraries + +From the Arduino IDE, open the Library Manager (Sketch->Include Library->Manage Libraries). In the search box, type the library name below and install the latest version. + +[MCCI Arduino LoRaWAN Library](https://github.com/mcci-catena/arduino-lmic) +[CayenneLPP](https://github.com/ElectronicCats/CayenneLPP) +[X-NUCLEO-IKS01A3](https://github.com/stm32duino/X-NUCLEO-IKS01A3) + +## Required Board Support + +### B-L072Z-LRWAN1 - ST STM32L0 Discovery kit +Install board support package, find instructions [here](https://github.com/stm32duino/Arduino_Core_STM32#getting-started). + +Arduino IDE: +1. Select Tools -> Board: -> Discovery +2. Select Tools -> Board part number: -> Discovery L072Z-LRWAN1 + +[B-L072Z-LRWAN1 Product Page](https://www.st.com/en/evaluation-tools/b-l072z-lrwan1.html) +[B-L072Z-LRWAN1 User Manual](https://www.st.com/content/ccc/resource/technical/document/user_manual/group0/ac/62/15/c7/60/ac/4e/9c/DM00329995/files/DM00329995.pdf/jcr:content/translations/en.DM00329995.pdf) + +### X-NUCLEO-IKS01A3 - ST Motion MEMS and Environmental Sensor Board + +[X-NUCLEO-IKS01A3 Product Page](https://www.st.com/en/ecosystems/x-nucleo-iks01a3.html) +[X-NUCLEO-IKS01A3 User Manual](https://www.st.com/resource/en/user_manual/dm00601501-getting-started-with-the-xnucleoiks01a3-motion-mems-and-environmental-sensor-expansion-board-for-stm32-nucleo-stmicroelectronics.pdf) +## Programming (Uploading Method): + +#### STM32CubeProgrammer(SWD) +Will use onboard ST-Link(Flasher/Debugger) to upload sketch. +Download and Install required utility from ST [here](https://www.st.com/en/development-tools/stm32cubeprog.html). + +Arduino IDE: +Select Tools -> Upload Method -> STM32CubeProgrammer(SWD) + +### PlatformIO Support + +The PlatformIO Board file for this board is currently using the incorrect OpenOCD (Upload/Debug) +script for the microcontroller on this board. We are in the process of pushing a fix upstream. When +uploading or debugging, hold the reset button down right until the upload or debug process initiates +it's routine in communicating with the board, this seems to aleviate the issue for right now. + +`platformio.ini` +``` +[env:disco_l072cz_lrwan1] +platform = ststm32 +board = disco_l072cz_lrwan1 +framework = arduino + +lib_deps = + STM32duino LSM6DSO + STM32duino LIS2DW12 + STM32duino STTS751 + STM32duino LIS2MDL + STM32duino LPS22HH + STM32duino HTS221 + MCCI LoRaWAN LMIC library + CayenneLPP +``` \ No newline at end of file diff --git a/devkit-cayenne/devkit-cayenne.ino b/devkit-cayenne/devkit-cayenne.ino new file mode 100644 index 0000000..3c77094 --- /dev/null +++ b/devkit-cayenne/devkit-cayenne.ino @@ -0,0 +1,402 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * Copyright (c) 2018 Terry Moore, MCCI + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. It's pre-configured for the Adafruit + * Feather M0 LoRa. + * + *******************************************************************************/ + +/******************************************************************************* + * + * For Helium developers, follow the Arduino Quickstart guide: + * https://developer.helium.com/device/arduino-quickstart + * TLDR: register your device on the console: + * https://console.helium.com/devices + * + * The App EUI (as lsb) and App Key (as msb) get inserted below. + * + *******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ARDUINO_SAM_DUE +#define DEV_I2C Wire1 +#elif defined(ARDUINO_ARCH_STM32) +#define DEV_I2C Wire +#else +#define DEV_I2C Wire +#endif + +// Sensors +LSM6DSOSensor *AccGyr; +LPS22HHSensor *PressTemp; +HTS221Sensor *HumTemp; + +// This is the "App EUI" in Helium. Make sure it is little-endian (lsb). +static const u1_t PROGMEM APPEUI[8] = {FILL_ME_IN}; +void os_getArtEui(u1_t *buf) { memcpy_P(buf, APPEUI, 8); } + +// This should also be in little endian format +// These are user configurable values and Helium console permits anything +static const u1_t PROGMEM DEVEUI[8] = {FILL_ME_IN}; +void os_getDevEui(u1_t *buf) { memcpy_P(buf, DEVEUI, 8); } + +// This is the "App Key" in Helium. It is big-endian (msb). +static const u1_t PROGMEM APPKEY[16] = {FILL_ME_IN}; +void os_getDevKey(u1_t *buf) { memcpy_P(buf, APPKEY, 16); } + +CayenneLPP lpp(51); +static osjob_t sendjob; +void do_send(osjob_t *j); + +// Schedule TX every this many seconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60; + +// Pin mapping +// +// Adafruit BSPs are not consistent -- m0 express defs ARDUINO_SAMD_FEATHER_M0, +// m0 defs ADAFRUIT_FEATHER_M0 +// +#if defined(ARDUINO_SAMD_FEATHER_M0) || defined(ADAFRUIT_FEATHER_M0) +// Pin mapping for Adafruit Feather M0 LoRa, etc. +const lmic_pinmap lmic_pins = { + .nss = 8, + .rxtx = LMIC_UNUSED_PIN, + .rst = 4, + .dio = {3, 6, LMIC_UNUSED_PIN}, + .rxtx_rx_active = 0, + .rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB + .spi_freq = 8000000, +}; +#elif defined(ARDUINO_AVR_FEATHER32U4) +// Pin mapping for Adafruit Feather 32u4 LoRa, etc. +// Just like Feather M0 LoRa, but uses SPI at 1MHz; and that's only +// because MCCI doesn't have a test board; probably higher frequencies +// will work. +const lmic_pinmap lmic_pins = { + .nss = 8, + .rxtx = LMIC_UNUSED_PIN, + .rst = 4, + .dio = {7, 6, LMIC_UNUSED_PIN}, + .rxtx_rx_active = 0, + .rssi_cal = 8, // LBT cal for the Adafruit Feather 32U4 LoRa, in dB + .spi_freq = 1000000, +}; +#elif defined(ARDUINO_CATENA_4551) +// Pin mapping for Murata module / Catena 4551 +const lmic_pinmap lmic_pins = { + .nss = 7, + .rxtx = 29, + .rst = 8, + .dio = + { + 25, // DIO0 (IRQ) is D25 + 26, // DIO1 is D26 + 27, // DIO2 is D27 + }, + .rxtx_rx_active = 1, + .rssi_cal = 10, + .spi_freq = 8000000 // 8MHz +}; +#elif defined(MCCI_CATENA_4610) +#include "arduino_lmic_hal_boards.h" +const lmic_pinmap lmic_pins = *Arduino_LMIC::GetPinmap_Catena4610(); +#elif defined(ARDUINO_DISCO_L072CZ_LRWAN1) +const lmic_pinmap lmic_pins = *Arduino_LMIC::GetPinmap_Disco_L072cz_Lrwan1(); +#else +#error "Unknown target" +#endif + +void onEvent(ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch (ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOIN_TXCOMPLETE: + Serial.println(F("EV_JOIN_TXCOMPLETE")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + { + u4_t netid = 0; + devaddr_t devaddr = 0; + u1_t nwkKey[16]; + u1_t artKey[16]; + LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); + Serial.print("netid: "); + Serial.println(netid, DEC); + Serial.print("devaddr: "); + Serial.println(devaddr, HEX); + Serial.print("artKey: "); + for (size_t i = 0; i < sizeof(artKey); ++i) { + if (i != 0) + Serial.print("-"); + Serial.print(artKey[i], HEX); + } + Serial.println(""); + Serial.print("nwkKey: "); + for (size_t i = 0; i < sizeof(nwkKey); ++i) { + if (i != 0) + Serial.print("-"); + Serial.print(nwkKey[i], HEX); + } + Serial.println(""); + } + // Disable link check validation (automatically enabled + // during join, but because slow data rates change max TX + // size, we don't use it in this example. + LMIC_setLinkCheckMode(0); + break; + /* + || This event is defined but not used in the code. No + || point in wasting codespace on it. + || + || case EV_RFU1: + || Serial.println(F("EV_RFU1")); + || break; + */ + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), + do_send); + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + /* + || This event is defined but not used in the code. No + || point in wasting codespace on it. + || + || case EV_SCAN_FOUND: + || Serial.println(F("EV_SCAN_FOUND")); + || break; + */ + case EV_TXSTART: + Serial.println(F("EV_TXSTART")); + break; + default: + Serial.print(F("Unknown event: ")); + Serial.println((unsigned)ev); + break; + } +} + +void readSensors() { + // Read humidity and temperature. + float humidity = 0; + float temperature = 0; + HumTemp->GetHumidity(&humidity); + HumTemp->GetTemperature(&temperature); + + // Read pressure and temperature. + float pressure = 0; + PressTemp->GetPressure(&pressure); + + // Read accelerometer and gyroscope. + int32_t accelerometer[3]; + int32_t gyroscope[3]; + AccGyr->Get_X_Axes(accelerometer); + AccGyr->Get_G_Axes(gyroscope); + + // Clear Payload + lpp.reset(); + + // Pack Packload + lpp.addTemperature(1, temperature); + lpp.addRelativeHumidity(2, humidity); + lpp.addBarometricPressure(3, pressure); + lpp.addAccelerometer(4, accelerometer[0], accelerometer[1], accelerometer[2]); + lpp.addGyrometer(5, gyroscope[0], gyroscope[1], gyroscope[2]); + + // Debug Print Data + Serial.print("| Hum[%]: "); + Serial.print(humidity, 2); + Serial.print(" | Temp[C]: "); + Serial.print(temperature, 2); + Serial.print(" | Pres[hPa]: "); + Serial.print(pressure, 2); + Serial.print(" | Acc[mg]: "); + Serial.print(accelerometer[0]); + Serial.print(" "); + Serial.print(accelerometer[1]); + Serial.print(" "); + Serial.print(accelerometer[2]); + Serial.print(" | Gyr[mdps]: "); + Serial.print(gyroscope[0]); + Serial.print(" "); + Serial.print(gyroscope[1]); + Serial.print(" "); + Serial.print(gyroscope[2]); +} + +void do_send(osjob_t *j) { + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + readSensors(); + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, lpp.getBuffer(), lpp.getSize(), 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void setup() { + // Initialize I2C bus. + DEV_I2C.begin(); + + AccGyr = new LSM6DSOSensor (&DEV_I2C); + AccGyr->Enable_X(); + AccGyr->Enable_G(); + PressTemp = new LPS22HHSensor(&DEV_I2C); + PressTemp->Enable(); + HumTemp = new HTS221Sensor (&DEV_I2C); + HumTemp->Enable(); + + delay(5000); + while (!Serial) + ; + Serial.begin(9600); + Serial.println(F("Starting")); + +#if defined(ARDUINO_DISCO_L072CZ_LRWAN1) + SPI.setMOSI(RADIO_MOSI_PORT); + SPI.setMISO(RADIO_MISO_PORT); + SPI.setSCLK(RADIO_SCLK_PORT); + SPI.setSSEL(RADIO_NSS_PORT); +// SPI.begin(); +#endif + +#ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); +#endif + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // allow much more clock error than the X/1000 default. See: + // https://github.com/mcci-catena/arduino-lorawan/issues/74#issuecomment-462171974 + // https://github.com/mcci-catena/arduino-lmic/commit/42da75b56#diff-16d75524a9920f5d043fe731a27cf85aL633 + // the X/1000 means an error rate of 0.1%; the above issue discusses using + // values up to 10%. so, values from 10 (10% error, the most lax) to 1000 + // (0.1% error, the most strict) can be used. + LMIC_setClockError(1 * MAX_CLOCK_ERROR / 40); + + LMIC_setLinkCheckMode(0); + LMIC_setDrTxpow(DR_SF8, 20); + LMIC_selectSubBand(6); + + // Start job (sending automatically starts OTAA too) + do_send(&sendjob); +} + +void loop() { os_runloop_once(); } + +namespace Arduino_LMIC { + +class HalConfiguration_Disco_L072cz_Lrwan1_t : public HalConfiguration_t { +public: + enum DIGITAL_PINS : uint8_t { + PIN_SX1276_NSS = 37, + PIN_SX1276_NRESET = 33, + PIN_SX1276_DIO0 = 38, + PIN_SX1276_DIO1 = 39, + PIN_SX1276_DIO2 = 40, + PIN_SX1276_RXTX = 21, + }; + + virtual bool queryUsingTcxo(void) override { return false; }; +}; +// save some typing by bringing the pin numbers into scope +static HalConfiguration_Disco_L072cz_Lrwan1_t myConfig; + +static const HalPinmap_t myPinmap = { + .nss = HalConfiguration_Disco_L072cz_Lrwan1_t::PIN_SX1276_NSS, + .rxtx = HalConfiguration_Disco_L072cz_Lrwan1_t::PIN_SX1276_RXTX, + .rst = HalConfiguration_Disco_L072cz_Lrwan1_t::PIN_SX1276_NRESET, + + .dio = + { + HalConfiguration_Disco_L072cz_Lrwan1_t::PIN_SX1276_DIO0, + HalConfiguration_Disco_L072cz_Lrwan1_t::PIN_SX1276_DIO1, + HalConfiguration_Disco_L072cz_Lrwan1_t::PIN_SX1276_DIO2, + }, + .rxtx_rx_active = 1, + .rssi_cal = 10, + .spi_freq = 8000000, /* 8MHz */ + .pConfig = &myConfig}; + +}; // end namespace Arduino_LMIC