A few weeks ago I ordered an STM32 Blue Pill LoRaWAN node from the M2M Shop on Tindie for evaluation. I have bought a few M2M client devices including a Low power LoRaWan Node Model A328, and Low power LoRaWan Node Model B1284 for projects and they have worked well. This one looked interesting as I had never used a maple like device before.
Bill of materials (Prices as at July 2019)
- STM32 Blue Pill LoRaWAN node USD21
- Grove – Temperature&Humidity Sensor USD11.5
- Grove – 4 pin Female Jumper to Grove 4 pin Conversion Cable USD3.90
The two sockets on the main board aren’t Grove compatible so I used the 4 pin female to Grove 4 pin conversion cable to connect the temperature and humidity sensor.

I used a modified version of my Arduino client code which worked after I got the pin reset pin sorted and the female sockets in the right order.
/* Copyright ® 2019 July devMobile Software, All Rights Reserved THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. Adapted from LoRa Duplex communication with Sync Word Sends temperature & humidity data from Seeedstudio https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.html To my Windows 10 IoT Core RFM 9X library https://blog.devmobile.co.nz/2018/09/03/rfm9x-iotcore-payload-addressing/ */ #include <itoa.h> #include <SPI.h> #include <LoRa.h> #include <TH02_dev.h> #define DEBUG //#define DEBUG_TELEMETRY //#define DEBUG_LORA // LoRa field gateway configuration (these settings must match your field gateway) const char DeviceAddress[] = {"BLUEPILL"}; // Azure IoT Hub FieldGateway const char FieldGatewayAddress[] = {"LoRaIoT1"}; const float FieldGatewayFrequency = 915000000.0; const byte FieldGatewaySyncWord = 0x12 ; // Bluepill hardware configuration const int ChipSelectPin = PA4; const int InterruptPin = PA0; const int ResetPin = -1; // LoRa radio payload configuration const byte SensorIdValueSeperator = ' ' ; const byte SensorReadingSeperator = ',' ; const byte PayloadSizeMaximum = 64 ; byte payload[PayloadSizeMaximum]; byte payloadLength = 0 ; const int LoopDelaySeconds = 300 ; // Sensor configuration const char SensorIdTemperature[] = {"t"}; const char SensorIdHumidity[] = {"h"}; void setup() { Serial.begin(9600); #ifdef DEBUG while (!Serial); #endif Serial.println("Setup called"); Serial.println("LoRa setup start"); // override the default chip select and reset pins LoRa.setPins(ChipSelectPin, ResetPin, InterruptPin); if (!LoRa.begin(FieldGatewayFrequency)) { Serial.println("LoRa begin failed"); while (true); // Drop into endless loop requiring restart } // Need to do this so field gateways pays attention to messsages from this device LoRa.enableCrc(); LoRa.setSyncWord(FieldGatewaySyncWord); #ifdef DEBUG_LORA LoRa.dumpRegisters(Serial); #endif Serial.println("LoRa setup done."); PayloadHeader((byte*)FieldGatewayAddress, strlen(FieldGatewayAddress), (byte*)DeviceAddress, strlen(DeviceAddress)); // Configure the Seeedstudio TH02 temperature & humidity sensor Serial.println("TH02 setup"); TH02.begin(); delay(100); Serial.println("TH02 Setup done"); Serial.println("Setup done"); } void loop() { // read the value from the sensor: double temperature = TH02.ReadTemperature(); double humidity = TH02.ReadHumidity(); Serial.print("Humidity: "); Serial.print(humidity, 0); Serial.print(" %\t"); Serial.print("Temperature: "); Serial.print(temperature, 1); Serial.println(" *C"); PayloadReset(); PayloadAdd(SensorIdHumidity, humidity, 0) ; PayloadAdd(SensorIdTemperature, temperature, 1) ; LoRa.beginPacket(); LoRa.write(payload, payloadLength); LoRa.endPacket(); Serial.println("Loop done"); delay(LoopDelaySeconds * 1000); } void PayloadHeader( byte *to, byte toAddressLength, byte *from, byte fromAddressLength) { byte addressesLength = toAddressLength + fromAddressLength ; #ifdef DEBUG_TELEMETRY Serial.println("PayloadHeader- "); Serial.print( "To Address len:"); Serial.print( toAddressLength ); Serial.print( " From Address len:"); Serial.print( fromAddressLength ); Serial.print( " Addresses length:"); Serial.print( addressesLength ); Serial.println( ); #endif payloadLength = 0 ; // prepare the payload header with "To" Address length (top nibble) and "From" address length (bottom nibble) payload[payloadLength] = (toAddressLength << 4) | fromAddressLength ; payloadLength += 1; // Copy the "To" address into payload memcpy(&payload[payloadLength], to, toAddressLength); payloadLength += toAddressLength ; // Copy the "From" into payload memcpy(&payload[payloadLength], from, fromAddressLength); payloadLength += fromAddressLength ; } void PayloadAdd( const char *sensorId, float value, byte decimalPlaces) { byte sensorIdLength = strlen( sensorId ) ; #ifdef DEBUG_TELEMETRY Serial.println("PayloadAdd-float "); Serial.print( "SensorId:"); Serial.print( sensorId ); Serial.print( " sensorIdLen:"); Serial.print( sensorIdLength ); Serial.print( " Value:"); Serial.print( value, decimalPlaces ); Serial.print( " payloadLength:"); Serial.print( payloadLength); #endif memcpy( &payload[payloadLength], sensorId, sensorIdLength) ; payloadLength += sensorIdLength ; payload[ payloadLength] = SensorIdValueSeperator; payloadLength += 1 ; payloadLength += strlen( dtostrf(value, -1, decimalPlaces, (char *)&payload[payloadLength])); payload[ payloadLength] = SensorReadingSeperator; payloadLength += 1 ; #ifdef DEBUG_TELEMETRY Serial.print( " payloadLength:"); Serial.print( payloadLength); Serial.println( ); #endif } void PayloadAdd( const char *sensorId, int value ) { byte sensorIdLength = strlen( sensorId ) ; #ifdef DEBUG_TELEMETRY Serial.println("PayloadAdd-int "); Serial.print( "SensorId:"); Serial.print( sensorId ); Serial.print( " sensorIdLen:"); Serial.print( sensorIdLength ); Serial.print( " Value:"); Serial.print( value ); Serial.print( " payloadLength:"); Serial.print( payloadLength); #endif memcpy( &payload[payloadLength], sensorId, sensorIdLength) ; payloadLength += sensorIdLength ; payload[ payloadLength] = SensorIdValueSeperator; payloadLength += 1 ; payloadLength += strlen( itoa( value, (char *)&payload[payloadLength], 10)); payload[ payloadLength] = SensorReadingSeperator; payloadLength += 1 ; #ifdef DEBUG_TELEMETRY Serial.print( " payloadLength:"); Serial.print( payloadLength); Serial.println( ); #endif } void PayloadAdd( const char *sensorId, unsigned int value ) { byte sensorIdLength = strlen( sensorId ) ; #ifdef DEBUG_TELEMETRY Serial.println("PayloadAdd-unsigned int "); Serial.print( "SensorId:"); Serial.print( sensorId ); Serial.print( " sensorIdLen:"); Serial.print( sensorIdLength ); Serial.print( " Value:"); Serial.print( value ); Serial.print( " payloadLength:"); Serial.print( payloadLength); #endif memcpy( &payload[payloadLength], sensorId, sensorIdLength) ; payloadLength += sensorIdLength ; payload[ payloadLength] = SensorIdValueSeperator; payloadLength += 1 ; payloadLength += strlen( utoa( value, (char *)&payload[payloadLength], 10)); payload[ payloadLength] = SensorReadingSeperator; payloadLength += 1 ; #ifdef DEBUG_TELEMETRY Serial.print( " payloadLength:"); Serial.print( payloadLength); Serial.println( ); #endif } void PayloadReset() { byte fromAddressLength = payload[0] & 0xf ; byte toAddressLength = payload[0] >> 4 ; byte addressesLength = toAddressLength + fromAddressLength ; payloadLength = addressesLength + 1; #ifdef DEBUG_TELEMETRY Serial.println("PayloadReset- "); Serial.print( "To Address len:"); Serial.print( toAddressLength ); Serial.print( " From Address len:"); Serial.print( fromAddressLength ); Serial.print( " Addresses length:"); Serial.print( addressesLength ); Serial.println( ); #endif }
To get the application to compile I also had to include itoa.h rather than stdlib.h.
maple_loader v0.1 Resetting to bootloader via DTR pulse [Reset via USB Serial Failed! Did you select the right serial port?] Searching for DFU device [1EAF:0003]... Assuming the board is in perpetual bootloader mode and continuing to attempt dfu programming... dfu-util - (C) 2007-2008 by OpenMoko Inc.
Initially I had some problems deploying my software because I hadn’t followed the instructions and run the installation batch file.
14:03:56.946 -> Setup called 14:03:56.946 -> LoRa setup start 14:03:56.946 -> LoRa setup done. 14:03:56.946 -> TH02 setup 14:03:57.046 -> TH02 Setup done 14:03:57.046 -> Setup done 14:03:57.115 -> Humidity: 76 % Temperature: 18.9 *C 14:03:57.182 -> Loop done 14:08:57.226 -> Humidity: 74 % Temperature: 18.7 *C 14:08:57.295 -> Loop done 14:13:57.360 -> Humidity: 76 % Temperature: 18.3 *C 14:13:57.430 -> Loop done 14:18:57.475 -> Humidity: 74 % Temperature: 18.2 *C 14:18:57.544 -> Loop done 14:23:57.593 -> Humidity: 70 % Temperature: 17.8 *C 14:23:57.662 -> Loop done 14:28:57.733 -> Humidity: 71 % Temperature: 17.8 *C 14:28:57.802 -> Loop done 14:33:57.883 -> Humidity: 73 % Temperature: 17.9 *C 14:33:57.952 -> Loop done 14:38:57.997 -> Humidity: 73 % Temperature: 18.0 *C 14:38:58.066 -> Loop done 14:43:58.138 -> Humidity: 73 % Temperature: 18.1 *C 14:43:58.208 -> Loop done 14:48:58.262 -> Humidity: 73 % Temperature: 18.3 *C 14:48:58.331 -> Loop done 14:53:58.374 -> Humidity: 73 % Temperature: 18.2 *C 14:53:58.444 -> Loop done 14:58:58.509 -> Humidity: 73 % Temperature: 18.3 *C 14:58:58.578 -> Loop done 15:03:58.624 -> Humidity: 65 % Temperature: 16.5 *C 15:03:58.694 -> Loop done 15:08:58.766 -> Humidity: 71 % Temperature: 18.8 *C 15:08:58.836 -> Loop done 15:13:58.893 -> Humidity: 75 % Temperature: 19.1 *C 15:13:58.963 -> Loop done
I configured the device to upload to my Azure IoT Hub/Azure IoT Central gateway and after getting the device name configuration right it has been running reliably for a couple of days

The device was sitting outside on the deck and rapid increase in temperature is me bringing it inside.