ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
cc1101.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file cc1101.cpp
6 * @brief CC1101 sub-GHz radio driver (see cc1101.h).
7 */
8
10
11#if PC_ENABLE_CC1101
12
13namespace
14{
15// SPI header bits.
16const uint8_t READ = 0x80;
17const uint8_t BURST = 0x40;
18
19// Register / strobe / FIFO addresses.
20const uint8_t REG_CHANNR = 0x0A;
21const uint8_t STROBE_SRES = 0x30; ///< reset chip.
22const uint8_t STROBE_SRX = 0x34; ///< enable RX.
23const uint8_t STROBE_STX = 0x35; ///< enable TX.
24const uint8_t STROBE_SIDLE = 0x36;
25const uint8_t STROBE_SFRX = 0x3A; ///< flush RX FIFO.
26const uint8_t STROBE_SFTX = 0x3B; ///< flush TX FIFO.
27const uint8_t STAT_VERSION = 0x31;
28const uint8_t STAT_RXBYTES = 0x3B;
29const uint8_t FIFO = 0x3F;
30
31// The chip status byte's state field (bits 6-4).
32const uint8_t STATE_IDLE = 0;
33
34void write_reg(const pc_cc1101_bus *b, uint8_t addr, uint8_t val)
35{
36 uint8_t tx[2] = {addr, val}; // header = address (write, single), then value
37 uint8_t rx[2] = {0, 0};
38 b->spi(tx, rx, 2, b->ctx);
39}
40
41uint8_t read_reg(const pc_cc1101_bus *b, uint8_t addr, bool status)
42{
43 // Status registers (0x30-0x3D) require the burst bit to distinguish them from strobes.
44 uint8_t hdr = (uint8_t)(addr | READ | (status ? BURST : 0));
45 uint8_t tx[2] = {hdr, 0};
46 uint8_t rx[2] = {0, 0};
47 b->spi(tx, rx, 2, b->ctx);
48 return rx[1];
49}
50
51void strobe(const pc_cc1101_bus *b, uint8_t cmd)
52{
53 uint8_t tx[1] = {cmd};
54 uint8_t rx[1] = {0};
55 b->spi(tx, rx, 1, b->ctx);
56}
57
58uint8_t status_byte(const pc_cc1101_bus *b)
59{
60 uint8_t tx[1] = {(uint8_t)(0x3D | READ | BURST)}; // SNOP as a read returns the status byte
61 uint8_t rx[1] = {0};
62 b->spi(tx, rx, 1, b->ctx);
63 return rx[0];
64}
65} // namespace
66
67int16_t pc_cc1101_rssi_dbm(uint8_t raw)
68{
69 // TI CC1101 datasheet: dBm = (raw >= 128 ? (raw - 256) : raw) / 2 - 74.
70 int16_t r = raw >= 128 ? (int16_t)raw - 256 : (int16_t)raw;
71 return (int16_t)(r / 2 - 74);
72}
73
74bool pc_cc1101_init(const pc_cc1101_bus *bus, const pc_cc1101_config *cfg)
75{
76 if (!bus || !bus->spi || !cfg)
77 {
78 return false;
79 }
80 strobe(bus, STROBE_SRES);
81 for (size_t i = 0; i < cfg->nregs && cfg->regs; i++)
82 {
83 write_reg(bus, cfg->regs[i].addr, cfg->regs[i].value);
84 }
85 write_reg(bus, REG_CHANNR, cfg->channel);
86 uint8_t ver = read_reg(bus, STAT_VERSION, true);
87 return ver != 0x00 && ver != 0xFF; // a floating bus reads all-0 or all-1
88}
89
90bool pc_cc1101_send(const pc_cc1101_bus *bus, const uint8_t *data, uint8_t len)
91{
92 if (!bus || !bus->spi || !data || len == 0 || len > 63)
93 {
94 return false;
95 }
96 strobe(bus, STROBE_SIDLE);
97 strobe(bus, STROBE_SFTX);
98 // Burst-write the FIFO: header, length byte, payload.
99 uint8_t tx[65];
100 uint8_t rx[65];
101 tx[0] = (uint8_t)(FIFO | BURST);
102 tx[1] = len;
103 for (uint8_t i = 0; i < len; i++)
104 {
105 tx[2 + i] = data[i];
106 }
107 bus->spi(tx, rx, (uint8_t)(2 + len), bus->ctx);
108 strobe(bus, STROBE_STX);
109 return true;
110}
111
112bool pc_cc1101_tx_done(const pc_cc1101_bus *bus)
113{
114 if (!bus || !bus->spi)
115 {
116 return false;
117 }
118 uint8_t st = (uint8_t)((status_byte(bus) >> 4) & 0x07);
119 return st == STATE_IDLE;
120}
121
122void pc_cc1101_set_rx(const pc_cc1101_bus *bus)
123{
124 if (!bus || !bus->spi)
125 {
126 return;
127 }
128 strobe(bus, STROBE_SIDLE);
129 strobe(bus, STROBE_SFRX);
130 strobe(bus, STROBE_SRX);
131}
132
133int pc_cc1101_recv(const pc_cc1101_bus *bus, uint8_t *buf, uint8_t cap, int16_t *rssi_dbm)
134{
135 if (!bus || !bus->spi || !buf)
136 {
137 return -1;
138 }
139 uint8_t rxbytes = (uint8_t)(read_reg(bus, STAT_RXBYTES, true) & 0x7F); // low 7 bits = count
140 if (rxbytes == 0)
141 {
142 return -1;
143 }
144 uint8_t len = read_reg(bus, FIFO, false); // variable-length: leading length byte
145 if (len == 0 || len > 63)
146 {
147 strobe(bus, STROBE_SFRX); // corrupt length: flush and bail
148 return -1;
149 }
150 // Burst-read payload + 2 appended status bytes (RSSI, LQI/CRC).
151 uint8_t tx[66];
152 uint8_t rx[66];
153 uint8_t n = (uint8_t)(len + 2);
154 tx[0] = (uint8_t)(FIFO | READ | BURST);
155 for (uint8_t i = 0; i < n; i++)
156 {
157 tx[1 + i] = 0;
158 }
159 bus->spi(tx, rx, (uint8_t)(1 + n), bus->ctx);
160 if (rssi_dbm)
161 {
162 *rssi_dbm = pc_cc1101_rssi_dbm(rx[1 + len]); // first appended status byte is raw RSSI
163 }
164 uint8_t out = len < cap ? len : cap;
165 for (uint8_t i = 0; i < out; i++)
166 {
167 buf[i] = rx[1 + i];
168 }
169 return out;
170}
171
172#endif // PC_ENABLE_CC1101
CC1101 sub-GHz radio driver (PC_ENABLE_CC1101) - TI 300-928 MHz over SPI.