mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2024-10-01 03:15:39 -04:00
Merge pull request #16 from interfect/linux-pr
Allow building for Linux as a Linux binary
This commit is contained in:
commit
475147711c
50
Config.h
50
Config.h
@ -1,18 +1,13 @@
|
|||||||
#include "ROM.h"
|
#include "ROM.h"
|
||||||
|
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#define MAJ_VERS 0x01
|
#define MAJ_VERS 0x01
|
||||||
#define MIN_VERS 0x1B
|
#define MIN_VERS 0x1B
|
||||||
|
|
||||||
#define PLATFORM_AVR 0x90
|
|
||||||
#define PLATFORM_ESP32 0x80
|
|
||||||
|
|
||||||
#define MCU_1284P 0x91
|
|
||||||
#define MCU_2560 0x92
|
|
||||||
#define MCU_ESP32 0x81
|
|
||||||
|
|
||||||
#define BOARD_RNODE 0x31
|
#define BOARD_RNODE 0x31
|
||||||
#define BOARD_HMBRW 0x32
|
#define BOARD_HMBRW 0x32
|
||||||
#define BOARD_TBEAM 0x33
|
#define BOARD_TBEAM 0x33
|
||||||
@ -21,22 +16,12 @@
|
|||||||
#define BOARD_LORA32_V2_0 0x36
|
#define BOARD_LORA32_V2_0 0x36
|
||||||
#define BOARD_LORA32_V2_1 0x37
|
#define BOARD_LORA32_V2_1 0x37
|
||||||
|
|
||||||
|
#define SERIAL_INTERRUPT 0x1
|
||||||
|
#define SERIAL_POLLING 0x2
|
||||||
|
|
||||||
#define MODE_HOST 0x11
|
#define MODE_HOST 0x11
|
||||||
#define MODE_TNC 0x12
|
#define MODE_TNC 0x12
|
||||||
|
|
||||||
#if defined(__AVR_ATmega1284P__)
|
|
||||||
#define PLATFORM PLATFORM_AVR
|
|
||||||
#define MCU_VARIANT MCU_1284P
|
|
||||||
#elif defined(__AVR_ATmega2560__)
|
|
||||||
#define PLATFORM PLATFORM_AVR
|
|
||||||
#define MCU_VARIANT MCU_2560
|
|
||||||
#elif defined(ESP32)
|
|
||||||
#define PLATFORM PLATFORM_ESP32
|
|
||||||
#define MCU_VARIANT MCU_ESP32
|
|
||||||
#else
|
|
||||||
#error "The firmware cannot be compiled for the selected MCU variant"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MTU 500
|
#define MTU 500
|
||||||
#define SINGLE_MTU 255
|
#define SINGLE_MTU 255
|
||||||
#define HEADER_L 1
|
#define HEADER_L 1
|
||||||
@ -54,6 +39,7 @@
|
|||||||
const int pin_led_tx = 13;
|
const int pin_led_tx = 13;
|
||||||
|
|
||||||
#define BOARD_MODEL BOARD_RNODE
|
#define BOARD_MODEL BOARD_RNODE
|
||||||
|
#define SERIAL_EVENTS SERIAL_INTERRUPT
|
||||||
|
|
||||||
#define CONFIG_UART_BUFFER_SIZE 6144
|
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||||
#define CONFIG_QUEUE_SIZE 6144
|
#define CONFIG_QUEUE_SIZE 6144
|
||||||
@ -70,6 +56,7 @@
|
|||||||
const int pin_led_tx = 13;
|
const int pin_led_tx = 13;
|
||||||
|
|
||||||
#define BOARD_MODEL BOARD_HMBRW
|
#define BOARD_MODEL BOARD_HMBRW
|
||||||
|
#define SERIAL_EVENTS SERIAL_INTERRUPT
|
||||||
|
|
||||||
#define CONFIG_UART_BUFFER_SIZE 768
|
#define CONFIG_UART_BUFFER_SIZE 768
|
||||||
#define CONFIG_QUEUE_SIZE 5120
|
#define CONFIG_QUEUE_SIZE 5120
|
||||||
@ -131,6 +118,8 @@
|
|||||||
#error An unsupported board was selected. Cannot compile RNode firmware.
|
#error An unsupported board was selected. Cannot compile RNode firmware.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define SERIAL_EVENTS SERIAL_POLLING
|
||||||
|
|
||||||
#define CONFIG_UART_BUFFER_SIZE 6144
|
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||||
#define CONFIG_QUEUE_SIZE 6144
|
#define CONFIG_QUEUE_SIZE 6144
|
||||||
#define CONFIG_QUEUE_MAX_LENGTH 200
|
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||||
@ -141,6 +130,22 @@
|
|||||||
#define GPS_BAUD_RATE 9600
|
#define GPS_BAUD_RATE 9600
|
||||||
#define PIN_GPS_TX 12
|
#define PIN_GPS_TX 12
|
||||||
#define PIN_GPS_RX 34
|
#define PIN_GPS_RX 34
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
const int pin_cs = -1;
|
||||||
|
const int pin_reset = -1;
|
||||||
|
const int pin_dio = -1;
|
||||||
|
const int pin_led_rx = -1;
|
||||||
|
const int pin_led_tx = -1;
|
||||||
|
|
||||||
|
#define BOARD_MODEL BOARD_HMBRW
|
||||||
|
#define SERIAL_EVENTS SERIAL_POLLING
|
||||||
|
|
||||||
|
#define CONFIG_UART_BUFFER_SIZE 6144
|
||||||
|
#define CONFIG_QUEUE_SIZE 6144
|
||||||
|
#define CONFIG_QUEUE_MAX_LENGTH 200
|
||||||
|
|
||||||
|
#define EEPROM_SIZE 4096
|
||||||
|
#define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if BOARD_MODEL == BOARD_TBEAM
|
#if BOARD_MODEL == BOARD_TBEAM
|
||||||
@ -149,6 +154,11 @@
|
|||||||
#define PMU_IRQ 35
|
#define PMU_IRQ 35
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
// We need standard int types before we go any further
|
||||||
|
#include <cstdint>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define eeprom_addr(a) (a+EEPROM_OFFSET)
|
#define eeprom_addr(a) (a+EEPROM_OFFSET)
|
||||||
|
|
||||||
// MCU independent configuration parameters
|
// MCU independent configuration parameters
|
||||||
|
283
LoRa.cpp
283
LoRa.cpp
@ -6,23 +6,23 @@
|
|||||||
|
|
||||||
#include "LoRa.h"
|
#include "LoRa.h"
|
||||||
|
|
||||||
#define MCU_1284P 0x91
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
#define MCU_2560 0x92
|
// We need sleep() to use instead of yield()
|
||||||
#define MCU_ESP32 0x81
|
#include <unistd.h>
|
||||||
#if defined(__AVR_ATmega1284P__)
|
// And we need to use the filesystem and IOCTLs instead of an SPI global
|
||||||
#define PLATFORM PLATFORM_AVR
|
#include <fcntl.h>
|
||||||
#define MCU_VARIANT MCU_1284P
|
#include <sys/ioctl.h>
|
||||||
#elif defined(__AVR_ATmega2560__)
|
#include <linux/spi/spidev.h>
|
||||||
#define PLATFORM PLATFORM_AVR
|
// And to have memset
|
||||||
#define MCU_VARIANT MCU_2560
|
#include <cstring>
|
||||||
#elif defined(ESP32)
|
// And we need to be able to report errors
|
||||||
#define PLATFORM PLATFORM_ESP32
|
#include <stdio.h>
|
||||||
#define MCU_VARIANT MCU_ESP32
|
#include <errno.h>
|
||||||
|
// And we need IO formatting functions for the C++-stream dumpRegisters()
|
||||||
|
#include <iomanip>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MCU_VARIANT
|
|
||||||
#error No MCU variant defined, cannot compile
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_ESP32
|
#if MCU_VARIANT == MCU_ESP32
|
||||||
#include "soc/rtc_wdt.h"
|
#include "soc/rtc_wdt.h"
|
||||||
@ -38,11 +38,14 @@
|
|||||||
#define REG_FRF_MID 0x07
|
#define REG_FRF_MID 0x07
|
||||||
#define REG_FRF_LSB 0x08
|
#define REG_FRF_LSB 0x08
|
||||||
#define REG_PA_CONFIG 0x09
|
#define REG_PA_CONFIG 0x09
|
||||||
|
#define REG_PA_RAMP 0x0a
|
||||||
|
#define REG_OCP 0x0b
|
||||||
#define REG_LNA 0x0c
|
#define REG_LNA 0x0c
|
||||||
#define REG_FIFO_ADDR_PTR 0x0d
|
#define REG_FIFO_ADDR_PTR 0x0d
|
||||||
#define REG_FIFO_TX_BASE_ADDR 0x0e
|
#define REG_FIFO_TX_BASE_ADDR 0x0e
|
||||||
#define REG_FIFO_RX_BASE_ADDR 0x0f
|
#define REG_FIFO_RX_BASE_ADDR 0x0f
|
||||||
#define REG_FIFO_RX_CURRENT_ADDR 0x10
|
#define REG_FIFO_RX_CURRENT_ADDR 0x10
|
||||||
|
#define REG_IRQ_FLAGS_MASK 0x11
|
||||||
#define REG_IRQ_FLAGS 0x12
|
#define REG_IRQ_FLAGS 0x12
|
||||||
#define REG_RX_NB_BYTES 0x13
|
#define REG_RX_NB_BYTES 0x13
|
||||||
#define REG_MODEM_STAT 0x18
|
#define REG_MODEM_STAT 0x18
|
||||||
@ -50,19 +53,38 @@
|
|||||||
#define REG_PKT_RSSI_VALUE 0x1a
|
#define REG_PKT_RSSI_VALUE 0x1a
|
||||||
#define REG_MODEM_CONFIG_1 0x1d
|
#define REG_MODEM_CONFIG_1 0x1d
|
||||||
#define REG_MODEM_CONFIG_2 0x1e
|
#define REG_MODEM_CONFIG_2 0x1e
|
||||||
|
#define REG_SYMB_TIMEOUT_LSB 0x1f
|
||||||
#define REG_PREAMBLE_MSB 0x20
|
#define REG_PREAMBLE_MSB 0x20
|
||||||
#define REG_PREAMBLE_LSB 0x21
|
#define REG_PREAMBLE_LSB 0x21
|
||||||
#define REG_PAYLOAD_LENGTH 0x22
|
#define REG_PAYLOAD_LENGTH 0x22
|
||||||
|
#define REG_PAYLOAD_MAX_LENGTH 0x23
|
||||||
|
#define REG_HOP_PERIOD 0x24
|
||||||
#define REG_MODEM_CONFIG_3 0x26
|
#define REG_MODEM_CONFIG_3 0x26
|
||||||
|
#define REG_PPM_CORRECTION 0x27
|
||||||
#define REG_FREQ_ERROR_MSB 0x28
|
#define REG_FREQ_ERROR_MSB 0x28
|
||||||
#define REG_FREQ_ERROR_MID 0x29
|
#define REG_FREQ_ERROR_MID 0x29
|
||||||
#define REG_FREQ_ERROR_LSB 0x2a
|
#define REG_FREQ_ERROR_LSB 0x2a
|
||||||
#define REG_RSSI_WIDEBAND 0x2c
|
#define REG_RSSI_WIDEBAND 0x2c
|
||||||
|
#define REG_IF_FREQ_2 0x2f
|
||||||
|
#define REG_IF_FREQ_1 0x30
|
||||||
#define REG_DETECTION_OPTIMIZE 0x31
|
#define REG_DETECTION_OPTIMIZE 0x31
|
||||||
|
#define REG_INVERT_IQ 0x33
|
||||||
|
#define REG_HIGH_BW_OPTIMIZE_1 0x36
|
||||||
#define REG_DETECTION_THRESHOLD 0x37
|
#define REG_DETECTION_THRESHOLD 0x37
|
||||||
#define REG_SYNC_WORD 0x39
|
#define REG_SYNC_WORD 0x39
|
||||||
|
#define REG_HIGH_BW_OPTIMIZE_2 0x3a
|
||||||
|
#define REG_INVERT_IQ_2 0x3b
|
||||||
#define REG_DIO_MAPPING_1 0x40
|
#define REG_DIO_MAPPING_1 0x40
|
||||||
#define REG_VERSION 0x42
|
#define REG_VERSION 0x42
|
||||||
|
#define REG_TXCO 0x4B
|
||||||
|
#define REG_PA_DAC 0x4D
|
||||||
|
// These registers have different values in high and low frequency modes (flag 0x08 in mode)
|
||||||
|
// We always stay in high frequency mode (flag is 0)
|
||||||
|
#define REG_AGC_REF 0x61
|
||||||
|
#define REG_AGC_THRESHOLD_1 0x62
|
||||||
|
#define REG_AGC_THRESHOLD_2 0x63
|
||||||
|
#define REG_AGC_THRESHOLD_3 0x64
|
||||||
|
#define REG_PLL 0x70
|
||||||
|
|
||||||
// Modes
|
// Modes
|
||||||
#define MODE_LONG_RANGE_MODE 0x80
|
#define MODE_LONG_RANGE_MODE 0x80
|
||||||
@ -88,41 +110,50 @@ LoRaClass::LoRaClass() :
|
|||||||
_frequency(0),
|
_frequency(0),
|
||||||
_packetIndex(0),
|
_packetIndex(0),
|
||||||
_implicitHeaderMode(0),
|
_implicitHeaderMode(0),
|
||||||
_onReceive(NULL)
|
_onReceive(NULL),
|
||||||
|
_spiBegun(false)
|
||||||
{
|
{
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
// overide Stream timeout value
|
// overide Stream timeout value
|
||||||
setTimeout(0);
|
setTimeout(0);
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
_fd = 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int LoRaClass::begin(long frequency)
|
int LoRaClass::begin(long frequency)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
// setup pins
|
// setup pins
|
||||||
pinMode(_ss, OUTPUT);
|
pinMode(_ss, OUTPUT);
|
||||||
// set SS high
|
// set SS high
|
||||||
digitalWrite(_ss, HIGH);
|
digitalWrite(_ss, HIGH);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_reset != -1) {
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
pinMode(_reset, OUTPUT);
|
|
||||||
|
|
||||||
// perform reset
|
|
||||||
digitalWrite(_reset, LOW);
|
|
||||||
delay(10);
|
|
||||||
digitalWrite(_reset, HIGH);
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// start SPI
|
// start SPI
|
||||||
SPI.begin();
|
SPI.begin();
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
const char* spi_filename = "/dev/spidev0.0";
|
||||||
|
// We need to be re-entrant for restart
|
||||||
|
if (_fd <= 0) {
|
||||||
|
std::cerr << "Opening SPI device " << spi_filename << std::endl;
|
||||||
|
_fd = open(spi_filename, O_RDWR);
|
||||||
|
if (_fd <= 0) {
|
||||||
|
perror("could not open SPI device");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cerr << "Skipping LoRa SPI reinitialization" << std::endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_spiBegun = true;
|
||||||
|
|
||||||
// check version
|
if (!resetModem()) {
|
||||||
uint8_t version = readRegister(REG_VERSION);
|
|
||||||
if (version != 0x12) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// put in sleep mode
|
|
||||||
sleep();
|
|
||||||
|
|
||||||
// set frequency
|
// set frequency
|
||||||
setFrequency(frequency);
|
setFrequency(frequency);
|
||||||
|
|
||||||
@ -147,11 +178,21 @@ int LoRaClass::begin(long frequency)
|
|||||||
|
|
||||||
void LoRaClass::end()
|
void LoRaClass::end()
|
||||||
{
|
{
|
||||||
|
// We need to be safe to call when the main loop is shutting down because
|
||||||
|
// it's in a bad state, even if we ourselves haven't been begun yet. We can't
|
||||||
|
// safely talk to the modem if the SPI link isn't begun, though.
|
||||||
|
if (_spiBegun) {
|
||||||
// put in sleep mode
|
// put in sleep mode
|
||||||
sleep();
|
this->sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
// stop SPI
|
// stop SPI
|
||||||
SPI.end();
|
SPI.end();
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
// Don't do anything. We need to keep things open for restart.
|
||||||
|
#endif
|
||||||
|
_spiBegun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LoRaClass::beginPacket(int implicitHeader)
|
int LoRaClass::beginPacket(int implicitHeader)
|
||||||
@ -179,7 +220,11 @@ int LoRaClass::endPacket()
|
|||||||
|
|
||||||
// wait for TX done
|
// wait for TX done
|
||||||
while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {
|
while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
yield();
|
yield();
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
::sleep(0);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear IRQ's
|
// clear IRQ's
|
||||||
@ -271,13 +316,13 @@ float ISR_VECT LoRaClass::packetSnr() {
|
|||||||
long LoRaClass::packetFrequencyError()
|
long LoRaClass::packetFrequencyError()
|
||||||
{
|
{
|
||||||
int32_t freqError = 0;
|
int32_t freqError = 0;
|
||||||
freqError = static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MSB) & B111);
|
freqError = static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MSB) & 0b111);
|
||||||
freqError <<= 8L;
|
freqError <<= 8L;
|
||||||
freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MID));
|
freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_MID));
|
||||||
freqError <<= 8L;
|
freqError <<= 8L;
|
||||||
freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_LSB));
|
freqError += static_cast<int32_t>(readRegister(REG_FREQ_ERROR_LSB));
|
||||||
|
|
||||||
if (readRegister(REG_FREQ_ERROR_MSB) & B1000) { // Sign bit is on
|
if (readRegister(REG_FREQ_ERROR_MSB) & 0b1000) { // Sign bit is on
|
||||||
freqError -= 524288; // B1000'0000'0000'0000'0000
|
freqError -= 524288; // B1000'0000'0000'0000'0000
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,22 +395,42 @@ void LoRaClass::flush()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoRaClass::pollReceive()
|
||||||
|
{
|
||||||
|
int irqFlags = readRegister(REG_IRQ_FLAGS);
|
||||||
|
|
||||||
|
// clear IRQ's
|
||||||
|
writeRegister(REG_IRQ_FLAGS, irqFlags);
|
||||||
|
|
||||||
|
if ((irqFlags & IRQ_RX_DONE_MASK) && !(irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK)) {
|
||||||
|
// received a packet
|
||||||
|
handleRx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LoRaClass::onReceive(void(*callback)(int))
|
void LoRaClass::onReceive(void(*callback)(int))
|
||||||
{
|
{
|
||||||
_onReceive = callback;
|
_onReceive = callback;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
pinMode(_dio0, INPUT);
|
pinMode(_dio0, INPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
writeRegister(REG_DIO_MAPPING_1, 0x00);
|
writeRegister(REG_DIO_MAPPING_1, 0x00);
|
||||||
|
|
||||||
|
#if MCU_VARIANT != MCU_LINUX && LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
#ifdef SPI_HAS_NOTUSINGINTERRUPT
|
#ifdef SPI_HAS_NOTUSINGINTERRUPT
|
||||||
SPI.usingInterrupt(digitalPinToInterrupt(_dio0));
|
SPI.usingInterrupt(digitalPinToInterrupt(_dio0));
|
||||||
#endif
|
#endif
|
||||||
attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);
|
attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
|
#if MCU_VARIANT != MCU_LINUX && LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
detachInterrupt(digitalPinToInterrupt(_dio0));
|
detachInterrupt(digitalPinToInterrupt(_dio0));
|
||||||
#ifdef SPI_HAS_NOTUSINGINTERRUPT
|
#ifdef SPI_HAS_NOTUSINGINTERRUPT
|
||||||
SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0));
|
SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0));
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -572,6 +637,7 @@ void LoRaClass::setSPIFrequency(uint32_t frequency)
|
|||||||
_spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0);
|
_spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
void LoRaClass::dumpRegisters(Stream& out)
|
void LoRaClass::dumpRegisters(Stream& out)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 128; i++) {
|
for (int i = 0; i < 128; i++) {
|
||||||
@ -581,6 +647,94 @@ void LoRaClass::dumpRegisters(Stream& out)
|
|||||||
out.println(readRegister(i), HEX);
|
out.println(readRegister(i), HEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
void LoRaClass::dumpRegisters(std::ostream& out)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
out << "0x" << std::hex << i << ": 0x" << std::hex << readRegister(i) << std::endl;
|
||||||
|
}
|
||||||
|
out << std::dec;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool LoRaClass::resetModem()
|
||||||
|
{
|
||||||
|
// Reset the modem to a known good default state and put it into sleep mode.
|
||||||
|
// Returns false if the modem doesn't appear to be the right version.
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
|
if (_reset != -1) {
|
||||||
|
pinMode(_reset, OUTPUT);
|
||||||
|
|
||||||
|
// perform reset
|
||||||
|
digitalWrite(_reset, LOW);
|
||||||
|
delay(10);
|
||||||
|
digitalWrite(_reset, HIGH);
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// check version
|
||||||
|
uint8_t version = readRegister(REG_VERSION);
|
||||||
|
if (version != 0x12) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sleep();
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
byte CLEAN_STATE[] = {
|
||||||
|
REG_PA_RAMP, 0x09,
|
||||||
|
REG_FRF_MSB, 0x6c,
|
||||||
|
REG_FRF_MID, 0x80,
|
||||||
|
REG_FRF_LSB, 0x00,
|
||||||
|
REG_PA_CONFIG, 0x4f,
|
||||||
|
REG_PA_RAMP, 0x09,
|
||||||
|
REG_OCP, 0x2b,
|
||||||
|
REG_LNA, 0x20,
|
||||||
|
REG_FIFO_ADDR_PTR, 0x00,
|
||||||
|
REG_FIFO_TX_BASE_ADDR, 0x80,
|
||||||
|
REG_FIFO_RX_BASE_ADDR, 0x00,
|
||||||
|
REG_FIFO_RX_CURRENT_ADDR, 0x00,
|
||||||
|
REG_IRQ_FLAGS_MASK, 0x00,
|
||||||
|
REG_MODEM_CONFIG_1, 0x72,
|
||||||
|
REG_MODEM_CONFIG_2, 0x70,
|
||||||
|
REG_SYMB_TIMEOUT_LSB, 0x64,
|
||||||
|
REG_PREAMBLE_MSB, 0x00,
|
||||||
|
REG_PREAMBLE_LSB, 0x08,
|
||||||
|
REG_PAYLOAD_LENGTH, 0x01,
|
||||||
|
REG_PAYLOAD_MAX_LENGTH, 0xff,
|
||||||
|
REG_HOP_PERIOD, 0x00,
|
||||||
|
REG_MODEM_CONFIG_3, 0x04,
|
||||||
|
REG_PPM_CORRECTION, 0x00,
|
||||||
|
REG_DETECTION_OPTIMIZE, 0xc3, // Errata says this needs to be set before REG_IF_FREQ_1 and REG_IF_FREQ_2
|
||||||
|
REG_IF_FREQ_2, 0x45, // Datasheet says this defaults to 0x20, but dumping says 0x45.
|
||||||
|
REG_IF_FREQ_1, 0x55, // Datasheet says this defaults to 0x00, but dumping says 0x55.
|
||||||
|
REG_INVERT_IQ, 0x27,
|
||||||
|
REG_HIGH_BW_OPTIMIZE_1, 0x03,
|
||||||
|
REG_DETECTION_THRESHOLD, 0x0a,
|
||||||
|
REG_SYNC_WORD, 0x12,
|
||||||
|
REG_HIGH_BW_OPTIMIZE_2, 0x52, // Datasheet says this defaults to 0x20, but dumping says 0x52.
|
||||||
|
REG_INVERT_IQ_2, 0x1d,
|
||||||
|
REG_TXCO, 0x09,
|
||||||
|
REG_PA_DAC, 0x84,
|
||||||
|
// These are the high frequency mode (mode flag 0x08 is 0) values
|
||||||
|
REG_AGC_REF, 0x1C,
|
||||||
|
REG_AGC_THRESHOLD_1, 0x0e,
|
||||||
|
REG_AGC_THRESHOLD_2, 0x5b,
|
||||||
|
REG_AGC_THRESHOLD_3, 0xcc,
|
||||||
|
REG_PLL, 0xd0,
|
||||||
|
0, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Manually set important registers to default values because we can't
|
||||||
|
// reset.
|
||||||
|
for (int i = 0; CLEAN_STATE[i] != 0; i += 2) {
|
||||||
|
writeRegister(CLEAN_STATE[i], CLEAN_STATE[i + 1]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void LoRaClass::explicitHeaderMode()
|
void LoRaClass::explicitHeaderMode()
|
||||||
{
|
{
|
||||||
@ -605,6 +759,13 @@ void ISR_VECT LoRaClass::handleDio0Rise()
|
|||||||
writeRegister(REG_IRQ_FLAGS, irqFlags);
|
writeRegister(REG_IRQ_FLAGS, irqFlags);
|
||||||
|
|
||||||
if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
|
if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
|
||||||
|
// received a packet
|
||||||
|
handleRx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISR_VECT LoRaClass::handleRx()
|
||||||
|
{
|
||||||
// received a packet
|
// received a packet
|
||||||
_packetIndex = 0;
|
_packetIndex = 0;
|
||||||
|
|
||||||
@ -621,7 +782,6 @@ void ISR_VECT LoRaClass::handleDio0Rise()
|
|||||||
// reset FIFO address
|
// reset FIFO address
|
||||||
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
writeRegister(REG_FIFO_ADDR_PTR, 0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ISR_VECT LoRaClass::readRegister(uint8_t address)
|
uint8_t ISR_VECT LoRaClass::readRegister(uint8_t address)
|
||||||
{
|
{
|
||||||
@ -637,6 +797,8 @@ uint8_t ISR_VECT LoRaClass::singleTransfer(uint8_t address, uint8_t value)
|
|||||||
{
|
{
|
||||||
uint8_t response;
|
uint8_t response;
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
|
// Select chip, send address, and send/read data, the Arduino way
|
||||||
digitalWrite(_ss, LOW);
|
digitalWrite(_ss, LOW);
|
||||||
|
|
||||||
SPI.beginTransaction(_spiSettings);
|
SPI.beginTransaction(_spiSettings);
|
||||||
@ -645,6 +807,55 @@ uint8_t ISR_VECT LoRaClass::singleTransfer(uint8_t address, uint8_t value)
|
|||||||
SPI.endTransaction();
|
SPI.endTransaction();
|
||||||
|
|
||||||
digitalWrite(_ss, HIGH);
|
digitalWrite(_ss, HIGH);
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
// Select chip, send address, and send/read data, the Linux way
|
||||||
|
|
||||||
|
// In Linux, chip select is automatically turned off outside of transactions.
|
||||||
|
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (_fd <= 0) {
|
||||||
|
throw std::runtime_error("Accessing SPI device without begin()!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure SPI speed and mode to match settings
|
||||||
|
status = ioctl(_fd, SPI_IOC_WR_MODE, &_spiSettings.mode);
|
||||||
|
if (status < 0) {
|
||||||
|
perror("ioctl SPI_IOC_WR_MODE failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
status = ioctl(_fd, SPI_IOC_WR_LSB_FIRST, &_spiSettings.bitness);
|
||||||
|
if (status < 0) {
|
||||||
|
perror("ioctl SPI_IOC_WR_LSB_FIRST failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
status = ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &_spiSettings.frequency);
|
||||||
|
if (status < 0) {
|
||||||
|
perror("ioctl SPI_IOC_WR_MAX_SPEED_HZ failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have two transfers: one send-only to send the address, and one
|
||||||
|
// send/receive, to send the value and get the response.
|
||||||
|
struct spi_ioc_transfer xfer[2];
|
||||||
|
memset(xfer, 0, sizeof xfer);
|
||||||
|
|
||||||
|
xfer[0].tx_buf = (unsigned long) &address;
|
||||||
|
xfer[0].len = 1;
|
||||||
|
|
||||||
|
xfer[1].tx_buf = (unsigned long) &value;
|
||||||
|
xfer[1].rx_buf = (unsigned long) &response;
|
||||||
|
xfer[1].len = 1;
|
||||||
|
|
||||||
|
// Do the transaction
|
||||||
|
status = ioctl(_fd, SPI_IOC_MESSAGE(2), xfer);
|
||||||
|
if (status < 0) {
|
||||||
|
perror("ioctl SPI_IOC_MESSAGE failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "SPI transfer not implemented for library type"
|
||||||
|
#endif
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
42
LoRa.h
42
LoRa.h
@ -7,8 +7,38 @@
|
|||||||
#ifndef LORA_H
|
#ifndef LORA_H
|
||||||
#define LORA_H
|
#define LORA_H
|
||||||
|
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// Arduino Stream is not available, but not actually needed.
|
||||||
|
class Stream {};
|
||||||
|
|
||||||
|
typedef unsigned char byte;
|
||||||
|
|
||||||
|
// Arduino SPI is not available, so make a Linux-ish version of SPISettings
|
||||||
|
#define MSBFIRST 0
|
||||||
|
#define LSBFIRST 1
|
||||||
|
#define SPI_MODE0 SPI_MODE_0
|
||||||
|
#define SPI_MODE1 SPI_MODE_1
|
||||||
|
#define SPI_MODE2 SPI_MODE_2
|
||||||
|
#define SPI_MODE3 SPI_MODE_3
|
||||||
|
class SPISettings {
|
||||||
|
public:
|
||||||
|
inline SPISettings(uint32_t frequency, byte bitness, byte mode) : frequency(frequency), bitness(bitness), mode(mode) {};
|
||||||
|
SPISettings& operator=(const SPISettings& other) = default;
|
||||||
|
uint32_t frequency;
|
||||||
|
byte bitness;
|
||||||
|
byte mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#define LORA_DEFAULT_SS_PIN 10
|
#define LORA_DEFAULT_SS_PIN 10
|
||||||
#define LORA_DEFAULT_RESET_PIN 9
|
#define LORA_DEFAULT_RESET_PIN 9
|
||||||
@ -46,6 +76,7 @@ public:
|
|||||||
virtual int peek();
|
virtual int peek();
|
||||||
virtual void flush();
|
virtual void flush();
|
||||||
|
|
||||||
|
void pollReceive();
|
||||||
void onReceive(void(*callback)(int));
|
void onReceive(void(*callback)(int));
|
||||||
|
|
||||||
void receive(int size = 0);
|
void receive(int size = 0);
|
||||||
@ -74,13 +105,20 @@ public:
|
|||||||
void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
|
void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
|
||||||
void setSPIFrequency(uint32_t frequency);
|
void setSPIFrequency(uint32_t frequency);
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
void dumpRegisters(Stream& out);
|
void dumpRegisters(Stream& out);
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
void dumpRegisters(std::ostream& out);
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool resetModem();
|
||||||
|
|
||||||
void explicitHeaderMode();
|
void explicitHeaderMode();
|
||||||
void implicitHeaderMode();
|
void implicitHeaderMode();
|
||||||
|
|
||||||
void handleDio0Rise();
|
void handleDio0Rise();
|
||||||
|
void handleRx();
|
||||||
|
|
||||||
uint8_t readRegister(uint8_t address);
|
uint8_t readRegister(uint8_t address);
|
||||||
void writeRegister(uint8_t address, uint8_t value);
|
void writeRegister(uint8_t address, uint8_t value);
|
||||||
@ -99,6 +137,10 @@ private:
|
|||||||
int _packetIndex;
|
int _packetIndex;
|
||||||
int _implicitHeaderMode;
|
int _implicitHeaderMode;
|
||||||
void (*_onReceive)(int);
|
void (*_onReceive)(int);
|
||||||
|
bool _spiBegun;
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
int _fd;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
extern LoRaClass LoRa;
|
extern LoRaClass LoRa;
|
||||||
|
6
MD5.cpp
6
MD5.cpp
@ -1,5 +1,11 @@
|
|||||||
#include "MD5.h"
|
#include "MD5.h"
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#elif LIBRARY_TYPE == LIBRARY_C
|
||||||
|
#include <cstdlib>
|
||||||
|
#endif
|
||||||
|
|
||||||
MD5::MD5()
|
MD5::MD5()
|
||||||
{
|
{
|
||||||
//nothing
|
//nothing
|
||||||
|
2
MD5.h
2
MD5.h
@ -1,7 +1,7 @@
|
|||||||
#ifndef MD5_h
|
#ifndef MD5_h
|
||||||
#define MD5_h
|
#define MD5_h
|
||||||
|
|
||||||
#include "Arduino.h"
|
#include "Platform.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is an OpenSSL-compatible implementation of the RSA Data Security,
|
* This is an OpenSSL-compatible implementation of the RSA Data Security,
|
||||||
|
24
Makefile
24
Makefile
@ -10,8 +10,6 @@ prep-samd:
|
|||||||
arduino-cli core update-index --config-file arduino-cli.yaml
|
arduino-cli core update-index --config-file arduino-cli.yaml
|
||||||
arduino-cli core install adafruit:samd
|
arduino-cli core install adafruit:samd
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
firmware:
|
firmware:
|
||||||
arduino-cli compile --fqbn unsignedio:avr:rnode
|
arduino-cli compile --fqbn unsignedio:avr:rnode
|
||||||
|
|
||||||
@ -135,3 +133,25 @@ release-mega2560:
|
|||||||
arduino-cli compile --fqbn arduino:avr:mega -e
|
arduino-cli compile --fqbn arduino:avr:mega -e
|
||||||
cp build/arduino.avr.mega/RNode_Firmware.ino.hex Release/rnode_firmware_latest_m2560.hex
|
cp build/arduino.avr.mega/RNode_Firmware.ino.hex Release/rnode_firmware_latest_m2560.hex
|
||||||
rm -r build
|
rm -r build
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -Rf bin
|
||||||
|
rm -Rf obj
|
||||||
|
|
||||||
|
CFLAGS += -g
|
||||||
|
|
||||||
|
obj/MD5.o: MD5.cpp MD5.h Platform.h
|
||||||
|
mkdir -p obj
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
obj/LoRa.o: LoRa.cpp LoRa.h Platform.h
|
||||||
|
mkdir -p obj
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
obj/RNode_Firmware.o: RNode_Firmware.ino Utilities.h Config.h LoRa.h ROM.h Framing.h MD5.h Platform.h
|
||||||
|
mkdir -p obj
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ -x c++ $<
|
||||||
|
|
||||||
|
bin/rnode: obj/RNode_Firmware.o obj/LoRa.o obj/MD5.o
|
||||||
|
mkdir -p bin
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ -lstdc++ -lutil
|
||||||
|
42
Platform.h
Normal file
42
Platform.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef PLATFORM_H
|
||||||
|
#define PLATFORM_H
|
||||||
|
|
||||||
|
// Determine the platform, MCU, and C library we are building for.
|
||||||
|
|
||||||
|
#define PLATFORM_AVR 0x90
|
||||||
|
#define PLATFORM_ESP32 0x80
|
||||||
|
#define PLATFORM_LINUX 0x70
|
||||||
|
|
||||||
|
#define MCU_1284P 0x91
|
||||||
|
#define MCU_2560 0x92
|
||||||
|
#define MCU_ESP32 0x81
|
||||||
|
#define MCU_LINUX 0x71
|
||||||
|
|
||||||
|
#define LIBRARY_ARDUINO 0x1
|
||||||
|
#define LIBRARY_C 0x2
|
||||||
|
|
||||||
|
#if defined(__AVR_ATmega1284P__)
|
||||||
|
#define PLATFORM PLATFORM_AVR
|
||||||
|
#define MCU_VARIANT MCU_1284P
|
||||||
|
#define LIBRARY_TYPE LIBRARY_ARDUINO
|
||||||
|
#elif defined(__AVR_ATmega2560__)
|
||||||
|
#define PLATFORM PLATFORM_AVR
|
||||||
|
#define MCU_VARIANT MCU_2560
|
||||||
|
#define LIBRARY_TYPE LIBRARY_ARDUINO
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define PLATFORM PLATFORM_ESP32
|
||||||
|
#define MCU_VARIANT MCU_ESP32
|
||||||
|
#define LIBRARY_TYPE LIBRARY_ARDUINO
|
||||||
|
#elif defined(__unix__)
|
||||||
|
#define PLATFORM PLATFORM_LINUX
|
||||||
|
#define MCU_VARIANT MCU_LINUX
|
||||||
|
#define LIBRARY_TYPE LIBRARY_C
|
||||||
|
#else
|
||||||
|
#error "The firmware cannot be compiled for the selected MCU variant"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MCU_VARIANT
|
||||||
|
#error No MCU variant defined, cannot compile
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -20,8 +20,12 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
#endif
|
||||||
#include "Utilities.h"
|
#include "Utilities.h"
|
||||||
|
|
||||||
FIFOBuffer serialFIFO;
|
FIFOBuffer serialFIFO;
|
||||||
@ -47,15 +51,26 @@ char sbuf[128];
|
|||||||
bool packet_ready = false;
|
bool packet_ready = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Arduino C doesn't need pre-declarations to call functions that appear later,
|
||||||
|
// but standard C does.
|
||||||
|
void serial_interrupt_init();
|
||||||
|
void validateStatus();
|
||||||
|
void update_radio_lock();
|
||||||
|
void transmit(uint16_t size);
|
||||||
|
void buffer_serial();
|
||||||
|
void serial_poll();
|
||||||
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
#if MCU_VARIANT == MCU_ESP32
|
#if MCU_VARIANT == MCU_ESP32
|
||||||
delay(500);
|
delay(500);
|
||||||
EEPROM.begin(EEPROM_SIZE);
|
|
||||||
Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE);
|
|
||||||
#endif
|
#endif
|
||||||
|
eeprom_open(EEPROM_SIZE);
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
// Seed the PRNG
|
// Seed the PRNG
|
||||||
randomSeed(analogRead(0));
|
randomSeed(analogRead(0));
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialise serial communication
|
// Initialise serial communication
|
||||||
memset(serialBuffer, 0, sizeof(serialBuffer));
|
memset(serialBuffer, 0, sizeof(serialBuffer));
|
||||||
@ -64,11 +79,17 @@ void setup() {
|
|||||||
Serial.begin(serial_baudrate);
|
Serial.begin(serial_baudrate);
|
||||||
while (!Serial);
|
while (!Serial);
|
||||||
|
|
||||||
|
#if MCU_VARIANT == MCU_ESP32
|
||||||
|
Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE);
|
||||||
|
#endif
|
||||||
|
|
||||||
serial_interrupt_init();
|
serial_interrupt_init();
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
// Configure input and output pins
|
// Configure input and output pins
|
||||||
pinMode(pin_led_rx, OUTPUT);
|
pinMode(pin_led_rx, OUTPUT);
|
||||||
pinMode(pin_led_tx, OUTPUT);
|
pinMode(pin_led_tx, OUTPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialise buffers
|
// Initialise buffers
|
||||||
memset(pbuf, 0, sizeof(pbuf));
|
memset(pbuf, 0, sizeof(pbuf));
|
||||||
@ -130,6 +151,9 @@ inline void getPacketData(uint16_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ISR_VECT receive_callback(int packet_size) {
|
void ISR_VECT receive_callback(int packet_size) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Got packet of " << packet_size << " bytes" << std::endl;
|
||||||
|
#endif
|
||||||
if (!promisc) {
|
if (!promisc) {
|
||||||
// The standard operating mode allows large
|
// The standard operating mode allows large
|
||||||
// packets with a payload up to 500 bytes,
|
// packets with a payload up to 500 bytes,
|
||||||
@ -144,6 +168,11 @@ void ISR_VECT receive_callback(int packet_size) {
|
|||||||
// This is the first part of a split
|
// This is the first part of a split
|
||||||
// packet, so we set the seq variable
|
// packet, so we set the seq variable
|
||||||
// and add the data to the buffer
|
// and add the data to the buffer
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "\tIs first part of split packet" << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
read_len = 0;
|
read_len = 0;
|
||||||
seq = sequence;
|
seq = sequence;
|
||||||
|
|
||||||
@ -159,6 +188,10 @@ void ISR_VECT receive_callback(int packet_size) {
|
|||||||
// packet, so we add it to the buffer
|
// packet, so we add it to the buffer
|
||||||
// and set the ready flag.
|
// and set the ready flag.
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "\tIs second part of split packet" << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if MCU_VARIANT != MCU_ESP32
|
#if MCU_VARIANT != MCU_ESP32
|
||||||
last_rssi = (last_rssi+LoRa.packetRssi())/2;
|
last_rssi = (last_rssi+LoRa.packetRssi())/2;
|
||||||
last_snr_raw = (last_snr_raw+LoRa.packetSnrRaw())/2;
|
last_snr_raw = (last_snr_raw+LoRa.packetSnrRaw())/2;
|
||||||
@ -173,6 +206,11 @@ void ISR_VECT receive_callback(int packet_size) {
|
|||||||
// same sequence id, so we must assume
|
// same sequence id, so we must assume
|
||||||
// that we are seeing the first part of
|
// that we are seeing the first part of
|
||||||
// a new split packet.
|
// a new split packet.
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "\tIs first part of a different split packet" << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
read_len = 0;
|
read_len = 0;
|
||||||
seq = sequence;
|
seq = sequence;
|
||||||
|
|
||||||
@ -188,6 +226,10 @@ void ISR_VECT receive_callback(int packet_size) {
|
|||||||
// just read it and set the ready
|
// just read it and set the ready
|
||||||
// flag to true.
|
// flag to true.
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "\tIs complete packet" << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (seq != SEQ_UNSET) {
|
if (seq != SEQ_UNSET) {
|
||||||
// If we already had part of a split
|
// If we already had part of a split
|
||||||
// packet in the buffer, we clear it.
|
// packet in the buffer, we clear it.
|
||||||
@ -318,7 +360,7 @@ void flushQueue(void) {
|
|||||||
|
|
||||||
uint16_t processed = 0;
|
uint16_t processed = 0;
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_ESP32
|
#if SERIAL_EVENTS == SERIAL_POLLING
|
||||||
while (!fifo16_isempty(&packet_starts)) {
|
while (!fifo16_isempty(&packet_starts)) {
|
||||||
#else
|
#else
|
||||||
while (!fifo16_isempty_locked(&packet_starts)) {
|
while (!fifo16_isempty_locked(&packet_starts)) {
|
||||||
@ -347,6 +389,9 @@ void flushQueue(void) {
|
|||||||
void transmit(uint16_t size) {
|
void transmit(uint16_t size) {
|
||||||
if (radio_online) {
|
if (radio_online) {
|
||||||
if (!promisc) {
|
if (!promisc) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Sending RNode packet(s) of " << size << " bytes" << std::endl;
|
||||||
|
#endif
|
||||||
led_tx_on();
|
led_tx_on();
|
||||||
uint16_t written = 0;
|
uint16_t written = 0;
|
||||||
uint8_t header = random(256) & 0xF0;
|
uint8_t header = random(256) & 0xF0;
|
||||||
@ -379,6 +424,11 @@ void transmit(uint16_t size) {
|
|||||||
// In promiscuous mode, we only send out
|
// In promiscuous mode, we only send out
|
||||||
// plain raw LoRa packets with a maximum
|
// plain raw LoRa packets with a maximum
|
||||||
// payload of 255 bytes
|
// payload of 255 bytes
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Sending standard packet of " << size << " bytes" << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
led_tx_on();
|
led_tx_on();
|
||||||
uint16_t written = 0;
|
uint16_t written = 0;
|
||||||
|
|
||||||
@ -683,13 +733,19 @@ void validateStatus() {
|
|||||||
uint8_t F_WDR = WDRF;
|
uint8_t F_WDR = WDRF;
|
||||||
#elif MCU_VARIANT == MCU_2560
|
#elif MCU_VARIANT == MCU_2560
|
||||||
uint8_t boot_flags = OPTIBOOT_MCUSR;
|
uint8_t boot_flags = OPTIBOOT_MCUSR;
|
||||||
if (boot_flags == 0x00) boot_flags = 0x03;
|
if (boot_flags == 0x00) boot_flags = START_FROM_BROWNOUT;
|
||||||
uint8_t F_POR = PORF;
|
uint8_t F_POR = PORF;
|
||||||
uint8_t F_BOR = BORF;
|
uint8_t F_BOR = BORF;
|
||||||
uint8_t F_WDR = WDRF;
|
uint8_t F_WDR = WDRF;
|
||||||
#elif MCU_VARIANT == MCU_ESP32
|
#elif MCU_VARIANT == MCU_ESP32
|
||||||
// TODO: Get ESP32 boot flags
|
// TODO: Get ESP32 boot flags
|
||||||
uint8_t boot_flags = 0x02;
|
uint8_t boot_flags = START_FROM_POWERON;
|
||||||
|
uint8_t F_POR = 0x00;
|
||||||
|
uint8_t F_BOR = 0x00;
|
||||||
|
uint8_t F_WDR = 0x01;
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
// Linux build always works like a clean boot.
|
||||||
|
uint8_t boot_flags = START_FROM_POWERON;
|
||||||
uint8_t F_POR = 0x00;
|
uint8_t F_POR = 0x00;
|
||||||
uint8_t F_BOR = 0x00;
|
uint8_t F_BOR = 0x00;
|
||||||
uint8_t F_WDR = 0x01;
|
uint8_t F_WDR = 0x01;
|
||||||
@ -702,12 +758,12 @@ void validateStatus() {
|
|||||||
} else if (boot_flags & (1<<F_WDR)) {
|
} else if (boot_flags & (1<<F_WDR)) {
|
||||||
boot_vector = START_FROM_BOOTLOADER;
|
boot_vector = START_FROM_BOOTLOADER;
|
||||||
} else {
|
} else {
|
||||||
Serial.write("Error, indeterminate boot vector\r\n");
|
debug("Error, indeterminate boot vector\r\n");
|
||||||
led_indicate_boot_error();
|
led_indicate_boot_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boot_vector == START_FROM_BOOTLOADER || boot_vector == START_FROM_POWERON) {
|
if (boot_vector == START_FROM_BOOTLOADER || boot_vector == START_FROM_POWERON) {
|
||||||
if (eeprom_lock_set()) {
|
if (eeprom_info_locked()) {
|
||||||
if (eeprom_product_valid() && eeprom_model_valid() && eeprom_hwrev_valid()) {
|
if (eeprom_product_valid() && eeprom_model_valid() && eeprom_hwrev_valid()) {
|
||||||
if (eeprom_checksum_valid()) {
|
if (eeprom_checksum_valid()) {
|
||||||
hw_ready = true;
|
hw_ready = true;
|
||||||
@ -717,16 +773,21 @@ void validateStatus() {
|
|||||||
op_mode = MODE_TNC;
|
op_mode = MODE_TNC;
|
||||||
startRadio();
|
startRadio();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
hw_ready = false;
|
||||||
|
debug("Error, EEPROM checksum incorrect\r\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hw_ready = false;
|
hw_ready = false;
|
||||||
|
debug("Error, EEPROM product, model, or revision not valid\r\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hw_ready = false;
|
hw_ready = false;
|
||||||
|
debug("Error, EEPROM info not locked\r\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hw_ready = false;
|
hw_ready = false;
|
||||||
Serial.write("Error, incorrect boot vector\r\n");
|
debug("Error, incorrect boot vector\r\n");
|
||||||
led_indicate_boot_error();
|
led_indicate_boot_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -747,6 +808,12 @@ void loop() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if MCU_VARIANT == MCU_LINUX
|
||||||
|
// We don't have interrupts, so we need to poll ofr received packets.
|
||||||
|
// TODO: Is this fast enough? Or do we need threads or something?
|
||||||
|
LoRa.pollReceive();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (queue_height > 0) {
|
if (queue_height > 0) {
|
||||||
if (!dcd_waiting) updateModemStatus();
|
if (!dcd_waiting) updateModemStatus();
|
||||||
|
|
||||||
@ -775,7 +842,7 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_ESP32
|
#if SERIAL_EVENTS == SERIAL_POLLING
|
||||||
buffer_serial();
|
buffer_serial();
|
||||||
if (!fifo_isempty(&serialFIFO)) serial_poll();
|
if (!fifo_isempty(&serialFIFO)) serial_poll();
|
||||||
#else
|
#else
|
||||||
@ -787,7 +854,7 @@ volatile bool serial_polling = false;
|
|||||||
void serial_poll() {
|
void serial_poll() {
|
||||||
serial_polling = true;
|
serial_polling = true;
|
||||||
|
|
||||||
#if MCU_VARIANT != MCU_ESP32
|
#if SERIAL_EVENTS == SERIAL_INTERRUPT
|
||||||
while (!fifo_isempty_locked(&serialFIFO)) {
|
while (!fifo_isempty_locked(&serialFIFO)) {
|
||||||
#else
|
#else
|
||||||
while (!fifo_isempty(&serialFIFO)) {
|
while (!fifo_isempty(&serialFIFO)) {
|
||||||
@ -812,7 +879,7 @@ void buffer_serial() {
|
|||||||
while (c < MAX_CYCLES && Serial.available()) {
|
while (c < MAX_CYCLES && Serial.available()) {
|
||||||
c++;
|
c++;
|
||||||
|
|
||||||
#if MCU_VARIANT != MCU_ESP32
|
#if SERIAL_EVENTS == SERIAL_INTERRUPT
|
||||||
if (!fifo_isfull_locked(&serialFIFO)) {
|
if (!fifo_isfull_locked(&serialFIFO)) {
|
||||||
fifo_push_locked(&serialFIFO, Serial.read());
|
fifo_push_locked(&serialFIFO, Serial.read());
|
||||||
}
|
}
|
||||||
@ -853,8 +920,8 @@ void serial_interrupt_init() {
|
|||||||
|
|
||||||
TIMSK3 = _BV(ICIE3);
|
TIMSK3 = _BV(ICIE3);
|
||||||
|
|
||||||
#elif MCU_VARIANT == MCU_ESP32
|
#else
|
||||||
// No interrupt-based polling on ESP32
|
// No interrupt-based polling on other MCUs.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -864,3 +931,12 @@ void serial_interrupt_init() {
|
|||||||
buffer_serial();
|
buffer_serial();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PLATFORM == PLATFORM_LINUX
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
setup();
|
||||||
|
while (true) {
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
370
Utilities.h
370
Utilities.h
@ -1,4 +1,6 @@
|
|||||||
|
#if LIBRARY_TYPE == LIBRARY_ARDUINO
|
||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
|
#endif
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "LoRa.h"
|
#include "LoRa.h"
|
||||||
@ -6,6 +8,143 @@
|
|||||||
#include "Framing.h"
|
#include "Framing.h"
|
||||||
#include "MD5.h"
|
#include "MD5.h"
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
#include <time.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pty.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
// We need a delay()
|
||||||
|
void delay(int ms) {
|
||||||
|
struct timespec interval;
|
||||||
|
interval.tv_sec = ms / 1000;
|
||||||
|
interval.tv_nsec = (ms % 1000) * 1000 * 1000;
|
||||||
|
// TODO: handle signals interrupting sleep
|
||||||
|
nanosleep(&interval, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And millis()
|
||||||
|
struct timespec millis_base;
|
||||||
|
uint32_t millis() {
|
||||||
|
// Time since first call is close enough.
|
||||||
|
static bool base_set(false);
|
||||||
|
if (!base_set) {
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &millis_base)) {
|
||||||
|
perror("Could not get time");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
base_set = true;
|
||||||
|
}
|
||||||
|
struct timespec now;
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &now)) {
|
||||||
|
perror("Could not get time");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return (now.tv_sec - millis_base.tv_sec) * 1000 + (now.tv_nsec - millis_base.tv_nsec)/(1000*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial will want to poll the EEPROM a bit for help text
|
||||||
|
bool eeprom_info_locked();
|
||||||
|
|
||||||
|
// We also need a Serial
|
||||||
|
class SerialClass {
|
||||||
|
public:
|
||||||
|
void begin(int baud) {
|
||||||
|
// Need to be rrentrant for restart
|
||||||
|
if (_fd <= 0) {
|
||||||
|
int other_end = 0;
|
||||||
|
int status = openpty(&_fd, &other_end, NULL, NULL, NULL);
|
||||||
|
if (status) {
|
||||||
|
perror("could not open PTY");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Listening on " << ttyname(other_end) << std::endl;
|
||||||
|
if (!eeprom_info_locked()) {
|
||||||
|
std::cerr << "EEPROM configuration is not initialized. You will want to flash it with something like:" << std::endl;
|
||||||
|
std::cerr << "\trnodeconf --key" << std::endl;
|
||||||
|
std::cerr << "\trnodeconf --rom --platform " << std::hex << PLATFORM << " --product " << PRODUCT_HMBRW << " --model " << MODEL_FF << std::dec << " --hwrev 1 " << ttyname(other_end) << std::endl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cerr << "Skipping Serial reinitialization" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() {
|
||||||
|
return _fd > 0;
|
||||||
|
}
|
||||||
|
void write(int b) {
|
||||||
|
uint8_t to_write = b;
|
||||||
|
ssize_t written = ::write(_fd, &to_write, 1);
|
||||||
|
while (written != 1) {
|
||||||
|
if (written < 0) {
|
||||||
|
perror("could not write to PTY");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
written = ::write(_fd, &to_write, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void write(const char* data) {
|
||||||
|
while(*data) {
|
||||||
|
write(*data);
|
||||||
|
++data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool available() {
|
||||||
|
struct pollfd request;
|
||||||
|
request.fd = _fd;
|
||||||
|
request.events = POLLIN;
|
||||||
|
request.revents = 0;
|
||||||
|
|
||||||
|
int result = poll(&request, 1, 0);
|
||||||
|
|
||||||
|
if (result == -1) {
|
||||||
|
perror("could not poll");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result > 0;
|
||||||
|
}
|
||||||
|
uint8_t read() {
|
||||||
|
uint8_t buffer;
|
||||||
|
ssize_t count = ::read(_fd, &buffer, 1);
|
||||||
|
while (count != 1) {
|
||||||
|
if (count < 0) {
|
||||||
|
perror("could not read from PTY");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
count = ::read(_fd, &buffer, 1);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
int _fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
SerialClass Serial;
|
||||||
|
|
||||||
|
// And random(below);
|
||||||
|
int random(int below) {
|
||||||
|
return rand() % below;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Log a debug message. Message should have a \r to return the cursor, if
|
||||||
|
// needed.
|
||||||
|
void debug(const char* message) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << message << std::endl;
|
||||||
|
#endif
|
||||||
|
if (Serial) {
|
||||||
|
Serial.write(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_ESP32
|
#if MCU_VARIANT == MCU_ESP32
|
||||||
#include "soc/rtc_wdt.h"
|
#include "soc/rtc_wdt.h"
|
||||||
#define ISR_VECT IRAM_ATTR
|
#define ISR_VECT IRAM_ATTR
|
||||||
@ -69,6 +208,17 @@ uint8_t boot_vector = 0x00;
|
|||||||
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
|
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
|
||||||
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
|
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
|
||||||
#endif
|
#endif
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
// No LEDs on Linux, probably. SPI only.
|
||||||
|
void led_rx_on() { }
|
||||||
|
void led_rx_off() { }
|
||||||
|
void led_tx_on() { }
|
||||||
|
void led_tx_off() { }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
// hard_reset needs a declaration for main
|
||||||
|
int main(int argc, char** argv);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void hard_reset(void) {
|
void hard_reset(void) {
|
||||||
@ -79,18 +229,27 @@ void hard_reset(void) {
|
|||||||
}
|
}
|
||||||
#elif MCU_VARIANT == MCU_ESP32
|
#elif MCU_VARIANT == MCU_ESP32
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
// TODO: re-exec ourselves?
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Restarting" << std::endl;
|
||||||
|
exit(main(0, NULL));
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void led_indicate_error(int cycles) {
|
void led_indicate_error(int cycles) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Indicating error" << std::endl;
|
||||||
|
#endif
|
||||||
bool forever = (cycles == 0) ? true : false;
|
bool forever = (cycles == 0) ? true : false;
|
||||||
cycles = forever ? 1 : cycles;
|
cycles = forever ? 1 : cycles;
|
||||||
while(cycles > 0) {
|
while(cycles > 0) {
|
||||||
digitalWrite(pin_led_rx, HIGH);
|
led_rx_on();
|
||||||
digitalWrite(pin_led_tx, LOW);
|
led_tx_off();
|
||||||
delay(100);
|
delay(100);
|
||||||
digitalWrite(pin_led_rx, LOW);
|
led_rx_off();
|
||||||
digitalWrite(pin_led_tx, HIGH);
|
led_tx_on();
|
||||||
delay(100);
|
delay(100);
|
||||||
if (!forever) cycles--;
|
if (!forever) cycles--;
|
||||||
}
|
}
|
||||||
@ -99,6 +258,9 @@ void led_indicate_error(int cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void led_indicate_boot_error() {
|
void led_indicate_boot_error() {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Indicating boot error" << std::endl;
|
||||||
|
#endif
|
||||||
while (true) {
|
while (true) {
|
||||||
led_tx_on();
|
led_tx_on();
|
||||||
led_rx_off();
|
led_rx_off();
|
||||||
@ -110,9 +272,12 @@ void led_indicate_boot_error() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void led_indicate_warning(int cycles) {
|
void led_indicate_warning(int cycles) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Indicating warning" << std::endl;
|
||||||
|
#endif
|
||||||
bool forever = (cycles == 0) ? true : false;
|
bool forever = (cycles == 0) ? true : false;
|
||||||
cycles = forever ? 1 : cycles;
|
cycles = forever ? 1 : cycles;
|
||||||
digitalWrite(pin_led_tx, HIGH);
|
led_tx_on();
|
||||||
while(cycles > 0) {
|
while(cycles > 0) {
|
||||||
led_tx_off();
|
led_tx_off();
|
||||||
delay(100);
|
delay(100);
|
||||||
@ -123,7 +288,7 @@ void led_indicate_warning(int cycles) {
|
|||||||
led_tx_off();
|
led_tx_off();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 || MCU_VARIANT == MCU_LINUX
|
||||||
void led_indicate_info(int cycles) {
|
void led_indicate_info(int cycles) {
|
||||||
bool forever = (cycles == 0) ? true : false;
|
bool forever = (cycles == 0) ? true : false;
|
||||||
cycles = forever ? 1 : cycles;
|
cycles = forever ? 1 : cycles;
|
||||||
@ -165,6 +330,9 @@ void led_indicate_warning(int cycles) {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
void led_indicate_info(int cycles) {
|
void led_indicate_info(int cycles) {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Indicating info" << std::endl;
|
||||||
|
#endif
|
||||||
bool forever = (cycles == 0) ? true : false;
|
bool forever = (cycles == 0) ? true : false;
|
||||||
cycles = forever ? 1 : cycles;
|
cycles = forever ? 1 : cycles;
|
||||||
while(cycles > 0) {
|
while(cycles > 0) {
|
||||||
@ -179,8 +347,9 @@ void led_indicate_warning(int cycles) {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 || MCU_VARIANT == MCU_ESP32
|
||||||
unsigned long led_standby_ticks = 0;
|
unsigned long led_standby_ticks = 0;
|
||||||
|
#endif
|
||||||
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
||||||
uint8_t led_standby_min = 1;
|
uint8_t led_standby_min = 1;
|
||||||
uint8_t led_standby_max = 40;
|
uint8_t led_standby_max = 40;
|
||||||
@ -196,8 +365,10 @@ unsigned long led_standby_ticks = 0;
|
|||||||
unsigned long led_standby_wait = 1768;
|
unsigned long led_standby_wait = 1768;
|
||||||
unsigned long led_notready_wait = 150;
|
unsigned long led_notready_wait = 150;
|
||||||
#endif
|
#endif
|
||||||
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 || MCU_VARIANT == MCU_ESP32
|
||||||
uint8_t led_standby_value = led_standby_min;
|
uint8_t led_standby_value = led_standby_min;
|
||||||
int8_t led_standby_direction = 0;
|
int8_t led_standby_direction = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
||||||
void led_indicate_standby() {
|
void led_indicate_standby() {
|
||||||
@ -243,6 +414,17 @@ int8_t led_standby_direction = 0;
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
// No LEDs available.
|
||||||
|
void led_indicate_standby() {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
static bool printed = false;
|
||||||
|
if (!printed) {
|
||||||
|
std::cerr << "Indicating standby" << std::endl;
|
||||||
|
printed = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
||||||
@ -289,6 +471,17 @@ int8_t led_standby_direction = 0;
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
// No LEDs available.
|
||||||
|
void led_indicate_not_ready() {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
static bool printed = false;
|
||||||
|
if (!printed) {
|
||||||
|
std::cerr << "Indicating not ready" << std::endl;
|
||||||
|
printed = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void escapedSerialWrite(uint8_t byte) {
|
void escapedSerialWrite(uint8_t byte) {
|
||||||
@ -551,8 +744,68 @@ void promisc_disable() {
|
|||||||
promisc = false;
|
promisc = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MCU_VARIANT == MCU_LINUX
|
||||||
|
// On Linux we always use memory-mapped EEPROM
|
||||||
|
uint8_t* eeprom_mapping = NULL;
|
||||||
|
#endif
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
// And when using the C library we set it up from a file descriptor.
|
||||||
|
int eeprom_fd = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void eeprom_open(int size) {
|
||||||
|
#if MCU_VARIANT == MCU_ESP32
|
||||||
|
// This MCU needs EEPROIM to be begun
|
||||||
|
EEPROM.begin(size);
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
// We need to use file-backed EEPROM emulation
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
const char* eeprom_filename = "eeprom.dat";
|
||||||
|
// We need to be reentrant for restarts
|
||||||
|
if (eeprom_fd <= 0) {
|
||||||
|
eeprom_fd = open(eeprom_filename, O_RDWR | O_CREAT, 0644);
|
||||||
|
if (eeprom_fd <= 0) {
|
||||||
|
perror("Could not open EEPROM file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int status = ftruncate(eeprom_fd, size);
|
||||||
|
if (status != 0) {
|
||||||
|
perror("Could not set size of EEPROM file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
// Map EEPROM into RAM
|
||||||
|
eeprom_mapping = (uint8_t*) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, eeprom_fd, 0);
|
||||||
|
if (eeprom_mapping == NULL) {
|
||||||
|
perror("Could not map EEPROM file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
std::cerr << "Mapped " << eeprom_filename << " as FD " << eeprom_fd << " to address " << (void*)eeprom_mapping << " size " << size << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Skipping EEPROM reinitialization" << std::endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t eeprom_read(uint8_t addr) {
|
||||||
|
#if MCU_VARIANT == MCU_LINUX
|
||||||
|
if (!eeprom_mapping) {
|
||||||
|
throw std::runtime_error("Tried to read EEPROM before opening it!");
|
||||||
|
}
|
||||||
|
int mapped_address = eeprom_addr(addr);
|
||||||
|
return eeprom_mapping[mapped_address];
|
||||||
|
#else
|
||||||
|
return EEPROM.read(eeprom_addr(addr));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool eeprom_info_locked() {
|
bool eeprom_info_locked() {
|
||||||
uint8_t lock_byte = EEPROM.read(eeprom_addr(ADDR_INFO_LOCK));
|
#if MCU_VARIANT == MCU_LINUX
|
||||||
|
if (!eeprom_mapping) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
uint8_t lock_byte = eeprom_read(ADDR_INFO_LOCK);
|
||||||
if (lock_byte == INFO_LOCK_BYTE) {
|
if (lock_byte == INFO_LOCK_BYTE) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -560,34 +813,6 @@ bool eeprom_info_locked() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void eeprom_dump_info() {
|
|
||||||
for (int addr = ADDR_PRODUCT; addr <= ADDR_INFO_LOCK; addr++) {
|
|
||||||
uint8_t byte = EEPROM.read(eeprom_addr(addr));
|
|
||||||
escapedSerialWrite(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void eeprom_dump_config() {
|
|
||||||
for (int addr = ADDR_CONF_SF; addr <= ADDR_CONF_OK; addr++) {
|
|
||||||
uint8_t byte = EEPROM.read(eeprom_addr(addr));
|
|
||||||
escapedSerialWrite(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void eeprom_dump_all() {
|
|
||||||
for (int addr = 0; addr < EEPROM_RESERVED; addr++) {
|
|
||||||
uint8_t byte = EEPROM.read(eeprom_addr(addr));
|
|
||||||
escapedSerialWrite(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void kiss_dump_eeprom() {
|
|
||||||
Serial.write(FEND);
|
|
||||||
Serial.write(CMD_ROM_READ);
|
|
||||||
eeprom_dump_all();
|
|
||||||
Serial.write(FEND);
|
|
||||||
}
|
|
||||||
|
|
||||||
void eeprom_update(int mapped_addr, uint8_t byte) {
|
void eeprom_update(int mapped_addr, uint8_t byte) {
|
||||||
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
|
||||||
EEPROM.update(mapped_addr, byte);
|
EEPROM.update(mapped_addr, byte);
|
||||||
@ -596,8 +821,12 @@ void eeprom_update(int mapped_addr, uint8_t byte) {
|
|||||||
EEPROM.write(mapped_addr, byte);
|
EEPROM.write(mapped_addr, byte);
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
}
|
}
|
||||||
|
#elif MCU_VARIANT == MCU_LINUX
|
||||||
|
if (!eeprom_mapping) {
|
||||||
|
throw std::runtime_error("Tried to write EEPROM before opening it!");
|
||||||
|
}
|
||||||
|
eeprom_mapping[mapped_addr] = byte;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void eeprom_write(uint8_t addr, uint8_t byte) {
|
void eeprom_write(uint8_t addr, uint8_t byte) {
|
||||||
@ -615,32 +844,57 @@ void eeprom_erase() {
|
|||||||
hard_reset();
|
hard_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eeprom_lock_set() {
|
void eeprom_dump_info() {
|
||||||
if (EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)) == INFO_LOCK_BYTE) {
|
for (int addr = ADDR_PRODUCT; addr <= ADDR_INFO_LOCK; addr++) {
|
||||||
return true;
|
uint8_t byte = eeprom_read(addr);
|
||||||
} else {
|
escapedSerialWrite(byte);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void eeprom_dump_config() {
|
||||||
|
for (int addr = ADDR_CONF_SF; addr <= ADDR_CONF_OK; addr++) {
|
||||||
|
uint8_t byte = eeprom_read(addr);
|
||||||
|
escapedSerialWrite(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void eeprom_dump_all() {
|
||||||
|
for (int addr = 0; addr < EEPROM_RESERVED; addr++) {
|
||||||
|
uint8_t byte = eeprom_read(addr);
|
||||||
|
escapedSerialWrite(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kiss_dump_eeprom() {
|
||||||
|
Serial.write(FEND);
|
||||||
|
Serial.write(CMD_ROM_READ);
|
||||||
|
eeprom_dump_all();
|
||||||
|
Serial.write(FEND);
|
||||||
|
}
|
||||||
|
|
||||||
bool eeprom_product_valid() {
|
bool eeprom_product_valid() {
|
||||||
uint8_t rval = EEPROM.read(eeprom_addr(ADDR_PRODUCT));
|
uint8_t rval = eeprom_read(ADDR_PRODUCT);
|
||||||
|
|
||||||
#if PLATFORM == PLATFORM_AVR
|
#if PLATFORM == PLATFORM_AVR
|
||||||
if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW) {
|
if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW) {
|
||||||
#elif PLATFORM == PLATFORM_ESP32
|
#elif PLATFORM == PLATFORM_ESP32
|
||||||
if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW || rval == PRODUCT_TBEAM || rval == PRODUCT_T32_20 || rval == PRODUCT_T32_21) {
|
if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW || rval == PRODUCT_TBEAM || rval == PRODUCT_T32_20 || rval == PRODUCT_T32_21) {
|
||||||
|
#elif PLATFORM == PLATFORM_LINUX
|
||||||
|
if (rval == PRODUCT_HMBRW) {
|
||||||
#else
|
#else
|
||||||
if (false) {
|
if (false) {
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Unacceptable platform: " << std::hex << "0x" << (int)rval << std::dec << std::endl;
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eeprom_model_valid() {
|
bool eeprom_model_valid() {
|
||||||
model = EEPROM.read(eeprom_addr(ADDR_MODEL));
|
model = eeprom_read(ADDR_MODEL);
|
||||||
#if BOARD_MODEL == BOARD_RNODE
|
#if BOARD_MODEL == BOARD_RNODE
|
||||||
if (model == MODEL_A4 || model == MODEL_A9) {
|
if (model == MODEL_A4 || model == MODEL_A9) {
|
||||||
#elif BOARD_MODEL == BOARD_HMBRW
|
#elif BOARD_MODEL == BOARD_HMBRW
|
||||||
@ -660,15 +914,21 @@ bool eeprom_model_valid() {
|
|||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Unacceptable model: " << std::hex << "0x" << (int)model << std::dec << std::endl;
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eeprom_hwrev_valid() {
|
bool eeprom_hwrev_valid() {
|
||||||
hwrev = EEPROM.read(eeprom_addr(ADDR_HW_REV));
|
hwrev = eeprom_read(ADDR_HW_REV);
|
||||||
if (hwrev != 0x00 && hwrev != 0xFF) {
|
if (hwrev != 0x00 && hwrev != 0xFF) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
#if LIBRARY_TYPE == LIBRARY_C
|
||||||
|
std::cerr << "Unacceptable revision: " << std::hex << "0x" << (int)hwrev << std::dec << std::endl;
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -676,14 +936,14 @@ bool eeprom_hwrev_valid() {
|
|||||||
bool eeprom_checksum_valid() {
|
bool eeprom_checksum_valid() {
|
||||||
char *data = (char*)malloc(CHECKSUMMED_SIZE);
|
char *data = (char*)malloc(CHECKSUMMED_SIZE);
|
||||||
for (uint8_t i = 0; i < CHECKSUMMED_SIZE; i++) {
|
for (uint8_t i = 0; i < CHECKSUMMED_SIZE; i++) {
|
||||||
char byte = EEPROM.read(eeprom_addr(i));
|
char byte = eeprom_read(i);
|
||||||
data[i] = byte;
|
data[i] = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char *hash = MD5::make_hash(data, CHECKSUMMED_SIZE);
|
unsigned char *hash = MD5::make_hash(data, CHECKSUMMED_SIZE);
|
||||||
bool checksum_valid = true;
|
bool checksum_valid = true;
|
||||||
for (uint8_t i = 0; i < 16; i++) {
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
uint8_t stored_chk_byte = EEPROM.read(eeprom_addr(ADDR_CHKSUM+i));
|
uint8_t stored_chk_byte = eeprom_read(ADDR_CHKSUM+i);
|
||||||
uint8_t calced_chk_byte = (uint8_t)hash[i];
|
uint8_t calced_chk_byte = (uint8_t)hash[i];
|
||||||
if (stored_chk_byte != calced_chk_byte) {
|
if (stored_chk_byte != calced_chk_byte) {
|
||||||
checksum_valid = false;
|
checksum_valid = false;
|
||||||
@ -696,7 +956,7 @@ bool eeprom_checksum_valid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool eeprom_have_conf() {
|
bool eeprom_have_conf() {
|
||||||
if (EEPROM.read(eeprom_addr(ADDR_CONF_OK)) == CONF_OK_BYTE) {
|
if (eeprom_read(ADDR_CONF_OK) == CONF_OK_BYTE) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -705,11 +965,11 @@ bool eeprom_have_conf() {
|
|||||||
|
|
||||||
void eeprom_conf_load() {
|
void eeprom_conf_load() {
|
||||||
if (eeprom_have_conf()) {
|
if (eeprom_have_conf()) {
|
||||||
lora_sf = EEPROM.read(eeprom_addr(ADDR_CONF_SF));
|
lora_sf = eeprom_read(ADDR_CONF_SF);
|
||||||
lora_cr = EEPROM.read(eeprom_addr(ADDR_CONF_CR));
|
lora_cr = eeprom_read(ADDR_CONF_CR);
|
||||||
lora_txp = EEPROM.read(eeprom_addr(ADDR_CONF_TXP));
|
lora_txp = eeprom_read(ADDR_CONF_TXP);
|
||||||
lora_freq = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x03);
|
lora_freq = (uint32_t)eeprom_read(ADDR_CONF_FREQ+0x00) << 24 | (uint32_t)eeprom_read(ADDR_CONF_FREQ+0x01) << 16 | (uint32_t)eeprom_read(ADDR_CONF_FREQ+0x02) << 8 | (uint32_t)eeprom_read(ADDR_CONF_FREQ+0x03);
|
||||||
lora_bw = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x03);
|
lora_bw = (uint32_t)eeprom_read(ADDR_CONF_BW+0x00) << 24 | (uint32_t)eeprom_read(ADDR_CONF_BW+0x01) << 16 | (uint32_t)eeprom_read(ADDR_CONF_BW+0x02) << 8 | (uint32_t)eeprom_read(ADDR_CONF_BW+0x03);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,7 +1044,7 @@ inline void fifo_flush(FIFOBuffer *f) {
|
|||||||
f->head = f->tail;
|
f->head = f->tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MCU_VARIANT != MCU_ESP32
|
#if SERIAL_EVENTS == SERIAL_INTERRUPT
|
||||||
static inline bool fifo_isempty_locked(const FIFOBuffer *f) {
|
static inline bool fifo_isempty_locked(const FIFOBuffer *f) {
|
||||||
bool result;
|
bool result;
|
||||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||||
@ -866,7 +1126,7 @@ inline void fifo16_flush(FIFOBuffer16 *f) {
|
|||||||
f->head = f->tail;
|
f->head = f->tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MCU_VARIANT != MCU_ESP32
|
#if SERIAL_EVENTS == SERIAL_INTERRUPT
|
||||||
static inline bool fifo16_isempty_locked(const FIFOBuffer16 *f) {
|
static inline bool fifo16_isempty_locked(const FIFOBuffer16 *f) {
|
||||||
bool result;
|
bool result;
|
||||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||||
|
Loading…
Reference in New Issue
Block a user