wip
[inav.git] / src / main / rx / spektrum.c
blob31e4f2f04a3a37b0b481500332077e993532ef6b
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <string.h>
19 #include <stdbool.h>
20 #include <stdint.h>
21 #include <stdlib.h>
23 #include "common/maths.h"
25 #include "platform.h"
27 #ifdef USE_SERIALRX_SPEKTRUM
29 #include "build/debug.h"
31 #include "drivers/io.h"
32 #include "drivers/io_impl.h"
33 #include "drivers/light_led.h"
34 #include "drivers/serial.h"
35 #include "drivers/system.h"
36 #include "drivers/time.h"
38 #include "fc/config.h"
40 #include "io/serial.h"
42 #ifdef USE_TELEMETRY
43 #include "telemetry/telemetry.h"
44 #endif
46 #include "rx/rx.h"
47 #include "rx/spektrum.h"
49 // driver for spektrum satellite receiver / sbus
50 #define SPEKTRUM_TELEMETRY_FRAME_DELAY_US 1000 // Gap between received Rc frame and transmited TM frame
52 #define SPEKTRUM_MAX_FADE_PER_SEC 40
53 #define SPEKTRUM_FADE_REPORTS_PER_SEC 2
55 bool srxlEnabled = false;
57 static uint8_t spek_chan_shift;
58 static uint8_t spek_chan_mask;
59 static bool rcFrameComplete = false;
60 static bool spekHiRes = false;
62 // Variables used for calculating a signal strength from satellite fade.
63 // This is time-variant and computed every second based on the fade
64 // count over the last second.
65 static uint32_t spek_fade_last_sec = 0; // Stores the timestamp of the last second.
66 static uint16_t spek_fade_last_sec_count = 0; // Stores the fade count at the last second.
67 static uint8_t rssi_channel; // Stores the RX RSSI channel.
69 static volatile uint8_t spekFrame[SPEK_FRAME_SIZE];
71 static rxRuntimeConfig_t *rxRuntimeConfigPtr;
72 static serialPort_t *serialPort;
74 #ifdef USE_SPEKTRUM_BIND
75 static IO_t BindPin = DEFIO_IO(NONE);
76 #endif
77 #ifdef HARDWARE_BIND_PLUG
78 static IO_t BindPlug = DEFIO_IO(NONE);
79 #endif
81 #if defined(USE_TELEMETRY_SRXL)
82 static uint8_t telemetryBuf[SRXL_FRAME_SIZE_MAX];
83 static uint8_t telemetryBufLen = 0;
84 #endif
86 // Receive ISR callback
87 static void spektrumDataReceive(uint16_t c, void *rxCallbackData)
89 UNUSED(rxCallbackData);
91 timeUs_t spekTime;
92 timeDelta_t spekTimeInterval;
93 static timeUs_t spekTimeLast = 0;
94 static uint8_t spekFramePosition = 0;
96 spekTime = micros();
97 spekTimeInterval = cmpTimeUs(spekTime, spekTimeLast);
98 spekTimeLast = spekTime;
100 if (spekTimeInterval > SPEKTRUM_NEEDED_FRAME_INTERVAL) {
101 spekFramePosition = 0;
104 if (spekFramePosition < SPEK_FRAME_SIZE) {
105 spekFrame[spekFramePosition++] = (uint8_t)c;
106 if (spekFramePosition == SPEK_FRAME_SIZE) {
107 rcFrameComplete = true;
108 } else {
109 rcFrameComplete = false;
114 static uint32_t spekChannelData[SPEKTRUM_MAX_SUPPORTED_CHANNEL_COUNT];
116 static uint8_t spektrumFrameStatus(rxRuntimeConfig_t *rxRuntimeConfig)
118 UNUSED(rxRuntimeConfig);
120 #if defined(USE_TELEMETRY_SRXL)
121 static timeUs_t telemetryFrameRequestedUs = 0;
123 timeUs_t currentTimeUs = micros();
124 #endif
126 uint8_t result = RX_FRAME_PENDING;
128 if (rcFrameComplete) {
129 rcFrameComplete = false;
131 // Fetch the fade count
132 const uint16_t fade = (spekFrame[0] << 8) + spekFrame[1];
133 const uint32_t current_secs = millis() / (1000 / SPEKTRUM_FADE_REPORTS_PER_SEC);
135 if (spek_fade_last_sec == 0) {
136 // This is the first frame status received.
137 spek_fade_last_sec_count = fade;
138 spek_fade_last_sec = current_secs;
139 } else if (spek_fade_last_sec != current_secs) {
140 // If the difference is > 1, then we missed several seconds worth of frames and
141 // should just throw out the fade calc (as it's likely a full signal loss).
142 if ((current_secs - spek_fade_last_sec) == 1) {
143 if (rssi_channel != 0) {
144 if (spekHiRes)
145 spekChannelData[rssi_channel] = 2048 - ((fade - spek_fade_last_sec_count) * 2048 / (SPEKTRUM_MAX_FADE_PER_SEC / SPEKTRUM_FADE_REPORTS_PER_SEC));
146 else
147 spekChannelData[rssi_channel] = 1024 - ((fade - spek_fade_last_sec_count) * 1024 / (SPEKTRUM_MAX_FADE_PER_SEC / SPEKTRUM_FADE_REPORTS_PER_SEC));
150 spek_fade_last_sec_count = fade;
151 spek_fade_last_sec = current_secs;
155 for (int b = 3; b < SPEK_FRAME_SIZE; b += 2) {
156 const uint8_t spekChannel = 0x0F & (spekFrame[b - 1] >> spek_chan_shift);
157 if (spekChannel < rxRuntimeConfigPtr->channelCount && spekChannel < SPEKTRUM_MAX_SUPPORTED_CHANNEL_COUNT) {
158 if (rssi_channel == 0 || spekChannel != rssi_channel) {
159 spekChannelData[spekChannel] = ((uint32_t)(spekFrame[b - 1] & spek_chan_mask) << 8) + spekFrame[b];
164 #if defined(USE_TELEMETRY_SRXL)
165 if (srxlEnabled && (spekFrame[2] & 0x80) == 0) {
166 telemetryFrameRequestedUs = currentTimeUs;
168 #endif
170 result = RX_FRAME_COMPLETE;
172 #if defined(USE_TELEMETRY_SRXL)
173 if (telemetryBufLen && telemetryFrameRequestedUs && cmpTimeUs(currentTimeUs, telemetryFrameRequestedUs) >= SPEKTRUM_TELEMETRY_FRAME_DELAY_US) {
174 telemetryFrameRequestedUs = 0;
176 result = (result & ~RX_FRAME_PENDING) | RX_FRAME_PROCESSING_REQUIRED;
178 #endif
180 return result;
183 static uint16_t spektrumReadRawRC(const rxRuntimeConfig_t *rxRuntimeConfig, uint8_t chan)
185 uint16_t data;
187 if (chan >= rxRuntimeConfig->channelCount) {
188 return 0;
191 if (spekHiRes)
192 data = 988 + (spekChannelData[chan] >> 1); // 2048 mode
193 else
194 data = 988 + spekChannelData[chan]; // 1024 mode
196 return data;
199 #ifdef USE_SPEKTRUM_BIND
201 bool spekShouldBind(uint8_t spektrum_sat_bind)
203 #ifdef HARDWARE_BIND_PLUG
204 BindPlug = IOGetByTag(IO_TAG(BINDPLUG_PIN));
205 IOInit(BindPlug, OWNER_RX, RESOURCE_INPUT, 0);
206 IOConfigGPIO(BindPlug, IOCFG_IPU);
208 // Check status of bind plug and exit if not active
209 delayMicroseconds(10); // allow configuration to settle
210 if (IORead(BindPlug)) {
211 return false;
213 #endif
215 return !(
216 isMPUSoftReset() ||
217 spektrum_sat_bind == SPEKTRUM_SAT_BIND_DISABLED ||
218 spektrum_sat_bind > SPEKTRUM_SAT_BIND_MAX
221 /* spektrumBind function ported from Baseflight. It's used to bind satellite receiver to TX.
222 * Function must be called immediately after startup so that we don't miss satellite bind window.
223 * Known parameters. Tested with DSMX satellite and DX8 radio. Framerate (11ms or 22ms) must be selected from TX.
224 * 9 = DSMX 11ms / DSMX 22ms
225 * 5 = DSM2 11ms 2048 / DSM2 22ms 1024
227 void spektrumBind(rxConfig_t *rxConfig)
229 int i;
230 if (!spekShouldBind(rxConfig->spektrum_sat_bind)) {
231 return;
234 LED1_ON;
236 BindPin = IOGetByTag(IO_TAG(BIND_PIN));
237 IOInit(BindPin, OWNER_RX, RESOURCE_OUTPUT, 0);
238 IOConfigGPIO(BindPin, IOCFG_OUT_PP);
240 // RX line, set high
241 IOWrite(BindPin, true);
243 // Bind window is around 20-140ms after powerup
244 delay(60);
245 LED1_OFF;
247 for (i = 0; i < rxConfig->spektrum_sat_bind; i++) {
249 LED0_OFF;
250 LED2_OFF;
251 // RX line, drive low for 120us
252 IOWrite(BindPin, false);
253 delayMicroseconds(120);
255 LED0_ON;
256 LED2_ON;
257 // RX line, drive high for 120us
258 IOWrite(BindPin, true);
259 delayMicroseconds(120);
263 #ifndef HARDWARE_BIND_PLUG
264 // If we came here as a result of hard reset (power up, with spektrum_sat_bind set), then reset it back to zero and write config
265 // Don't reset if hardware bind plug is present
266 // Reset only when autoreset is enabled
267 if (rxConfig->spektrum_sat_bind_autoreset == 1 && !isMPUSoftReset()) {
268 rxConfig->spektrum_sat_bind = 0;
269 saveConfigAndNotify();
271 #endif
274 #endif // SPEKTRUM_BIND
276 #if defined(USE_TELEMETRY_SRXL)
278 bool srxlTelemetryBufferEmpty(void)
280 if (telemetryBufLen == 0) {
281 return true;
282 } else {
283 return false;
287 void srxlRxWriteTelemetryData(const void *data, int len)
289 len = MIN(len, (int)sizeof(telemetryBuf));
290 memcpy(telemetryBuf, data, len);
291 telemetryBufLen = len;
293 #endif
295 bool spektrumInit(const rxConfig_t *rxConfig, rxRuntimeConfig_t *rxRuntimeConfig)
297 rxRuntimeConfigPtr = rxRuntimeConfig;
299 switch (rxConfig->serialrx_provider) {
300 case SERIALRX_SPEKTRUM2048:
301 // 11 bit frames
302 spek_chan_shift = 3;
303 spek_chan_mask = 0x07;
304 spekHiRes = true;
305 rxRuntimeConfig->channelCount = SPEKTRUM_2048_CHANNEL_COUNT;
306 break;
307 case SERIALRX_SPEKTRUM1024:
308 // 10 bit frames
309 spek_chan_shift = 2;
310 spek_chan_mask = 0x03;
311 spekHiRes = false;
312 rxRuntimeConfig->channelCount = SPEKTRUM_1024_CHANNEL_COUNT;
313 break;
316 rxRuntimeConfig->rcReadRawFn = spektrumReadRawRC;
317 rxRuntimeConfig->rcFrameStatusFn = spektrumFrameStatus;
319 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_RX_SERIAL);
320 if (!portConfig) {
321 return false;
324 #ifdef USE_TELEMETRY
325 bool portShared = telemetryCheckRxPortShared(portConfig);
326 #else
327 bool portShared = false;
328 #endif
330 serialPort = openSerialPort(portConfig->identifier,
331 FUNCTION_RX_SERIAL,
332 spektrumDataReceive,
333 NULL,
334 SPEKTRUM_BAUDRATE,
335 portShared ? MODE_RXTX : MODE_RX,
336 SERIAL_NOT_INVERTED | (tristateWithDefaultOffIsActive(rxConfig->halfDuplex) ? SERIAL_BIDIR : 0)
339 #ifdef USE_TELEMETRY
340 if (portShared) {
341 telemetrySharedPort = serialPort;
343 #endif
345 rssi_channel = rxConfig->rssi_channel - 1; // -1 because rxConfig->rssi_channel is 1-based and rssi_channel is 0-based.
346 if (rssi_channel >= rxRuntimeConfig->channelCount) {
347 rssi_channel = 0;
350 return serialPort != NULL;
353 bool srxlRxIsActive(void)
355 return serialPort != NULL;
358 #endif // USE_SERIALRX_SPEKTRUM