Skip to content

Instantly share code, notes, and snippets.

@tve
Last active June 16, 2024 06:41
Show Gist options
  • Save tve/dc8a9076528a3cd1116dca6d57b689de to your computer and use it in GitHub Desktop.
Save tve/dc8a9076528a3cd1116dca6d57b689de to your computer and use it in GitHub Desktop.
Motus test tag code
// Simple Motus/CTT test tag
// Adapted from https://forum.seeedstudio.com/t/274974
#include <Arduino.h>
#include <RadioLib.h> // https://github.com/jgromes/RadioLib
#include <CRC.h> // https://github.com/RobTillaart/CRC
#define UID ((uint32_t*)0x1FFF7580) // IEEE 64-bit unique device ID register
//#define UID ((uint32_t)0x613455FF)
STM32WLx radio = new STM32WLx_Module();
static const uint32_t rfswitch_pins[] =
{PA4, PA5, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
{STM32WLx::MODE_IDLE, {LOW, LOW}},
{STM32WLx::MODE_RX, {HIGH, LOW}},
{STM32WLx::MODE_TX_HP, {LOW, HIGH}}, // for LoRa-E5 mini
//{STM32WLx::MODE_TX_LP, {HIGH, HIGH}}, // for LoRa-E5-LE mini
END_OF_MODE_TABLE,
};
// Encode 20-bit value into 32 bits
uint8_t code[] = {
0x00, 0x07, 0x19, 0x1E, 0x2A, 0x2D, 0x33, 0x34, 0x4B, 0x4C, 0x52, 0x55,
0x61, 0x66, 0x78, 0x7F, 0x80, 0x87, 0x99, 0x9E, 0xAA, 0xAD, 0xB3, 0xB4,
0xCB, 0xCC, 0xD2, 0xD5, 0xE1, 0xE6, 0xF8, 0xFF };
uint32_t encode(uint32_t val20) {
uint32_t val32 = 0;
for (int i=0; i<4; i++) {
val32 <<= 8;
val32 |= code[val20 & 0x1F];
val20 >>= 5;
}
return val32;
}
uint32_t uid; // Master device UID
bool receivedFlag = false; // flag that a packet was received
bool transmittedFlag = false; // flag that a packet was transmitted
uint8_t packet[5]; // buffer for the packet
uint8_t sync[] = {0xD3, 0x91}; // sync word
void setFlag(void);
// *******************************************************************************************************
void setup() {
Serial.begin(115200);
// prep packet
uid = UID[0] ^ UID[1];
packet[0] = (uid >> 24) & 0xFF ^ 0xAA;
packet[1] = (uid >> 16) & 0xFF;
packet[2] = (uid >> 8) & 0xFF;
packet[3] = uid & 0xFF;
// tag 0x613455FF (B7)
// packet[0] = 0x61;
// packet[1] = 0x34;
// packet[2] = 0x55;
// packet[3] = 0xFF;
// tag 0x 78554c33 (58)
packet[0] = 0x78;
packet[1] = 0x55;
packet[2] = 0x4C;
packet[3] = 0x33;
packet[4] = calcCRC8(packet, 4);
while(!Serial);
delay(200);
Serial.printf("\n\n\n\nSeeed E5 mini test tag [%08X %02X]\n", packet, packet[4]);
// Pin initialization
pinMode(LED_RED, OUTPUT); // built-in LED
// STM32WL initialization
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
#define FREQ 434
#define BR 25
#define FREQDEV 25
#define RXBW 58.6
#define POW 10
#define PRELEN 32
#define TCXOV 1.6
#define USELDO false
int state = radio.beginFSK(FREQ, BR, FREQDEV, RXBW, POW, PRELEN, TCXOV, USELDO);
radio.fixedPacketLengthMode(5);
radio.setSyncWord(sync, sizeof(sync));
//radio.setSyncWord(sync+1, 1);
radio.setWhitening(false);
radio.setDataShaping(RADIOLIB_SHAPING_NONE);
radio.setCRC(0);
if (state == RADIOLIB_ERR_NONE) {
Serial.println("radio initialized");
} else {
Serial.print("radio init failed, code ");
Serial.println(state);
while (true) {
digitalWrite(LED_RED, LOW);
delay(100);
digitalWrite(LED_RED, HIGH);
delay(100);
}
}
// callback function when received or transmitted
radio.setDio1Action(setFlag);
}
// callback function when received or transmitted
void setFlag(void) {
uint16_t irqstatus = radio.getIrqStatus();
if (irqstatus == RADIOLIB_SX126X_IRQ_RX_DONE) {
receivedFlag = true;
} else if (irqstatus == RADIOLIB_SX126X_IRQ_TX_DONE) {
transmittedFlag = true;
} else {
receivedFlag = false;
transmittedFlag = false;
}
}
// ***********************************************************************************************************
void loop() {
digitalWrite(LED_RED, LOW);
for (uint32_t i=0; i<1<<20; i+=10483) {
uint32_t i32 = encode(i);
packet[0] = (i32 >> 24) & 0xFF;
packet[1] = (i32 >> 16) & 0xFF;
packet[2] = (i32 >> 8) & 0xFF;
packet[3] = i32 & 0xFF;
packet[4] = calcCRC8(packet, 4);
transmittedFlag = false;
int16_t state = radio.startTransmit(packet, sizeof(packet));
// print the packet
Serial.printf("Packet: %06d %02X %02X %02X %02X %02X\n",
i, packet[0], packet[1], packet[2], packet[3], packet[4]);
// wait for transmittion completion
while (!transmittedFlag) ;
// error status check
if (state == RADIOLIB_ERR_NONE) {
// packet was successfully sent
//Serial.println("TX done");
} else {
// some other error occurred
Serial.print("TX failed, code ");
Serial.println(state);
}
delay(20);
}
digitalWrite(LED_RED, HIGH);
delay(60000);
}
// Simple Motus/Lotek test tag
#include <Arduino.h>
#include <RadioLib.h> // https://github.com/jgromes/RadioLib
#include "STM32LowPower.h"
#include "stm32yyxx_ll_adc.h"
#define LL_ADC_RESOLUTION LL_ADC_RESOLUTION_12B
#define ADC_RANGE 4096
#define BOR_LEVEL 1 // 1: cut@2.0V, enable@2.1V
// TestTags,1.2, 21.973,19.531,24.414,25.6982002258301
static uint16_t tagCode[] = { 22, 54, 29, 80 }; // ms, ms, ms, 1/10th sec
//static uint16_t tagCode[] = { 10, 20, 30, 50 }; // ms, ms, ms, 1/10th sec
static float lotekFreq = 166.38;
static uint8_t packet[100];
STM32WLx radio = new STM32WLx_Module();
#ifdef ARDUINO_LORA_E5_MINI
static const uint32_t rfswitch_pins[] = {PA4, PA5, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
{STM32WLx::MODE_IDLE, {LOW, LOW}},
{STM32WLx::MODE_RX, {HIGH, LOW}},
{STM32WLx::MODE_TX_HP, {LOW, HIGH}}, // for LoRa-E5 mini
//{STM32WLx::MODE_TX_LP, {HIGH, HIGH}}, // for LoRa-E5-LE mini
END_OF_MODE_TABLE,
};
#endif
#if defined(ARDUINO_MOSTAG_V1) || defined(ARDUINO_MOSTAG_V2)
static const uint32_t rfswitch_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
{STM32WLx::MODE_IDLE, {}},
{STM32WLx::MODE_RX, {}},
{STM32WLx::MODE_TX_LP, {}},
END_OF_MODE_TABLE,
};
#endif
// *******************************************************************************************************
// voltage stuff copied from https://github.com/stm32duino/STM32Examples/blob/main/examples/Peripherals/ADC/Internal_channels/Internal_channels.ino
static int32_t readVref() {
return (__LL_ADC_CALC_VREFANALOG_VOLTAGE(analogRead(AVREF), LL_ADC_RESOLUTION));
}
static int32_t readVoltage(int32_t VRef, uint32_t pin) {
return (__LL_ADC_CALC_DATA_TO_VOLTAGE(VRef, analogRead(pin), LL_ADC_RESOLUTION));
}
// *******************************************************************************************************
// Read the Brown-Out-Reset (BOR) level
uint32_t readBOR() {
//return FLASH->OPTR;
return (FLASH->OPTR & FLASH_OPTR_BOR_LEV) >> FLASH_OPTR_BOR_LEV_Pos;
}
// Set the BOR level, from https://github.com/orgs/stm32duino/discussions/2251
void Set_BOR_Level() {
FLASH_OBProgramInitTypeDef OBInit;
HAL_FLASH_Unlock(); // Unlock the Flash to enable the flash control register access
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR); // Clear any existing option bytes error flags
HAL_FLASH_OB_Unlock(); // Unlock the Options Bytes
HAL_FLASHEx_OBGetConfig(&OBInit); // Get the current option bytes configuration including other settings
// Modify the BOR Level
OBInit.OptionType = OPTIONBYTE_USER;
OBInit.UserType = OB_USER_BOR_LEV;
OBInit.UserConfig = OB_BOR_LEVEL_1;
HAL_FLASHEx_OBProgram(&OBInit); // Change the BOR level while preserving other settings
HAL_FLASH_OB_Launch(); // Actually program -> this causes a reset!
// the following should never be reached
HAL_FLASH_OB_Lock(); // Lock the Options Bytes
HAL_FLASH_Lock(); // Lock the Flash to disable the flash control register access
}
void Never() {}
// *******************************************************************************************************
void setup() {
// read battery voltage and go to sleep if it's too low
// @4Mhz MSI takes 20ms @500uA
while (true) {
analogReadResolution(12); // for vRef & vBat
int32_t vDD = readVref();
if (vDD > 2300) break;
LowPower.begin();
LowPower.deepSleep(20000);
}
LowPower.begin();
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
Serial.begin(115200);
while(!Serial);
delay(200);
Serial.printf("\n\n\n\nMostag Lotek test [%d %d %d %d.%ds]\n",
tagCode[0], tagCode[1], tagCode[2], tagCode[3]/10, tagCode[3]%10);
analogReadResolution(12); // for vRef & vBat
int32_t vRef = readVref();
int32_t vBat = readVoltage(vRef, AVBAT) * 3; // internal voltage divider to allow Vbat>Vdd
Serial.printf("vRef=%d mV vBat=%d mV\n", vRef, vBat);
// ensure the brown-out-reset level is set the way we want
uint32_t bor = readBOR();
if (bor != 1) {
Serial.printf("BOR=%X setting to 1 ... this will reset!\n", bor, 1);
delay(2000);
Set_BOR_Level();
Serial.println("BOR setting failed, should have reset");
} else {
Serial.printf("BOR=%d\n", bor);
}
delay(1000);
// Pin initialization
pinMode(DBG_0, OUTPUT);
digitalWrite(DBG_0, LOW);
pinMode(DBG_1, OUTPUT);
digitalWrite(DBG_1, LOW);
pinMode(LED_YLW, OUTPUT);
digitalWrite(LED_YLW, LOW);
// STM32WL initialization
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
#define FREQ lotekFreq
#define BR 0.6
#define FREQDEV 0.6
#define RXBW 9.7
#define POW 10
#define PRELEN 24
#ifdef ARDUINO_LORA_E5_MINI
#define TCXOV 1.6
#else
#define TCXOV 0 // no TCXO, using xtal
#endif
#define USELDO false
int state = radio.beginFSK(FREQ, BR, FREQDEV, RXBW, POW, PRELEN, TCXOV, USELDO);
Serial.printf("FSK init returned %d\n", state); delay(10);
if (state == RADIOLIB_ERR_NONE) {
radio.startDirect();
}
// if (state == RADIOLIB_ERR_NONE) {
// Serial.println("radio initialized");
// } else {
// Serial.print("radio init failed! code ");
// Serial.println(state);
// while (true) {
// digitalWrite(LED_BUILTIN, LOW);
// delay(100);
// digitalWrite(LED_BUILTIN, HIGH);
// delay(100);
// }
// Serial.println("Huh???");
// }
digitalWrite(LED_YLW, LOW);
Serial.println("ready!");
delay(10);
Serial.end();
for (int i=0; i<100; i++) packet[i] = 0xAA;
}
void rapidBlink() {
for (int i=0; i<10; i++) {
digitalWrite(LED_BUILTIN, LOW);
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
}
}
void pulsePkt() {
radio.startTransmit(packet, 100);
delay(40);
}
void pulse() {
int16_t state = radio.transmitDirect(); //lotekFreq);
delayMicroseconds(1300); // target 2.5ms
//delay(10); // for testing with TinySA
radio.standby();
if (state != RADIOLIB_ERR_NONE) {
Serial.print("TX failed, code ");
Serial.println(state);
rapidBlink();
}
}
int8_t levels[] = {14, 10, 5, 0, -5, -9};
const uint8_t power = 5;
int32_t vEnd;
uint32_t deadbatt = 2200; // stop transmitting below this voltage
uint32_t seq = 0;
// ***********************************************************************************************************
void loop() {
seq++;
int32_t vBat = readVref(); // reads and calibrates Vref+ which is tied to Vdd in UFQFPN48 package
//int32_t vBat = readVoltage(vRef, AVBAT) * 3; // internal voltage divider to allow Vbat>Vdd
if (vBat < deadbatt-10 || (vBat < deadbatt+10 && (seq & 0x1f) != 0)) {
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(DBG_0, LOW);
LowPower.deepSleep(2000);
digitalWrite(DBG_0, HIGH);
return;
}
radio.standby(RADIOLIB_SX126X_STANDBY_XOSC, true);
delay(1); // some delay needed so first pulse has same length as others
digitalWrite(LED_BUILTIN, HIGH);
digitalWrite(DBG_1, HIGH);
radio.setOutputPower(power);
pulse();
delay(tagCode[0]-7);
pulse();
delay(tagCode[1]-7);
pulse();
delay(tagCode[2]-7);
pulse();
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(DBG_0, LOW);
delay(4); // was: 2
vEnd = readVref(); // voltage during TX
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(DBG_0, HIGH);
// go to sleep
radio.sleep(true); // true=>retain config
delay(1);
pinMode(PA3, INPUT_PULLDOWN);
bool rx = digitalRead(PA3);
if (rx == HIGH) {
Serial.begin(115200);
Serial.printf("seq=%d vBat=%d->%dmV\n", seq, vBat, vEnd);
Serial.end();
}
HAL_RCCEx_DisableLSCO(); // WTF?? it was outputting the local osc (32khz) after going to deep sleep
digitalWrite(DBG_0, LOW);
LowPower.deepSleep(tagCode[3]*100);
digitalWrite(DBG_0, HIGH);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment