arduino2/libraries/arduino-lmic-master/examples/compliance-otaa-halconfig/compliance-otaa-halconfig.ino
2021-02-10 18:05:49 +01:00

1282 lines
34 KiB
C++

/*
Module: compliance-otaa-halconfig.ino
Function:
Test program for developing and checking LMIC compliance test support.
Copyright and License:
Please see accompanying LICENSE file.
Author:
Terry Moore, MCCI Corporation March 2019
*/
#include <Arduino.h>
#include <arduino_lmic.h>
#include <arduino_lmic_hal_boards.h>
#include <arduino_lmic_lorawan_compliance.h>
#include <SPI.h>
class cEventQueue;
#define APPLICATION_VERSION ARDUINO_LMIC_VERSION_CALC(3,0,99,10)
//
// For compliance tests with the RWC5020A, we use the default addresses
// from the tester; except that we use APPKEY 0,..., 0, 2, to avoid
// collisions with a registered app on TTN.
//
// This EUI must be in little-endian format, so least-significant-byte
// first. This corresponds to 0x0000000000000001
// static const u1_t PROGMEM APPEUI[8]= { 1, 0, 0, 0, 0, 0, 0, 0 };
void os_getArtEui (u1_t* buf) { memset(buf, 0, 8); buf[0] = 1; }
// This should also be in little endian format, see above.
// This corresponds to 0x0000000000000001
// static const u1_t PROGMEM DEVEUI[8]= { 1, 0, 0, 0, 0, 0, 0, 0 };
void os_getDevEui (u1_t* buf) { memset(buf, 0, 8); buf[0] = 1; }
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply).
// static const u1_t PROGMEM APPKEY[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
void os_getDevKey (u1_t* buf) { memset(buf, 0, 16); buf[15] = 2; }
// this data must be kept short -- max is 11 bytes for US DR0
static uint8_t mydata[] = { 0xCA, 0xFE, 0xF0, 0x0D };
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 5;
// global flag for test mode.
bool g_fTestMode = false;
// forward declarations
lmic_event_cb_t myEventCb;
lmic_rxmessage_cb_t myRxMessageCb;
const char * const evNames[] = { LMIC_EVENT_NAME_TABLE__INIT };
static void rtccount_begin();
static uint16_t rtccount_read();
#define NEED_USBD_LL_ConnectionState 0
#ifdef ARDUINO_ARCH_STM32
# ifdef _mcci_arduino_version
# if _mcci_arduino_version < _mcci_arduino_version_calc(2, 5, 0, 10)
# undef NEED_USBD_LL_ConnectionState
# define NEED_USBD_LL_ConnectionState 1
# endif // _mcci_arduino_version < _mcci_arduino_version_calc(2, 5, 0, 10)
# endif // def _mcci_arduino_version
#endif // def ARDUINO_ARCH_STM32
#define NEED_STM32_ClockCalibration 0
#ifdef ARDUINO_ARCH_STM32
# ifdef _mcci_arduino_version
# if _mcci_arduino_version <= _mcci_arduino_version_calc(2, 5, 0, 10)
# undef NEED_STM32_ClockCalibration
# define NEED_STM32_ClockCalibration 1
# endif // _mcci_arduino_version <= _mcci_arduino_version_calc(2, 5, 0, 10)
# endif // def _mcci_arduino_version
# define SUPPORT_STM32_ClockCalibration 1
#else
# define SUPPORT_STM32_ClockCalibration 0
#endif // def ARDUINO_ARCH_STM32
/*
Name: myEventCb()
Function:
lmic_event_cb_t myEventCb;
extern "C" { void myEventCb(void *pUserData, ev_t ev); }
Description:
This function is registered for event notifications from the LMIC
during setup() processing. Its main job is to display events in a
user-friendly way.
Returns:
No explicit result.
*/
static osjobcbfn_t eventjob_cb;
class cEventQueue {
public:
cEventQueue() {};
~cEventQueue() {};
struct eventnode_t {
osjob_t job;
ev_t event;
const char *pMessage;
uint32_t datum;
ostime_t time;
ostime_t txend;
ostime_t rxtime;
ostime_t globalDutyAvail;
u4_t nLateRx;
ostime_t ticksLateRx;
u4_t freq;
u2_t rtccount;
u2_t opmode;
u2_t fcntDn;
u2_t fcntUp;
rxsyms_t rxsyms;
rps_t rps;
u1_t txChnl;
u1_t datarate;
u1_t txrxFlags;
u1_t saveIrqFlags;
};
bool getEvent(eventnode_t &node) {
if (m_head == m_tail) {
return false;
}
node = m_queue[m_head];
if (++m_head == sizeof(m_queue) / sizeof(m_queue[0])) {
m_head = 0;
}
return true;
}
bool putEvent(ev_t event, const char *pMessage = nullptr, uint32_t datum = 0) {
auto i = m_tail + 1;
if (i == sizeof(m_queue) / sizeof(m_queue[0])) {
i = 0;
}
if (i != m_head) {
auto const pn = &m_queue[m_tail];
pn->job = LMIC.osjob;
pn->time = os_getTime();
pn->rtccount = rtccount_read();
pn->txend = LMIC.txend;
pn->rxtime = LMIC.rxtime;
pn->globalDutyAvail = LMIC.globalDutyAvail;
pn->event = event;
pn->pMessage = pMessage;
pn->datum = datum;
pn->nLateRx = LMIC.radio.rxlate_count;
pn->ticksLateRx = LMIC.radio.rxlate_ticks;
pn->freq = LMIC.freq;
pn->opmode = LMIC.opmode;
pn->fcntDn = (u2_t) LMIC.seqnoDn;
pn->fcntUp = (u2_t) LMIC.seqnoUp;
pn->rxsyms = LMIC.rxsyms;
pn->rps = LMIC.rps;
pn->txChnl = LMIC.txChnl;
pn->datarate = LMIC.datarate;
pn->txrxFlags = LMIC.txrxFlags;
pn->saveIrqFlags = LMIC.saveIrqFlags;
m_tail = i;
return true;
} else {
return false;
}
}
private:
unsigned m_head, m_tail;
eventnode_t m_queue[32];
osjob_t m_job;
};
cEventQueue eventQueue;
#if LMIC_ENABLE_event_logging
extern "C" {
void LMICOS_logEvent(const char *pMessage);
void LMICOS_logEventUint32(const char *pMessage, uint32_t datum);
}
void LMICOS_logEvent(const char *pMessage)
{
eventQueue.putEvent(ev_t(-1), pMessage);
}
void LMICOS_logEventUint32(const char *pMessage, uint32_t datum)
{
eventQueue.putEvent(ev_t(-2), pMessage, datum);
}
#endif // LMIC_ENABLE_event_logging
hal_failure_handler_t log_assertion;
void log_assertion(const char *pMessage, uint16_t line) {
eventQueue.putEvent(ev_t(-3), pMessage, line);
eventPrintAll();
Serial.println(F("***HALTED BY ASSERT***"));
while (true)
yield();
}
bool lastWasTxStart;
uint32_t lastTxStartTime;
void myEventCb(void *pUserData, ev_t ev) {
eventQueue.putEvent(ev);
if (ev == EV_TXSTART) {
lastWasTxStart = true;
lastTxStartTime = millis();
} else if (ev == EV_RXSTART) {
lastWasTxStart = false;
}
if (ev == EV_JOINING) {
setupForNetwork(true);
} else if (ev == EV_JOINED) {
setupForNetwork(false);
}
}
void eventPrint(cEventQueue::eventnode_t &e);
void printFcnts(cEventQueue::eventnode_t &e);
void printTxend(cEventQueue::eventnode_t &e);
void printRxtime(cEventQueue::eventnode_t &e);
void printLateStats(cEventQueue::eventnode_t &e);
void eventPrintAll(void) {
while (eventPrintOne())
;
}
bool eventPrintOne(void) {
cEventQueue::eventnode_t e;
if (eventQueue.getEvent(e)) {
eventPrint(e);
return true;
} else {
return false;
}
}
static void eventjob_cb(osjob_t *j) {
eventPrintAll();
}
const char *getSfName(rps_t rps) {
const char * const t[] = { "FSK", "SF7", "SF8", "SF9", "SF10", "SF11", "SF12", "SFrfu" };
return t[getSf(rps)];
}
const char *getBwName(rps_t rps) {
const char * const t[] = { "BW125", "BW250", "BW500", "BWrfu" };
return t[getBw(rps)];
}
const char *getCrName(rps_t rps) {
const char * const t[] = { "CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8" };
return t[getCr(rps)];
}
const char *getCrcName(rps_t rps) {
return getNocrc(rps) ? "NoCrc" : "Crc";
}
void printHex2(unsigned v) {
v &= 0xff;
if (v < 16)
Serial.print('0');
Serial.print(v, HEX);
}
void printHex4(unsigned v) {
printHex2(v >> 8u);
printHex2(v);
}
void printSpace(void) {
Serial.print(' ');
}
void printFreq(u4_t freq) {
Serial.print(F(": freq="));
Serial.print(freq / 1000000);
Serial.print('.');
Serial.print((freq % 1000000) / 100000);
}
void printRps(rps_t rps) {
Serial.print(F(" rps=0x")); printHex2(rps);
Serial.print(F(" (")); Serial.print(getSfName(rps));
printSpace(); Serial.print(getBwName(rps));
printSpace(); Serial.print(getCrName(rps));
printSpace(); Serial.print(getCrcName(rps));
Serial.print(F(" IH=")); Serial.print(unsigned(getIh(rps)));
Serial.print(')');
}
void printOpmode(uint16_t opmode, char sep = ',') {
if (sep != 0)
Serial.print(sep);
Serial.print(F(" opmode=")); Serial.print(opmode, HEX);
}
void printTxend(cEventQueue::eventnode_t &e) {
Serial.print(F(", txend=")); Serial.print(e.txend);
Serial.print(F(", avail=")); Serial.print(e.globalDutyAvail);
}
void printRxtime(cEventQueue::eventnode_t &e) {
Serial.print(F(", rxtime=")); Serial.print(e.rxtime);
}
void printTxChnl(u1_t txChnl) {
Serial.print(F(": ch="));
Serial.print(unsigned(txChnl));
}
void printDatarate(u1_t datarate) {
Serial.print(F(", datarate=")); Serial.print(unsigned(datarate));
}
void printTxrxflags(u1_t txrxFlags) {
Serial.print(F(", txrxFlags=0x")); printHex2(txrxFlags);
if (txrxFlags & TXRX_ACK)
Serial.print(F("; Received ack"));
}
void printSaveIrqFlags(u1_t saveIrqFlags) {
Serial.print(F(", saveIrqFlags 0x"));
printHex2(saveIrqFlags);
}
void printLateStats(cEventQueue::eventnode_t &e) {
Serial.print(F(", nLateRx="));
Serial.print(e.nLateRx);
Serial.print(F(" ticks="));
Serial.print(e.ticksLateRx);
}
void printFcnts(cEventQueue::eventnode_t &e) {
Serial.print(F(", FcntUp="));
printHex4(e.fcntUp);
Serial.print(F(", FcntDn="));
printHex4(e.fcntDn);
}
#if LMIC_ENABLE_event_logging
// dump all the registers.
void printAllRegisters(void) {
uint8_t regbuf[0x80];
regbuf[0] = 0;
hal_spi_read(1, regbuf + 1, sizeof(regbuf) - 1);
for (unsigned i = 0; i < sizeof(regbuf); ++i) {
if (i % 16 == 0) {
printNl();
printHex2(i);
}
Serial.print(((i % 8) == 0) ? F(" - ") : F(" "));
printHex2(regbuf[i]);
}
// reset the radio, just in case the register dump caused issues.
hal_pin_rst(0);
delay(2);
hal_pin_rst(2);
delay(6);
// restore the radio to idle.
const uint8_t opmode = 0x88; // LoRa and sleep.
hal_spi_write(0x81, &opmode, 1);
}
#endif
void printNl(void) {
Serial.println();
}
void eventPrint(cEventQueue::eventnode_t &e) {
ev_t ev = e.event;
Serial.print(e.time);
Serial.print(F(" ("));
Serial.print(osticks2ms(e.time));
#if SUPPORT_STM32_ClockCalibration
Serial.print(F(" ms, lptim1="));
Serial.print(e.rtccount);
Serial.print(F("): "));
#else
Serial.print(F(" ms): "));
#endif
if (ev == ev_t(-1) || ev == ev_t(-2)) {
Serial.print(e.pMessage);
if (ev == ev_t(-2)) {
Serial.print(F(", datum=0x")); Serial.print(e.datum, HEX);
}
printOpmode(e.opmode, '.');
} else if (ev == ev_t(-3)) {
Serial.print(e.pMessage);
Serial.print(F(", line ")); Serial.print(e.datum);
printFreq(e.freq);
printTxend(e);
printTxChnl(e.txChnl);
printRps(e.rps);
printOpmode(e.opmode);
printTxrxflags(e.txrxFlags);
printSaveIrqFlags(e.saveIrqFlags);
printLateStats(e);
#if LMIC_ENABLE_event_logging
printAllRegisters();
#endif
} else {
if (ev < sizeof(evNames) / sizeof(evNames[0])) {
Serial.print(evNames[ev]);
} else {
Serial.print(F("Unknown event: "));
Serial.print((unsigned) ev);
}
switch(ev) {
case EV_SCAN_TIMEOUT:
break;
case EV_BEACON_FOUND:
break;
case EV_BEACON_MISSED:
break;
case EV_BEACON_TRACKED:
break;
case EV_JOINING:
break;
case EV_JOINED:
printTxChnl(e.txChnl);
printNl();
do {
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print(F("netid: "));
Serial.println(netid, DEC);
Serial.print(F("devaddr: "));
Serial.println(devaddr, HEX);
Serial.print(F("artKey: "));
for (size_t i=0; i<sizeof(artKey); ++i) {
if (i != 0)
Serial.print('-');
printHex2(artKey[i]);
}
printNl();
Serial.print(F("nwkKey: "));
for (size_t i=0; i<sizeof(nwkKey); ++i) {
if (i != 0)
Serial.print('-');
printHex2(nwkKey[i]);
}
} while (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:
// print out rx info
printFreq(e.freq);
printRps(e.rps);
printOpmode(e.opmode);
#if LMIC_ENABLE_event_logging
printAllRegisters();
#endif
break;
case EV_REJOIN_FAILED:
// this event means that someone tried a rejoin, and it failed.
// it doesn't really mean anything bad, it's just advisory.
break;
case EV_TXCOMPLETE:
printTxChnl(e.txChnl);
printRps(e.rps);
printTxrxflags(e.txrxFlags);
printFcnts(e);
printTxend(e);
printLateStats(e);
break;
case EV_LOST_TSYNC:
break;
case EV_RESET:
break;
case EV_RXCOMPLETE:
break;
case EV_LINK_DEAD:
break;
case 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:
// this event tells us that a transmit is about to start.
// but printing here is bad for timing.
printTxChnl(e.txChnl);
printRps(e.rps);
printDatarate(e.datarate);
printOpmode(e.opmode);
printTxend(e);
break;
case EV_RXSTART:
printFreq(e.freq);
printRps(e.rps);
printDatarate(e.datarate);
printOpmode(e.opmode);
printTxend(e);
printRxtime(e);
Serial.print(F(", rxsyms=")); Serial.print(unsigned(e.rxsyms));
break;
case EV_JOIN_TXCOMPLETE:
printSaveIrqFlags(e.saveIrqFlags);
printLateStats(e);
break;
default:
break;
}
}
printNl();
}
/*
Name: myRxMessageCb()
Function:
Handle received LoRaWAN downlink messages.
Definition:
lmic_rxmessage_cb_t myRxMessageCb;
extern "C" {
void myRxMessageCb(
void *pUserData,
uint8_t port,
const uint8_t *pMessage,
size_t nMessage
);
}
Description:
This function is called whenever a non-Join downlink message
is received over LoRaWAN by LMIC. Its job is to invoke the
compliance handler (if compliance support is needed), and
then decode any non-compliance messages.
Returns:
No explicit result.
*/
void myRxMessageCb(
void *pUserData,
uint8_t port,
const uint8_t *pMessage,
size_t nMessage
) {
lmic_compliance_rx_action_t const action = LMIC_complianceRxMessage(port, pMessage, nMessage);
switch (action) {
case LMIC_COMPLIANCE_RX_ACTION_START: {
Serial.println(F("Enter test mode"));
os_clearCallback(&sendjob);
g_fTestMode = true;
return;
}
case LMIC_COMPLIANCE_RX_ACTION_END: {
Serial.println(F("Exit test mode"));
g_fTestMode = false;
// we're in the LMIC, we don't want to send from here. Schedule a job.
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
return;
}
case LMIC_COMPLIANCE_RX_ACTION_IGNORE: {
if (port == LORAWAN_PORT_COMPLIANCE) {
Serial.print(F("Received test packet 0x"));
if (nMessage > 0)
printHex2(pMessage[0]);
Serial.print(F(" length "));
Serial.println((unsigned) nMessage);
}
return;
}
default:
// continue.
break;
}
Serial.print(F("Received message on port "));
Serial.print(port);
Serial.print(F(": "));
Serial.print(unsigned(nMessage));
Serial.println(F(" bytes"));
}
lmic_txmessage_cb_t sendComplete;
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"));
sendComplete(j, 0);
} else if (g_fTestMode) {
Serial.println(F("test mode, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
if (LMIC_sendWithCallback_strict(1, mydata, sizeof(mydata), 0, sendComplete, j) == 0) {
Serial.println(F("Packet queued"));
} else {
Serial.println(F("Packet queue failure; sleeping"));
sendComplete(j, 0);
}
}
}
void sendComplete(
void *pUserData,
int fSuccess
) {
osjob_t * const j = (osjob_t *) pUserData;
if (! fSuccess)
Serial.println(F("sendComplete: uplink failed"));
if (! g_fTestMode) {
// Schedule next transmission
os_setTimedCallback(j, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
}
}
void myFail(const char *pMessage) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
// alert
Serial.println(pMessage);
// flash lights, sleep.
for (int i = 0; i < 5; ++i) {
digitalWrite(LED_BUILTIN, 1);
delay(100);
digitalWrite(LED_BUILTIN, 0);
delay(900);
}
}
}
void setup() {
delay(5000);
while (! Serial)
;
Serial.begin(115200);
setup_printSignOn();
setup_calibrateSystemClock();
// LMIC init using the computed target
const auto pPinMap = Arduino_LMIC::GetPinmap_ThisBoard();
// don't die mysteriously; die noisily.
if (pPinMap == nullptr) {
myFail("board not known to library; add pinmap or update getconfig_thisboard.cpp");
}
// now that we have a pinmap, initalize the low levels accordingly.
hal_set_failure_handler(log_assertion);
os_init_ex(pPinMap);
// LMIC_reset() doesn't affect callbacks, so we can do this first.
if (! (LMIC_registerRxMessageCb(myRxMessageCb, /* userData */ nullptr) &&
LMIC_registerEventCb(myEventCb, /* userData */ nullptr))) {
myFail("couldn't register callbacks");
}
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// do the network-specific setup prior to join.
setupForNetwork(false);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void setup_printSignOnDashLine(void)
{
for (unsigned i = 0; i < 78; ++i)
Serial.print('-');
printNl();
}
static constexpr const char *filebasename2(const char *s, const char *p) {
return p[0] == '\0' ? s :
(p[0] == '/' || p[0] == '\\') ? filebasename2(p + 1, p + 1) :
filebasename2(s, p + 1) ;
}
static constexpr const char *filebasename(const char *s)
{
return filebasename2(s, s);
}
void printVersionFragment(char sep, uint8_t v) {
if (sep != 0) {
Serial.print(sep);
}
Serial.print(unsigned(v));
}
void printVersion(uint32_t v) {
printVersionFragment(0, uint8_t(v >> 24u));
printVersionFragment('.', uint8_t(v >> 16u));
printVersionFragment('.', uint8_t(v >> 8u));
if (uint8_t(v) != 0) {
printVersionFragment('.', uint8_t(v));
}
}
void setup_printSignOn()
{
printNl();
setup_printSignOnDashLine();
Serial.println(filebasename(__FILE__));
Serial.print(F("Version "));
printVersion(APPLICATION_VERSION);
Serial.print(F("\nLMIC version "));
printVersion(ARDUINO_LMIC_VERSION);
Serial.print(F(" configured for region "));
Serial.print(CFG_region);
Serial.println(F(".\nRemember to select 'Line Ending: Newline' at the bottom of the monitor window."));
setup_printSignOnDashLine();
printNl();
}
void setupForNetwork(bool preJoin) {
#if CFG_LMIC_US_like
LMIC_selectSubBand(0);
#endif
}
void loop() {
os_runloop_once();
if (lastWasTxStart && millis() - lastTxStartTime > 10000) {
/* ugh. TX timed out */
Serial.println(F("Tx timed out"));
#if LMIC_ENABLE_event_logging
printAllRegisters();
#endif
LMIC_clrTxData();
lastWasTxStart = false;
}
if ((LMIC.opmode & OP_TXRXPEND) == 0 &&
!os_queryTimeCriticalJobs(ms2osticks(1000))) {
eventPrintAll();
}
}
// there's a problem with running 2.5 of the MCCI STM32 BSPs;
// hack around it.
#if NEED_USBD_LL_ConnectionState
uint32_t USBD_LL_ConnectionState(void) {
return 1;
}
#endif // NEED_USBD_LL_ConnectionState
static constexpr bool kMustCalibrateLSE = NEED_STM32_ClockCalibration; // _mcci_arduino_version indicates that LSE clock is used.
static constexpr bool kCanCalibrateLSE = SUPPORT_STM32_ClockCalibration;
void setup_calibrateSystemClock(void) {
if (kMustCalibrateLSE) {
Serial.println("need to calibrate clock");
#if NEED_STM32_ClockCalibration
Stm32_CalibrateSystemClock();
#endif // NEED_STM32_ClockCalibration
Serial.println("setting LPTIM1");
// set clock rate error to 0.4%
LMIC_setClockError(4 * MAX_CLOCK_ERROR / 1000);
rtccount_begin();
} else if (kCanCalibrateLSE) {
Serial.println("assuming BIOS has calibrated clock, setting LPTIM1");
LMIC_setClockError(4 * MAX_CLOCK_ERROR / 1000);
rtccount_begin();
} else {
Serial.println("calibration not supported");
}
}
#if NEED_STM32_ClockCalibration
// RTC needs to be initialized before we calibrate the clock.
bool rtcbegin() {
RTC_TimeTypeDef Time;
RTC_DateTypeDef Date;
uint32_t RtcClock;
RTC_HandleTypeDef hRtc;
memset(&hRtc, 0, sizeof(hRtc));
hRtc.Instance = RTC;
hRtc.Init.HourFormat = RTC_HOURFORMAT_24;
RtcClock = __HAL_RCC_GET_RTC_SOURCE();
if (RtcClock == RCC_RTCCLKSOURCE_LSI)
{
hRtc.Init.AsynchPrediv = 37 - 1; /* 37kHz / 37 = 1000Hz */
hRtc.Init.SynchPrediv = 1000 - 1; /* 1000Hz / 1000 = 1Hz */
}
else if (RtcClock == RCC_RTCCLKSOURCE_LSE)
{
hRtc.Init.AsynchPrediv = 128 - 1; /* 32768Hz / 128 = 256Hz */
hRtc.Init.SynchPrediv = 256 - 1; /* 256Hz / 256 = 1Hz */
}
else
{
/*
|| use HSE clock --
|| we don't support use of HSE as RTC because it's connected to
|| TCXO_OUT, and that's controlled by the LoRaWAN software.
*/
Serial.println(
" HSE can not be used for RTC clock!"
);
return false;
}
hRtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hRtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
hRtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hRtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
if (HAL_RTC_Init(&hRtc) != HAL_OK)
{
Serial.println(
"HAL_RTC_Init() failed"
);
return false;
}
/* Initialize RTC and set the Time and Date */
if (HAL_RTCEx_BKUPRead(&hRtc, RTC_BKP_DR0) != 0x32F2)
{
Time.Hours = 0x0;
Time.Minutes = 0x0;
Time.Seconds = 0x0;
Time.SubSeconds = 0x0;
Time.TimeFormat = RTC_HOURFORMAT12_AM;
Time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
Time.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(
&hRtc,
&Time,
RTC_FORMAT_BIN
) != HAL_OK)
{
Serial.print(
"HAL_RTC_SetTime() failed"
);
return false;
}
/* Sunday 1st January 2017 */
Date.WeekDay = RTC_WEEKDAY_SUNDAY;
Date.Month = RTC_MONTH_JANUARY;
Date.Date = 0x1;
Date.Year = 0x0;
if (HAL_RTC_SetDate(
&hRtc,
&Date,
RTC_FORMAT_BIN
) != HAL_OK)
{
Serial.print(
"HAL_RTC_SetDate() failed"
);
return false;
}
HAL_RTCEx_BKUPWrite(&hRtc, RTC_BKP_DR0, 0x32F2);
}
/* Enable Direct Read of the calendar registers (not through Shadow) */
HAL_RTCEx_EnableBypassShadow(&hRtc);
HAL_RTC_DeactivateAlarm(&hRtc, RTC_ALARM_A);
return true;
}
extern "C" {
static volatile uint32_t *gs_pAlarm;
static RTC_HandleTypeDef *gs_phRtc;
void RTC_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler(gs_phRtc);
}
void HAL_RTC_AlarmAEventCallback(
RTC_HandleTypeDef * hRtc
)
{
if (gs_pAlarm)
*gs_pAlarm = 1;
}
void HAL_RTC_MspInit(
RTC_HandleTypeDef * hRtc
)
{
if (hRtc->Instance == RTC)
{
/* USER CODE BEGIN RTC_MspInit 0 */
/* USER CODE END RTC_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_RTC_ENABLE();
/* USER CODE BEGIN RTC_MspInit 1 */
HAL_NVIC_SetPriority(RTC_IRQn, TICK_INT_PRIORITY, 0U);
HAL_NVIC_EnableIRQ(RTC_IRQn);
/* USER CODE END RTC_MspInit 1 */
}
}
void HAL_RTC_MspDeInit(
RTC_HandleTypeDef * hRtc
)
{
if (hRtc->Instance == RTC)
{
/* USER CODE BEGIN RTC_MspDeInit 0 */
HAL_NVIC_DisableIRQ(RTC_IRQn);
/* USER CODE END RTC_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_RTC_DISABLE();
/* USER CODE BEGIN RTC_MspDeInit 1 */
/* USER CODE END RTC_MspDeInit 1 */
}
}
uint32_t HAL_AddTick(
uint32_t delta
)
{
extern __IO uint32_t uwTick;
// copy old interrupt-enable state to flags.
uint32_t const flags = __get_PRIMASK();
// disable interrupts
__set_PRIMASK(1);
// observe uwTick, and advance it.
uint32_t const tickCount = uwTick + delta;
// save uwTick
uwTick = tickCount;
// restore interrupts (does nothing if ints were disabled on entry)
__set_PRIMASK(flags);
// return the new value of uwTick.
return tickCount;
}
} /* extern "C" */
uint32_t Stm32_CalibrateSystemClock(void)
{
uint32_t Calib;
uint32_t CalibNew;
uint32_t CalibLow;
uint32_t CalibHigh;
uint32_t mSecond;
uint32_t mSecondNew;
uint32_t mSecondLow;
uint32_t mSecondHigh;
bool fHaveSeenLow;
bool fHaveSeenHigh;
const bool fCalibrateMSI = HAL_RCC_GetHCLKFreq() < 16000000;
if (! rtcbegin()) {
return 0;
}
if (fCalibrateMSI)
{
Calib = (RCC->ICSCR & RCC_ICSCR_MSITRIM) >> 24;
}
else
{
Calib = (RCC->ICSCR & RCC_ICSCR_HSITRIM) >> 8;
}
/* preapre to loop, setting suitable defaults */
CalibNew = Calib;
CalibLow = 0;
CalibHigh = 0;
mSecondLow = 0;
mSecondHigh = 2000000;
fHaveSeenLow = fHaveSeenHigh = false;
/* loop until we have a new value */
do {
/* meassure the # of millis per RTC second */
mSecond = MeasureMicrosPerRtcSecond();
/* invariant: */
if (Calib == CalibNew)
mSecondNew = mSecond;
/* if mSecond is low, this meaans we must increase the system clock */
if (mSecond <= 1000000)
{
Serial.print('-');
/*
|| the following condition establishes that we're
|| below the target frequency, but closer than we've been
|| before (mSecondLow is the previous "low" limit). If
|| so, we reduce the limit, and capture the "low" calibration
|| value.
*/
if (mSecond > mSecondLow)
{
mSecondLow = mSecond;
CalibLow = Calib; /* save previous calibration value */
fHaveSeenLow = true;
}
/*
|| if we are low, and we have never exceeded the high limit,
|| we can increase the clock.
*/
if (! fHaveSeenHigh)
{
if (fCalibrateMSI)
{
if (Calib < 0xFF)
{
++Calib;
__HAL_RCC_MSI_CALIBRATIONVALUE_ADJUST(Calib);
}
else
break;
}
else
{
if (Calib < 0x1F)
{
++Calib;
__HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(Calib);
}
else
{
break;
}
}
/* let the clock settle */
delay(500);
}
}
/* if mSecond is high, we must reduce the system clock */
else
{
Serial.print('+');
/*
|| the following condition establishes that we're
|| above the target frequency, but closer than we've been
|| before (mSecondHigh is the previous "high" limit). If
|| so, we reduce the limit, and capture the calibration
|| value.
*/
if (mSecond < mSecondHigh)
{
mSecondHigh = mSecond;
CalibHigh = Calib;
fHaveSeenHigh = true;
}
/*
|| if we are above the target frequency, and we have
|| never raised the frequence, we can lower the
|| frequency
*/
if (! fHaveSeenLow)
{
if (Calib == 0)
break;
--Calib;
if (fCalibrateMSI)
{
__HAL_RCC_MSI_CALIBRATIONVALUE_ADJUST(Calib);
}
else
{
__HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(Calib);
}
delay(500);
}
}
} while ((Calib != CalibNew) &&
(! fHaveSeenLow || !fHaveSeenHigh));
//
// We are going to take higher calibration value first and
// it allows us not to call LMIC_setClockError().
//
if (fHaveSeenHigh)
{
mSecondNew = mSecondHigh;
CalibNew = CalibHigh;
}
else if (fHaveSeenLow)
{
mSecondNew = mSecondLow;
CalibNew = CalibLow;
}
else
{
// Use original value
Serial.println(
"?CalibrateSystemClock: can't calibrate"
);
}
if (CalibNew != Calib)
{
Serial.print(CalibNew < Calib ? '+' : '-');
if (fCalibrateMSI)
{
__HAL_RCC_MSI_CALIBRATIONVALUE_ADJUST(CalibNew);
}
else
{
__HAL_RCC_HSI_CALIBRATIONVALUE_ADJUST(CalibNew);
}
delay(500);
}
Serial.print(" 0x");
Serial.println(CalibNew, HEX);
return CalibNew;
}
uint32_t
MeasureMicrosPerRtcSecond(
void
)
{
uint32_t second;
uint32_t now;
uint32_t start;
uint32_t end;
/* get the starting time */
second = RTC->TR & (RTC_TR_ST | RTC_TR_SU);
/* wait for a new second to start, and capture millis() in start */
do {
now = RTC->TR & (RTC_TR_ST | RTC_TR_SU);
start = micros();
} while (second == now);
/* update our second of interest */
second = now;
/* no point in watching the register until we get close */
delay(500);
/* wait for the next second to start, and capture millis() */
do {
now = RTC->TR & (RTC_TR_ST | RTC_TR_SU);
end = micros();
} while (second == now);
/* return the delta */
return end - start;
}
#endif // NEED_STM32_ClockCalibration
#if SUPPORT_STM32_ClockCalibration
static void rtccount_begin()
{
// enable clock to LPTIM1
__HAL_RCC_LPTIM1_CLK_ENABLE();
auto const pLptim = LPTIM1;
// set LPTIM1 clock to LSE clock.
__HAL_RCC_LPTIM1_CONFIG(RCC_LPTIM1CLKSOURCE_LSE);
// disable everything so we can tweak the CFGR
pLptim->CR = 0;
// disable interrupts (needs to be done while disabled globally)
pLptim->IER = 0;
// upcount from selected internal clock (which is LSE)
auto rCfg = pLptim->CFGR & ~0x01FEEEDF;
rCfg |= 0;
pLptim->CFGR = rCfg;
// enable the counter but don't start it
pLptim->CR = LPTIM_CR_ENABLE;
delayMicroseconds(100);
// set ARR to max value so we can count from 0 to 0xFFFF.
// must be done after enabling.
pLptim->ARR = 0xFFFF;
// start in continuous mode.
pLptim->CR = LPTIM_CR_ENABLE | LPTIM_CR_CNTSTRT;
}
static uint16_t rtccount_read()
{
auto const pLptim = LPTIM1;
uint32_t v1, v2;
for (v1 = pLptim->CNT & 0xFFFF; (v2 = pLptim->CNT & 0xFFFF) != v1; v1 = v2)
/* loop */;
return (uint16_t) v1;
}
#else
static void rtccount_begin()
{
// nothing
}
static uint16_t rtccount_read()
{
return 0;
}
#endif