From cd7b496bc38cb8ddee632ab5c3a527fceab0579b Mon Sep 17 00:00:00 2001 From: Yvan Date: Sun, 20 Jul 2025 19:11:39 +0100 Subject: [PATCH] initial checkin of code/etc --- CMakeLists.txt | 18 ++ pico/pico_extras_import_optional.cmake | 59 ++++++ pico/pico_sdk_import.cmake | 84 ++++++++ src/CMakeLists.txt | 24 +++ src/receiver.cpp | 282 +++++++++++++++++++++++++ src/sender.cpp | 274 ++++++++++++++++++++++++ 6 files changed, 741 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 pico/pico_extras_import_optional.cmake create mode 100644 pico/pico_sdk_import.cmake create mode 100644 src/CMakeLists.txt create mode 100644 src/receiver.cpp create mode 100644 src/sender.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9c1b77a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.12) + +# Specify the project name +project(HortiTel) + +# Pull in SDK (must be before project) +include(pico/pico_sdk_import.cmake) +include(pico/pico_extras_import_optional.cmake) + +# Initialize the Pico SDK +pico_sdk_init() + +# Point this to whever you have downloaded the Melopero source from: +# https://github.com/melopero/Melopero_Perpetuo_Lora/tree/main/C%2B%2B/src +add_subdirectory(../Pico/Melopero/Melopero_Perpetuo_Lora/C++/src/ build) + +# Add our src directory +add_subdirectory(src) diff --git a/pico/pico_extras_import_optional.cmake b/pico/pico_extras_import_optional.cmake new file mode 100644 index 0000000..692e14a --- /dev/null +++ b/pico/pico_extras_import_optional.cmake @@ -0,0 +1,59 @@ +# This is a copy of /external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_extras + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT pico_extras) + message("Downloading Raspberry Pi Pico Extras") + FetchContent_Populate(pico_extras) + set(PICO_EXTRAS_PATH ${pico_extras_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + endif() + endif () +endif () + +if (PICO_EXTRAS_PATH) + set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") + set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + + get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") + if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") + endif () + + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) +endif() \ No newline at end of file diff --git a/pico/pico_sdk_import.cmake b/pico/pico_sdk_import.cmake new file mode 100644 index 0000000..a0721d0 --- /dev/null +++ b/pico/pico_sdk_import.cmake @@ -0,0 +1,84 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..5a0d572 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,24 @@ +add_executable(receiver + receiver.cpp +) +target_link_libraries(receiver + MeloperoPerpetuo + pico_stdlib + hardware_adc +) +pico_enable_stdio_usb(receiver 1) +pico_enable_stdio_uart(receiver 0) +pico_add_extra_outputs(receiver) + + +add_executable(sender + sender.cpp +) +target_link_libraries(sender + MeloperoPerpetuo + pico_stdlib + hardware_adc +) +pico_enable_stdio_usb(sender 1) +pico_enable_stdio_uart(sender 0) +pico_add_extra_outputs(sender) diff --git a/src/receiver.cpp b/src/receiver.cpp new file mode 100644 index 0000000..05e05bd --- /dev/null +++ b/src/receiver.cpp @@ -0,0 +1,282 @@ +/** + * Melopero Perpetou LoRa - Allotment Telemetry Receiver + * + * This code began life as the Melopero sample code the Perpetuo LoRa board. + * This can be found here: https://github.com/melopero/Melopero_Perpetuo_Lora + * + * As per their code I choose to continue the MIT licencing for this. + * + * This is the code for a LoRa reciever note that collects the data from + * one or many LoRa sender nodes which transmit sensor data from various + * points on an allotment or similar environment where you may want to + * record sensor data. The sensor data is output via the serial console + * so that a host computer can then do whatever is needed with it (i.e. + * send out to IP network via MQTT, or place into a database, etc.) + * + * Yvan Seth + * https://yvan.seth.id.au/tag/lora.html + */ +#include +#include +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/adc.h" +#include "MeloperoPerpetuo.h" + +static const int SAMPLES = 16; + +float readADCVoltage( int adc ) { + std::vector values; + + // get ADC readings + adc_select_input( adc ); + for ( int i = 0; i < SAMPLES; ++i ) { + uint16_t adc_val = adc_read(); + values.push_back( adc_val ); + } + + // calculate standard deviation + float avg = std::accumulate( values.begin(), values.end(), (float)0.0 ) / values.size(); + float sd = 0.0; + for ( int i = 0; i < values.size(); ++i ) { + sd += pow( values[i] - avg, 2); + } + + std::vector filtered; + if ( 0 == sd ) { + filtered = values; + } else { + // eliminate outliers here + std::copy_if( values.begin(), values.end(), std::back_inserter(filtered), [=](uint16_t val) { + bool pass = abs( (float)val - avg ) < (3 * sd); + if ( ! pass ) { + printf( "rejected: %d\n", val ); + } + return pass; + }); + } + + // calculate the average of remaining values + float adc_avg = std::accumulate( filtered.begin(), filtered.end(), 0.0 ) / filtered.size(); + + // convert to pin voltage + float adc_volts = adc_avg * (3.3f / (1 << 12)); + + return adc_volts; +} + +// this is the structure of the received data +// TODO: we should support the core real data being a variable-length array +// perhaps like: null-terminated-label, int-type-code, value-length-dep-on-type +// or just do what normal folks do these days and make it a text format +struct rxdata { + // the "header" + // see LoRaEMB on page 42: https://www.embit.eu/wp-content/uploads/2020/10/ebi-LoRa_rev1.0.1.pdf + uint16_t length; + uint16_t options; + uint8_t wtf; // possibly an extra byte in the received data here? what is it? padding? + int16_t rssi; + uint16_t src; + uint16_t dst; // see LoRaEMB on page 42: https://www.embit.eu/wp-content/uploads/2020/10/ebi-LoRa_rev1.0.1.pdf + + // our data starts here + uint8_t charge_state; // the charge status as supplied by Melopero library - actual struct +3 bytes padding/alignment + float mcu_temp; // the internal RPi temperature sensor value + float vbat; // battery circuit voltage - charging voltage or battery + float vin; // supply voltage, i.e. USB, solar, or battery + + // 1 byte checksum, simply the low byte of the sum of the previous bytes + // not documented in the above doc for some reason, I suspect it's not our problem to validate it + uint8_t checksum; +}; + +// FIXME: error detection - format, length, etc - this code is unsafe +uint8_t * deserialise_i16(uint8_t * buf, int16_t * val) { + uint16_t uval = 0; + uval |= ((uint16_t)buf[0]) << 8; + uval |= (uint16_t)buf[1]; + *val = (int16_t)uval; + return buf + 2; +} +uint8_t * deserialise_u16(uint8_t * buf, uint16_t * val) { + *val = 0; // just to be sure + *val |= ((uint16_t)buf[0]) << 8; + *val |= (uint16_t)buf[1]; + return buf + 2; +} +uint8_t * deserialise_u8(uint8_t * buf, uint8_t * val) { + *val = (uint8_t)buf[0]; + return buf + 1; +} +// not cross-plaform, but my application doesn't need to be +uint8_t * deserialise_float(uint8_t * buf, float * val) { + *val = 0; // just to be sure + *((uint32_t*)val) |= ((uint32_t)buf[0]) << 24; + *((uint32_t*)val) |= ((uint32_t)buf[1]) << 16; + *((uint32_t*)val) |= ((uint32_t)buf[2]) << 8; + *((uint32_t*)val) |= (uint32_t)buf[3]; + return buf + 4; +} +void deseralise_rxdata(uint8_t * buf, struct rxdata *rxd) { + buf = deserialise_u16(buf, &(rxd->length)); + buf = deserialise_u16(buf, &(rxd->options)); + buf = deserialise_u8(buf, &(rxd->wtf)); + buf = deserialise_i16(buf, &(rxd->rssi)); + buf = deserialise_u16(buf, &(rxd->src)); + buf = deserialise_u16(buf, &(rxd->dst)); + buf = deserialise_u8(buf, &(rxd->charge_state)); + buf = deserialise_float(buf, &(rxd->mcu_temp)); + buf = deserialise_float(buf, &(rxd->vbat)); + buf = deserialise_float(buf, &(rxd->vin)); + buf = deserialise_u8(buf, &(rxd->checksum)); +} + +// Main function +int main() { + stdio_init_all(); // Initialize all standard IO + + MeloperoPerpetuo melopero; + + melopero.init(); // Initialize the board and peripherals + + melopero.led_init(); + melopero.blink_led(3, 250); + + melopero.sendCmd(0x01); + sleep_ms(500); + printf("response to deviceId\n"); + melopero.printResponse(); + + // LoRaEMB operating mode configuration + melopero.stopNetwork(); + sleep_ms(500); + printf("response to stopNetwork\n"); + melopero.printResponse(); + + melopero.setNetworkPreferences(false, false, false); + sleep_ms(500); + printf("response to setNetworkPreferences\n"); + melopero.printResponse(); + + melopero.setOutputPower(0x0a); // Example power level + sleep_ms(500); + printf("response to setOutputPower\n"); + melopero.printResponse(); + + melopero.setOperatingChannel(1, SPREADING_FACTOR_7, BANDWIDTH_125, CODING_RATE_4_5); // Example channel + sleep_ms(500); + printf("response to setOperatingChannel\n"); + melopero.printResponse(); + + melopero.setNetworkAddress(0x1235); // Example network address + sleep_ms(500); + printf("response to setNetworkAddress\n"); + melopero.printResponse(); + + uint8_t network_id[] = {0x00, 0x01}; // Example network ID + melopero.setNetworkId(network_id, sizeof(network_id)); + sleep_ms(500); + printf("response to setNetworkId\n"); + melopero.printResponse(); + + melopero.setEnergySaveMode(ENERGY_SAVE_MODE_ALWAYS_ON); + sleep_ms(500); + printf("response to setEnergySaveModeRxAlways\n"); + melopero.printResponse(); + + melopero.startNetwork(); + sleep_ms(500); + printf("response to startNetwork\n"); + melopero.printResponse(); + + adc_init(); + adc_set_temp_sensor_enabled(true); + melopero.enablelWs2812(true); + while (1) { + + printf("\n=============================================\n"); + + // print out the battery charging state, also set LED colour code + printf("Battery: %d (", melopero.getChargerStatus()); + if (melopero.isCharging()) { + printf("charging)\n"); + melopero.setWs2812Color(255, 255, 0, 0.1); + } + else if (melopero.isFullyCharged()) { + printf("charged)\n"); + melopero.setWs2812Color(0, 255, 0, 0.05); + } + else if (melopero.hasRecoverableFault()) { + printf("fault: recoverable)\n"); + melopero.setWs2812Color(0, 0, 255, 0.05); + } + else if (melopero.hasNonRecoverableFault()) { + printf("fault: non-recoverable)\n"); + melopero.setWs2812Color(255, 255, 255, 0.25); + } + sleep_ms(500); + + // check the temperature of the RP2350 + float voltage = readADCVoltage( 4 ); + float temp = 27.0 - ((voltage - 0.706) / 0.001721); // values from RP2350 documentation + printf( "RP2350 Temperature: %0.2f C\n", temp ); + + // read received LoRa data, if available + uint8_t rxbuff[sizeof(struct rxdata)]; // at least enough for our expected data + size_t rxbuff_ptr = 0; + if (melopero.checkRxFifo(500)) { // if there is data received... + // TODO: this really needs some sort of validation + do { + printf("rx: "); + for (size_t i = 0; i < melopero.responseLen; i++) { + if (rxbuff_ptr < sizeof(rxbuff)) { + rxbuff[rxbuff_ptr] = melopero.response[i]; + printf("0x%02X ", rxbuff[rxbuff_ptr]); + } else { + // discarded excess data + printf("(0x%02X) ", melopero.response[i]); + } + rxbuff_ptr++; + } + printf("\n"); + } while (melopero.checkRxFifo(500)); // Keep checking the FIFO for new data + + // NOTE: the length could be < or > actual buffer length + printf( "Received Data:\nlength=%d\ndata={", rxbuff_ptr ); + for (size_t i = 0; i < sizeof(rxbuff); i++) { + printf("0x%02X ", rxbuff[i]); + } + printf("}\n"); + + struct rxdata rxd = {}; + deseralise_rxdata( rxbuff, &rxd ); + + printf( "Received\n" ); + printf( " Data Length: %u bytes\n", rxd.length ); + printf( " Options: 0x%08X (bitfield)\n", rxd.options ); + printf( " WTF: 0x%02X (undocumented field?)\n", rxd.wtf ); + printf( " Signal Strength: %d dBm\n", rxd.rssi ); + printf( " Source Addr: 0x%04X\n", rxd.src ); + printf( " Dest Addr: 0x%04X (0xFFFF is broadcast)\n", rxd.dst ); + printf( " Payload: \n" ); + printf( " RP2350 Temperature: %0.2f C\n", rxd.mcu_temp ); + printf( " Charge State: %u\n", rxd.charge_state ); + printf( " Battery Voltage: %0.2fV\n", rxd.vbat ); + printf( " Supply Voltage: %0.2fV\n", rxd.vin ); + printf( " Checksum: %02X\n", rxd.checksum ); + + } else { + printf("nothing in the rx fifo\n"); + } + + // take a nap + sleep_ms(1000); + } + + // technically this is unreachable? + melopero.enablelWs2812(false); + return 0; +} diff --git a/src/sender.cpp b/src/sender.cpp new file mode 100644 index 0000000..f919a68 --- /dev/null +++ b/src/sender.cpp @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/adc.h" +#include "MeloperoPerpetuo.h" + +static const int ADC_SAMPLE_COUNT = 16; + +/** + * Read a given ADC value, returns a voltage value. + * + * Actually reads ACD_SAMPLE_COUNT values and returns an average after an + * outlier elimination filter. + */ +float readADCVoltage( int adc ) { std::vector values; + + // get ADC readings + adc_select_input( adc ); + for ( int i = 0; i < ADC_SAMPLE_COUNT; ++i ) { + uint16_t adc_val = adc_read(); + values.push_back( adc_val ); + } + + // calculate standard deviation + float avg = std::accumulate( values.begin(), values.end(), (float)0.0 ) / values.size(); + float sd = 0.0; + for ( int i = 0; i < values.size(); ++i ) { + sd += pow( values[i] - avg, 2); + } + + std::vector filtered; + if ( 0 == sd ) { + filtered = values; + } else { + // eliminate outliers here + std::copy_if( values.begin(), values.end(), std::back_inserter(filtered), [=](uint16_t val) { + bool pass = abs( (float)val - avg ) < (3 * sd); + if ( ! pass ) { + printf( "rejected: %d\n", val ); + } + return pass; + }); + } + + // calculate the average of remaining values + float adc_avg = std::accumulate( filtered.begin(), filtered.end(), 0.0 ) / filtered.size(); + + // convert to pin voltage + float adc_volts = adc_avg * (3.3f / (1 << 12)); + + return adc_volts; +} + +// this is our data transfer/packet struct - note: it isn't platform/endian portable +struct txdata { + uint16_t options; // options as defined page 42: https://www.embit.eu/wp-content/uploads/2020/10/ebi-LoRa_rev1.0.1.pdf + uint16_t dest; // destination id, 0xFFFF for broadcast + uint8_t charge_state; // the charge status as supplied by Melopero library + float mcu_temp; // the internal RPi temperature sensor value + float vbat; // battery circuit voltage - charging voltage or battery + float vin; // supply voltage, i.e. USB, solar, or battery +}; + +uint8_t* serialise_u8(uint8_t* buf, uint8_t val) { + buf[0] = val; + return buf + 1; +} +uint8_t* serialise_u16(uint8_t* buf, uint16_t val) { + buf[0] = val >> 8; + buf[1] = val; + return buf + 2; +} +uint8_t* serialise_u32(uint8_t* buf, uint32_t val) { + buf[0] = (val & 0xff000000) >> 24; + buf[1] = (val & 0x00ff0000) >> 16; + buf[2] = (val & 0x0000ff00) >> 8; + buf[3] = (val & 0x000000ff); + return buf + 4; +} +size_t serialise_txdata(struct txdata* txd, uint8_t* buf) { + uint8_t* bufptr = buf; + bufptr = serialise_u16(bufptr, txd->options); + bufptr = serialise_u16(bufptr, txd->dest); + bufptr = serialise_u8(bufptr, txd->charge_state); + float mcu_temp = txd->mcu_temp; + bufptr = serialise_u32(bufptr, *((uint32_t*)(&mcu_temp))); + float vbat = txd->vbat; + bufptr = serialise_u32(bufptr, *((uint32_t*)(&vbat))); + float vin = txd->vin; + bufptr = serialise_u32(bufptr, *((uint32_t*)(&vin))); + return (bufptr - buf); +} + +// Main function +int main() { + stdio_init_all(); // Initialize all standard IO + + MeloperoPerpetuo melopero; + melopero.init(); // Initialize the board and peripherals + + melopero.led_init(); + melopero.blink_led(2, 500); + + melopero.sendCmd(0x01); + sleep_ms(500); + printf("response to deviceId\n"); + melopero.printResponse(); + + // LoRaEMB operating mode configuration sequence: + // Stop Network + // Set Network preferences + // Set Output power + // Set Operating channel + // Set Network address + // Set Network ID + // Set Energy save mode (the default value is TX_ONLY, set RX_ALWAYS) + // Start Network + + melopero.stopNetwork(); + sleep_ms(500); + printf("response to stopNetwork\n"); + melopero.printResponse(); + + melopero.setNetworkPreferences(false, false, false); + sleep_ms(500); + printf("response to setNetworkPreferences\n"); + melopero.printResponse(); + + melopero.setOutputPower(0x0a); // Example power level + sleep_ms(500); + printf("response to setOutputPower\n"); + melopero.printResponse(); + + melopero.setOperatingChannel(1, SPREADING_FACTOR_7, BANDWIDTH_125, CODING_RATE_4_5); // Example channel + sleep_ms(500); + printf("response to setOperatingChannel\n"); + melopero.printResponse(); + + uint16_t network_address = 0x1234; + melopero.setNetworkAddress(network_address); // Example network address + sleep_ms(500); + printf("response to setNetworkAddress\n"); + melopero.printResponse(); + + uint8_t network_id[] = {0x00, 0x01}; // Example network ID + melopero.setNetworkId(network_id, sizeof(network_id)); + sleep_ms(500); + printf("response to setNetworkId\n"); + melopero.printResponse(); + + melopero.setEnergySaveMode(ENERGY_SAVE_MODE_TX_ONLY); + sleep_ms(500); + printf("response to setEnergySaveModeRxAlways\n"); + melopero.printResponse(); + + //melopero.setEnergySaveModeRxAlways(); + melopero.startNetwork(); + sleep_ms(500); + printf("response to startNetwork\n"); + melopero.printResponse(); + + + // and GO! + adc_init(); + adc_gpio_init(26); + adc_gpio_init(27); + adc_set_temp_sensor_enabled(true); + while (1) { + + //////////////////////////////////////////////////////// + // do a litte dance + + // simple LED on + gpio_put(23, 1); + + // cycle the RGB LEDS - just for fun + melopero.enablelWs2812(true); + melopero.setWs2812Color(255, 0, 0, 0.2); + sleep_ms(500); + melopero.setWs2812Color(0, 255, 0, 0.2); + sleep_ms(500); + melopero.setWs2812Color(0, 0, 255, 0.2); + sleep_ms(500); + melopero.setWs2812Color(0, 0, 0, 0); + melopero.enablelWs2812(false); + + //////////////////////////////////////////////////////// + // read sensor values + printf("\n============================================\n"); + + struct txdata txd = {}; + txd.options = 0; + txd.dest = 0xFFFF; + + // print out the battery charging state + txd.charge_state = melopero.getChargerStatus(); + printf("Battery: %d (", txd.charge_state); + if (melopero.isCharging()) { + printf("charging)\n"); + melopero.setWs2812Color(255, 255, 0, 0.1); + } + else if (melopero.isFullyCharged()) { + printf("charged)\n"); + melopero.setWs2812Color(0, 255, 0, 0.05); + } + else if (melopero.hasRecoverableFault()) { + printf("fault: recoverable)\n"); + melopero.setWs2812Color(0, 0, 255, 0.05); + } + else if (melopero.hasNonRecoverableFault()) { + printf("fault: non-recoverable)\n"); + melopero.setWs2812Color(255, 255, 255, 0.25); + } + sleep_ms(500); + melopero.setWs2812Color(0, 0, 0, 0); + + // flip the enable on the VSEN on/off each iteration, this is just + // testing it can turn an LED on/off if you like, or enable/disable + // other external hardware (thus saving power, only power on sensors + // to read data every 5 minutes, say...) + int vsen = gpio_get( 0 ); + printf( "vsen: %d\n", vsen ); + if ( vsen ) { + printf("vsen off\n"); + gpio_put( 0, 0 ); + } else { + printf("vsen on\n"); + gpio_put( 0, 1 ); + } + + // check the temperature of the RP2350 + float voltage = readADCVoltage( 4 ); + txd.mcu_temp = 27.0 - ((voltage - 0.706) / 0.001721); + printf( "RP2350 Temperature: %0.2f C\n", txd.mcu_temp ); + + // read voltage on ADC0 (battery voltage sense) + float adc0_v = readADCVoltage( 0 ); + txd.vbat = (adc0_v * (98600 + 149100)) / 149100; // measured values of volage divider + printf( "Battery Voltage: %0.2fV\n", txd.vbat ); + + // read voltage on ACD1 (supply voltage sense) + float adc1_v = readADCVoltage( 1 ); + txd.vin = (adc1_v * (98600 + 14890)) / 14890; // measured values of volage divider + printf( "Supply Voltage: %0.2fV\n", txd.vin ); + + //////////////////////////////////////////////////////// + // send some data + uint8_t sendbuf[sizeof(txdata)]; // at least big enough... + size_t data_length = serialise_txdata(&txd, sendbuf); + printf( "Sending Data:\n length=%d\n data={", data_length ); + for (size_t i = 0; i < data_length; i++) { + printf("0x%02X ", sendbuf[i]); + } + printf("}\n"); + melopero.transmitData(sendbuf, data_length); + + sleep_ms(500); // seems a bit kludgy? + printf("response to transmitData\n"); + melopero.printResponse(); + + // simple LED off + gpio_put(23, 0); + + // snoozZzZzZzZzzze + sleep_ms(5000); + } + + return 0; +}