Update RadioLib

This commit is contained in:
lewisxhe 2026-04-20 18:00:37 +08:00
commit 16bc3e2b42
46 changed files with 1102 additions and 97 deletions

View file

@ -0,0 +1,46 @@
name: "Static Memory Management Test"
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
jobs:
static-memory:
name: Build Arduino and non-Arduino with enabled static memory management
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies for unit test
run: |
sudo apt-get update
sudo apt-get install -y libboost-all-dev libfmt-dev lcov
- name: Run unit test for static memory management
run: |
cd extras/test/unit
./test.sh -DRADIOLIB_STATIC_ONLY
- name: Install arduino-cli
run:
|
mkdir -p ~/.local/bin
echo "~/.local/bin" >> $GITHUB_PATH
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh
- name: Install platform
run:
|
arduino-cli core update-index
arduino-cli core install arduino:avr
- name: Build Arduino example
run:
arduino-cli compile --libraries /home/runner/work/RadioLib --fqbn arduino:avr:mega $PWD/examples/SX123x/SX123x_Transmit_Blocking/SX123x_Transmit_Blocking.ino --warnings=all --build-property compiler.cpp.extra_flags="-DRADIOLIB_STATIC_ONLY"

View file

@ -16,6 +16,19 @@ Issues with generic titles (e.g. "not working", "lora", etc.) will be **CLOSED**
4. **Issues deserve some attention too.**
Issues that are left for 2 weeks without response by the original author when asked for further information will be closed due to inactivity. This is to keep track of important issues, the author is encouraged to reopen the issue at a later date.
## AI Use
**RadioLib was written by humans, for humans**. Its authors, contributors, maintainers and community members were not asked for permission or approval when billion-dollar companies decided to scrape our hard work for training data, to be sold back to the world at a profit. While we consider this approach to be bordering on violating our license, this seems to be the world we live in. Therefore, if you decide to contribute to RadioLib while using an AI-based tool, please follow the rules below.
1. **Mark the slop**
The issue or pull request must be clearly marked as fully or partially AI-generated. If you don't do it, we'll do it for you by the means of a dedicated label - and then consider whether to process your input at all. Since people like that have not even bothered to read this statement, it would be unreasonable to expect they will read the code the machine generated and that is not code we want.
2. **Less is more**
If your AI has produced a novel-length wall of text to describe a miniscule change, we consider it somewhat rude to copy-paste that output into the issue/PR description. Not only are you ignoring the templates which are asking for generally useful info; this approach also has the added bonus of explaining our own codebase back to us, while trying to sound as simple as possible. In reality though, it sounds like a junior developer giving us a lecture on a project that is older then they are. So please, try to get the actual point across in your own, human-generated words.
3. **Discuss first**
It is usually not a great idea to hit us with a large, unsolicited pull request. Especially if it changes APIs or tries to work around something. It's a lot easier to discuss these things in advance and agree on an approach in an issue. Your AI can write the PR quickly; but then we need to spend our free time on reviewing something that we did not ask for.
RadioLib maintainers reserve the right to reject your contribution if it is not following these rules without further explanation other than directing you to this guideline document.
## Code style guidelines
I like pretty code! Or at least, I like *consistent* code style. When creating pull requests, please follow these style guidelines, they're in place to keep high code readability.

View file

@ -0,0 +1,141 @@
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
import argparse
import sys
import zmq
import serial
import threading
import queue
import time
from argparse import RawTextHelpFormatter
# default settings
DEFAULT_BAUDRATE = 115200
DEFAULT_SERVER_PORT = 30002
# marker to filter out ADS-B messages from the rest of the serial stream
ADSB_MESSAGE_MARKER = '[ADS-B]'
class SerialToZMQBridge:
def __init__(self, serial_port, baudrate, zmq_host="0.0.0.0", zmq_port=5555):
self.serial_port = serial_port
self.baudrate = baudrate
self.zmq_host = zmq_host
self.zmq_port = zmq_port
self.context = zmq.Context()
self.socket = self.context.socket(zmq.STREAM)
self.socket.setsockopt(zmq.LINGER, 0)
self.socket.bind(f"tcp://{self.zmq_host}:{self.zmq_port}")
self.serial = serial.Serial(
port=self.serial_port,
baudrate=self.baudrate,
timeout=1
)
self.clients = set()
self.serial_queue = queue.Queue()
self.running = True
def serial_reader(self):
"""Continuously read from serial and queue messages."""
while self.running:
try:
line = self.serial.readline()
if line:
line = line.decode(errors='ignore').strip()
print(f"[SERIAL RX] {line}")
# read the ADS-B frames and add the markers expected by pyModeS
if ADSB_MESSAGE_MARKER in line:
msg = '*' + line.split(' ')[1].strip() + ';'
self.serial_queue.put(msg.encode('utf-8'))
except Exception as e:
print(f"Serial read error: {e}")
time.sleep(1)
def run(self):
print(f"ZMQ STREAM server listening on tcp://{self.zmq_host}:{self.zmq_port}")
print(f"Listening to serial port {self.serial_port} @ {self.baudrate}")
threading.Thread(target=self.serial_reader, daemon=True).start()
poller = zmq.Poller()
poller.register(self.socket, zmq.POLLIN)
try:
while self.running:
# Poll ZMQ socket
events = dict(poller.poll(100))
if self.socket in events:
identity, message = self.socket.recv_multipart()
if message == b'':
# Connection event
print(f"[ZMQ] Client connected/disconnected: {identity}")
self.clients.add(identity)
continue
print(f"[ZMQ RX] {identity}: {message.decode(errors='ignore')}")
# Send serial data to all connected clients
while not self.serial_queue.empty():
data = self.serial_queue.get()
for client_id in list(self.clients):
try:
self.socket.send_multipart([client_id, data])
print(f"[ZMQ TX] Sent to {client_id}")
except zmq.ZMQError:
self.clients.discard(client_id)
except KeyboardInterrupt:
print("Shutting down...")
finally:
self.running = False
self.serial.close()
self.socket.close()
self.context.term()
def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''
RadioLib ADS-B Monitor script. Serves as server for "modeslive" live traffic decoder from pyModeS
(https://github.com/junzis/pyModeS).
Depends on pyserial and pyModeS, install by:
'python3 -m pip install pyserial pyModeS'
Step-by-step guide on how to use the script:
1. Upload the ADSB_Monitor example to your Arduino board with LR2021 connected.
2. Run the script with appropriate arguments.
3. Run "modeslive --source net --connect localhost 30002 raw"
''')
parser.add_argument('port',
type=str,
help='COM port to connect to the device')
parser.add_argument('--speed',
default=DEFAULT_BAUDRATE,
type=int,
help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
parser.add_argument('--server-port',
default=DEFAULT_SERVER_PORT,
type=int,
help=f'server port to be used by modeslive (defaults to {DEFAULT_SERVER_PORT})')
args = parser.parse_args()
bridge = SerialToZMQBridge(
serial_port=args.port,
baudrate=args.speed,
zmq_port=args.server_port
)
bridge.run()
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,50 @@
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
import argparse
import numpy as np
from PIL import Image
from argparse import RawTextHelpFormatter
def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''
RadioLib image to array conversion tool.
Input is a PNG image to be transmitted via RadioLib SSTV.
The image must have correct size for the chose SSTV mode!
Output is a file (by default named "img.h") which can be included and transmitted.
The resulting array will be very large (typically 320 kB),
make sure your platform has sufficient Flash/RAM space.
''')
parser.add_argument('input',
type=str,
help='Input PNG file')
parser.add_argument('output',
type=str,
nargs='?',
default='img',
help='Output header file')
args = parser.parse_args()
outfile = f'{args.output}.h'
print(f'Converting "{args.input}" to "{outfile}"')
# open the image as numpy array
img = Image.open(args.input)
arr = np.array(img)
# open the output file
with open(outfile, 'w') as f:
print(f'const uint32_t img[{arr.shape[0]}][{arr.shape[1]}] = {{', file=f)
for row in arr:
print(' { ', end='', file=f)
for pix in row:
rgb = pix[0] << 16 | pix[1] << 8 | pix[2]
print(hex(rgb), end=', ', file=f)
print(' },', file=f)
print('};', file=f)
print('Done!')
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,176 @@
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
import argparse
import serial
import sys
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from datetime import datetime
from argparse import RawTextHelpFormatter
# number of samples in each scanline
SCAN_WIDTH = 33
# scanline Serial start/end markers
SCAN_MARK_START = 'SCAN '
SCAN_MARK_FREQ = 'FREQ '
SCAN_MARK_END = ' END'
# output path
OUT_PATH = 'out'
# default settings
DEFAULT_BAUDRATE = 115200
DEFAULT_COLOR_MAP = 'viridis'
DEFAULT_SCAN_LEN = 200
DEFAULT_RSSI_OFFSET = -11
# Print iterations progress
# from https://stackoverflow.com/questions/3173320/text-progress-bar-in-terminal-with-block-characters
def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 50, fill = '', printEnd = "\r"):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
bar = fill * filledLength + '-' * (length - filledLength)
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd)
if iteration == total:
print()
def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''
RadioLib SX126x_Spectrum_Scan plotter script. Displays output from SX126x_Spectrum_Scan example
as grayscale and
Depends on pyserial and matplotlib, install by:
'python3 -m pip install pyserial matplotlib'
Step-by-step guide on how to use the script:
1. Upload the SX126x_Spectrum_Scan example to your Arduino board with SX1262 connected.
2. Run the script with appropriate arguments.
3. Once the scan is complete, output files will be saved to out/
''')
parser.add_argument('port',
type=str,
help='COM port to connect to the device')
parser.add_argument('--speed',
default=DEFAULT_BAUDRATE,
type=int,
help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
parser.add_argument('--map',
default=DEFAULT_COLOR_MAP,
type=str,
help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")')
parser.add_argument('--len',
default=DEFAULT_SCAN_LEN,
type=int,
help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
parser.add_argument('--offset',
default=DEFAULT_RSSI_OFFSET,
type=int,
help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
parser.add_argument('--freq',
default=-1,
type=float,
help=f'Default starting frequency in MHz')
args = parser.parse_args()
freq_mode = False
scan_len = args.len
if (args.freq != -1):
freq_mode = True
scan_len = 1000
# create the color map and the result array
arr = np.zeros((SCAN_WIDTH, scan_len))
# scanline counter
row = 0
# list of frequencies in frequency mode
freq_list = []
# open the COM port
with serial.Serial(args.port, args.speed, timeout=None) as com:
while(True):
# update the progress bar
if not freq_mode:
printProgressBar(row, scan_len)
# read a single line
try:
line = com.readline().decode('utf-8')
except:
continue
if SCAN_MARK_FREQ in line:
new_freq = float(line.split(' ')[1])
if (len(freq_list) > 1) and (new_freq < freq_list[-1]):
break
freq_list.append(new_freq)
print('{:.3f}'.format(new_freq), end = '\r')
continue
# check the markers
if (SCAN_MARK_START in line) and (SCAN_MARK_END in line):
# get the values
scanline = line[len(SCAN_MARK_START):-len(SCAN_MARK_END)].split(',')
for col in range(SCAN_WIDTH):
arr[col][row] = int(scanline[col])
# increment the row counter
row = row + 1
# check if we're done
if (not freq_mode) and (row >= scan_len):
break
# scale to the number of scans (sum of any given scanline)
num_samples = arr.sum(axis=0)[0]
arr *= (num_samples/arr.max())
if freq_mode:
scan_len = len(freq_list)
# create the figure
fig, ax = plt.subplots()
# display the result as heatmap
extent = [0, scan_len, -4*(SCAN_WIDTH + 1), args.offset]
if freq_mode:
extent[0] = freq_list[0]
extent[1] = freq_list[-1]
im = ax.imshow(arr[:,:scan_len], cmap=args.map, extent=extent)
fig.colorbar(im)
# set some properites and show
timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S')
title = f'RadioLib SX126x Spectral Scan {timestamp}'
if freq_mode:
plt.xlabel("Frequency [Hz]")
else:
plt.xlabel("Time [sample]")
plt.ylabel("RSSI [dBm]")
ax.set_aspect('auto')
fig.suptitle(title)
fig.canvas.manager.set_window_title(title)
plt.savefig(f'{OUT_PATH}/{title.replace(" ", "_")}.png', dpi=300)
plt.show()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,18 @@
#!/bin/bash
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <path to check>"
exit 1
fi
path=$1
cppcheck --version
cppcheck $path --enable=all \
--force \
--inline-suppr \
--suppress=ConfigurationNotChecked \
--suppress=unusedFunction \
--suppress=missingIncludeSystem \
--suppress=missingInclude \
--quiet

View file

@ -0,0 +1,27 @@
#! /bin/bash
file=cppcheck.txt
cppcheck --version
cppcheck src --enable=all \
--force \
--inline-suppr \
--suppress=ConfigurationNotChecked \
--suppress=unusedFunction \
--suppress=missingIncludeSystem \
--suppress=missingInclude \
--quiet >> $file 2>&1
echo "Cppcheck finished with exit code $?"
error=$(grep ": error:" $file | wc -l)
warning=$(grep ": warning:" $file | wc -l)
style=$(grep ": style:" $file | wc -l)
performance=$(grep ": performance:" $file | wc -l)
echo "found $error erros, $warning warnings, $style style and $performance performance issues"
if [ $error -gt "0" ] || [ $warning -gt "0" ] || [ $style -gt "0" ] || [ $performance -gt "0" ]
then
cat $file
exitcode=1
fi
rm $file
exit $exitcode

View file

@ -0,0 +1,22 @@
#include "<module_name>.h"
#if !defined(RADIOLIB_EXCLUDE_<module_name>)
<module_name>::<module_name>(Module* mod) {
/*
Constructor implementation MUST assign the provided "mod" pointer to the private "_mod" pointer.
*/
_mod = mod;
}
int16_t <module_name>::begin() {
/*
"begin" method implementation MUST call the "init" method with appropriate settings.
*/
_mod->init();
/*
"begin" method SHOULD implement some sort of mechanism to verify the connection between Arduino and the module.
For example, reading a version register
*/
}

View file

@ -0,0 +1,99 @@
/*
RadioLib Module Template header file
Before opening pull request, please make sure that:
1. All files MUST be compiled without errors using default Arduino IDE settings.
2. All files SHOULD be compiled without warnings with compiler warnings set to "All".
3. Example sketches MUST be working correctly and MUST be stable enough to run for prolonged periods of time.
4. Writing style SHOULD be consistent.
5. Comments SHOULD be in place for the most important chunks of code and SHOULD be free of typos.
6. To indent, 2 spaces MUST be used.
If at any point you are unsure about the required style, please refer to the rest of the modules.
*/
#if !defined(_RADIOLIB_<module_name>_H) && !defined(RADIOLIB_EXCLUDE_<module_name>)
#if !defined(_RADIOLIB_<module_name>_H)
#define _RADIOLIB_<module_name>_H
/*
Header file for each module MUST include Module.h and TypeDef.h in the src folder.
The header file MAY include additional header files.
*/
#include "../../Module.h"
#include "../../TypeDef.h"
/*
Only use the following include if the module implements methods for OSI physical layer control.
This concerns only modules similar to SX127x/RF69/CC1101 etc.
In this case, your class MUST implement all virtual methods of PhysicalLayer class.
*/
//#include "../../protocols/PhysicalLayer/PhysicalLayer.h"
/*
Register map
Definition of SPI register map SHOULD be placed here. The register map SHOULD have two parts:
1 - Address map: only defines register names and addresses. Register names MUST match names in
official documentation (datasheets etc.).
2 - Variable map: defines variables inside register. This functions as a bit range map for a specific register.
Bit range (MSB and LSB) as well as short description for each variable MUST be provided in a comment.
See RF69 and SX127x header files for examples of register maps.
*/
// <module_name> register map | spaces up to this point
#define RADIOLIB_<module_name>_REG_<register_name> 0x00
// <module_name>_REG_<register_name> MSB LSB DESCRIPTION
#define RADIOLIB_<module_name>_<register_variable> 0b00000000 // 7 0 <description>
/*
Module class definition
The module class MAY inherit from the following classes:
1 - PhysicalLayer: In case the module implements methods for OSI physical layer control (e.g. SX127x).
2 - Common class: In case the module further specifies some more generic class (e.g. SX127x/SX1278)
*/
class <module_name> {
public:
/*
Constructor MUST have only one parameter "Module* mod".
The class MAY implement additional overloaded constructors.
*/
// constructor
<module_name>(Module* mod);
/*
The class MUST implement at least one basic method called "begin".
The "begin" method MUST initialize the module and return the status as int16_t type.
*/
// basic methods
int16_t begin();
/*
The class MAY implement additional methods.
All implemented methods SHOULD return the status as int16_t type.
*/
#if !defined(RADIOLIB_GODMODE)
private:
#endif
/*
The class MUST contain private member "Module* _mod"
*/
Module* _mod;
/*
The class MAY contain additional private variables and/or methods.
Private member variables MUST have a name prefixed with "_" (underscore, ASCII 0x5F)
Usually, these are variables for saving module configuration, or methods that do not have to be exposed to the end user.
*/
};
#endif
#endif

View file

@ -385,6 +385,7 @@ setDutyCycle KEYWORD2
setDwellTime KEYWORD2
setCSMA KEYWORD2
setDeviceStatus KEYWORD2
setActivityLeds KEYWORD2
scheduleTransmission KEYWORD2
getBand KEYWORD2
getClass KEYWORD2

View file

@ -106,6 +106,12 @@
#define RADIOLIB_STATIC_ARRAY_SIZE (256)
#endif
// allow user to set custom SPI buffer size
// the default covers the maximum supported SPI command, address and status
#if !defined(RADIOLIB_STATIC_SPI_ARRAY_SIZE)
#define RADIOLIB_STATIC_SPI_ARRAY_SIZE (3*sizeof(uint32_t) + (RADIOLIB_STATIC_ARRAY_SIZE))
#endif
/*
* Uncomment on boards whose clock runs too slow or too fast
* Set the value according to the following scheme:

View file

@ -178,8 +178,8 @@ void Module::SPItransfer(uint16_t cmd, uint32_t reg, const uint8_t* dataOut, uin
// prepare the buffers
size_t buffLen = this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 + this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8 + numBytes;
#if RADIOLIB_STATIC_ONLY
uint8_t buffOut[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t buffIn[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t buffOut[RADIOLIB_STATIC_SPI_ARRAY_SIZE];
uint8_t buffIn[RADIOLIB_STATIC_SPI_ARRAY_SIZE];
#else
uint8_t* buffOut = new uint8_t[buffLen];
uint8_t* buffIn = new uint8_t[buffLen];
@ -323,7 +323,7 @@ int16_t Module::SPItransferStream(const uint8_t* cmd, uint8_t cmdLen, bool write
buffLen += (this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] / 8);
}
#if RADIOLIB_STATIC_ONLY
uint8_t buffOut[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t buffOut[RADIOLIB_STATIC_SPI_ARRAY_SIZE];
#else
uint8_t* buffOut = new uint8_t[buffLen];
#endif
@ -366,7 +366,7 @@ int16_t Module::SPItransferStream(const uint8_t* cmd, uint8_t cmdLen, bool write
// prepare the input buffer
#if RADIOLIB_STATIC_ONLY
uint8_t buffIn[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t buffIn[RADIOLIB_STATIC_SPI_ARRAY_SIZE];
#else
uint8_t* buffIn = new uint8_t[buffLen];
#endif

View file

@ -10,7 +10,7 @@ void ArduinoHal::init() {
if(initInterface) {
spiBegin();
}
#if defined(ARDUINO_ARCH_STM32)
#if defined(ARDUINO_ARCH_STM32) && defined(DWT_BASE)
dwt_init();
#endif
}

View file

@ -266,8 +266,8 @@ int16_t CC1101::startTransmit(const uint8_t* data, size_t len, uint8_t addr) {
// optionally write packet length
if(this->packetLengthConfig == RADIOLIB_CC1101_LENGTH_CONFIG_VARIABLE) {
if (len > RADIOLIB_CC1101_MAX_PACKET_LENGTH - 1) {
return(RADIOLIB_ERR_PACKET_TOO_LONG);
if(len > RADIOLIB_CC1101_MAX_PACKET_LENGTH - 1) {
return(RADIOLIB_ERR_PACKET_TOO_LONG);
}
SPIwriteRegister(RADIOLIB_CC1101_REG_FIFO, len + (filter != RADIOLIB_CC1101_ADR_CHK_NONE? 1:0));
dataSent+= 1;
@ -282,7 +282,9 @@ int16_t CC1101::startTransmit(const uint8_t* data, size_t len, uint8_t addr) {
// fill the FIFO
uint8_t initialWrite = RADIOLIB_MIN((uint8_t)len, (uint8_t)(RADIOLIB_CC1101_FIFO_SIZE - dataSent));
SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_FIFO, const_cast<uint8_t*>(data), initialWrite);
dataSent += initialWrite;
// reset the data sent counter as it will be used later to calculate the remaining number of bytes to read from the user buffer
dataSent = initialWrite;
// set RF switch (if present)
this->mod->setRfSwitchState(Module::MODE_TX);
@ -290,24 +292,40 @@ int16_t CC1101::startTransmit(const uint8_t* data, size_t len, uint8_t addr) {
// set mode to transmit
SPIsendCommand(RADIOLIB_CC1101_CMD_TX);
// Keep feeding the FIFO until the packet is done
while (dataSent < len) {
// keep feeding the FIFO until the packet is done
// calculate timeout (5ms + 500 % of expected time-on-air)
RadioLibTime_t timeout = 5 + (RadioLibTime_t)((((float)(len * 8)) / this->bitRate) * 5);
start = this->mod->hal->millis();
while(dataSent < len) {
uint8_t fifoBytes = 0;
uint8_t prevFifobytes = 0;
// Check number of bytes on FIFO twice due to the CC1101 errata. Block until two reads are equal.
do{
// check number of bytes on FIFO twice due to the CC1101 errata
// block until two reads are equal
do {
fifoBytes = SPIgetRegValue(RADIOLIB_CC1101_REG_TXBYTES, 6, 0);
prevFifobytes = SPIgetRegValue(RADIOLIB_CC1101_REG_TXBYTES, 6, 0);
} while (fifoBytes != prevFifobytes);
} while(fifoBytes != prevFifobytes);
//If there is room add more data to the FIFO
if (fifoBytes < RADIOLIB_CC1101_FIFO_SIZE) {
uint8_t bytesToWrite = RADIOLIB_MIN((uint8_t)(RADIOLIB_CC1101_FIFO_SIZE - fifoBytes), (uint8_t)(len - dataSent));
SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_FIFO, const_cast<uint8_t*>(&data[dataSent]), bytesToWrite);
dataSent += bytesToWrite;
// if there is room, add more data to the FIFO
if(fifoBytes < RADIOLIB_CC1101_FIFO_SIZE) {
uint8_t bytesToWrite = RADIOLIB_MIN((uint8_t)(RADIOLIB_CC1101_FIFO_SIZE - fifoBytes), (uint8_t)(len - dataSent));
SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_FIFO, const_cast<uint8_t*>(&data[dataSent]), bytesToWrite);
dataSent += bytesToWrite;
}
// check a timeout - this really shouldn't happen, but some packets can be quite long
if(this->mod->hal->millis() - start > timeout) {
(void)finishTransmit();
return(RADIOLIB_ERR_TX_TIMEOUT);
}
}
// enable interrupt for the final part of the packet
if(len > RADIOLIB_CC1101_FIFO_SIZE) {
state = SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG2, RADIOLIB_CC1101_GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED, 5, 0);
}
return(state);
}

View file

@ -900,7 +900,7 @@ class CC1101: public PhysicalLayer {
int16_t setOOK(bool enableOOK);
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet.
\brief Gets RSSI (Received Signal Strength Indicator) of the last received packet.
In direct or asynchronous direct mode, returns the current RSSI level.
\returns RSSI in dBm.
*/

View file

@ -1109,6 +1109,22 @@ float LR11x0::getRSSI() {
return(val);
}
float LR11x0::getRSSI(bool packet, bool skipReceive) {
float val = 0;
// check if RSSI of packet is requested
if (packet) {
val = getRSSI();
} else {
if(!skipReceive) { (void)startReceive(); }
int16_t state = getRssiInst(&val);
if(!skipReceive) { (void)standby(); }
if(state != RADIOLIB_ERR_NONE) { return(0); }
}
return(val);
}
float LR11x0::getSNR() {
float val = 0;

View file

@ -424,11 +424,20 @@ class LR11x0: public LRxxxx {
int16_t invertIQ(bool enable) override;
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet. Only available for LoRa or GFSK modem.
\brief Gets RSSI (Received Signal Strength Indicator) of the last received packet. Only available for LoRa or GFSK modem.
\returns RSSI of the last received packet in dBm.
*/
float getRSSI() override;
/*!
\brief Gets RSSI (Received Signal Strength Indicator).
\param packet Whether to read last packet RSSI, or the current value.
\param skipReceive Set to true to skip putting radio in receive mode for instantaneous RSSI measurement.
If false, after the RSSI measurement, the radio will be in standby mode.
\returns RSSI value in dBm.
*/
float getRSSI(bool packet, bool skipReceive = false);
/*!
\brief Gets SNR (Signal to Noise Ratio) of the last received packet. Only available for LoRa modem.
\returns SNR of the last received packet in dB.
@ -922,7 +931,6 @@ class LR11x0: public LRxxxx {
#endif
uint8_t wifiScanMode = 0;
bool gnss = false;
int16_t modSetup(float tcxoVoltage, uint8_t modem);
bool findChip(uint8_t ver);
int16_t config(uint8_t modem);

View file

@ -550,7 +550,7 @@ int16_t LR2021::startChannelScan(const ChannelScanConfig_t &config) {
RADIOLIB_ASSERT(state);
// set mode to CAD
return(startCad(config.cad.symNum, config.cad.detPeak, config.cad.detMin, config.cad.exitMode, config.cad.timeout));
return(startCad(config.cad.symNum, config.cad.detPeak, this->fastCad, config.cad.exitMode, config.cad.timeout));
}
int16_t LR2021::getChannelScanResult() {
@ -685,6 +685,11 @@ int16_t LR2021::config(uint8_t modem) {
state = this->clearIrqState(RADIOLIB_LR2021_IRQ_ALL);
RADIOLIB_ASSERT(state);
// Regulator / ramp resolution (datasheet SetRegMode); avoids relying on reset defaults.
const uint8_t rampTimes[4] = { 0x00, 0x00, 0x00, 0x00 };
state = this->setRegMode((uint8_t)(RADIOLIB_LR2021_REG_MODE_SIMO_NORMAL | RADIOLIB_LR2021_REG_MODE_RAMP_RES_4_US), rampTimes);
RADIOLIB_ASSERT(state);
// validate DIO pin number
if((this->irqDioNum < 5) || (this->irqDioNum > 11)) {
return(RADIOLIB_ERR_INVALID_DIO_PIN);
@ -721,7 +726,7 @@ int16_t LR2021::config(uint8_t modem) {
return(state);
}
int16_t LR2021::startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uint8_t exitMode, RadioLibTime_t timeout) {
int16_t LR2021::startCad(uint8_t symbolNum, uint8_t detPeak, bool fast, uint8_t exitMode, RadioLibTime_t timeout) {
// check active modem
uint8_t type = RADIOLIB_LR2021_PACKET_TYPE_NONE;
int16_t state = getPacketType(&type);
@ -731,22 +736,21 @@ int16_t LR2021::startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uin
}
// select CAD parameters
//! \TODO: [LR2021] the magic numbers for CAD are based on Semtech examples, this is probably suboptimal
uint8_t num = symbolNum;
if(num == RADIOLIB_LR2021_CAD_PARAM_DEFAULT) {
num = 2;
}
const uint8_t detPeakValues[8] = { 48, 48, 50, 55, 55, 59, 61, 65 };
// reference values from the datasheet for 2 symbols
//! \TODO: [LR2021] allow CAD peak detection autoconfiguration
const uint8_t detPeakValues[8] = { 56, 56, 56, 58, 58, 60, 64, 68 };
uint8_t peak = detPeak;
if(peak == RADIOLIB_LR2021_CAD_PARAM_DEFAULT) {
peak = detPeakValues[this->spreadingFactor - 5];
}
uint8_t min = detMin;
if(min == RADIOLIB_LR2021_CAD_PARAM_DEFAULT) {
min = 10;
}
// in Fast CAD mode enable acceleration
uint8_t pnrDelta = fast ? RADIOLIB_LR2021_LORA_CAD_PNR_DELTA_FAST : RADIOLIB_LR2021_LORA_CAD_PNR_DELTA_STANDARD;
uint8_t mode = exitMode;
if(mode == RADIOLIB_LR2021_CAD_PARAM_DEFAULT) {
@ -755,17 +759,13 @@ int16_t LR2021::startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uin
uint32_t timeout_raw = (float)timeout / 30.52f;
//! \TODO: [LR2021] The datasheet says this CAD is only based on RSSI, but the reference to the LoRa CAD is GetLoraRxStats ...?
(void)peak;
(void)min;
// set CAD parameters
//! \TODO: [LR2021] add configurable exit mode and timeout
state = setCadParams(timeout_raw, num, mode, timeout_raw);
// set LoRa CAD parameters
// preamble only mode is intentionally disabled, as it is unreliable according to the datasheet
state = setLoRaCadParams(num, false, pnrDelta, mode, timeout_raw, peak);
RADIOLIB_ASSERT(state);
// start CAD
return(setCad());
// start LoraCAD
return(setLoRaCad());
}
RadioLibTime_t LR2021::getTimeOnAir(size_t len) {
@ -1021,6 +1021,8 @@ float LR2021::getRSSI(bool packet, bool skipReceive) {
state = this->getLoRaPacketStatus(NULL, NULL, NULL, NULL, &rssi, NULL);
} else if(modem == RADIOLIB_LR2021_PACKET_TYPE_GFSK) {
state = this->getGfskPacketStatus(NULL, &rssi, NULL, NULL, NULL, NULL);
} else if(modem == RADIOLIB_LR2021_PACKET_TYPE_OOK) {
state = this->getOokPacketStatus(NULL, NULL, &rssi, NULL, NULL, NULL);
} else {
return(0);
}
@ -1046,4 +1048,8 @@ uint8_t LR2021::randomByte() {
return((uint8_t)num);
}
int16_t LR2021::getLoRaRxHeaderInfo(uint8_t* cr, bool* hasCRC){
return(this->getLoRaPacketStatus(cr, hasCRC, NULL, NULL, NULL, NULL));
}
#endif

View file

@ -42,6 +42,13 @@ class LR2021: public LRxxxx {
*/
uint32_t irqDioNum = 5;
/*!
\brief Determines the type of Lora CAD to perform, either "standard" CAD
(same as is implem,ented LR11x0, SX126x and others), or a "fast" CAD if set to true.
If there is no signal to be detected, fast CAD should return faster than standard CAD.
*/
bool fastCad = false;
/*!
\brief Custom operation modes for LR2021.
Needed because LR2021 has several modems (sub-GHz, 2.4 GHz etc.) in one package
@ -595,16 +602,18 @@ class LR2021: public LRxxxx {
float getTemperature(uint8_t source, uint8_t bits = 13);
/*!
\brief Gets recorded signal strength indicator.
\brief Gets received signal strength indicator.
Overload with packet mode enabled for PhysicalLayer compatibility.
\returns RSSI value in dBm.
*/
float getRSSI() override;
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator).
\brief Gets RSSI (Received Signal Strength Indicator).
\param packet Whether to read last packet RSSI, or the current value.
\param skipReceive Set to true to skip putting radio in receive mode for the RSSI measurement in FSK/OOK mode.
NOTE: With OOK modem, the "packet" RSSI value is the received power level of the high bits (digital 1).
\param skipReceive Set to true to skip putting radio in receive mode for instantaneous RSSI measurement.
If false, after the RSSI measurement, the radio will be in standby mode.
\returns RSSI value in dBm.
*/
float getRSSI(bool packet, bool skipReceive = false);
@ -677,6 +686,27 @@ class LR2021: public LRxxxx {
*/
int16_t setGain(uint8_t gain);
/*!
\brief Read status of the last received packet.
Each parameter can be set to NULL if the caller is not intending to process it.
\param cr Coding rate of the last received packet
\param crc Will be set to true if the last packet had a CRC, false otherwise
\param packetLen Length of the last received packet in bytes
\param snrPacket SNR of the last received packet in dB
\param rssiPacket RSSI of the last received packet in dBm
\param rssiSignalPacket Estimation of the RSSI of LoRa signal after despreading in dBm
\returns \ref status_codes
*/
int16_t getLoRaPacketStatus(uint8_t* cr, bool* crc, uint8_t* packetLen = NULL, float* snrPacket = NULL, float* rssiPacket = NULL, float* rssiSignalPacket = NULL);
/*!
\brief Get LoRa header information from last received packet. Implementation based on getLoRaPacketStatus.
\param cr Pointer to variable to store the coding rate.
\param hasCRC Pointer to variable to store the CRC status.
\returns \ref status_codes
*/
int16_t getLoRaRxHeaderInfo(uint8_t* cr, bool* hasCRC);
#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL
protected:
#endif
@ -703,7 +733,7 @@ class LR2021: public LRxxxx {
bool findChip(void);
int16_t config(uint8_t modem);
int16_t setPacketMode(uint8_t mode, uint8_t len);
int16_t startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uint8_t exitMode, RadioLibTime_t timeout);
int16_t startCad(uint8_t symbolNum, uint8_t detPeak, bool fast, uint8_t exitMode, RadioLibTime_t timeout);
// chip control commands
int16_t readRadioRxFifo(uint8_t* data, size_t len);
@ -763,6 +793,8 @@ class LR2021: public LRxxxx {
int16_t selPa(uint8_t pa);
int16_t setPaConfig(uint8_t pa, uint8_t paLfMode, uint8_t paLfDutyCycle, uint8_t paLfSlices, uint8_t paHfDutyCycle);
int16_t setTxParams(int8_t txPower, uint8_t rampTime);
/*! \brief SetTxParams first byte as signed half-dBm steps (datasheet / lr20xx `power_half_dbm`). */
int16_t setTxParamsHalfDbm(int8_t powerHalfDbm, uint8_t rampTime);
// modem configuration commands
int16_t setPacketType(uint8_t packetType);
@ -778,7 +810,6 @@ class LR2021: public LRxxxx {
int16_t setLoRaCadParams(uint8_t numSymbols, bool preambleOnly, uint8_t pnrDelta, uint8_t cadExitMode, uint32_t timeout, uint8_t detPeak);
int16_t setLoRaCad(void);
int16_t getLoRaRxStats(uint16_t* pktRxTotal, uint16_t* pktCrcError, uint16_t* headerCrcError, uint16_t* falseSynch);
int16_t getLoRaPacketStatus(uint8_t* crc, uint8_t* cr, uint8_t* packetLen, float* snrPacket, float* rssiPacket, float* rssiSignalPacket);
int16_t setLoRaAddress(uint8_t addrLen, uint8_t addrPos, const uint8_t* addr);
int16_t setLoRaHopping(uint8_t hopCtrl, uint16_t hopPeriod, const uint32_t* freqHops, size_t numFreqHops);
int16_t setLoRaTxSync(uint8_t function, uint8_t dioNum);
@ -834,7 +865,7 @@ class LR2021: public LRxxxx {
int16_t setOokSyncword(const uint8_t* syncWord, size_t syncWordLen, bool msbFirst);
int16_t setOokAddress(uint8_t addrNode, uint8_t addrBroadcast);
int16_t getOokRxStats(uint16_t* packetRx, uint16_t* crcError, uint16_t* lenError);
int16_t getOokPacketStatus(uint16_t* packetLen, float* rssiAvg, float* rssiSync, bool* addrMatchNode, bool* addrMatchBroadcast, float* lqi);
int16_t getOokPacketStatus(uint16_t* packetLen, float* rssiAvg, float* rssiHigh, bool* addrMatchNode, bool* addrMatchBroadcast, float* lqi);
int16_t setOokDetector(uint16_t preamblePattern, uint8_t patternLen, uint8_t patternNumRepeaters, bool syncWordRaw, bool sofDelimiterRising, uint8_t sofDelimiterLen);
int16_t setOokWhiteningParams(uint8_t bitIdx, uint16_t poly, uint16_t init);

View file

@ -72,7 +72,7 @@ int16_t LR2021::getLoRaRxStats(uint16_t* pktRxTotal, uint16_t* pktCrcError, uint
return(state);
}
int16_t LR2021::getLoRaPacketStatus(uint8_t* crc, uint8_t* cr, uint8_t* packetLen, float* snrPacket, float* rssiPacket, float* rssiSignalPacket) {
int16_t LR2021::getLoRaPacketStatus(uint8_t* cr, bool* crc, uint8_t* packetLen, float* snrPacket, float* rssiPacket, float* rssiSignalPacket) {
uint8_t buff[6] = { 0 };
int16_t state = this->SPIcommand(RADIOLIB_LR2021_CMD_GET_LORA_PACKET_STATUS, false, buff, sizeof(buff));
uint16_t raw;

View file

@ -37,9 +37,14 @@ int16_t LR2021::setOokCrcParams(uint32_t poly, uint32_t init) {
}
int16_t LR2021::setOokSyncword(const uint8_t* syncWord, size_t syncWordLen, bool msbFirst) {
// OOK maximum sync word length is just 32-bits, unlike GFSK
if(syncWordLen > RADIOLIB_LR2021_OOK_SYNC_WORD_LEN) {
return(RADIOLIB_ERR_INVALID_SYNC_WORD);
}
uint8_t buff[5] = { 0 };
for(size_t i = 0; i < syncWordLen; i++) {
buff[3 - i] = syncWord[i];
for(int8_t i = 3; i >= (int8_t) (RADIOLIB_LR2021_OOK_SYNC_WORD_LEN - syncWordLen); i--) {
buff[i] = syncWord[i - (int8_t) (RADIOLIB_LR2021_OOK_SYNC_WORD_LEN - syncWordLen)];
}
buff[4] = (uint8_t)msbFirst << 7 | ((syncWordLen*8) & 0x7F);
return(this->SPIcommand(RADIOLIB_LR2021_CMD_SET_OOK_SYNCWORD, true, buff, sizeof(buff)));
@ -59,7 +64,7 @@ int16_t LR2021::getOokRxStats(uint16_t* packetRx, uint16_t* crcError, uint16_t*
return(state);
}
int16_t LR2021::getOokPacketStatus(uint16_t* packetLen, float* rssiAvg, float* rssiSync, bool* addrMatchNode, bool* addrMatchBroadcast, float* lqi) {
int16_t LR2021::getOokPacketStatus(uint16_t* packetLen, float* rssiAvg, float* rssiHigh, bool* addrMatchNode, bool* addrMatchBroadcast, float* lqi) {
uint8_t buff[6] = { 0 };
int16_t state = this->SPIcommand(RADIOLIB_LR2021_CMD_GET_OOK_PACKET_STATUS, false, buff, sizeof(buff));
uint16_t raw;
@ -69,10 +74,10 @@ int16_t LR2021::getOokPacketStatus(uint16_t* packetLen, float* rssiAvg, float* r
raw |= (buff[4] & 0x04) >> 2;
*rssiAvg = (float)raw / -2.0f;
}
if(rssiSync) {
if(rssiHigh) {
raw = (uint16_t)buff[3] << 1;
raw |= (buff[4] & 0x01);
*rssiSync = (float)raw / -2.0f;
*rssiHigh = (float)raw / -2.0f;
}
if(addrMatchNode) { *addrMatchNode = (buff[4] & 0x10); }
if(addrMatchBroadcast) { *addrMatchBroadcast = (buff[4] & 0x20); }

View file

@ -94,17 +94,24 @@ int16_t LR2021::selPa(uint8_t pa) {
}
int16_t LR2021::setPaConfig(uint8_t pa, uint8_t paLfMode, uint8_t paLfDutyCycle, uint8_t paLfSlices, uint8_t paHfDutyCycle) {
// Matches Semtech lr20xx_radio_common_set_pa_cfg(): 3 payload bytes after opcode.
uint8_t buff[] = {
(uint8_t)(pa << 7), (uint8_t)(paLfMode & 0x03), (uint8_t)(paLfDutyCycle & 0xF0), (uint8_t)(paLfSlices & 0x0F), (uint8_t)(paHfDutyCycle & 0x1F),
(uint8_t)(((pa & 0x01) << 7) | (paLfMode & 0x03)),
(uint8_t)(((paLfDutyCycle & 0x0F) << 4) | (paLfSlices & 0x0F)),
(uint8_t)(paHfDutyCycle & 0x1F),
};
return(this->SPIcommand(RADIOLIB_LR2021_CMD_SET_PA_CONFIG, true, buff, sizeof(buff)));
}
int16_t LR2021::setTxParams(int8_t txPower, uint8_t rampTime) {
uint8_t buff[] = { (uint8_t)(txPower * 2), rampTime };
int16_t LR2021::setTxParamsHalfDbm(int8_t powerHalfDbm, uint8_t rampTime) {
uint8_t buff[] = { (uint8_t)powerHalfDbm, rampTime };
return(this->SPIcommand(RADIOLIB_LR2021_CMD_SET_TX_PARAMS, true, buff, sizeof(buff)));
}
int16_t LR2021::setTxParams(int8_t txPower, uint8_t rampTime) {
return(this->setTxParamsHalfDbm((int8_t)(txPower * 2), rampTime));
}
int16_t LR2021::setPacketType(uint8_t packetType) {
return(this->SPIcommand(RADIOLIB_LR2021_CMD_SET_PACKET_TYPE, true, &packetType, sizeof(packetType)));
}

View file

@ -345,7 +345,7 @@
// RADIOLIB_LR2021_CMD_SET_PA_CONFIG
#define RADIOLIB_LR2021_PA_LF_MODE_FSM (0x00UL << 0) // 1 0 PA LF mode: full single-ended mode
#define RADIOLIB_LR2021_PA_LF_DUTY_CYCLE_UNUSED (0x06UL << 4) // 7 4 PA LF duty cycle: PA not used
#define RADIOLIB_LR2021_PA_LF_DUTY_CYCLE_UNUSED (0x06UL << 0) // 7 4 PA LF duty cycle: PA not used (nibble; packed in setPaConfig)
#define RADIOLIB_LR2021_PA_LF_SLICES_UNUSED (0x07UL << 0) // 3 0 PA LF slices: PA not used
#define RADIOLIB_LR2021_PA_HF_DUTY_CYCLE_UNUSED (0x10UL << 0) // 4 0 PA HF duty cycle: PA not used
@ -404,6 +404,10 @@
#define RADIOLIB_LR2021_LORA_SYNC_WORD_PRIVATE (0x12UL << 0) // 7 0 LoRa sync word: 0x12 (private networks)
#define RADIOLIB_LR2021_LORA_SYNC_WORD_LORAWAN (0x34UL << 0) // 7 0 0x34 (LoRaWAN reserved)
// RADIOLIB_LR2021_CMD_SET_LORA_CAD_PARAMS
#define RADIOLIB_LR2021_LORA_CAD_PNR_DELTA_STANDARD (0x00UL << 0) // 7 0 LoRa CAD speed: normal
#define RADIOLIB_LR2021_LORA_CAD_PNR_DELTA_FAST (0x08UL << 0) // 7 0 fast CAD
// RADIOLIB_LR2021_CMD_SET_LORA_HOPPING
#define RADIOLIB_LR2021_LORA_HOPPING_DISABLED (0x00UL << 6) // 7 6 LoRa intra-packet hopping: disabled
#define RADIOLIB_LR2021_LORA_HOPPING_ENABLED (0x01UL << 6) // 7 6 enabled
@ -516,6 +520,9 @@
#define RADIOLIB_LR2021_OOK_MANCHESTER_ON (0x01UL << 0) // 3 0 enabled
#define RADIOLIB_LR2021_OOK_MANCHESTER_ON_INV (0x09UL << 0) // 3 0 enabled, inverted
// RADIOLIB_LR2021_CMD_SET_OOK_SYNCWORD
#define RADIOLIB_LR2021_OOK_SYNC_WORD_LEN (4)
// RADIOLIB_LR2021_CMD_SET_TX_TEST_MODE
#define RADIOLIB_LR2021_TX_TEST_MODE_NORMAL_TX (0x00UL << 0) // 7 0 Tx test mode: normal
#define RADIOLIB_LR2021_TX_TEST_MODE_INF_PREAMBLE (0x01UL << 0) // 7 0 infinite preamble

View file

@ -11,6 +11,98 @@
// maximum number of allowed frontend calibration attempts
#define RADIOLIB_LR2021_MAX_CAL_ATTEMPTS (10)
// Sub-GHz: below this RF frequency (MHz) use Table 7-17 (490 MHz ref.); at/above use Table 7-16 (915 MHz ref.).
#define RADIOLIB_LR2021_LF_PA_TABLE_490_MHZ_MAX (700.0f)
// Table 7-16: Optimal Values for 915 MHz Semtech Reference Design (LF PA).
// Integer targeted dBm 22..10 only: half-dBm rows (e.g. 21.5) need a future API that passes 0.5 dB steps.
// SetTxParams first byte = `txHalfDbm` (signed half-dBm); matches datasheet TX_PARAM column * 2.
static const struct {
int8_t txHalfDbm;
uint8_t paLfDutyCycle;
uint8_t paLfSlices;
} RADIOLIB_LR2021_TABLE_7_16_915_LF[] = {
/* 22 */ { 44, 7, 6 },
/* 21 */ { 42, 7, 7 },
/* 20 */ { 41, 6, 6 },
/* 19 */ { 39, 6, 6 },
/* 18 */ { 38, 5, 6 },
/* 17 */ { 36, 5, 6 },
/* 16 */ { 36, 4, 4 },
/* 15 */ { 33, 5, 4 },
/* 14 */ { 34, 4, 2 },
/* 13 */ { 31, 4, 3 },
/* 12 */ { 30, 5, 1 },
/* 11 */ { 32, 2, 2 },
/* 10 */ { 32, 2, 1 },
};
// `targetedDbm` integer 10..22 only (Table 7-16 rows).
static void lr2021Table716LfRow(int8_t targetedDbm, int8_t* txHalfDbm, uint8_t* duty, uint8_t* slices) {
if(targetedDbm < 10) { targetedDbm = 10; }
if(targetedDbm > 22) { targetedDbm = 22; }
const auto& e = RADIOLIB_LR2021_TABLE_7_16_915_LF[22 - targetedDbm];
*txHalfDbm = e.txHalfDbm;
*duty = e.paLfDutyCycle;
*slices = e.paLfSlices;
}
// Table 7-17: Optimal Values for 490 MHz Semtech Reference Design (LF PA). Integer targeted dBm 20..10.
static const struct {
int8_t txHalfDbm;
uint8_t paLfDutyCycle;
uint8_t paLfSlices;
} RADIOLIB_LR2021_TABLE_7_17_490_LF[] = {
/* 20 */ { 40, 7, 7 },
/* 19 */ { 38, 7, 7 },
/* 18 */ { 36, 7, 6 },
/* 17 */ { 34, 7, 6 },
/* 16 */ { 32, 7, 6 },
/* 15 */ { 31, 7, 4 },
/* 14 */ { 31, 6, 4 },
/* 13 */ { 29, 7, 2 },
/* 12 */ { 30, 5, 3 },
/* 11 */ { 29, 5, 2 },
/* 10 */ { 31, 4, 2 },
};
static void lr2021Table717LfRow(int8_t targetedDbm, int8_t* txHalfDbm, uint8_t* duty, uint8_t* slices) {
if(targetedDbm < 10) { targetedDbm = 10; }
if(targetedDbm > 20) { targetedDbm = 20; }
const auto& e = RADIOLIB_LR2021_TABLE_7_17_490_LF[20 - targetedDbm];
*txHalfDbm = e.txHalfDbm;
*duty = e.paLfDutyCycle;
*slices = e.paLfSlices;
}
// Table 7-18: Optimal Values for 2445 MHz Semtech Reference Design (HF PA). Integer targeted dBm 0..12.
static const struct {
int8_t txHalfDbm;
uint8_t paHfDutyCycle;
} RADIOLIB_LR2021_TABLE_7_18_2445_HF[] = {
/* 12 */ { 24, 16 },
/* 11 */ { 24, 26 },
/* 10 */ { 24, 30 },
/* 9 */ { 22, 30 },
/* 8 */ { 21, 31 },
/* 7 */ { 18, 30 },
/* 6 */ { 16, 30 },
/* 5 */ { 15, 31 },
/* 4 */ { 10, 25 },
/* 3 */ { 8, 25 },
/* 2 */ { 7, 28 },
/* 1 */ { 6, 30 },
/* 0 */ { 4, 30 },
};
static void lr2021Table718HfRow(int8_t targetedDbm, int8_t* txHalfDbm, uint8_t* hfDuty) {
if(targetedDbm < 0) { targetedDbm = 0; }
if(targetedDbm > 12) { targetedDbm = 12; }
const auto& e = RADIOLIB_LR2021_TABLE_7_18_2445_HF[12 - targetedDbm];
*txHalfDbm = e.txHalfDbm;
*hfDuty = e.paHfDutyCycle;
}
int16_t LR2021::setFrequency(float freq) {
return(this->setFrequency(freq, false));
}
@ -76,22 +168,78 @@ int16_t LR2021::setOutputPower(int8_t power) {
}
int16_t LR2021::setOutputPower(int8_t power, uint32_t rampTimeUs) {
// check if power value is configurable
int16_t state = this->checkOutputPower(power, NULL);
RADIOLIB_ASSERT(state);
//! \TODO: [LR2021] how and when to configure OCP?
//! \TODO: [LR2021] Determine the optimal PA configuration
// update PA config
state = setPaConfig(this->highFreq,
RADIOLIB_LR2021_PA_LF_MODE_FSM,
RADIOLIB_LR2021_PA_LF_DUTY_CYCLE_UNUSED,
RADIOLIB_LR2021_PA_LF_SLICES_UNUSED,
RADIOLIB_LR2021_PA_HF_DUTY_CYCLE_UNUSED);
RADIOLIB_ASSERT(state);
// set output power
state = setTxParams(power, roundRampTime(rampTimeUs));
// pa_sel: 0 = LF (Sub-GHz), 1 = HF (1.92.5 GHz). Same encoding as lr20xx reference driver.
uint8_t paSel = this->highFreq ? (uint8_t)1 : (uint8_t)0;
uint8_t paLfMode = RADIOLIB_LR2021_PA_LF_MODE_FSM;
uint8_t paLfDutyCycle = RADIOLIB_LR2021_PA_LF_DUTY_CYCLE_UNUSED;
uint8_t paLfSlices = RADIOLIB_LR2021_PA_LF_SLICES_UNUSED;
uint8_t paHfDutyCycle = RADIOLIB_LR2021_PA_HF_DUTY_CYCLE_UNUSED;
// TX_PARAM from tables is signed half-dBm (not always 2 * targeted dBm).
int8_t lfTxHalfDbm = (int8_t)(power * 2);
int8_t hfTxHalfDbm = (int8_t)(power * 2);
if(this->highFreq) {
// HF PA: Table 7-18 (2445 MHz); LF nibbles per Semtech reference (7/6).
paLfDutyCycle = (uint8_t)0x07;
paLfSlices = (uint8_t)0x06;
if(power >= 0) {
lr2021Table718HfRow(power, &hfTxHalfDbm, &paHfDutyCycle);
} else {
lr2021Table718HfRow(0, &hfTxHalfDbm, &paHfDutyCycle);
hfTxHalfDbm = (int8_t)(power * 2);
}
} else if(this->freqMHz < RADIOLIB_LR2021_LF_PA_TABLE_490_MHZ_MAX) {
// LF PA: Table 7-17 (490 MHz ref.), max +20 dBm in table; 2122 use row 20 PA + requested TX half-dBm.
if(power > 20) {
lr2021Table717LfRow(20, &lfTxHalfDbm, &paLfDutyCycle, &paLfSlices);
lfTxHalfDbm = (int8_t)(power * 2);
} else if(power >= 10) {
lr2021Table717LfRow(power, &lfTxHalfDbm, &paLfDutyCycle, &paLfSlices);
} else {
lr2021Table717LfRow(10, &lfTxHalfDbm, &paLfDutyCycle, &paLfSlices);
lfTxHalfDbm = (int8_t)(power * 2);
}
} else {
// LF PA: Table 7-16 (915 MHz ref.)
if(power >= 10) {
lr2021Table716LfRow(power, &lfTxHalfDbm, &paLfDutyCycle, &paLfSlices);
} else {
lr2021Table716LfRow(10, &lfTxHalfDbm, &paLfDutyCycle, &paLfSlices);
lfTxHalfDbm = (int8_t)(power * 2);
}
}
(void)clearErrors();
state = setPaConfig(paSel, paLfMode, paLfDutyCycle, paLfSlices, paHfDutyCycle);
RADIOLIB_ASSERT(state);
#if RADIOLIB_DEBUG_BASIC
RADIOLIB_DEBUG_BASIC_PRINTLN("LR2021 PA cfg: hf=%d lf_dc=0x%X lf_sl=0x%X hf_dc=0x%X",
(int)this->highFreq, (int)paLfDutyCycle, (int)paLfSlices, (int)paHfDutyCycle);
#endif
state = selPa(paSel);
RADIOLIB_ASSERT(state);
#if RADIOLIB_DEBUG_BASIC
RADIOLIB_DEBUG_BASIC_PRINTLN("LR2021 PA sel: %s", paSel ? "HF" : "LF");
#endif
if(this->highFreq) {
state = setTxParamsHalfDbm(hfTxHalfDbm, roundRampTime(rampTimeUs));
} else {
state = setTxParamsHalfDbm(lfTxHalfDbm, roundRampTime(rampTimeUs));
}
RADIOLIB_ASSERT(state);
#if RADIOLIB_DEBUG_BASIC
uint16_t err = 0;
if(getErrors(&err) == RADIOLIB_ERR_NONE) {
RADIOLIB_DEBUG_BASIC_PRINTLN("LR2021 device errors after PA/TX: 0x%X", err);
}
#endif
return(state);
}

View file

@ -957,7 +957,7 @@ class RF69: public PhysicalLayer {
int16_t setLnaTestBoost(bool value);
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet.
\brief Gets RSSI (Received Signal Strength Indicator) of the last received packet.
\returns Last packet RSSI in dBm.
*/
float getRSSI() override;

View file

@ -184,6 +184,22 @@ class SX126x: public PhysicalLayer {
*/
int16_t scanChannel(const ChannelScanConfig_t &config) override;
/*!
\brief Reset the AGC gain state by performing a warm sleep, recalibration, and
image rejection calibration cycle. Re-applies DIO2 RF switch and RX boosted gain
settings if previously configured. Leaves the radio in standby mode.
\returns \ref status_codes
*/
int16_t resetAGC();
/*!
\brief Perform calibration of the specified blocks.
\param params Calibration parameters - bitfield of RADIOLIB_SX126X_CALIBRATE_* values.
Use RADIOLIB_SX126X_CALIBRATE_ALL to calibrate all blocks.
\returns \ref status_codes
*/
int16_t calibrate(uint8_t params);
/*!
\brief Sets the module to sleep mode. To wake the device up, call standby().
Overload with warm start enabled for PhysicalLayer compatibility.
@ -528,14 +544,14 @@ class SX126x: public PhysicalLayer {
int16_t setDio2AsRfSwitch(bool enable = true);
/*!
\brief Gets recorded signal strength indicator.
\brief Gets received signal strength indicator.
Overload with packet mode enabled for PhysicalLayer compatibility.
\returns RSSI value in dBm.
*/
float getRSSI() override;
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator).
\brief Gets RSSI (Received Signal Strength Indicator).
\param packet Whether to read last packet RSSI, or the current value.
\returns RSSI value in dBm.
*/
@ -889,6 +905,8 @@ class SX126x: public PhysicalLayer {
uint32_t tcxoDelay = 0;
uint8_t pwr = 0;
bool dio2RfSwitch = false;
bool rxBoostedGainMode = false;
size_t implicitLen = 0;
uint8_t invertIQEnabled = RADIOLIB_SX126X_LORA_IQ_STANDARD;

View file

@ -8,6 +8,42 @@
#if !RADIOLIB_EXCLUDE_SX126X
int16_t SX126x::resetAGC() {
// warm sleep to power down the analog frontend
int16_t state = sleep(true);
RADIOLIB_ASSERT(state);
// wake to RC standby
state = standby(RADIOLIB_SX126X_STANDBY_RC, true);
RADIOLIB_ASSERT(state);
// recalibrate all blocks
state = calibrate(RADIOLIB_SX126X_CALIBRATE_ALL);
RADIOLIB_ASSERT(state);
// re-calibrate image rejection for the operating frequency
state = calibrateImage(this->freqMHz);
RADIOLIB_ASSERT(state);
// re-apply DIO2 RF switch if it was configured
if(this->dio2RfSwitch) {
state = setDio2AsRfSwitch(true);
RADIOLIB_ASSERT(state);
}
// re-apply RX boosted gain if it was configured
if(this->rxBoostedGainMode) {
state = setRxBoostedGainMode(true);
RADIOLIB_ASSERT(state);
}
return(RADIOLIB_ERR_NONE);
}
int16_t SX126x::calibrate(uint8_t params) {
return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &params, 1, true, false));
}
int16_t SX126x::sleep() {
return(SX126x::sleep(true));
}

View file

@ -394,6 +394,8 @@ int16_t SX126x::setRxBandwidth(float rxBw) {
}
int16_t SX126x::setRxBoostedGainMode(bool rxbgm, bool persist) {
this->rxBoostedGainMode = rxbgm;
// update RX gain setting register
uint8_t rxGain = rxbgm ? RADIOLIB_SX126X_RX_GAIN_BOOSTED : RADIOLIB_SX126X_RX_GAIN_POWER_SAVING;
int16_t state = writeRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
@ -708,6 +710,7 @@ int16_t SX126x::setTCXO(float voltage, uint32_t delay) {
}
int16_t SX126x::setDio2AsRfSwitch(bool enable) {
this->dio2RfSwitch = enable;
uint8_t data = enable ? RADIOLIB_SX126X_DIO2_AS_RF_SWITCH : RADIOLIB_SX126X_DIO2_AS_IRQ;
return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, &data, 1));
}

View file

@ -258,14 +258,14 @@ class SX1272: public SX127x {
int16_t setDataShapingOOK(uint8_t sh);
/*!
\brief Gets recorded signal strength indicator.
\brief Gets received signal strength indicator.
Overload with packet mode enabled for PhysicalLayer compatibility.
\returns RSSI value in dBm.
*/
float getRSSI() override;
/*!
\brief Gets recorded signal strength indicator.
\brief Gets received signal strength indicator.
\param packet Whether to read last packet RSSI, or the current value. LoRa mode only, ignored for FSK.
\param skipReceive Set to true to skip putting radio in receive mode for the RSSI measurement in FSK/OOK mode.
\returns RSSI value in dBm.

View file

@ -69,7 +69,15 @@ int16_t SX1276::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t
}
int16_t SX1276::setFrequency(float freq) {
RADIOLIB_CHECK_RANGE(freq, 137.0f, 1020.0f, RADIOLIB_ERR_INVALID_FREQUENCY);
// NOTE: The datasheet specifies Band 2 as 410-525 MHz, but the hardware has been
// verified to work down to ~395 MHz. The lower bound is set here to 395 MHz to
// accommodate real-world use cases (e.g. TinyGS satellites, radiosondes) while
// adding a small margin below the 400 MHz practical limit.
if(!(((freq >= 137.0f) && (freq <= 175.0f)) ||
((freq >= 395.0f) && (freq <= 525.0f)) ||
((freq >= 862.0f) && (freq <= 1020.0f)))) {
return(RADIOLIB_ERR_INVALID_FREQUENCY);
}
// set frequency and if successful, save the new setting
int16_t state = SX127x::setFrequencyRaw(freq);

View file

@ -58,7 +58,7 @@ class SX1276: public SX1278 {
// configuration methods
/*!
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz.
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 175.0 MHz, 395.0 to 525.0 MHz (datasheet minimum is 410.0 MHz, hardware works lower) and 862.0 to 1020 MHz.
\param freq Carrier frequency to be set in MHz.
\returns \ref status_codes
*/

View file

@ -69,7 +69,15 @@ int16_t SX1277::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t
}
int16_t SX1277::setFrequency(float freq) {
RADIOLIB_CHECK_RANGE(freq, 137.0f, 1020.0f, RADIOLIB_ERR_INVALID_FREQUENCY);
// NOTE: The datasheet specifies Band 2 as 410-525 MHz, but the hardware has been
// verified to work down to ~395 MHz. The lower bound is set here to 395 MHz to
// accommodate real-world use cases (e.g. TinyGS satellites, radiosondes) while
// adding a small margin below the 400 MHz practical limit.
if(!(((freq >= 137.0f) && (freq <= 175.0f)) ||
((freq >= 395.0f) && (freq <= 525.0f)) ||
((freq >= 862.0f) && (freq <= 1020.0f)))) {
return(RADIOLIB_ERR_INVALID_FREQUENCY);
}
// set frequency and if successful, save the new setting
int16_t state = SX127x::setFrequencyRaw(freq);

View file

@ -58,7 +58,7 @@ class SX1277: public SX1278 {
// configuration methods
/*!
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz.
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 175.0 MHz, 395.0 to 525.0 MHz (datasheet minimum is 410.0 MHz, hardware works lower) and 862.0 to 1020 MHz.
\param freq Carrier frequency to be set in MHz.
\returns \ref status_codes
*/

View file

@ -83,7 +83,14 @@ void SX1278::reset() {
}
int16_t SX1278::setFrequency(float freq) {
RADIOLIB_CHECK_RANGE(freq, 137.0f, 525.0f, RADIOLIB_ERR_INVALID_FREQUENCY);
// NOTE: The datasheet specifies Band 2 as 410-525 MHz, but the hardware has been
// verified to work down to ~395 MHz. The lower bound is set here to 395 MHz to
// accommodate real-world use cases (e.g. TinyGS satellites, radiosondes) while
// adding a small margin below the 400 MHz practical limit.
if(!(((freq >= 137.0f) && (freq <= 175.0f)) ||
((freq >= 395.0f) && (freq <= 525.0f)))) {
return(RADIOLIB_ERR_INVALID_FREQUENCY);
}
// set frequency and if successful, save the new setting
int16_t state = SX127x::setFrequencyRaw(freq);

View file

@ -155,7 +155,7 @@ class SX1278: public SX127x {
// configuration methods
/*!
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 525.0 MHz.
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 175.0 MHz and 395.0 to 525.0 MHz (datasheet minimum is 410.0 MHz, hardware works lower).
\param freq Carrier frequency to be set in MHz.
\returns \ref status_codes
*/
@ -270,14 +270,14 @@ class SX1278: public SX127x {
int16_t setDataShapingOOK(uint8_t sh);
/*!
\brief Gets recorded signal strength indicator.
\brief Gets received signal strength indicator.
Overload with packet mode enabled for PhysicalLayer compatibility.
\returns RSSI value in dBm.
*/
float getRSSI() override;
/*!
\brief Gets recorded signal strength indicator.
\brief Gets received signal strength indicator.
\param packet Whether to read last packet RSSI, or the current value. LoRa mode only, ignored for FSK.
\param skipReceive Set to true to skip putting radio in receive mode for the RSSI measurement in FSK/OOK mode.
\returns RSSI value in dBm.

View file

@ -69,7 +69,15 @@ int16_t SX1279::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t
}
int16_t SX1279::setFrequency(float freq) {
RADIOLIB_CHECK_RANGE(freq, 137.0f, 960.0f, RADIOLIB_ERR_INVALID_FREQUENCY);
// NOTE: The datasheet specifies Band 2 as 410-480 MHz, but the hardware has been
// verified to work down to ~395 MHz. The lower bound is set here to 395 MHz to
// accommodate real-world use cases (e.g. TinyGS satellites, radiosondes) while
// adding a small margin below the 400 MHz practical limit.
if(!(((freq >= 137.0f) && (freq <= 160.0f)) ||
((freq >= 395.0f) && (freq <= 480.0f)) ||
((freq >= 779.0f) && (freq <= 960.0f)))) {
return(RADIOLIB_ERR_INVALID_FREQUENCY);
}
// set frequency and if successful, save the new setting
int16_t state = SX127x::setFrequencyRaw(freq);

View file

@ -58,7 +58,7 @@ class SX1279: public SX1278 {
// configuration methods
/*!
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 960.0 MHz.
\brief Sets carrier frequency. Allowed values range from 137.0 MHz to 160.0 MHz, 395.0 to 480.0 MHz (datasheet minimum is 410.0 MHz, hardware works lower) and 779.0 to 960 MHz.
\param freq Carrier frequency to be set in MHz.
\returns \ref status_codes
*/

View file

@ -785,13 +785,13 @@ class SX128x: public PhysicalLayer {
int16_t setGainControl(uint8_t gain = 0);
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet.
\brief Gets RSSI (Received Signal Strength Indicator) of the last received packet.
\returns RSSI of the last received packet in dBm.
*/
float getRSSI() override;
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator).
\brief Gets RSSI (Received Signal Strength Indicator).
\param packet Whether to read last packet RSSI, or the current value.
\returns RSSI value in dBm.
*/

View file

@ -50,7 +50,7 @@ int16_t APRSClient::sendPosition(char* destCallsign, uint8_t destSSID, const cha
#if !RADIOLIB_STATIC_ONLY
char* info = new char[len + 2];
#else
char info[RADIOLIB_STATIC_ARRAY_SIZE];
char info[RADIOLIB_STATIC_ARRAY_SIZE + 2];
#endif
// build the info field

View file

@ -263,6 +263,11 @@ int16_t AX25Client::begin(const char* srcCallsign, uint8_t srcSSID, uint8_t preL
return(phyLayer->startDirect());
}
void AX25Client::setScrambler(uint32_t poly, uint32_t init) {
this->scramblerPoly = poly;
this->scramblerInit = init;
}
#if defined(RADIOLIB_BUILD_ARDUINO)
int16_t AX25Client::transmit(String& str, const char* destCallsign, uint8_t destSSID) {
return(transmit(str.c_str(), destCallsign, destSSID));
@ -307,7 +312,7 @@ int16_t AX25Client::sendFrame(AX25Frame* frame) {
#if !RADIOLIB_STATIC_ONLY
uint8_t* frameBuff = new uint8_t[frameBuffLen + 2];
#else
uint8_t frameBuff[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t frameBuff[RADIOLIB_STATIC_ARRAY_SIZE + 2];
#endif
uint8_t* frameBuffPtr = frameBuff;
@ -390,7 +395,7 @@ int16_t AX25Client::sendFrame(AX25Frame* frame) {
// worst-case scenario: sequence of 1s, will have 120% of the original length, stuffed frame also includes both flags
uint8_t* stuffedFrameBuff = new uint8_t[preambleLen + 1 + (6*frameBuffLen)/5 + 2];
#else
uint8_t stuffedFrameBuff[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t stuffedFrameBuff[1 + (6*RADIOLIB_STATIC_ARRAY_SIZE)/5 + 2];
#endif
// initialize buffer to all zeros
@ -474,6 +479,11 @@ int16_t AX25Client::sendFrame(AX25Frame* frame) {
}
}
// do the scrambling
if(scramblerPoly) {
rlb_scrambler(stuffedFrameBuff, stuffedFrameBuffLen, scramblerPoly, scramblerInit, true);
}
// transmit
int16_t state = RADIOLIB_ERR_NONE;
#if !RADIOLIB_EXCLUDE_AFSK

View file

@ -281,6 +281,13 @@ class AX25Client {
*/
int16_t begin(const char* srcCallsign, uint8_t srcSSID = 0x00, uint8_t preLen = 8);
/*!
\brief Set scrambling polynomail and initial value.
\param poly Scramling polynomial. Use RADIOLIB_SCRAMBLER_G3RUH_POLY for G3RUH coding.
\param poly Initial scrambler value. Use RADIOLIB_SCRAMBLER_G3RUH_INIT for G3RUH coding.
*/
void setScrambler(uint32_t poly, uint32_t init = 0);
#if defined(RADIOLIB_BUILD_ARDUINO)
/*!
\brief Transmit unnumbered information (UI) frame.
@ -324,6 +331,8 @@ class AX25Client {
char sourceCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1] = { 0 };
uint8_t sourceSSID = 0;
uint16_t preambleLen = 0;
uint32_t scramblerInit = 0;
uint32_t scramblerPoly = 0;
void getCallsign(char* buff);
uint8_t getSSID();

View file

@ -25,7 +25,7 @@ int16_t LoRaWANNode::sendReceive(const String& strUp, uint8_t fPort, String& str
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t lenDown = 0;
uint8_t dataDown[RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE + 1];
uint8_t dataDown[RADIOLIB_LORAWAN_MAX_PAYLOAD_SIZE + 1];
state = this->sendReceive(reinterpret_cast<const uint8_t*>(dataUp), strlen(dataUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown);
@ -45,7 +45,7 @@ int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, bool isConfir
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t lenDown = 0;
uint8_t dataDown[RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE + 1];
uint8_t dataDown[RADIOLIB_LORAWAN_MAX_PAYLOAD_SIZE + 1];
return(this->sendReceive(reinterpret_cast<uint8_t*>(const_cast<char*>(strUp)), strlen(strUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown));
}
@ -58,7 +58,7 @@ int16_t LoRaWANNode::sendReceive(const uint8_t* dataUp, size_t lenUp, uint8_t fP
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t lenDown = 0;
uint8_t dataDown[RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE + 1];
uint8_t dataDown[RADIOLIB_LORAWAN_MAX_PAYLOAD_SIZE + 1];
return(this->sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown));
}
@ -1200,6 +1200,11 @@ void LoRaWANNode::stopMulticastSession() {
// stop any ongoing activity
this->phyLayer->standby();
if(this->ledPins[RADIOLIB_LORAWAN_RX_BC] != RADIOLIB_NC) {
Module *mod = this->phyLayer->getMod();
mod->hal->digitalWrite(this->ledPins[RADIOLIB_LORAWAN_RX_BC], mod->hal->GpioLevelLow);
}
// if in Class C, re-open RxC window with normal unicast configuration
if(this->lwClass == RADIOLIB_LORAWAN_CLASS_C) {
@ -1467,6 +1472,10 @@ int16_t LoRaWANNode::transmitUplink(const LoRaWANChannel_t* chnl, uint8_t* in, u
}
}
if(this->ledPins[0] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[0], mod->hal->GpioLevelHigh);
}
// start transmission, and time the duration of launchMode() to offset window timing
RadioLibTime_t spiStart = mod->hal->millis();
state = this->phyLayer->launchMode();
@ -1491,6 +1500,11 @@ int16_t LoRaWANNode::transmitUplink(const LoRaWANChannel_t* chnl, uint8_t* in, u
// set the timestamp so that we can measure when to start receiving
this->tUplinkEnd = mod->hal->millis();
if(this->ledPins[0] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[0], mod->hal->GpioLevelLow);
}
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink sent (ToA = %d ms)", toa);
// increase Time on Air of the uplink sequence
@ -1568,6 +1582,10 @@ int16_t LoRaWANNode::receiveClassA(uint8_t dir, const LoRaWANChannel_t* dlChanne
this->sleepDelay(tWindow - tNow);
}
if(window < 4 && this->ledPins[window] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[window], mod->hal->GpioLevelHigh);
}
// open Rx window by starting receive with specified timeout
state = this->phyLayer->launchMode();
RadioLibTime_t tOpen = mod->hal->millis();
@ -1595,6 +1613,9 @@ int16_t LoRaWANNode::receiveClassA(uint8_t dir, const LoRaWANChannel_t* dlChanne
this->phyLayer->clearPacketReceivedAction();
this->phyLayer->clearIrq(1UL << RADIOLIB_IRQ_TIMEOUT);
this->phyLayer->standby();
if(window < 4 && this->ledPins[window] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[window], mod->hal->GpioLevelLow);
}
return(0); // no downlink
}
@ -1624,6 +1645,9 @@ int16_t LoRaWANNode::receiveClassA(uint8_t dir, const LoRaWANChannel_t* dlChanne
// we have a message, clear actions, go to standby
this->phyLayer->clearPacketReceivedAction();
this->phyLayer->standby();
if(window < 4 && this->ledPins[window] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[window], mod->hal->GpioLevelLow);
}
// if all windows passed without receiving anything, return 0 for no window
if(!downlinkAction) {
@ -1675,6 +1699,10 @@ int16_t LoRaWANNode::receiveClassC(RadioLibTime_t timeout) {
state = this->phyLayer->stageMode(RADIOLIB_RADIO_MODE_RX, &modeCfg);
RADIOLIB_ASSERT(state);
if(this->ledPins[RADIOLIB_LORAWAN_RX_BC] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[RADIOLIB_LORAWAN_RX_BC], mod->hal->GpioLevelHigh);
}
// open RxC window by starting receive with specified timeout
state = this->phyLayer->launchMode();
RadioLibTime_t tOpen = mod->hal->millis();
@ -1683,8 +1711,7 @@ int16_t LoRaWANNode::receiveClassC(RadioLibTime_t timeout) {
if(timeout) {
// wait for the DIO interrupt to fire (RxDone or RxTimeout)
// use a small additional delay in case the RxTimeout interrupt is slow to fire
while(!downlinkAction && mod->hal->millis() - tOpen <= timeout + this->scanGuard) {
while(!downlinkAction && mod->hal->millis() - tOpen <= timeout) {
mod->hal->yield();
}
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closed RxC window");
@ -1700,6 +1727,9 @@ int16_t LoRaWANNode::receiveClassC(RadioLibTime_t timeout) {
this->phyLayer->clearPacketReceivedAction();
this->phyLayer->clearIrq(1UL << RADIOLIB_IRQ_TIMEOUT);
this->phyLayer->standby();
if(this->ledPins[RADIOLIB_LORAWAN_RX_BC] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[RADIOLIB_LORAWAN_RX_BC], mod->hal->GpioLevelLow);
}
return(0); // no downlink
}
@ -1711,6 +1741,9 @@ int16_t LoRaWANNode::receiveClassC(RadioLibTime_t timeout) {
// we have a message, clear actions, go to standby
this->phyLayer->clearPacketReceivedAction();
this->phyLayer->standby();
if(this->ledPins[RADIOLIB_LORAWAN_RX_BC] != RADIOLIB_NC) {
mod->hal->digitalWrite(this->ledPins[RADIOLIB_LORAWAN_RX_BC], mod->hal->GpioLevelLow);
}
// if all windows passed without receiving anything, return 0 for no window
if(!downlinkAction) {
@ -1798,7 +1831,7 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
#if !RADIOLIB_STATIC_ONLY
uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen];
#else
uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
uint8_t downlinkMsg[RADIOLIB_AES128_BLOCK_SIZE + RADIOLIB_STATIC_ARRAY_SIZE];
#endif
// read the data
@ -2061,7 +2094,7 @@ int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, uint8_t window, L
if(fOptsLen > 0) {
uint8_t* mPtr = fOptsPtr;
uint8_t procLen = 0;
uint8_t fOptsRe[RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE] = { 0 };
uint8_t fOptsRe[RADIOLIB_LORAWAN_MAX_PAYLOAD_SIZE] = { 0 };
uint8_t fOptsReLen = 0;
// indication whether LinkAdr MAC command has been processed
@ -3216,6 +3249,17 @@ void LoRaWANNode::setDeviceStatus(uint8_t battLevel) {
this->battLevel = battLevel;
}
void LoRaWANNode::setActivityLeds(const uint32_t pins[4]) {
Module *mod = this->phyLayer->getMod();
// configure each provided pin and store in the ledPins array
for(uint8_t i = 0; i < 4; i++) {
if(pins[i] != RADIOLIB_NC) {
mod->hal->pinMode(pins[i], mod->hal->GpioModeOutput);
}
this->ledPins[i] = pins[i];
}
}
void LoRaWANNode::scheduleTransmission(RadioLibTime_t tUplink) {
this->tUplink = tUplink;
}

View file

@ -207,7 +207,7 @@
#define RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_UP (2)
#define RADIOLIB_LORAWAN_MAX_NUM_ADR_COMMANDS (8)
#define RADIOLIB_LORAWAN_MAX_DOWNLINK_SIZE (250)
#define RADIOLIB_LORAWAN_MAX_PAYLOAD_SIZE (242)
// session states
#define RADIOLIB_LORAWAN_SESSION_NONE (0x00)
@ -848,6 +848,13 @@ class LoRaWANNode {
*/
void setDeviceStatus(uint8_t battLevel);
/*!
\brief Set pins for activity LEDs that will indicate when the radio is transmitting (Tx) or receiving (Rx).
\param pins Array of 4 pin numbers: [Tx, Rx1, Rx2, RxBC].
Use RADIOLIB_NC to disable individual indicators.
*/
void setActivityLeds(const uint32_t pins[4]);
/*!
\brief Set the exact time a transmission should occur. Note: this is the internal clock time.
On Arduino platforms, this is the usual time supplied by millis().
@ -1093,6 +1100,8 @@ class LoRaWANNode {
RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS,
0 };
uint32_t ledPins[4] = { RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC };
// offset between Tx and Rx1 (such that Rx1 has equal or lower DR)
uint8_t rx1DrOffset = 0;

View file

@ -547,7 +547,7 @@ class PhysicalLayer {
virtual size_t getPacketLength(bool update = true);
/*!
\brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet.
\brief Gets RSSI (Received Signal Strength Indicator) of the last received packet.
\returns RSSI of the last received packet in dBm.
*/
virtual float getRSSI();

View file

@ -18,7 +18,7 @@
// frequently used scrambling configurations
// the final bit (x^0 term in polynomial notation) is assumed to always be present
#define RADIOLIB_SCRAMBLER_G3RUH_POLY (0x00021001UL) // x^17 + x^12 + 1
#define RADIOLIB_SCRAMBLER_G3RUH_POLY (0x00010800UL) // x^17 + x^12 + 1
#define RADIOLIB_SCRAMBLER_G3RUH_INIT (0x00000000UL)
/*!