opencom_xl_firmware/opencom_xl_firmware.ino
2024-10-16 21:43:16 +01:00

1564 lines
46 KiB
C++

// Copyright (C) 2024, Mark Qvist
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <Arduino.h>
#include <SPI.h>
#include "Utilities.h"
#if MCU_VARIANT == MCU_NRF52
#define INTERFACE_SPI
#if BOARD_MODEL == BOARD_OPENCOM_XL
// Required because on RAK4631, non-default SPI pins must be initialised when class is declared.
SPIClass interface_spi[1] = {
// SX1262
SPIClass(
NRF_SPIM2,
interface_pins[0][3],
interface_pins[0][1],
interface_pins[0][2]
)
};
#elif BOARD_MODEL == BOARD_TECHO
SPIClass interface_spi[1] = {
// SX1262
SPIClass(
NRF_SPIM3,
interface_pins[0][3],
interface_pins[0][1],
interface_pins[0][2]
)
};
#endif
#endif
#ifndef INTERFACE_SPI
// INTERFACE_SPI is only required on NRF52 platforms, as the SPI pins are set in the class constructor and not by a setter method.
// Even if custom SPI interfaces are not needed, the array must exist to prevent compilation errors.
#define INTERFACE_SPI
SPIClass interface_spi[1];
#endif
FIFOBuffer serialFIFO;
uint8_t serialBuffer[CONFIG_UART_BUFFER_SIZE+1];
uint16_t packet_starts_buf[(CONFIG_QUEUE_MAX_LENGTH)+1];
uint16_t packet_lengths_buf[(CONFIG_QUEUE_MAX_LENGTH)+1];
FIFOBuffer16 packet_starts[INTERFACE_COUNT];
FIFOBuffer16 packet_lengths[INTERFACE_COUNT];
volatile uint8_t queue_height[INTERFACE_COUNT] = {0};
volatile uint16_t queued_bytes[INTERFACE_COUNT] = {0};
volatile uint16_t queue_cursor[INTERFACE_COUNT] = {0};
volatile uint16_t current_packet_start[INTERFACE_COUNT] = {0};
volatile bool serial_buffering = false;
#if HAS_BLUETOOTH || HAS_BLE == true
bool bt_init_ran = false;
#endif
#if HAS_CONSOLE
#include "Console.h"
#endif
#define MODEM_QUEUE_SIZE 4*INTERFACE_COUNT
typedef struct {
size_t len;
int rssi;
int snr_raw;
uint8_t interface;
uint8_t data[];
} modem_packet_t;
static xQueueHandle modem_packet_queue = NULL;
char sbuf[128];
uint8_t *packet_queue[INTERFACE_COUNT];
void setup() {
#if MCU_VARIANT == MCU_ESP32
boot_seq();
EEPROM.begin(EEPROM_SIZE);
Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE);
#if BOARD_MODEL == BOARD_TDECK
pinMode(pin_poweron, OUTPUT);
digitalWrite(pin_poweron, HIGH);
pinMode(SD_CS, OUTPUT);
pinMode(DISPLAY_CS, OUTPUT);
digitalWrite(SD_CS, HIGH);
digitalWrite(DISPLAY_CS, HIGH);
pinMode(DISPLAY_BL_PIN, OUTPUT);
#endif
#endif
#if MCU_VARIANT == MCU_NRF52
if (!eeprom_begin()) {
Serial.write("EEPROM initialisation failed.\r\n");
}
#endif
// Seed the PRNG for CSMA R-value selection
# if MCU_VARIANT == MCU_ESP32
// On ESP32, get the seed value from the
// hardware RNG
int seed_val = (int)esp_random();
#else
// Otherwise, get a pseudo-random seed
// value from an unconnected analog pin
int seed_val = analogRead(0);
#endif
randomSeed(seed_val);
// Initialise serial communication
memset(serialBuffer, 0, sizeof(serialBuffer));
fifo_init(&serialFIFO, serialBuffer, CONFIG_UART_BUFFER_SIZE);
Serial.begin(serial_baudrate);
#if HAS_NP
led_init();
#endif
#if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_RNODE_NG_22 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_OPENCOM_XL
// Some boards need to wait until the hardware UART is set up before booting
// the full firmware. In the case of the RAK4631/TECHO, the line below will wait
// until a serial connection is actually established with a master. Thus, it
// is disabled on this platform.
while (!Serial);
#endif
// Configure input and output pins
#if HAS_INPUT
input_init();
#endif
#if HAS_BUZZER
pinMode(PIN_BUZZER, OUTPUT);
#endif
#if HAS_NP == false
pinMode(pin_led_rx, OUTPUT);
pinMode(pin_led_tx, OUTPUT);
#endif
for (int i = 0; i < INTERFACE_COUNT; i++) {
if (interface_pins[i][9] != -1) {
pinMode(interface_pins[i][9], OUTPUT);
digitalWrite(interface_pins[i][9], HIGH);
}
}
// Initialise buffers
memset(pbuf, 0, sizeof(pbuf));
memset(cmdbuf, 0, sizeof(cmdbuf));
memset(packet_starts_buf, 0, sizeof(packet_starts_buf));
memset(packet_lengths_buf, 0, sizeof(packet_starts_buf));
memset(seq, 0xFF, sizeof(seq));
modem_packet_queue = xQueueCreate(MODEM_QUEUE_SIZE, sizeof(modem_packet_t*));
for (int i = 0; i < INTERFACE_COUNT; i++) {
fifo16_init(&packet_starts[i], packet_starts_buf, CONFIG_QUEUE_MAX_LENGTH+1);
fifo16_init(&packet_lengths[i], packet_lengths_buf, CONFIG_QUEUE_MAX_LENGTH+1);
packet_queue[i] = (uint8_t*)malloc(getQueueSize(i)+1);
}
memset(packet_rdy_interfaces_buf, 0, sizeof(packet_rdy_interfaces_buf));
fifo_init(&packet_rdy_interfaces, packet_rdy_interfaces_buf, MAX_INTERFACES);
// add call to init_channel_stats here? \todo
// Create and configure interface objects
for (uint8_t i = 0; i < INTERFACE_COUNT; i++) {
switch (interfaces[i]) {
case SX126X:
case SX1262:
{
sx126x* obj;
// if default spi enabled
if (interface_cfg[i][0]) {
obj = new sx126x(i, &SPI, interface_cfg[i][1],
interface_cfg[i][2], interface_pins[i][0], interface_pins[i][1],
interface_pins[i][2], interface_pins[i][3], interface_pins[i][6],
interface_pins[i][5], interface_pins[i][4], interface_pins[i][8]);
}
else {
obj = new sx126x(i, &interface_spi[i], interface_cfg[i][1],
interface_cfg[i][2], interface_pins[i][0], interface_pins[i][1],
interface_pins[i][2], interface_pins[i][3], interface_pins[i][6],
interface_pins[i][5], interface_pins[i][4], interface_pins[i][8]);
}
interface_obj[i] = obj;
interface_obj_sorted[i] = obj;
break;
}
case SX127X:
case SX1276:
case SX1278:
{
sx127x* obj;
// if default spi enabled
if (interface_cfg[i][0]) {
obj = new sx127x(i, &SPI, interface_pins[i][0],
interface_pins[i][1], interface_pins[i][2], interface_pins[i][3],
interface_pins[i][6], interface_pins[i][5], interface_pins[i][4]);
}
else {
obj = new sx127x(i, &interface_spi[i], interface_pins[i][0],
interface_pins[i][1], interface_pins[i][2], interface_pins[i][3],
interface_pins[i][6], interface_pins[i][5], interface_pins[i][4]);
}
interface_obj[i] = obj;
interface_obj_sorted[i] = obj;
break;
}
case SX128X:
case SX1280:
{
sx128x* obj;
// if default spi enabled
if (interface_cfg[i][0]) {
obj = new sx128x(i, &SPI, interface_cfg[i][1],
interface_pins[i][0], interface_pins[i][1], interface_pins[i][2],
interface_pins[i][3], interface_pins[i][6], interface_pins[i][5],
interface_pins[i][4], interface_pins[i][8], interface_pins[i][7]);
}
else {
obj = new sx128x(i, &interface_spi[i], interface_cfg[i][1],
interface_pins[i][0], interface_pins[i][1], interface_pins[i][2],
interface_pins[i][3], interface_pins[i][6], interface_pins[i][5],
interface_pins[i][4], interface_pins[i][8], interface_pins[i][7]);
}
interface_obj[i] = obj;
interface_obj_sorted[i] = obj;
break;
}
default:
break;
}
}
// Check installed transceiver chip(s) and probe boot parameters. If any of
// the configured modems cannot be initialised, do not boot
for (int i = 0; i < INTERFACE_COUNT; i++) {
switch (interfaces[i]) {
case SX126X:
case SX1262:
case SX127X:
case SX1276:
case SX1278:
case SX128X:
case SX1280:
selected_radio = interface_obj[i];
break;
default:
modems_installed = false;
break;
}
if (selected_radio->preInit()) {
modems_installed = true;
uint32_t lfr = selected_radio->getFrequency();
if (lfr == 0) {
// Normal boot
} else if (lfr == M_FRQ_R) {
// Quick reboot
#if HAS_CONSOLE
if (rtc_get_reset_reason(0) == POWERON_RESET) {
console_active = true;
}
#endif
} else {
// Unknown boot
}
selected_radio->setFrequency(M_FRQ_S);
} else {
modems_installed = false;
}
if (!modems_installed) {
break;
}
}
#if HAS_DISPLAY
#if HAS_EEPROM
if (EEPROM.read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) {
#elif MCU_VARIANT == MCU_NRF52
if (eeprom_read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) {
#endif
eeprom_update(eeprom_addr(ADDR_CONF_DSET), CONF_OK_BYTE);
eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0xFF);
}
#if DISPLAY == EINK_BW || DISPLAY == EINK_3C
// Poll and process incoming serial commands whilst e-ink display is
// refreshing to make device still seem responsive
display_add_callback(process_serial);
#endif
disp_ready = display_init();
update_display();
#endif
#if HAS_PMU == true
pmu_ready = init_pmu();
#endif
#if HAS_BLUETOOTH || HAS_BLE == true
bt_init();
bt_init_ran = true;
#endif
if (console_active) {
#if HAS_CONSOLE
console_start();
#else
kiss_indicate_reset();
#endif
} else {
kiss_indicate_reset();
}
// Validate board health, EEPROM and config
validate_status();
}
void lora_receive(RadioInterface* radio) {
if (!implicit) {
radio->receive();
} else {
radio->receive(implicit_l);
}
}
inline void kiss_write_packet(int index) {
// We need to convert the interface index to the command byte representation
uint8_t cmd_byte = getInterfaceCommandByte(index);
serial_write(FEND);
// Add index of interface the packet came from
serial_write(cmd_byte);
for (uint16_t i = 0; i < read_len; i++) {
#if MCU_VARIANT == MCU_NRF52
portENTER_CRITICAL();
uint8_t byte = pbuf[i];
portEXIT_CRITICAL();
#else
uint8_t byte = pbuf[i];
#endif
if (byte == FEND) { serial_write(FESC); byte = TFEND; }
if (byte == FESC) { serial_write(FESC); byte = TFESC; }
serial_write(byte);
}
serial_write(FEND);
read_len = 0;
#if MCU_VARIANT == MCU_ESP32 && HAS_BLE
bt_flush();
#endif
}
inline void getPacketData(RadioInterface* radio, uint16_t len) {
#if MCU_VARIANT != MCU_NRF52
while (len-- && read_len < MTU) {
pbuf[read_len++] = radio->read();
}
#else
BaseType_t int_mask = taskENTER_CRITICAL_FROM_ISR();
while (len-- && read_len < MTU) {
pbuf[read_len++] = radio->read();
}
taskEXIT_CRITICAL_FROM_ISR(int_mask);
#endif
}
inline bool queue_packet(RadioInterface* radio, uint8_t index) {
// Allocate packet struct, but abort if there
// is not enough memory available.
modem_packet_t *modem_packet = (modem_packet_t*)malloc(sizeof(modem_packet_t) + read_len);
if(!modem_packet) { memory_low = true; return false; }
// Get packet RSSI and SNR
modem_packet->snr_raw = radio->packetSnrRaw();
// Pass raw SNR to get RSSI as SX127X driver requires it for calculations
modem_packet->rssi = radio->packetRssi(modem_packet->snr_raw);
modem_packet->interface = index;
// Send packet to event queue, but free the
// allocated memory again if the queue is
// unable to receive the packet.
modem_packet->len = read_len;
memcpy(modem_packet->data, pbuf, read_len);
if (!modem_packet_queue || xQueueSendFromISR(modem_packet_queue, &modem_packet, NULL) != pdPASS) {
free(modem_packet);
return false;