Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e254b7a
added narrowband radiolib interface
tillx4 Feb 27, 2026
215438a
fixing cmake needing requirement
tillx4 Feb 27, 2026
c646b4d
added some reasonable init parameters, implemented narrowband_thread …
tillx4 Feb 27, 2026
6dba473
upadte header file
tillx4 Feb 27, 2026
44af46d
implemented basic thread safe command queue, refractored narrowband m…
tillx4 Mar 3, 2026
d7bea1f
fix: interrupt handler for receiving narrowband packets is now ISR an…
tillx4 Mar 3, 2026
10c0e3f
fixed narroband header file
tillx4 Mar 3, 2026
8302c01
refractored narrowband class, better interface using freertos queues …
tillx4 Mar 11, 2026
dc5f49e
implemented interrupt based receive via freertos task notifications (…
tillx4 Mar 12, 2026
8a0ee6c
finished sketch for interrupt based send and receive with freeRTOS no…
tillx4 Mar 15, 2026
b00b9c4
implemented freertos queues for sensor data and received commands
tillx4 Mar 17, 2026
5bc1582
Merge branch 'bears-space:main' into narrowband
tillx4 Mar 24, 2026
0cc1259
start rxtx task in nb_radio.init()
tillx4 Mar 24, 2026
5286e2f
added init code to main to start nb module
tillx4 Mar 24, 2026
5fee59c
started working on fragmentation, UNFINSIHED
tillx4 Apr 7, 2026
8c0e225
comments
tillx4 Apr 14, 2026
2ea2262
finished pack_message for narrowband tp
tillx4 Apr 14, 2026
088b1d1
added narrowband to the correct module
tillx4 Apr 14, 2026
b923912
implemented pack and unpack message functions for transport layer
tillx4 Apr 14, 2026
aba95f0
added some todo comments
tillx4 Apr 14, 2026
1254aec
Merge remote-tracking branch 'comms/narrowband' into feature/narrowband
tillx4 Apr 16, 2026
7758fa4
refractored unpack and pack to use span instead of std::array, pack i…
tillx4 Apr 16, 2026
5faa49b
implemented message fragmentation
tillx4 Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
idf_component_register(
SRCS "main.c"
SRCS "main.c" "narrowband.cpp"
INCLUDE_DIRS "."
REQUIRES vigilant_engine
REQUIRES vigilant_engine esp_timer driver
)

# Ensure web UI is built before compiling the firmware
Expand Down
322 changes: 322 additions & 0 deletions main/EspHal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
#ifndef ESP_HAL_H
#define ESP_HAL_H

// include RadioLib
#include <RadioLib.h>

// this example only works on ESP32 and is unlikely to work on ESP32S2/S3 etc.
// if you need high portability, you should probably use Arduino anyway ...
#if CONFIG_IDF_TARGET_ESP32 == 0
#error This example HAL only supports ESP32 targets. Support for ESP32S2/S3 etc. can be added by adjusting this file to user needs.
#endif

// include all the dependencies
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp32/rom/gpio.h"
#include "soc/rtc.h"
#include "soc/dport_reg.h"
#include "soc/spi_reg.h"
#include "soc/spi_struct.h"
#include "driver/gpio.h"
#include "hal/gpio_hal.h"
#include "esp_timer.h"
#include "esp_log.h"

// define Arduino-style macros
#define LOW (0x0)
#define HIGH (0x1)
#define INPUT (0x01)
#define OUTPUT (0x03)
#define RISING (0x01)
#define FALLING (0x02)
#define NOP() asm volatile ("nop")

#define MATRIX_DETACH_OUT_SIG (0x100)
#define MATRIX_DETACH_IN_LOW_PIN (0x30)

// all of the following is needed to calculate SPI clock divider
#define ClkRegToFreq(reg) (apb_freq / (((reg)->clkdiv_pre + 1) * ((reg)->clkcnt_n + 1)))

typedef union {
uint32_t value;
struct {
uint32_t clkcnt_l: 6;
uint32_t clkcnt_h: 6;
uint32_t clkcnt_n: 6;
uint32_t clkdiv_pre: 13;
uint32_t clk_equ_sysclk: 1;
};
} spiClk_t;

uint32_t getApbFrequency() {
rtc_cpu_freq_config_t conf;
rtc_clk_cpu_freq_get_config(&conf);

if(conf.freq_mhz >= 80) {
return(80 * MHZ);
}

return((conf.source_freq_mhz * MHZ) / conf.div);
}

uint32_t spiFrequencyToClockDiv(uint32_t freq) {
uint32_t apb_freq = getApbFrequency();
if(freq >= apb_freq) {
return SPI_CLK_EQU_SYSCLK;
}

const spiClk_t minFreqReg = { 0x7FFFF000 };
uint32_t minFreq = ClkRegToFreq((spiClk_t*) &minFreqReg);
if(freq < minFreq) {
return minFreqReg.value;
}

uint8_t calN = 1;
spiClk_t bestReg = { 0 };
int32_t bestFreq = 0;
while(calN <= 0x3F) {
spiClk_t reg = { 0 };
int32_t calFreq;
int32_t calPre;
int8_t calPreVari = -2;

reg.clkcnt_n = calN;

while(calPreVari++ <= 1) {
calPre = (((apb_freq / (reg.clkcnt_n + 1)) / freq) - 1) + calPreVari;
if(calPre > 0x1FFF) {
reg.clkdiv_pre = 0x1FFF;
} else if(calPre <= 0) {
reg.clkdiv_pre = 0;
} else {
reg.clkdiv_pre = calPre;
}
reg.clkcnt_l = ((reg.clkcnt_n + 1) / 2);
calFreq = ClkRegToFreq(&reg);
if(calFreq == (int32_t) freq) {
memcpy(&bestReg, &reg, sizeof(bestReg));
break;
} else if(calFreq < (int32_t) freq) {
if(RADIOLIB_ABS(freq - calFreq) < RADIOLIB_ABS(freq - bestFreq)) {
bestFreq = calFreq;
memcpy(&bestReg, &reg, sizeof(bestReg));
}
}
}
if(calFreq == (int32_t) freq) {
break;
}
calN++;
}
return(bestReg.value);
}

// create a new ESP-IDF hardware abstraction layer
// the HAL must inherit from the base RadioLibHal class
// and implement all of its virtual methods
// this is pretty much just copied from Arduino ESP32 core
class EspHal : public RadioLibHal {
public:
// default constructor - initializes the base HAL and any needed private members
EspHal(int8_t sck, int8_t miso, int8_t mosi)
: RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING),
spiSCK(sck), spiMISO(miso), spiMOSI(mosi) {
}

void init() override {
// we only need to init the SPI here
spiBegin();
}

void term() override {
// we only need to stop the SPI here
spiEnd();
}

// GPIO-related methods (pinMode, digitalWrite etc.) should check
// RADIOLIB_NC as an alias for non-connected pins
void pinMode(uint32_t pin, uint32_t mode) override {
if(pin == RADIOLIB_NC) {
return;
}

gpio_hal_context_t gpiohal;
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);

gpio_config_t conf = {
.pin_bit_mask = (1ULL<<pin),
.mode = (gpio_mode_t)mode,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[pin].int_type,
};
gpio_config(&conf);
}

void digitalWrite(uint32_t pin, uint32_t value) override {
if(pin == RADIOLIB_NC) {
return;
}

gpio_set_level((gpio_num_t)pin, value);
}

uint32_t digitalRead(uint32_t pin) override {
if(pin == RADIOLIB_NC) {
return(0);
}

return(gpio_get_level((gpio_num_t)pin));
}

void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override {
if(interruptNum == RADIOLIB_NC) {
return;
}

gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7));

// this uses function typecasting, which is not defined when the functions have different signatures
// untested and might not work
gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL);
}

void detachInterrupt(uint32_t interruptNum) override {
if(interruptNum == RADIOLIB_NC) {
return;
}

gpio_isr_handler_remove((gpio_num_t)interruptNum);
gpio_wakeup_disable((gpio_num_t)interruptNum);
gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE);
}

void delay(unsigned long ms) override {
vTaskDelay(ms / portTICK_PERIOD_MS);
}

void delayMicroseconds(unsigned long us) override {
uint64_t m = (uint64_t)esp_timer_get_time();
if(us) {
uint64_t e = (m + us);
if(m > e) { // overflow
while((uint64_t)esp_timer_get_time() > e) {
NOP();
}
}
while((uint64_t)esp_timer_get_time() < e) {
NOP();
}
}
}

unsigned long millis() override {
return((unsigned long)(esp_timer_get_time() / 1000ULL));
}

unsigned long micros() override {
return((unsigned long)(esp_timer_get_time()));
}

long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
if(pin == RADIOLIB_NC) {
return(0);
}

this->pinMode(pin, INPUT);
uint32_t start = this->micros();
uint32_t curtick = this->micros();

while(this->digitalRead(pin) == state) {
if((this->micros() - curtick) > timeout) {
return(0);
}
}

return(this->micros() - start);
}

void spiBegin() {
// enable peripheral
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN);
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST);

// reset the control struct
this->spi->slave.trans_done = 0;
this->spi->slave.val = 0;
this->spi->pin.val = 0;
this->spi->user.val = 0;
this->spi->user1.val = 0;
this->spi->ctrl.val = 0;
this->spi->ctrl1.val = 0;
this->spi->ctrl2.val = 0;
this->spi->clock.val = 0;
this->spi->user.usr_mosi = 1;
this->spi->user.usr_miso = 1;
this->spi->user.doutdin = 1;
for(uint8_t i = 0; i < 16; i++) {
this->spi->data_buf[i] = 0x00000000;
}

// set SPI mode 0
this->spi->pin.ck_idle_edge = 0;
this->spi->user.ck_out_edge = 0;

// set bit order to MSB first
this->spi->ctrl.wr_bit_order = 0;
this->spi->ctrl.rd_bit_order = 0;

// set the clock
this->spi->clock.val = spiFrequencyToClockDiv(2000000);

// initialize pins
this->pinMode(this->spiSCK, OUTPUT);
this->pinMode(this->spiMISO, INPUT);
this->pinMode(this->spiMOSI, OUTPUT);
gpio_matrix_out(this->spiSCK, HSPICLK_OUT_IDX, false, false);
gpio_matrix_in(this->spiMISO, HSPIQ_OUT_IDX, false);
gpio_matrix_out(this->spiMOSI, HSPID_IN_IDX, false, false);
}

void spiBeginTransaction() {
// not needed - in ESP32 Arduino core, this function
// repeats clock div, mode and bit order configuration
}

uint8_t spiTransferByte(uint8_t b) {
this->spi->mosi_dlen.usr_mosi_dbitlen = 7;
this->spi->miso_dlen.usr_miso_dbitlen = 7;
this->spi->data_buf[0] = b;
this->spi->cmd.usr = 1;
while(this->spi->cmd.usr);
return(this->spi->data_buf[0] & 0xFF);
}

void spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
for(size_t i = 0; i < len; i++) {
in[i] = this->spiTransferByte(out[i]);
}
}

void spiEndTransaction() {
// nothing needs to be done here
}

void spiEnd() {
// detach pins
gpio_matrix_out(this->spiSCK, MATRIX_DETACH_OUT_SIG, false, false);
gpio_matrix_in(this->spiMISO, MATRIX_DETACH_IN_LOW_PIN, false);
gpio_matrix_out(this->spiMOSI, MATRIX_DETACH_OUT_SIG, false, false);
}

private:
// the HAL can contain any additional private members
int8_t spiSCK;
int8_t spiMISO;
int8_t spiMOSI;
spi_dev_t * spi = (volatile spi_dev_t *)(DR_REG_SPI2_BASE);
};

#endif
17 changes: 17 additions & 0 deletions main/idf_component.yml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to set the idf version >6.0.0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah, that's an artifact because the branch is based on an old version of vigilant engine.
all of this is work in progress btw, just wanted to create a PR to have some overview of my changes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea no worries, i thought that ^^

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
jgromes/radiolib: ^7.6.0
9 changes: 9 additions & 0 deletions main/main.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<<<<<<< HEAD
#include <unistd.h>
#include "esp_log.h"
#include "vigilant.h"
#include "status_led.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "message.h"
#include "narrowband.h"

// static const char *TAG = "app_main";

void app_main(void)
Expand Down Expand Up @@ -35,4 +39,9 @@ void app_main(void)
ESP_LOGI("app_main i2c test", "reg 0x27 value: 0x%02X", value);
ESP_ERROR_CHECK(vigilant_i2c_remove_device(&device)); // Removing a device
}

// init narrowband communication
QueueHandle_t commandQueue = xQueueCreate(10, sizeof(message_t)); // for now we initialize the queues to 10 elements, perhaps subject to change
QueueHandle_t sensorDataQueue = xQueueCreate(10, sizeof(message_t));
init_narrowband(&commandQueue, &sensorDataQueue);
}
15 changes: 15 additions & 0 deletions main/message.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

struct message_t {
uint8_t *data; // max packet size for LLCC68 is 255 bytes, and 254 with address filtering, but we don't use address filtering
size_t length;
};

#ifdef __cplusplus
}
#endif
Loading