[cc1101] Add new cc1101 component (#11849)

Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
lygris
2025-12-03 09:42:04 -06:00
committed by GitHub
parent 669bcad458
commit 87ac4baf3a
9 changed files with 1735 additions and 0 deletions

View File

@@ -97,6 +97,7 @@ esphome/components/camera_encoder/* @DT-art1
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core
esphome/components/cc1101/* @gabest11 @lygris
esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @clydebarrow @jesterret

View File

@@ -0,0 +1,220 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME
CODEOWNERS = ["@lygris", "@gabest11"]
DEPENDENCIES = ["spi"]
MULTI_CONF = True
ns = cg.esphome_ns.namespace("cc1101")
CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice)
# Config keys
CONF_OUTPUT_POWER = "output_power"
CONF_RX_ATTENUATION = "rx_attenuation"
CONF_DC_BLOCKING_FILTER = "dc_blocking_filter"
CONF_IF_FREQUENCY = "if_frequency"
CONF_FILTER_BANDWIDTH = "filter_bandwidth"
CONF_CHANNEL_SPACING = "channel_spacing"
CONF_FSK_DEVIATION = "fsk_deviation"
CONF_MSK_DEVIATION = "msk_deviation"
CONF_SYMBOL_RATE = "symbol_rate"
CONF_SYNC_MODE = "sync_mode"
CONF_CARRIER_SENSE_ABOVE_THRESHOLD = "carrier_sense_above_threshold"
CONF_MODULATION_TYPE = "modulation_type"
CONF_MANCHESTER = "manchester"
CONF_NUM_PREAMBLE = "num_preamble"
CONF_SYNC1 = "sync1"
CONF_SYNC0 = "sync0"
CONF_PKTLEN = "pktlen"
CONF_MAGN_TARGET = "magn_target"
CONF_MAX_LNA_GAIN = "max_lna_gain"
CONF_MAX_DVGA_GAIN = "max_dvga_gain"
CONF_CARRIER_SENSE_ABS_THR = "carrier_sense_abs_thr"
CONF_CARRIER_SENSE_REL_THR = "carrier_sense_rel_thr"
CONF_LNA_PRIORITY = "lna_priority"
CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk"
CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
CONF_FREEZE = "freeze"
CONF_HYST_LEVEL = "hyst_level"
# Enums
SyncMode = ns.enum("SyncMode", True)
SYNC_MODE = {
"None": SyncMode.SYNC_MODE_NONE,
"15/16": SyncMode.SYNC_MODE_15_16,
"16/16": SyncMode.SYNC_MODE_16_16,
"30/32": SyncMode.SYNC_MODE_30_32,
}
Modulation = ns.enum("Modulation", True)
MODULATION = {
"2-FSK": Modulation.MODULATION_2_FSK,
"GFSK": Modulation.MODULATION_GFSK,
"ASK/OOK": Modulation.MODULATION_ASK_OOK,
"4-FSK": Modulation.MODULATION_4_FSK,
"MSK": Modulation.MODULATION_MSK,
}
RxAttenuation = ns.enum("RxAttenuation", True)
RX_ATTENUATION = {
"0dB": RxAttenuation.RX_ATTENUATION_0DB,
"6dB": RxAttenuation.RX_ATTENUATION_6DB,
"12dB": RxAttenuation.RX_ATTENUATION_12DB,
"18dB": RxAttenuation.RX_ATTENUATION_18DB,
}
MagnTarget = ns.enum("MagnTarget", True)
MAGN_TARGET = {
"24dB": MagnTarget.MAGN_TARGET_24DB,
"27dB": MagnTarget.MAGN_TARGET_27DB,
"30dB": MagnTarget.MAGN_TARGET_30DB,
"33dB": MagnTarget.MAGN_TARGET_33DB,
"36dB": MagnTarget.MAGN_TARGET_36DB,
"38dB": MagnTarget.MAGN_TARGET_38DB,
"40dB": MagnTarget.MAGN_TARGET_40DB,
"42dB": MagnTarget.MAGN_TARGET_42DB,
}
MaxLnaGain = ns.enum("MaxLnaGain", True)
MAX_LNA_GAIN = {
"Default": MaxLnaGain.MAX_LNA_GAIN_DEFAULT,
"2.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_2P6DB,
"6.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_6P1DB,
"7.4dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_7P4DB,
"9.2dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_9P2DB,
"11.5dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_11P5DB,
"14.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_14P6DB,
"17.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_17P1DB,
}
MaxDvgaGain = ns.enum("MaxDvgaGain", True)
MAX_DVGA_GAIN = {
"Default": MaxDvgaGain.MAX_DVGA_GAIN_DEFAULT,
"-1": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_1,
"-2": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_2,
"-3": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_3,
}
CarrierSenseRelThr = ns.enum("CarrierSenseRelThr", True)
CARRIER_SENSE_REL_THR = {
"Default": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_DEFAULT,
"+6dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_6DB,
"+10dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_10DB,
"+14dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_14DB,
}
FilterLengthFskMsk = ns.enum("FilterLengthFskMsk", True)
FILTER_LENGTH_FSK_MSK = {
"8": FilterLengthFskMsk.FILTER_LENGTH_8DB,
"16": FilterLengthFskMsk.FILTER_LENGTH_16DB,
"32": FilterLengthFskMsk.FILTER_LENGTH_32DB,
"64": FilterLengthFskMsk.FILTER_LENGTH_64DB,
}
FilterLengthAskOok = ns.enum("FilterLengthAskOok", True)
FILTER_LENGTH_ASK_OOK = {
"4dB": FilterLengthAskOok.FILTER_LENGTH_4DB,
"8dB": FilterLengthAskOok.FILTER_LENGTH_8DB,
"12dB": FilterLengthAskOok.FILTER_LENGTH_12DB,
"16dB": FilterLengthAskOok.FILTER_LENGTH_16DB,
}
Freeze = ns.enum("Freeze", True)
FREEZE = {
"Default": Freeze.FREEZE_DEFAULT,
"On Sync": Freeze.FREEZE_ON_SYNC,
"Analog Only": Freeze.FREEZE_ANALOG_ONLY,
"Analog And Digital": Freeze.FREEZE_ANALOG_AND_DIGITAL,
}
WaitTime = ns.enum("WaitTime", True)
WAIT_TIME = {
"8": WaitTime.WAIT_TIME_8_SAMPLES,
"16": WaitTime.WAIT_TIME_16_SAMPLES,
"24": WaitTime.WAIT_TIME_24_SAMPLES,
"32": WaitTime.WAIT_TIME_32_SAMPLES,
}
HystLevel = ns.enum("HystLevel", True)
HYST_LEVEL = {
"None": HystLevel.HYST_LEVEL_NONE,
"Low": HystLevel.HYST_LEVEL_LOW,
"Medium": HystLevel.HYST_LEVEL_MEDIUM,
"High": HystLevel.HYST_LEVEL_HIGH,
}
# Config key -> Validator mapping
CONFIG_MAP = {
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
CONF_DC_BLOCKING_FILTER: cv.boolean,
CONF_FREQUENCY: cv.float_range(min=300000.0, max=928000.0),
CONF_IF_FREQUENCY: cv.float_range(min=25, max=788),
CONF_FILTER_BANDWIDTH: cv.float_range(min=58.0, max=812.0),
CONF_CHANNEL: cv.uint8_t,
CONF_CHANNEL_SPACING: cv.float_range(min=25, max=405),
CONF_FSK_DEVIATION: cv.float_range(min=1.5, max=381),
CONF_MSK_DEVIATION: cv.int_range(min=1, max=8),
CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000),
CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False),
CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean,
CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False),
CONF_MANCHESTER: cv.boolean,
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7),
CONF_SYNC1: cv.hex_uint8_t,
CONF_SYNC0: cv.hex_uint8_t,
CONF_PKTLEN: cv.uint8_t,
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False),
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False),
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False),
CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7),
CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False),
CONF_LNA_PRIORITY: cv.boolean,
CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False),
CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False),
CONF_FREEZE: cv.enum(FREEZE, upper=False),
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False),
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False),
}
CONFIG_SCHEMA = (
cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)})
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()})
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema(cs_pin_required=True))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
for key in CONFIG_MAP:
if key in config:
cg.add(getattr(var, f"set_{key}")(config[key]))
# Actions
BeginTxAction = ns.class_("BeginTxAction", automation.Action)
BeginRxAction = ns.class_("BeginRxAction", automation.Action)
ResetAction = ns.class_("ResetAction", automation.Action)
SetIdleAction = ns.class_("SetIdleAction", automation.Action)
CC1101_ACTION_SCHEMA = cv.Schema(
maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)})
)
@automation.register_action("cc1101.begin_tx", BeginTxAction, CC1101_ACTION_SCHEMA)
@automation.register_action("cc1101.begin_rx", BeginRxAction, CC1101_ACTION_SCHEMA)
@automation.register_action("cc1101.reset", ResetAction, CC1101_ACTION_SCHEMA)
@automation.register_action("cc1101.set_idle", SetIdleAction, CC1101_ACTION_SCHEMA)
async def cc1101_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View File

@@ -0,0 +1,550 @@
#include "cc1101.h"
#include "cc1101pa.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cmath>
namespace esphome::cc1101 {
static const char *const TAG = "cc1101";
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
int e_tmp;
float m_tmp = std::frexp(value, &e_tmp);
if (e_tmp <= mbits) {
e = 0;
m = 0;
return;
}
e = static_cast<uint8_t>(e_tmp - mbits - 1);
m = static_cast<uint32_t>(((m_tmp * 2 - 1) * (1 << (mbits + 1))) + 1) >> 1;
if (m == (1UL << mbits)) {
e = e + 1;
m = 0;
}
}
CC1101Component::CC1101Component() {
// Datasheet defaults
memset(&this->state_, 0, sizeof(this->state_));
this->state_.GDO2_CFG = 0x0D; // Serial Data (for RX on GDO2)
this->state_.GDO1_CFG = 0x2E;
this->state_.GDO0_CFG = 0x0D; // Serial Data (for RX on GDO0 / TX Input)
this->state_.FIFO_THR = 7;
this->state_.SYNC1 = 0xD3;
this->state_.SYNC0 = 0x91;
this->state_.PKTLEN = 0xFF;
this->state_.APPEND_STATUS = 1;
this->state_.LENGTH_CONFIG = 1;
this->state_.CRC_EN = 1;
this->state_.WHITE_DATA = 1;
this->state_.FREQ_IF = 0x0F;
this->state_.FREQ2 = 0x1E;
this->state_.FREQ1 = 0xC4;
this->state_.FREQ0 = 0xEC;
this->state_.DRATE_E = 0x0C;
this->state_.CHANBW_E = 0x02;
this->state_.DRATE_M = 0x22;
this->state_.SYNC_MODE = 2;
this->state_.CHANSPC_E = 2;
this->state_.NUM_PREAMBLE = 2;
this->state_.CHANSPC_M = 0xF8;
this->state_.DEVIATION_M = 7;
this->state_.DEVIATION_E = 4;
this->state_.RX_TIME = 7;
this->state_.CCA_MODE = 3;
this->state_.PO_TIMEOUT = 1;
this->state_.FOC_LIMIT = 2;
this->state_.FOC_POST_K = 1;
this->state_.FOC_PRE_K = 2;
this->state_.FOC_BS_CS_GATE = 1;
this->state_.BS_POST_KP = 1;
this->state_.BS_POST_KI = 1;
this->state_.BS_PRE_KP = 2;
this->state_.BS_PRE_KI = 1;
this->state_.MAGN_TARGET = 3;
this->state_.AGC_LNA_PRIORITY = 1;
this->state_.FILTER_LENGTH = 1;
this->state_.WAIT_TIME = 1;
this->state_.HYST_LEVEL = 2;
this->state_.WOREVT1 = 0x87;
this->state_.WOREVT0 = 0x6B;
this->state_.RC_CAL = 1;
this->state_.EVENT1 = 7;
this->state_.RC_PD = 1;
this->state_.MIX_CURRENT = 2;
this->state_.LODIV_BUF_CURRENT_RX = 1;
this->state_.LNA2MIX_CURRENT = 1;
this->state_.LNA_CURRENT = 1;
this->state_.LODIV_BUF_CURRENT_TX = 1;
this->state_.FSCAL3_LO = 9;
this->state_.CHP_CURR_CAL_EN = 2;
this->state_.FSCAL3_HI = 2;
this->state_.FSCAL2 = 0x0A;
this->state_.FSCAL1 = 0x20;
this->state_.FSCAL0 = 0x0D;
this->state_.RCCTRL1 = 0x41;
this->state_.FSTEST = 0x59;
this->state_.PTEST = 0x7F;
this->state_.AGCTEST = 0x3F;
this->state_.TEST2 = 0x88;
this->state_.TEST1 = 0x31;
this->state_.TEST0_LO = 1;
this->state_.VCO_SEL_CAL_EN = 1;
this->state_.TEST0_HI = 2;
// PKTCTRL0
this->state_.PKT_FORMAT = 3;
this->state_.LENGTH_CONFIG = 2;
this->state_.FS_AUTOCAL = 1;
// Default Settings
this->set_frequency(433920);
this->set_if_frequency(153);
this->set_filter_bandwidth(203);
this->set_channel(0);
this->set_channel_spacing(200);
this->set_symbol_rate(5000);
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
this->set_carrier_sense_above_threshold(true);
this->set_modulation_type(Modulation::MODULATION_ASK_OOK);
this->set_magn_target(MagnTarget::MAGN_TARGET_42DB);
this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT);
this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3);
this->set_lna_priority(false);
this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES);
// CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence)
memset(this->pa_table_, 0, sizeof(this->pa_table_));
this->set_output_power(10.0f);
}
void CC1101Component::setup() {
this->spi_setup();
this->cs_->digital_write(true);
delayMicroseconds(1);
this->cs_->digital_write(false);
delayMicroseconds(1);
this->cs_->digital_write(true);
delayMicroseconds(41);
this->cs_->digital_write(false);
delay(5);
this->strobe_(Command::RES);
delay(5);
this->read_(Register::PARTNUM);
this->read_(Register::VERSION);
this->chip_id_ = encode_uint16(this->state_.PARTNUM, this->state_.VERSION);
ESP_LOGD(TAG, "CC1101 found! Chip ID: 0x%04X", this->chip_id_);
if (this->state_.VERSION == 0 || this->state_.PARTNUM == 0xFF) {
ESP_LOGE(TAG, "Failed to verify CC1101.");
this->mark_failed();
return;
}
this->initialized_ = true;
for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) {
if (i == static_cast<uint8_t>(Register::FSTEST) || i == static_cast<uint8_t>(Register::AGCTEST)) {
continue;
}
this->write_(static_cast<Register>(i));
}
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_));
this->strobe_(Command::RX);
}
void CC1101Component::dump_config() {
static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK",
"4-FSK", "UNUSED", "UNUSED", "MSK"};
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
XTAL_FREQUENCY / (1 << 16);
float symbol_rate =
(((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY * 1000.0f;
float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E));
ESP_LOGCONFIG(TAG, "CC1101:");
LOG_PIN(" CS Pin: ", this->cs_);
ESP_LOGCONFIG(TAG,
" Chip ID: 0x%04X\n"
" Frequency: %" PRId32 " kHz\n"
" Channel: %u\n"
" Modulation: %s\n"
" Symbol Rate: %.0f baud\n"
" Filter Bandwidth: %.1f kHz\n"
" Output Power: %.1f dBm",
this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
symbol_rate, bw, this->output_power_effective_);
}
void CC1101Component::begin_tx() {
// Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX
this->write_(Register::PKTCTRL0, 0x32);
ESP_LOGV(TAG, "Beginning TX sequence");
this->strobe_(Command::TX);
if (!this->wait_for_state_(State::TX, 50)) {
ESP_LOGW(TAG, "Timed out waiting for TX state!");
}
}
void CC1101Component::begin_rx() {
ESP_LOGV(TAG, "Beginning RX sequence");
this->strobe_(Command::RX);
}
void CC1101Component::reset() {
this->strobe_(Command::RES);
this->setup();
}
void CC1101Component::set_idle() {
ESP_LOGV(TAG, "Setting IDLE state");
this->enter_idle_();
}
void CC1101Component::set_gdo0_config(uint8_t value) {
this->state_.GDO0_CFG = value;
if (this->initialized_) {
this->write_(Register::IOCFG0);
}
}
void CC1101Component::set_gdo2_config(uint8_t value) {
this->state_.GDO2_CFG = value;
if (this->initialized_) {
this->write_(Register::IOCFG2);
}
}
bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
uint32_t start = millis();
while (millis() - start < timeout_ms) {
this->read_(Register::MARCSTATE);
State s = static_cast<State>(this->state_.MARC_STATE);
if (s == target_state) {
return true;
}
delayMicroseconds(100);
}
return false;
}
void CC1101Component::enter_idle_() {
this->strobe_(Command::IDLE);
this->wait_for_state_(State::IDLE);
}
uint8_t CC1101Component::strobe_(Command cmd) {
uint8_t index = static_cast<uint8_t>(cmd);
if (cmd < Command::RES || cmd > Command::NOP) {
return 0xFF;
}
this->enable();
uint8_t status_byte = this->transfer_byte(index);
this->disable();
return status_byte;
}
void CC1101Component::write_(Register reg) {
uint8_t index = static_cast<uint8_t>(reg);
this->enable();
this->write_byte(index);
this->write_array(&this->state_.regs()[index], 1);
this->disable();
}
void CC1101Component::write_(Register reg, uint8_t value) {
uint8_t index = static_cast<uint8_t>(reg);
this->state_.regs()[index] = value;
this->write_(reg);
}
void CC1101Component::write_(Register reg, const uint8_t *buffer, size_t length) {
uint8_t index = static_cast<uint8_t>(reg);
this->enable();
this->write_byte(index | BUS_WRITE | BUS_BURST);
this->write_array(buffer, length);
this->disable();
}
void CC1101Component::read_(Register reg) {
uint8_t index = static_cast<uint8_t>(reg);
this->enable();
this->write_byte(index | BUS_READ | BUS_BURST);
this->state_.regs()[index] = this->transfer_byte(0);
this->disable();
}
void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) {
uint8_t index = static_cast<uint8_t>(reg);
this->enable();
this->write_byte(index | BUS_READ | BUS_BURST);
this->read_array(buffer, length);
this->disable();
}
// Setters
void CC1101Component::set_output_power(float value) {
this->output_power_requested_ = value;
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
XTAL_FREQUENCY / (1 << 16);
uint8_t a = 0xC0;
if (freq >= 300000 && freq <= 348000) {
a = PowerTableItem::find(PA_TABLE_315, sizeof(PA_TABLE_315) / sizeof(PA_TABLE_315[0]), value);
} else if (freq >= 378000 && freq <= 464000) {
a = PowerTableItem::find(PA_TABLE_433, sizeof(PA_TABLE_433) / sizeof(PA_TABLE_433[0]), value);
} else if (freq >= 779000 && freq < 900000) {
a = PowerTableItem::find(PA_TABLE_868, sizeof(PA_TABLE_868) / sizeof(PA_TABLE_868[0]), value);
} else if (freq >= 900000 && freq <= 928000) {
a = PowerTableItem::find(PA_TABLE_915, sizeof(PA_TABLE_915) / sizeof(PA_TABLE_915[0]), value);
}
if (static_cast<Modulation>(this->state_.MOD_FORMAT) == Modulation::MODULATION_ASK_OOK) {
this->pa_table_[0] = 0;
this->pa_table_[1] = a;
} else {
this->pa_table_[0] = a;
this->pa_table_[1] = 0;
}
this->output_power_effective_ = value;
if (this->initialized_) {
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_));
}
}
void CC1101Component::set_rx_attenuation(RxAttenuation value) {
this->state_.CLOSE_IN_RX = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FIFOTHR);
}
}
void CC1101Component::set_dc_blocking_filter(bool value) {
this->state_.DEM_DCFILT_OFF = value ? 0 : 1;
if (this->initialized_) {
this->write_(Register::MDMCFG2);
}
}
void CC1101Component::set_frequency(float value) {
int32_t freq = static_cast<int32_t>(value * (1 << 16) / XTAL_FREQUENCY);
this->state_.FREQ2 = static_cast<uint8_t>(freq >> 16);
this->state_.FREQ1 = static_cast<uint8_t>(freq >> 8);
this->state_.FREQ0 = static_cast<uint8_t>(freq);
if (this->initialized_) {
this->enter_idle_();
this->write_(Register::FREQ2);
this->write_(Register::FREQ1);
this->write_(Register::FREQ0);
this->strobe_(Command::RX);
}
}
void CC1101Component::set_if_frequency(float value) {
this->state_.FREQ_IF = value * (1 << 10) / XTAL_FREQUENCY;
if (this->initialized_) {
this->write_(Register::FSCTRL1);
}
}
void CC1101Component::set_filter_bandwidth(float value) {
uint8_t e;
uint32_t m;
split_float(XTAL_FREQUENCY / (value * 8), 2, e, m);
this->state_.CHANBW_E = e;
this->state_.CHANBW_M = static_cast<uint8_t>(m);
if (this->initialized_) {
this->write_(Register::MDMCFG4);
}
}
void CC1101Component::set_channel(uint8_t value) {
this->state_.CHANNR = value;
if (this->initialized_) {
this->enter_idle_();
this->write_(Register::CHANNR);
this->strobe_(Command::RX);
}
}
void CC1101Component::set_channel_spacing(float value) {
uint8_t e;
uint32_t m;
split_float(value * (1 << 18) / XTAL_FREQUENCY, 8, e, m);
this->state_.CHANSPC_E = e;
this->state_.CHANSPC_M = static_cast<uint8_t>(m);
if (this->initialized_) {
this->write_(Register::MDMCFG1);
this->write_(Register::MDMCFG0);
}
}
void CC1101Component::set_fsk_deviation(float value) {
uint8_t e;
uint32_t m;
split_float(value * (1 << 17) / XTAL_FREQUENCY, 3, e, m);
this->state_.DEVIATION_E = e;
this->state_.DEVIATION_M = static_cast<uint8_t>(m);
if (this->initialized_) {
this->write_(Register::DEVIATN);
}
}
void CC1101Component::set_msk_deviation(uint8_t value) {
this->state_.DEVIATION_E = 0;
this->state_.DEVIATION_M = value - 1;
if (this->initialized_) {
this->write_(Register::DEVIATN);
}
}
void CC1101Component::set_symbol_rate(float value) {
uint8_t e;
uint32_t m;
split_float(value * (1 << 28) / (XTAL_FREQUENCY * 1000), 8, e, m);
this->state_.DRATE_E = e;
this->state_.DRATE_M = static_cast<uint8_t>(m);
if (this->initialized_) {
this->write_(Register::MDMCFG4);
this->write_(Register::MDMCFG3);
}
}
void CC1101Component::set_sync_mode(SyncMode value) {
this->state_.SYNC_MODE = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::MDMCFG2);
}
}
void CC1101Component::set_carrier_sense_above_threshold(bool value) {
this->state_.CARRIER_SENSE_ABOVE_THRESHOLD = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::MDMCFG2);
}
}
void CC1101Component::set_modulation_type(Modulation value) {
this->state_.MOD_FORMAT = static_cast<uint8_t>(value);
this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0;
if (this->initialized_) {
this->enter_idle_();
this->write_(Register::MDMCFG2);
this->write_(Register::FREND0);
this->strobe_(Command::RX);
}
}
void CC1101Component::set_manchester(bool value) {
this->state_.MANCHESTER_EN = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::MDMCFG2);
}
}
void CC1101Component::set_num_preamble(uint8_t value) {
this->state_.NUM_PREAMBLE = value;
if (this->initialized_) {
this->write_(Register::MDMCFG1);
}
}
void CC1101Component::set_sync1(uint8_t value) {
this->state_.SYNC1 = value;
if (this->initialized_) {
this->write_(Register::SYNC1);
}
}
void CC1101Component::set_sync0(uint8_t value) {
this->state_.SYNC0 = value;
if (this->initialized_) {
this->write_(Register::SYNC0);
}
}
void CC1101Component::set_pktlen(uint8_t value) {
this->state_.PKTLEN = value;
if (this->initialized_) {
this->write_(Register::PKTLEN);
}
}
void CC1101Component::set_magn_target(MagnTarget value) {
this->state_.MAGN_TARGET = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL2);
}
}
void CC1101Component::set_max_lna_gain(MaxLnaGain value) {
this->state_.MAX_LNA_GAIN = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL2);
}
}
void CC1101Component::set_max_dvga_gain(MaxDvgaGain value) {
this->state_.MAX_DVGA_GAIN = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL2);
}
}
void CC1101Component::set_carrier_sense_abs_thr(int8_t value) {
this->state_.CARRIER_SENSE_ABS_THR = static_cast<uint8_t>(value & 0b1111);
if (this->initialized_) {
this->write_(Register::AGCCTRL1);
}
}
void CC1101Component::set_carrier_sense_rel_thr(CarrierSenseRelThr value) {
this->state_.CARRIER_SENSE_REL_THR = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL1);
}
}
void CC1101Component::set_lna_priority(bool value) {
this->state_.AGC_LNA_PRIORITY = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::AGCCTRL1);
}
}
void CC1101Component::set_filter_length_fsk_msk(FilterLengthFskMsk value) {
this->state_.FILTER_LENGTH = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL0);
}
}
void CC1101Component::set_filter_length_ask_ook(FilterLengthAskOok value) {
this->state_.FILTER_LENGTH = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL0);
}
}
void CC1101Component::set_freeze(Freeze value) {
this->state_.AGC_FREEZE = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL0);
}
}
void CC1101Component::set_wait_time(WaitTime value) {
this->state_.WAIT_TIME = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL0);
}
}
void CC1101Component::set_hyst_level(HystLevel value) {
this->state_.HYST_LEVEL = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::AGCCTRL0);
}
}
} // namespace esphome::cc1101

View File

@@ -0,0 +1,110 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/spi/spi.h"
#include "esphome/core/automation.h"
#include "cc1101defs.h"
namespace esphome::cc1101 {
class CC1101Component : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
CC1101Component();
void setup() override;
void dump_config() override;
// Actions
void begin_tx();
void begin_rx();
void reset();
void set_idle();
// GDO Pin Configuration
void set_gdo0_config(uint8_t value);
void set_gdo2_config(uint8_t value);
// Configuration Setters
void set_output_power(float value);
void set_rx_attenuation(RxAttenuation value);
void set_dc_blocking_filter(bool value);
// Tuner settings
void set_frequency(float value);
void set_if_frequency(float value);
void set_filter_bandwidth(float value);
void set_channel(uint8_t value);
void set_channel_spacing(float value);
void set_fsk_deviation(float value);
void set_msk_deviation(uint8_t value);
void set_symbol_rate(float value);
void set_sync_mode(SyncMode value);
void set_carrier_sense_above_threshold(bool value);
void set_modulation_type(Modulation value);
void set_manchester(bool value);
void set_num_preamble(uint8_t value);
void set_sync1(uint8_t value);
void set_sync0(uint8_t value);
void set_pktlen(uint8_t value);
// AGC settings
void set_magn_target(MagnTarget value);
void set_max_lna_gain(MaxLnaGain value);
void set_max_dvga_gain(MaxDvgaGain value);
void set_carrier_sense_abs_thr(int8_t value);
void set_carrier_sense_rel_thr(CarrierSenseRelThr value);
void set_lna_priority(bool value);
void set_filter_length_fsk_msk(FilterLengthFskMsk value);
void set_filter_length_ask_ook(FilterLengthAskOok value);
void set_freeze(Freeze value);
void set_wait_time(WaitTime value);
void set_hyst_level(HystLevel value);
protected:
uint16_t chip_id_{0};
bool initialized_{false};
float output_power_requested_{10.0f};
float output_power_effective_{10.0f};
uint8_t pa_table_[PA_TABLE_SIZE]{};
CC1101State state_;
// Low-level Helpers
uint8_t strobe_(Command cmd);
void write_(Register reg);
void write_(Register reg, uint8_t value);
void write_(Register reg, const uint8_t *buffer, size_t length);
void read_(Register reg);
void read_(Register reg, uint8_t *buffer, size_t length);
// State Management
bool wait_for_state_(State target_state, uint32_t timeout_ms = 100);
void enter_idle_();
};
// Action Wrappers
template<typename... Ts> class BeginTxAction : public Action<Ts...>, public Parented<CC1101Component> {
public:
void play(const Ts &...x) override { this->parent_->begin_tx(); }
};
template<typename... Ts> class BeginRxAction : public Action<Ts...>, public Parented<CC1101Component> {
public:
void play(const Ts &...x) override { this->parent_->begin_rx(); }
};
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<CC1101Component> {
public:
void play(const Ts &...x) override { this->parent_->reset(); }
};
template<typename... Ts> class SetIdleAction : public Action<Ts...>, public Parented<CC1101Component> {
public:
void play(const Ts &...x) override { this->parent_->set_idle(); }
};
} // namespace esphome::cc1101

View File

@@ -0,0 +1,644 @@
#pragma once
#include <cinttypes>
namespace esphome::cc1101 {
static constexpr float XTAL_FREQUENCY = 26000;
static constexpr uint8_t BUS_BURST = 0x40;
static constexpr uint8_t BUS_READ = 0x80;
static constexpr uint8_t BUS_WRITE = 0x00;
static constexpr uint8_t BYTES_IN_RXFIFO = 0x7F; // byte number in RXfifo
static constexpr size_t PA_TABLE_SIZE = 8;
enum class Register : uint8_t {
IOCFG2 = 0x00, // GDO2 output pin configuration
IOCFG1 = 0x01, // GDO1 output pin configuration
IOCFG0 = 0x02, // GDO0 output pin configuration
FIFOTHR = 0x03, // RX FIFO and TX FIFO thresholds
SYNC1 = 0x04, // Sync word, high INT8U
SYNC0 = 0x05, // Sync word, low INT8U
PKTLEN = 0x06, // Packet length
PKTCTRL1 = 0x07, // Packet automation control
PKTCTRL0 = 0x08, // Packet automation control
ADDR = 0x09, // Device address
CHANNR = 0x0A, // Channel number
FSCTRL1 = 0x0B, // Frequency synthesizer control
FSCTRL0 = 0x0C, // Frequency synthesizer control
FREQ2 = 0x0D, // Frequency control word, high INT8U
FREQ1 = 0x0E, // Frequency control word, middle INT8U
FREQ0 = 0x0F, // Frequency control word, low INT8U
MDMCFG4 = 0x10, // Modem configuration
MDMCFG3 = 0x11, // Modem configuration
MDMCFG2 = 0x12, // Modem configuration
MDMCFG1 = 0x13, // Modem configuration
MDMCFG0 = 0x14, // Modem configuration
DEVIATN = 0x15, // Modem deviation setting
MCSM2 = 0x16, // Main Radio Control State Machine configuration
MCSM1 = 0x17, // Main Radio Control State Machine configuration
MCSM0 = 0x18, // Main Radio Control State Machine configuration
FOCCFG = 0x19, // Frequency Offset Compensation configuration
BSCFG = 0x1A, // Bit Synchronization configuration
AGCCTRL2 = 0x1B, // AGC control
AGCCTRL1 = 0x1C, // AGC control
AGCCTRL0 = 0x1D, // AGC control
WOREVT1 = 0x1E, // High INT8U Event 0 timeout
WOREVT0 = 0x1F, // Low INT8U Event 0 timeout
WORCTRL = 0x20, // Wake On Radio control
FREND1 = 0x21, // Front end RX configuration
FREND0 = 0x22, // Front end TX configuration
FSCAL3 = 0x23, // Frequency synthesizer calibration
FSCAL2 = 0x24, // Frequency synthesizer calibration
FSCAL1 = 0x25, // Frequency synthesizer calibration
FSCAL0 = 0x26, // Frequency synthesizer calibration
RCCTRL1 = 0x27, // RC oscillator configuration
RCCTRL0 = 0x28, // RC oscillator configuration
FSTEST = 0x29, // Frequency synthesizer calibration control
PTEST = 0x2A, // Production test
AGCTEST = 0x2B, // AGC test
TEST2 = 0x2C, // Various test settings
TEST1 = 0x2D, // Various test settings
TEST0 = 0x2E, // Various test settings
UNUSED = 0x2F,
PARTNUM = 0x30,
VERSION = 0x31,
FREQEST = 0x32,
LQI = 0x33,
RSSI = 0x34,
MARCSTATE = 0x35,
WORTIME1 = 0x36,
WORTIME0 = 0x37,
PKTSTATUS = 0x38,
VCO_VC_DAC = 0x39,
TXBYTES = 0x3A,
RXBYTES = 0x3B,
RCCTRL1_STATUS = 0x3C,
RCCTRL0_STATUS = 0x3D,
PATABLE = 0x3E,
FIFO = 0x3F,
};
enum class Command : uint8_t {
RES = 0x30, // Reset chip.
FSTXON = 0x31, // Enable and calibrate frequency synthesizer
XOFF = 0x32, // Turn off crystal oscillator.
CAL = 0x33, // Calibrate frequency synthesizer and turn it off
RX = 0x34, // Enable RX.
TX = 0x35, // Enable TX.
IDLE = 0x36, // Exit RX / TX
// 0x37 is RESERVED / UNDEFINED in CC1101 Datasheet
WOR = 0x38, // Start automatic RX polling sequence (Wake-on-Radio)
PWD = 0x39, // Enter power down mode when CSn goes high.
FRX = 0x3A, // Flush the RX FIFO buffer.
FTX = 0x3B, // Flush the TX FIFO buffer.
WORRST = 0x3C, // Reset real time clock.
NOP = 0x3D, // No operation.
};
enum class State : uint8_t {
SLEEP,
IDLE,
XOFF,
VCOON_MC,
REGON_MC,
MANCAL,
VCOON,
REGON,
STARTCAL,
BWBOOST,
FS_LOCK,
IFADCON,
ENDCAL,
RX,
RX_END,
RX_RST,
TXRX_SWITCH,
RXFIFO_OVERFLOW,
FSTXON,
TX,
TX_END,
RXTX_SWITCH,
TXFIFO_UNDERFLOW,
};
enum class RxAttenuation : uint8_t {
RX_ATTENUATION_0DB,
RX_ATTENUATION_6DB,
RX_ATTENUATION_12DB,
RX_ATTENUATION_18DB,
};
enum class SyncMode : uint8_t {
SYNC_MODE_NONE,
SYNC_MODE_15_16,
SYNC_MODE_16_16,
SYNC_MODE_30_32,
};
enum class Modulation : uint8_t {
MODULATION_2_FSK,
MODULATION_GFSK,
MODULATION_UNUSED_2,
MODULATION_ASK_OOK,
MODULATION_4_FSK,
MODULATION_UNUSED_5,
MODULATION_UNUSED_6,
MODULATION_MSK,
};
enum class MagnTarget : uint8_t {
MAGN_TARGET_24DB,
MAGN_TARGET_27DB,
MAGN_TARGET_30DB,
MAGN_TARGET_33DB,
MAGN_TARGET_36DB,
MAGN_TARGET_38DB,
MAGN_TARGET_40DB,
MAGN_TARGET_42DB,
};
enum class MaxLnaGain : uint8_t {
MAX_LNA_GAIN_DEFAULT,
MAX_LNA_GAIN_MINUS_2P6DB,
MAX_LNA_GAIN_MINUS_6P1DB,
MAX_LNA_GAIN_MINUS_7P4DB,
MAX_LNA_GAIN_MINUS_9P2DB,
MAX_LNA_GAIN_MINUS_11P5DB,
MAX_LNA_GAIN_MINUS_14P6DB,
MAX_LNA_GAIN_MINUS_17P1DB,
};
enum class MaxDvgaGain : uint8_t {
MAX_DVGA_GAIN_DEFAULT,
MAX_DVGA_GAIN_MINUS_1,
MAX_DVGA_GAIN_MINUS_2,
MAX_DVGA_GAIN_MINUS_3,
};
enum class CarrierSenseRelThr : uint8_t {
CARRIER_SENSE_REL_THR_DEFAULT,
CARRIER_SENSE_REL_THR_PLUS_6DB,
CARRIER_SENSE_REL_THR_PLUS_10DB,
CARRIER_SENSE_REL_THR_PLUS_14DB,
};
enum class FilterLengthFskMsk : uint8_t {
FILTER_LENGTH_8DB,
FILTER_LENGTH_16DB,
FILTER_LENGTH_32DB,
FILTER_LENGTH_64DB,
};
enum class FilterLengthAskOok : uint8_t {
FILTER_LENGTH_4DB,
FILTER_LENGTH_8DB,
FILTER_LENGTH_12DB,
FILTER_LENGTH_16DB,
};
enum class Freeze : uint8_t {
FREEZE_DEFAULT,
FREEZE_ON_SYNC,
FREEZE_ANALOG_ONLY,
FREEZE_ANALOG_AND_DIGITAL,
};
enum class WaitTime : uint8_t {
WAIT_TIME_8_SAMPLES,
WAIT_TIME_16_SAMPLES,
WAIT_TIME_24_SAMPLES,
WAIT_TIME_32_SAMPLES,
};
enum class HystLevel : uint8_t {
HYST_LEVEL_NONE,
HYST_LEVEL_LOW,
HYST_LEVEL_MEDIUM,
HYST_LEVEL_HIGH,
};
struct __attribute__((packed)) CC1101State {
// Byte array accessors for bulk SPI transfers
uint8_t *regs() { return reinterpret_cast<uint8_t *>(this); }
const uint8_t *regs() const { return reinterpret_cast<const uint8_t *>(this); }
// 0x00
union {
uint8_t IOCFG2;
struct {
uint8_t GDO2_CFG : 6;
uint8_t GDO2_INV : 1;
uint8_t : 1;
};
};
// 0x01
union {
uint8_t IOCFG1;
struct {
uint8_t GDO1_CFG : 6;
uint8_t GDO1_INV : 1;
uint8_t GDO_DS : 1; // GDO, not GD0
};
};
// 0x02
union {
uint8_t IOCFG0;
struct {
uint8_t GDO0_CFG : 6;
uint8_t GDO0_INV : 1;
uint8_t TEMP_SENSOR_ENABLE : 1;
};
};
// 0x03
union {
uint8_t FIFOTHR;
struct {
uint8_t FIFO_THR : 4;
uint8_t CLOSE_IN_RX : 2; // RxAttenuation
uint8_t ADC_RETENTION : 1;
uint8_t : 1;
};
};
// 0x04
uint8_t SYNC1;
// 0x05
uint8_t SYNC0;
// 0x06
uint8_t PKTLEN;
// 0x07
union {
uint8_t PKTCTRL1;
struct {
uint8_t ADR_CHK : 2;
uint8_t APPEND_STATUS : 1;
uint8_t CRC_AUTOFLUSH : 1;
uint8_t : 1;
uint8_t PQT : 3;
};
};
// 0x08
union {
uint8_t PKTCTRL0;
struct {
uint8_t LENGTH_CONFIG : 2;
uint8_t CRC_EN : 1;
uint8_t : 1;
uint8_t PKT_FORMAT : 2;
uint8_t WHITE_DATA : 1;
uint8_t : 1;
};
};
// 0x09
uint8_t ADDR;
// 0x0A
uint8_t CHANNR;
// 0x0B
union {
uint8_t FSCTRL1;
struct {
uint8_t FREQ_IF : 5;
uint8_t RESERVED : 1; // hm?
uint8_t : 2;
};
};
// 0x0C
uint8_t FSCTRL0;
// 0x0D
uint8_t FREQ2; // [7:6] always zero
// 0x0E
uint8_t FREQ1;
// 0x0F
uint8_t FREQ0;
// 0x10
union {
uint8_t MDMCFG4;
struct {
uint8_t DRATE_E : 4;
uint8_t CHANBW_M : 2;
uint8_t CHANBW_E : 2;
};
};
// 0x11
union {
uint8_t MDMCFG3;
struct {
uint8_t DRATE_M : 8;
};
};
// 0x12
union {
uint8_t MDMCFG2;
struct {
uint8_t SYNC_MODE : 2;
uint8_t CARRIER_SENSE_ABOVE_THRESHOLD : 1;
uint8_t MANCHESTER_EN : 1;
uint8_t MOD_FORMAT : 3; // Modulation
uint8_t DEM_DCFILT_OFF : 1;
};
};
// 0x13
union {
uint8_t MDMCFG1;
struct {
uint8_t CHANSPC_E : 2;
uint8_t : 2;
uint8_t NUM_PREAMBLE : 3;
uint8_t FEC_EN : 1;
};
};
// 0x14
union {
uint8_t MDMCFG0;
struct {
uint8_t CHANSPC_M : 8;
};
};
// 0x15
union {
uint8_t DEVIATN;
struct {
uint8_t DEVIATION_M : 3;
uint8_t : 1;
uint8_t DEVIATION_E : 3;
uint8_t : 1;
};
};
// 0x16
union {
uint8_t MCSM2;
struct {
uint8_t RX_TIME : 3;
uint8_t RX_TIME_QUAL : 1;
uint8_t RX_TIME_RSSI : 1;
uint8_t : 3;
};
};
// 0x17
union {
uint8_t MCSM1;
struct {
uint8_t TXOFF_MODE : 2;
uint8_t RXOFF_MODE : 2;
uint8_t CCA_MODE : 2;
uint8_t : 2;
};
};
// 0x18
union {
uint8_t MCSM0;
struct {
uint8_t XOSC_FORCE_ON : 1;
uint8_t PIN_CTRL_EN : 1;
uint8_t PO_TIMEOUT : 2;
uint8_t FS_AUTOCAL : 2;
uint8_t : 2;
};
};
// 0x19
union {
uint8_t FOCCFG;
struct {
uint8_t FOC_LIMIT : 2;
uint8_t FOC_POST_K : 1;
uint8_t FOC_PRE_K : 2;
uint8_t FOC_BS_CS_GATE : 1;
uint8_t : 2;
};
};
// 0x1A
union {
uint8_t BSCFG;
struct {
uint8_t BS_LIMIT : 2;
uint8_t BS_POST_KP : 1;
uint8_t BS_POST_KI : 1;
uint8_t BS_PRE_KP : 2;
uint8_t BS_PRE_KI : 2;
};
};
// 0x1B
union {
uint8_t AGCCTRL2;
struct {
uint8_t MAGN_TARGET : 3; // MagnTarget
uint8_t MAX_LNA_GAIN : 3; // MaxLnaGain
uint8_t MAX_DVGA_GAIN : 2; // MaxDvgaGain
};
};
// 0x1C
union {
uint8_t AGCCTRL1;
struct {
uint8_t CARRIER_SENSE_ABS_THR : 4;
uint8_t CARRIER_SENSE_REL_THR : 2; // CarrierSenseRelThr
uint8_t AGC_LNA_PRIORITY : 1;
uint8_t : 1;
};
};
// 0x1D
union {
uint8_t AGCCTRL0;
struct {
uint8_t FILTER_LENGTH : 2; // FilterLengthFskMsk or FilterLengthAskOok
uint8_t AGC_FREEZE : 2; // Freeze
uint8_t WAIT_TIME : 2; // WaitTime
uint8_t HYST_LEVEL : 2; // HystLevel
};
};
// 0x1E
uint8_t WOREVT1;
// 0x1F
uint8_t WOREVT0;
// 0x20
union {
uint8_t WORCTRL;
struct {
uint8_t WOR_RES : 2;
uint8_t : 1;
uint8_t RC_CAL : 1;
uint8_t EVENT1 : 3;
uint8_t RC_PD : 1;
};
};
// 0x21
union {
uint8_t FREND1;
struct {
uint8_t MIX_CURRENT : 2;
uint8_t LODIV_BUF_CURRENT_RX : 2;
uint8_t LNA2MIX_CURRENT : 2;
uint8_t LNA_CURRENT : 2;
};
};
// 0x22
union {
uint8_t FREND0;
struct {
uint8_t PA_POWER : 3;
uint8_t : 1;
uint8_t LODIV_BUF_CURRENT_TX : 2;
uint8_t : 2;
};
};
// 0x23
union {
uint8_t FSCAL3;
struct {
uint8_t FSCAL3_LO : 4;
uint8_t CHP_CURR_CAL_EN : 2; // Disable charge pump calibration stage when 0.
uint8_t FSCAL3_HI : 2;
};
};
// 0x24
union {
// uint8_t FSCAL2;
struct {
uint8_t FSCAL2 : 5;
uint8_t VCO_CORE_H_EN : 1;
uint8_t : 2;
};
};
// 0x25
union {
// uint8_t FSCAL1;
struct {
uint8_t FSCAL1 : 6;
uint8_t : 2;
};
};
// 0x26
union {
// uint8_t FSCAL0;
struct {
uint8_t FSCAL0 : 7;
uint8_t : 1;
};
};
// 0x27
union {
// uint8_t RCCTRL1;
struct {
uint8_t RCCTRL1 : 7;
uint8_t : 1;
};
};
// 0x28
union {
// uint8_t RCCTRL0;
struct {
uint8_t RCCTRL0 : 7;
uint8_t : 1;
};
};
// 0x29
uint8_t FSTEST;
// 0x2A
uint8_t PTEST;
// 0x2B
uint8_t AGCTEST;
// 0x2C
uint8_t TEST2;
// 0x2D
uint8_t TEST1;
// 0x2E
union {
uint8_t TEST0;
struct {
uint8_t TEST0_LO : 1;
uint8_t VCO_SEL_CAL_EN : 1; // Enable VCO selection calibration stage when 1
uint8_t TEST0_HI : 6;
};
};
// 0x2F
uint8_t REG_2F;
// 0x30
uint8_t PARTNUM;
// 0x31
uint8_t VERSION;
// 0x32
union {
uint8_t FREQEST;
struct {
int8_t FREQOFF_EST : 8;
};
};
// 0x33
union {
uint8_t LQI;
struct {
uint8_t LQI_EST : 7;
uint8_t LQI_CRC_OK : 1;
};
};
// 0x34
int8_t RSSI;
// 0x35
union {
// uint8_t MARCSTATE;
struct {
uint8_t MARC_STATE : 5; // State
uint8_t : 3;
};
};
// 0x36
uint8_t WORTIME1;
// 0x37
uint8_t WORTIME0;
// 0x38
union {
uint8_t PKTSTATUS;
struct {
uint8_t GDO0 : 1;
uint8_t : 1;
uint8_t GDO2 : 1;
uint8_t SFD : 1;
uint8_t CCA : 1;
uint8_t PQT_REACHED : 1;
uint8_t CS : 1;
uint8_t CRC_OK : 1; // same as LQI_CRC_OK?
};
};
// 0x39
uint8_t VCO_VC_DAC;
// 0x3A
union {
uint8_t TXBYTES;
struct {
uint8_t NUM_TXBYTES : 7;
uint8_t TXFIFO_UNDERFLOW : 1;
};
};
// 0x3B
union {
uint8_t RXBYTES;
struct {
uint8_t NUM_RXBYTES : 7;
uint8_t RXFIFO_OVERFLOW : 1;
};
};
// 0x3C
union {
// uint8_t RCCTRL1_STATUS;
struct {
uint8_t RCCTRL1_STATUS : 7;
uint8_t : 1;
};
};
// 0x3D
union {
// uint8_t RCCTRL0_STATUS;
struct {
uint8_t RCCTRL0_STATUS : 7;
uint8_t : 1;
};
};
// 0x3E
uint8_t REG_3E;
// 0x3F
uint8_t REG_3F;
};
static_assert(sizeof(CC1101State) == 0x40, "CC1101State size mismatch");
} // namespace esphome::cc1101

View File

@@ -0,0 +1,174 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <cmath>
namespace esphome::cc1101 {
// CC1101 Design Note DN013
struct PowerTableItem {
uint8_t value;
uint8_t dbm_diff; // starts from 12.0, diff to previous entry, scaled by 10
static uint8_t find(const PowerTableItem *items, size_t count, float &dbm_target) {
int32_t dbmi = 120;
int32_t dbmi_target = static_cast<int32_t>(std::lround(dbm_target * 10));
for (size_t i = 0; i < count; i++) {
dbmi -= items[i].dbm_diff;
if (dbmi_target >= dbmi) {
// Skip invalid PA settings (magic numbers derived from TI DN013/SmartRC logic)
if (items[i].value >= 0x61 && items[i].value <= 0x6F) {
continue;
}
dbm_target = static_cast<float>(dbmi) / 10.0f;
return items[i].value;
}
}
dbm_target = -30.0f;
return 0x03;
}
};
static const PowerTableItem PA_TABLE_315[] = {
{0xC0, 14}, // C0 10.6 -35.3 -44.4 -57.8 -53.8 -58.3 -57.2 -57.8 -56.7 28.5
{0xC3, 10}, // C3 9.6 -39.2 -45.3 -59.0 -54.2 -59.0 -57.5 -58.3 -57.2 26.2
{0xC6, 11}, // C6 8.5 -43.2 -46.3 -59.2 -54.7 -59.1 -57.7 -58.3 -57.4 24.4
{0xC9, 10}, // C9 7.5 -47.0 -47.3 -58.9 -55.0 -59.0 -57.9 -58.4 -57.5 23.0
{0x81, 12}, // 81 6.3 -49.2 -45.7 -57.3 -53.6 -59.0 -56.0 -56.5 -57.5 19.5
{0x85, 13}, // 85 5.0 -51.0 -47.2 -59.8 -54.2 -59.0 -56.9 -57.9 -58.0 18.3
{0x88, 11}, // 88 3.9 -46.6 -48.1 -60.0 -55.0 -58.9 -57.5 -58.2 -58.2 17.4
{0xCF, 11}, // CF 2.8 -49.8 -51.3 -57.6 -56.8 -59.1 -58.4 -58.1 -58.3 18.0
{0x8D, 11}, // 8D 1.7 -43.8 -49.5 -58.9 -56.3 -58.8 -58.2 -58.4 -58.5 15.8
{0x50, 10}, // 50 0.7 -59.2 -51.2 -59.0 -56.5 -59.0 -58.3 -58.3 -58.2 15.3
{0x40, 10}, // 40 -0.3 -58.2 -52.1 -59.4 -56.9 -59.0 -58.4 -58.4 -58.3 14.7
{0x3D, 10}, // 3D -1.3 -54.4 -48.4 -59.8 -57.5 -58.9 -58.3 -58.5 -58.5 19.3
{0x55, 10}, // 55 -2.3 -56.7 -53.6 -59.7 -57.5 -59.1 -58.7 -58.4 -58.4 13.7
{0x39, 11}, // 39 -3.4 -50.9 -49.5 -59.8 -58.0 -59.0 -58.5 -58.4 -58.4 16.8
{0x2B, 15}, // 2B -4.9 -51.2 -50.4 -59.9 -58.0 -58.9 -58.7 -58.3 -58.4 15.6
{0x29, 16}, // 29 -6.5 -51.8 -51.6 -59.9 -58.4 -59.0 -58.8 -58.3 -58.3 14.7
{0x28, 10}, // 28 -7.5 -52.2 -52.5 -60.0 -58.6 -59.0 -58.8 -58.2 -58.4 14.3
{0x27, 11}, // 27 -8.6 -52.9 -53.1 -60.0 -58.8 -59.1 -58.8 -58.3 -58.5 13.9
{0x26, 12}, // 26 -9.8 -53.6 -54.3 -60.1 -58.7 -59.0 -58.7 -58.4 -58.4 13.4
{0x25, 13}, // 25 -11.1 -54.3 -55.5 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 13.0
{0x33, 11}, // 33 -12.2 -55.0 -56.3 -60.0 -58.7 -59.0 -58.9 -58.4 -58.4 12.8
{0x1F, 11}, // 1F -13.3 -55.6 -57.2 -60.0 -58.8 -58.9 -58.9 -58.3 -58.4 12.4
{0x1D, 12}, // 1D -14.5 -56.0 -58.0 -60.0 -58.8 -59.1 -58.7 -58.4 -58.5 12.1
{0x32, 11}, // 32 -15.6 -56.9 -58.8 -59.9 -58.8 -59.0 -58.8 -58.3 -58.5 12.2
{0x1A, 10}, // 1A -16.6 -57.3 -59.5 -59.9 -58.8 -59.1 -58.8 -58.4 -58.4 11.8
{0x18, 19}, // 18 -18.5 -57.8 -60.3 -60.0 -58.8 -59.0 -58.9 -58.2 -58.5 11.6
{0x17, 11}, // 17 -19.6 -58.7 -60.9 -60.0 -58.7 -58.9 -58.9 -58.5 -58.4 11.4
{0x0C, 11}, // C -20.7 -59.4 -61.1 -60.0 -58.8 -59.1 -58.9 -58.4 -58.3 11.3
{0x0A, 15}, // A -22.2 -59.9 -61.9 -60.0 -58.9 -59.0 -58.9 -58.4 -58.5 11.2
{0x08, 18}, // 8 -24.0 -60.5 -62.5 -60.0 -58.7 -59.1 -58.8 -58.3 -58.5 11.1
{0x07, 11}, // 7 -25.1 -61.3 -62.9 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 11.0
{0x06, 13}, // 6 -26.4 -61.6 -63.2 -60.1 -58.7 -59.0 -58.9 -58.5 -58.5 11.0
{0x05, 13}, // 5 -27.7 -62.3 -63.4 -60.1 -58.7 -59.2 -58.8 -58.4 -58.5 10.9
{0x04, 19}, // 4 -29.6 -62.7 -63.6 -59.9 -58.7 -59.0 -58.9 -58.4 -58.4 10.8
};
static const PowerTableItem PA_TABLE_433[] = {
{0xC0, 21}, // C0 9.9 -43.4 -45.0 -53.9 -55.2 -55.8 -52.3 -55.6 29.1
{0xC3, 11}, // C3 8.8 -49.3 -45.9 -55.9 -55.4 -57.2 -52.6 -57.5 26.9
{0xC6, 10}, // C6 7.8 -56.2 -46.9 -56.9 -55.6 -58.2 -53.2 -57.9 25.2
{0xC9, 10}, // C9 6.8 -56.1 -47.9 -57.3 -55.9 -58.5 -54.0 -56.9 23.8
{0xCC, 10}, // CC 5.8 -52.8 -48.9 -57.0 -56.1 -58.4 -54.6 -56.2 22.6
{0x85, 10}, // 85 4.8 -54.2 -53.0 -58.3 -55.0 -57.8 -56.8 -58.0 19.1
{0x88, 12}, // 88 3.6 -56.2 -53.8 -58.3 -55.7 -58.1 -57.2 -58.2 18.2
{0x8B, 13}, // 8B 2.3 -57.7 -54.5 -58.0 -56.3 -58.1 -57.5 -58.2 17.3
{0x8E, 19}, // 8E 0.4 -58.0 -55.5 -57.8 -57.4 -58.2 -58.1 -58.4 16.2
{0x40, 12}, // 40 -0.8 -59.7 -56.1 -58.2 -57.7 -58.4 -58.3 -58.2 15.4
{0x3C, 13}, // 3C -2.1 -60.6 -57.3 -58.2 -58.0 -58.5 -58.4 -58.5 19.3
{0x3A, 10}, // 3A -3.1 -59.5 -57.5 -58.3 -58.3 -58.6 -58.1 -58.6 18.1
{0x8F, 15}, // 8F -4.6 -52.2 -57.7 -58.1 -58.8 -58.4 -58.7 -58.3 14.4
{0x37, 10}, // 37 -5.6 -56.8 -58.3 -58.3 -58.8 -58.4 -58.5 -58.4 16.2
{0x36, 12}, // 36 -6.8 -56.8 -58.9 -58.3 -58.8 -58.3 -58.5 -58.5 15.6
{0x28, 10}, // 28 -7.8 -56.6 -59.0 -58.2 -59.0 -58.4 -58.5 -58.4 15.1
{0x26, 21}, // 26 -9.9 -57.0 -59.4 -58.3 -59.0 -58.4 -58.7 -58.4 14.3
{0x25, 15}, // 25 -11.4 -57.3 -59.7 -58.4 -59.0 -58.3 -58.7 -58.5 13.9
{0x24, 19}, // 24 -13.3 -57.9 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 13.5
{0x1E, 10}, // 1E -14.3 -58.4 -59.8 -58.2 -59.0 -58.4 -58.6 -58.6 13.2
{0x1C, 12}, // 1C -15.5 -58.6 -59.9 -58.4 -58.8 -58.6 -58.8 -58.5 12.9
{0x1A, 15}, // 1A -17.0 -59.4 -59.9 -58.3 -59.1 -58.5 -58.7 -58.4 12.7
{0x18, 18}, // 18 -18.8 -60.2 -59.9 -58.2 -59.0 -58.5 -58.7 -58.6 12.5
{0x17, 10}, // 17 -19.8 -60.6 -59.9 -58.2 -58.9 -58.4 -58.7 -58.4 12.4
{0x0C, 12}, // C -21.0 -61.1 -59.9 -58.4 -59.0 -58.5 -58.7 -58.6 12.3
{0x15, 15}, // 15 -22.5 -61.7 -60.0 -58.2 -59.1 -58.3 -58.6 -58.7 12.2
{0x08, 18}, // 8 -24.3 -62.3 -59.9 -58.3 -59.0 -58.4 -58.8 -58.5 12.1
{0x07, 10}, // 7 -25.3 -62.6 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 12.0
{0x06, 12}, // 6 -26.5 -63.2 -59.9 -58.3 -58.9 -58.5 -58.6 -58.6 12.0
{0x05, 14}, // 5 -27.9 -63.5 -59.8 -58.3 -59.1 -58.5 -58.7 -58.4 11.9
{0x04, 16}, // 4 -29.5 -63.7 -59.9 -58.3 -58.9 -58.5 -58.5 -58.5 11.9
};
static const PowerTableItem PA_TABLE_868[] = {
{0xC0, 13}, // C0 10.7 -35.1 -58.6 -58.6 -57.5 -50.0 34.2
{0xC3, 11}, // C3 9.6 -41.5 -58.5 -58.3 -57.4 -54.4 31.6
{0xC6, 11}, // C6 8.5 -47.7 -58.5 -58.3 -57.6 -55.0 29.5
{0xC9, 10}, // C9 7.5 -44.4 -58.5 -58.5 -57.7 -53.6 27.8
{0xCC, 10}, // CC 6.5 -40.6 -58.6 -58.4 -57.6 -52.5 26.3
{0xCE, 10}, // CE 5.5 -38.5 -58.5 -58.4 -57.8 -52.2 25.0
{0x84, 11}, // 84 4.4 -35.3 -58.7 -58.5 -57.8 -55.8 20.3
{0x87, 10}, // 87 3.4 -39.4 -58.6 -58.6 -57.8 -55.7 19.5
{0xCF, 10}, // CF 2.4 -36.6 -58.6 -58.4 -57.7 -53.6 22.0
{0x8C, 13}, // 8C 1.1 -50.2 -58.6 -58.5 -57.7 -55.9 17.9
{0x50, 14}, // 50 -0.3 -42.1 -58.5 -58.5 -57.6 -57.1 16.9
{0x40, 12}, // 40 -1.5 -43.2 -58.5 -58.7 -57.7 -57.2 16.1
{0x3F, 11}, // 3F -2.6 -53.7 -58.6 -58.5 -57.8 -57.5 21.4
{0x55, 10}, // 55 -3.6 -44.9 -58.6 -58.4 -57.8 -57.5 15.0
{0x57, 12}, // 57 -4.8 -46.0 -58.6 -58.5 -57.6 -57.4 14.5
{0x8F, 12}, // 8F -6.0 -51.6 -58.5 -58.6 -57.7 -57.1 15.0
{0x2A, 14}, // 2A -7.4 -49.3 -58.5 -58.6 -57.7 -57.4 16.2
{0x28, 16}, // 28 -9.0 -49.0 -58.5 -58.6 -57.7 -57.4 15.4
{0x26, 20}, // 26 -11.0 -49.2 -58.5 -58.5 -57.7 -57.4 14.6
{0x25, 15}, // 25 -12.5 -49.5 -58.6 -58.6 -57.8 -57.3 14.1
{0x24, 18}, // 24 -14.3 -50.2 -58.5 -58.4 -57.8 -57.4 13.7
{0x1D, 14}, // 1D -15.7 -50.7 -58.6 -58.6 -57.8 -57.5 13.3
{0x1B, 13}, // 1B -17.0 -51.3 -58.5 -58.4 -57.7 -57.5 13.1
{0x19, 16}, // 19 -18.6 -52.0 -58.6 -58.5 -57.8 -57.5 12.9
{0x22, 10}, // 22 -19.6 -52.5 -58.5 -58.6 -57.7 -57.4 12.9
{0x0D, 15}, // D -21.1 -53.3 -58.6 -58.6 -57.8 -57.4 12.6
{0x0B, 12}, // B -22.3 -53.9 -58.6 -58.5 -57.8 -57.4 12.5
{0x09, 15}, // 9 -23.8 -54.7 -58.5 -58.5 -57.8 -57.5 12.4
{0x21, 10}, // 21 -24.8 -55.1 -58.5 -58.5 -57.7 -57.5 12.5
{0x13, 17}, // 13 -26.5 -55.9 -58.6 -58.5 -57.6 -57.6 12.3
{0x05, 12}, // 5 -27.7 -56.4 -58.4 -58.4 -57.7 -57.5 12.2
{0x12, 12}, // 12 -28.9 -57.1 -58.4 -58.5 -57.7 -57.3 12.2
};
static const PowerTableItem PA_TABLE_915[] = {
{0xC0, 26}, // C0 9.4 -33.5 -58.5 -58.4 -55.8 -32.6 31.8
{0xC3, 11}, // C3 8.3 -41.5 -58.6 -58.4 -56.3 -38.0 29.3
{0xC6, 11}, // C6 7.2 -42.5 -58.5 -58.4 -56.7 -40.5 27.4
{0xC9, 10}, // C9 6.2 -37.6 -58.6 -58.4 -57.2 -38.8 25.9
{0xCD, 12}, // CD 5.0 -34.2 -58.6 -58.5 -57.5 -37.3 24.3
{0x84, 11}, // 84 3.9 -32.0 -58.6 -58.4 -57.7 -40.1 19.7
{0x87, 10}, // 87 2.9 -36.5 -58.4 -58.5 -57.7 -39.6 18.9
{0x8A, 11}, // 8A 1.8 -42.2 -58.5 -58.4 -57.7 -39.6 18.1
{0x8D, 13}, // 8D 0.5 -46.8 -58.5 -58.5 -57.7 -40.4 17.3
{0x8E, 11}, // 8E -0.6 -46.6 -58.5 -58.5 -57.8 -41.1 16.7
{0x51, 10}, // 51 -1.6 -38.7 -58.4 -58.5 -57.7 -46.9 16.0
{0x3E, 11}, // 3E -2.7 -50.0 -58.5 -58.4 -57.6 -55.3 20.7
{0x3B, 11}, // 3B -3.8 -50.7 -58.6 -58.4 -57.6 -55.2 18.9
{0x39, 13}, // 39 -5.1 -50.0 -58.5 -58.5 -57.6 -54.0 17.7
{0x2B, 13}, // 2B -6.4 -47.6 -58.4 -58.4 -57.8 -52.1 16.5
{0x36, 15}, // 36 -7.9 -46.9 -58.5 -58.4 -57.7 -51.2 15.8
{0x35, 14}, // 35 -9.3 -46.7 -58.6 -58.4 -57.7 -50.7 15.2
{0x26, 16}, // 26 -10.9 -47.0 -58.6 -58.4 -57.8 -50.9 14.5
{0x25, 14}, // 25 -12.3 -47.2 -58.6 -58.3 -57.7 -51.0 14.1
{0x24, 18}, // 24 -14.1 -48.1 -58.4 -58.4 -57.8 -51.4 13.7
{0x1D, 14}, // 1D -15.5 -48.7 -58.4 -58.5 -57.7 -51.9 13.2
{0x1B, 13}, // 1B -16.8 -49.3 -58.6 -58.4 -57.8 -52.3 13.0
{0x19, 15}, // 19 -18.3 -50.2 -58.5 -58.5 -57.6 -52.8 12.8
{0x18, 10}, // 18 -19.3 -50.6 -58.5 -58.5 -57.7 -53.1 12.7
{0x17, 10}, // 17 -20.3 -51.2 -58.6 -58.5 -57.8 -53.1 12.6
{0x0C, 11}, // C -21.4 -51.8 -58.4 -58.5 -57.7 -53.4 12.5
{0x0A, 13}, // A -22.7 -52.6 -58.5 -58.4 -57.7 -53.6 12.4
{0x08, 16}, // 8 -24.3 -53.6 -58.4 -58.4 -57.6 -54.1 12.3
{0x13, 19}, // 13 -26.2 -54.6 -58.4 -58.5 -57.7 -54.3 12.2
{0x05, 11}, // 5 -27.3 -55.3 -58.4 -58.4 -57.8 -54.5 12.1
{0x12, 13}, // 12 -28.6 -55.9 -58.6 -58.5 -57.7 -54.7 12.1
{0x03, 12}, // 3 -29.8 -56.9 -58.5 -58.4 -57.7 -54.7 12.0
};
} // namespace esphome::cc1101

View File

@@ -0,0 +1,20 @@
cc1101:
id: transceiver
cs_pin: ${cs_pin}
frequency: 433920
if_frequency: 153
filter_bandwidth: 203
channel: 0
channel_spacing: 200
symbol_rate: 5000
modulation_type: ASK/OOK
button:
- platform: template
name: "CC1101 Button"
on_press:
then:
- cc1101.begin_tx: transceiver
- cc1101.begin_rx: transceiver
- cc1101.set_idle: transceiver
- cc1101.reset: transceiver

View File

@@ -0,0 +1,8 @@
substitutions:
cs_pin: GPIO5
packages:
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,8 @@
substitutions:
cs_pin: GPIO5
packages:
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml
<<: !include common.yaml