Last active
September 3, 2024 02:03
-
-
Save tve/dc8a9076528a3cd1116dca6d57b689de to your computer and use it in GitHub Desktop.
Motus test tag
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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: [email protected], [email protected] | |
// 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
For a more complete and ready-to use implementation see https://github.com/tve/motus-test-tags