LoRa device type + SX1262 kernel driver (first checkpoint) (#565)
Adds sub-GHz radio support to Tactility as a first-class kernel device type, plus a driver for the Semtech SX1262. Continues the radio work discussed in #342, brought up to the current kernel driver model (rather than the deprecated tt::hal layer the earlier prototype targeted).
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
|
||||
/**
|
||||
* Device type and API for sub-GHz packet transceivers such as the Semtech SX126x family.
|
||||
* The API is LoRa-centric but exposes the modem's other modulation schemes as well.
|
||||
*/
|
||||
|
||||
enum LoraModulation {
|
||||
LORA_MODULATION_NONE = 0,
|
||||
LORA_MODULATION_FSK,
|
||||
LORA_MODULATION_LORA,
|
||||
LORA_MODULATION_LR_FHSS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Tunable radio parameters. Values are int32_t; the unit is documented per parameter.
|
||||
* Frequencies and rates use their base SI unit (Hz, bit/s) on purpose: a float can't hold
|
||||
* a value like 906875000 Hz (906.875 MHz) exactly, so integers avoid the rounding a
|
||||
* fractional MHz/kHz representation would introduce.
|
||||
*
|
||||
* Which parameters are available depends on the driver and the selected modulation.
|
||||
*/
|
||||
enum LoraParameter {
|
||||
/** TX output power in dBm */
|
||||
LORA_PARAMETER_POWER = 0,
|
||||
/** Boosted RX gain mode: 0 = off, 1 = on */
|
||||
LORA_PARAMETER_BOOSTED_GAIN,
|
||||
/** Carrier frequency in Hz */
|
||||
LORA_PARAMETER_FREQUENCY,
|
||||
/** Bandwidth in Hz */
|
||||
LORA_PARAMETER_BANDWIDTH,
|
||||
/** LoRa spreading factor (7-12) */
|
||||
LORA_PARAMETER_SPREADING_FACTOR,
|
||||
/** LoRa coding rate denominator (5-8 for 4/5 to 4/8) */
|
||||
LORA_PARAMETER_CODING_RATE,
|
||||
/**
|
||||
* LoRa sync word (0x00-0xFF). Distinguishes otherwise-identical networks: a receiver
|
||||
* only accepts packets whose sync word matches. Conventionally 0x12 for private
|
||||
* networks and 0x34 for public/LoRaWAN. LoRa modulation only.
|
||||
*/
|
||||
LORA_PARAMETER_SYNC_WORD,
|
||||
/** Preamble length in symbols (LoRa) or bits (FSK) */
|
||||
LORA_PARAMETER_PREAMBLE_LENGTH,
|
||||
/** FSK frequency deviation from the carrier in Hz */
|
||||
LORA_PARAMETER_FREQUENCY_DEVIATION,
|
||||
/** FSK bit rate in bits per second */
|
||||
LORA_PARAMETER_DATA_RATE,
|
||||
/** LR-FHSS grid spacing: 0 = 25 kHz (wide), 1 = 3.9 kHz (narrow) */
|
||||
LORA_PARAMETER_NARROW_GRID,
|
||||
/**
|
||||
* PA over-current protection limit in mA. Fail-safe: a low limit caps the current the
|
||||
* PA can push into a bad or disconnected antenna, but also caps the achievable output
|
||||
* power. Drivers keep a conservative default; a board-aware consumer that knows its
|
||||
* antenna and PA can raise it to reach full output power.
|
||||
*/
|
||||
LORA_PARAMETER_CURRENT_LIMIT,
|
||||
};
|
||||
|
||||
enum LoraRadioState {
|
||||
LORA_RADIO_STATE_OFF,
|
||||
LORA_RADIO_STATE_ON_PENDING,
|
||||
LORA_RADIO_STATE_ON,
|
||||
LORA_RADIO_STATE_OFF_PENDING,
|
||||
LORA_RADIO_STATE_ERROR,
|
||||
};
|
||||
|
||||
/** Identifies a queued transmission. Unique per device until it wraps around. */
|
||||
typedef int32_t LoraTxId;
|
||||
|
||||
enum LoraTransmissionState {
|
||||
/** Accepted into the TX queue */
|
||||
LORA_TRANSMISSION_STATE_QUEUED,
|
||||
/** Handed to the modem, waiting for TX-done */
|
||||
LORA_TRANSMISSION_STATE_TRANSMIT_PENDING,
|
||||
/** TX-done confirmed by the modem */
|
||||
LORA_TRANSMISSION_STATE_TRANSMITTED,
|
||||
/** No TX-done within the driver's timeout */
|
||||
LORA_TRANSMISSION_STATE_TIMEOUT,
|
||||
/** The modem rejected the transmission */
|
||||
LORA_TRANSMISSION_STATE_ERROR,
|
||||
};
|
||||
|
||||
struct LoraRxPacket {
|
||||
/** Packet payload. Only valid for the duration of the RX callback. */
|
||||
const uint8_t* data;
|
||||
size_t length;
|
||||
/** Received signal strength in dBm */
|
||||
float rssi;
|
||||
/** Signal-to-noise ratio in dB */
|
||||
float snr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callbacks are invoked without any driver lock held, either from the driver's radio
|
||||
* thread (RX, TX progress, state) or from the thread calling transmit()/set_enabled()
|
||||
* (the QUEUED TX event and enable/disable state changes). Taking consumer locks in a
|
||||
* callback is therefore safe, but keep callbacks short: RX processing stalls while
|
||||
* they run. After remove_*_callback returns, a callback that was already in flight
|
||||
* may still complete once — disable the radio before destroying callback context.
|
||||
*/
|
||||
typedef void (*LoraStateCallback)(struct Device* device, void* context, enum LoraRadioState state);
|
||||
typedef void (*LoraRxCallback)(struct Device* device, void* context, const struct LoraRxPacket* packet);
|
||||
typedef void (*LoraTxCallback)(struct Device* device, void* context, LoraTxId id, enum LoraTransmissionState state);
|
||||
|
||||
struct LoraApi {
|
||||
/**
|
||||
* Get the radio state of the device.
|
||||
* @param[in] device the lora device
|
||||
* @param[out] state the radio state
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*get_radio_state)(struct Device* device, enum LoraRadioState* state);
|
||||
|
||||
/**
|
||||
* Turn the radio on or off. Requires a modulation to be set before enabling.
|
||||
* Turning on is asynchronous: observe the radio state to know when it's up.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] enabled true to turn the radio on
|
||||
* @return ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_STATE when enabling without a modulation set
|
||||
*/
|
||||
error_t (*set_enabled)(struct Device* device, bool enabled);
|
||||
|
||||
/**
|
||||
* Set the modulation scheme. Only allowed while the radio is off.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] modulation the modulation scheme
|
||||
* @return ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_STATE when the radio is on or turning on
|
||||
* @retval ERROR_NOT_SUPPORTED when the device supports neither TX nor RX for this modulation
|
||||
*/
|
||||
error_t (*set_modulation)(struct Device* device, enum LoraModulation modulation);
|
||||
|
||||
/**
|
||||
* Get the current modulation scheme.
|
||||
* @param[in] device the lora device
|
||||
* @param[out] modulation the modulation scheme
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*get_modulation)(struct Device* device, enum LoraModulation* modulation);
|
||||
|
||||
/**
|
||||
* @param[in] device the lora device
|
||||
* @param[in] modulation the modulation scheme
|
||||
* @return true when the device can transmit with the given modulation
|
||||
*/
|
||||
bool (*can_transmit)(struct Device* device, enum LoraModulation modulation);
|
||||
|
||||
/**
|
||||
* @param[in] device the lora device
|
||||
* @param[in] modulation the modulation scheme
|
||||
* @return true when the device can receive with the given modulation
|
||||
*/
|
||||
bool (*can_receive)(struct Device* device, enum LoraModulation modulation);
|
||||
|
||||
/**
|
||||
* Set a radio parameter. See LoraParameter for units.
|
||||
* Parameters apply to the current modulation and take effect the next time the radio turns on.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] parameter the parameter to set
|
||||
* @param[in] value the value to set
|
||||
* @return ERROR_NONE on success
|
||||
* @retval ERROR_NOT_SUPPORTED when the parameter doesn't apply to the device or modulation
|
||||
* @retval ERROR_OUT_OF_RANGE when the value is invalid for the parameter
|
||||
*/
|
||||
error_t (*set_parameter)(struct Device* device, enum LoraParameter parameter, int32_t value);
|
||||
|
||||
/**
|
||||
* Get a radio parameter. See LoraParameter for units.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] parameter the parameter to get
|
||||
* @param[out] value the current value
|
||||
* @return ERROR_NONE on success
|
||||
* @retval ERROR_NOT_SUPPORTED when the parameter doesn't apply to the device or modulation
|
||||
*/
|
||||
error_t (*get_parameter)(struct Device* device, enum LoraParameter parameter, int32_t* value);
|
||||
|
||||
/**
|
||||
* Queue a packet for transmission. The data is copied.
|
||||
* Progress is reported through the TX callbacks, starting with QUEUED.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] data the packet payload
|
||||
* @param[in] length the payload length in bytes
|
||||
* @param[out] id the id assigned to this transmission (optional, can be NULL)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*transmit)(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id);
|
||||
|
||||
/**
|
||||
* Add a callback for received packets.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] callback_context the context to pass to the callback
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*add_rx_callback)(struct Device* device, void* callback_context, LoraRxCallback callback);
|
||||
|
||||
/**
|
||||
* Remove a callback for received packets.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*remove_rx_callback)(struct Device* device, LoraRxCallback callback);
|
||||
|
||||
/**
|
||||
* Add a callback for radio state changes.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] callback_context the context to pass to the callback
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*add_state_callback)(struct Device* device, void* callback_context, LoraStateCallback callback);
|
||||
|
||||
/**
|
||||
* Remove a callback for radio state changes.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*remove_state_callback)(struct Device* device, LoraStateCallback callback);
|
||||
|
||||
/**
|
||||
* Add a callback for transmission progress.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] callback_context the context to pass to the callback
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*add_tx_callback)(struct Device* device, void* callback_context, LoraTxCallback callback);
|
||||
|
||||
/**
|
||||
* Remove a callback for transmission progress.
|
||||
* @param[in] device the lora device
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*remove_tx_callback)(struct Device* device, LoraTxCallback callback);
|
||||
};
|
||||
|
||||
extern const struct DeviceType LORA_TYPE;
|
||||
|
||||
/** @return the first registered lora device, regardless of started state, or NULL if none exists */
|
||||
struct Device* lora_find_first_registered_device(void);
|
||||
|
||||
error_t lora_get_radio_state(struct Device* device, enum LoraRadioState* state);
|
||||
error_t lora_set_enabled(struct Device* device, bool enabled);
|
||||
error_t lora_set_modulation(struct Device* device, enum LoraModulation modulation);
|
||||
error_t lora_get_modulation(struct Device* device, enum LoraModulation* modulation);
|
||||
bool lora_can_transmit(struct Device* device, enum LoraModulation modulation);
|
||||
bool lora_can_receive(struct Device* device, enum LoraModulation modulation);
|
||||
error_t lora_set_parameter(struct Device* device, enum LoraParameter parameter, int32_t value);
|
||||
error_t lora_get_parameter(struct Device* device, enum LoraParameter parameter, int32_t* value);
|
||||
error_t lora_transmit(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id);
|
||||
error_t lora_add_rx_callback(struct Device* device, void* callback_context, LoraRxCallback callback);
|
||||
error_t lora_remove_rx_callback(struct Device* device, LoraRxCallback callback);
|
||||
error_t lora_add_state_callback(struct Device* device, void* callback_context, LoraStateCallback callback);
|
||||
error_t lora_remove_state_callback(struct Device* device, LoraStateCallback callback);
|
||||
error_t lora_add_tx_callback(struct Device* device, void* callback_context, LoraTxCallback callback);
|
||||
error_t lora_remove_tx_callback(struct Device* device, LoraTxCallback callback);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/drivers/lora.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define LORA_API(device) ((const struct LoraApi*)device_get_driver(device)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct Device* lora_find_first_registered_device() {
|
||||
struct Device* found = nullptr;
|
||||
device_for_each_of_type(&LORA_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
|
||||
*static_cast<struct Device**>(ctx) = dev;
|
||||
return false;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
error_t lora_get_radio_state(struct Device* device, enum LoraRadioState* state) {
|
||||
return LORA_API(device)->get_radio_state(device, state);
|
||||
}
|
||||
|
||||
error_t lora_set_enabled(struct Device* device, bool enabled) {
|
||||
return LORA_API(device)->set_enabled(device, enabled);
|
||||
}
|
||||
|
||||
error_t lora_set_modulation(struct Device* device, enum LoraModulation modulation) {
|
||||
return LORA_API(device)->set_modulation(device, modulation);
|
||||
}
|
||||
|
||||
error_t lora_get_modulation(struct Device* device, enum LoraModulation* modulation) {
|
||||
return LORA_API(device)->get_modulation(device, modulation);
|
||||
}
|
||||
|
||||
bool lora_can_transmit(struct Device* device, enum LoraModulation modulation) {
|
||||
return LORA_API(device)->can_transmit(device, modulation);
|
||||
}
|
||||
|
||||
bool lora_can_receive(struct Device* device, enum LoraModulation modulation) {
|
||||
return LORA_API(device)->can_receive(device, modulation);
|
||||
}
|
||||
|
||||
error_t lora_set_parameter(struct Device* device, enum LoraParameter parameter, int32_t value) {
|
||||
return LORA_API(device)->set_parameter(device, parameter, value);
|
||||
}
|
||||
|
||||
error_t lora_get_parameter(struct Device* device, enum LoraParameter parameter, int32_t* value) {
|
||||
return LORA_API(device)->get_parameter(device, parameter, value);
|
||||
}
|
||||
|
||||
error_t lora_transmit(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id) {
|
||||
return LORA_API(device)->transmit(device, data, length, id);
|
||||
}
|
||||
|
||||
error_t lora_add_rx_callback(struct Device* device, void* callback_context, LoraRxCallback callback) {
|
||||
return LORA_API(device)->add_rx_callback(device, callback_context, callback);
|
||||
}
|
||||
|
||||
error_t lora_remove_rx_callback(struct Device* device, LoraRxCallback callback) {
|
||||
return LORA_API(device)->remove_rx_callback(device, callback);
|
||||
}
|
||||
|
||||
error_t lora_add_state_callback(struct Device* device, void* callback_context, LoraStateCallback callback) {
|
||||
return LORA_API(device)->add_state_callback(device, callback_context, callback);
|
||||
}
|
||||
|
||||
error_t lora_remove_state_callback(struct Device* device, LoraStateCallback callback) {
|
||||
return LORA_API(device)->remove_state_callback(device, callback);
|
||||
}
|
||||
|
||||
error_t lora_add_tx_callback(struct Device* device, void* callback_context, LoraTxCallback callback) {
|
||||
return LORA_API(device)->add_tx_callback(device, callback_context, callback);
|
||||
}
|
||||
|
||||
error_t lora_remove_tx_callback(struct Device* device, LoraTxCallback callback) {
|
||||
return LORA_API(device)->remove_tx_callback(device, callback);
|
||||
}
|
||||
|
||||
const struct DeviceType LORA_TYPE = {
|
||||
.name = "lora"
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <tactility/drivers/i2s_controller.h>
|
||||
#include <tactility/drivers/i8080_controller.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/drivers/lora.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/drivers/power_supply.h>
|
||||
#include <tactility/drivers/pwm.h>
|
||||
@@ -387,6 +388,24 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(WIFI_TYPE),
|
||||
// wifi_auto_scan
|
||||
DEFINE_MODULE_SYMBOL(wifi_auto_scan_set_paused),
|
||||
// drivers/lora
|
||||
DEFINE_MODULE_SYMBOL(lora_find_first_registered_device),
|
||||
DEFINE_MODULE_SYMBOL(lora_get_radio_state),
|
||||
DEFINE_MODULE_SYMBOL(lora_set_enabled),
|
||||
DEFINE_MODULE_SYMBOL(lora_set_modulation),
|
||||
DEFINE_MODULE_SYMBOL(lora_get_modulation),
|
||||
DEFINE_MODULE_SYMBOL(lora_can_transmit),
|
||||
DEFINE_MODULE_SYMBOL(lora_can_receive),
|
||||
DEFINE_MODULE_SYMBOL(lora_set_parameter),
|
||||
DEFINE_MODULE_SYMBOL(lora_get_parameter),
|
||||
DEFINE_MODULE_SYMBOL(lora_transmit),
|
||||
DEFINE_MODULE_SYMBOL(lora_add_rx_callback),
|
||||
DEFINE_MODULE_SYMBOL(lora_remove_rx_callback),
|
||||
DEFINE_MODULE_SYMBOL(lora_add_state_callback),
|
||||
DEFINE_MODULE_SYMBOL(lora_remove_state_callback),
|
||||
DEFINE_MODULE_SYMBOL(lora_add_tx_callback),
|
||||
DEFINE_MODULE_SYMBOL(lora_remove_tx_callback),
|
||||
DEFINE_MODULE_SYMBOL(LORA_TYPE),
|
||||
// drivers/usb_host_hid
|
||||
DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(usb_host_hid_subscribe),
|
||||
|
||||
Reference in New Issue
Block a user