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/>.
24 #ifdef USE_SERIALRX_SPEKTRUM
26 #include "build/debug.h"
28 #include "drivers/io.h"
29 #include "drivers/io_impl.h"
30 #include "drivers/light_led.h"
31 #include "drivers/serial.h"
32 #include "drivers/system.h"
34 #include "fc/config.h"
36 #include "io/serial.h"
39 #include "telemetry/telemetry.h"
43 #include "rx/spektrum.h"
45 // driver for spektrum satellite receiver / sbus
47 #define SPEKTRUM_MAX_SUPPORTED_CHANNEL_COUNT 12
48 #define SPEKTRUM_2048_CHANNEL_COUNT 12
49 #define SPEKTRUM_1024_CHANNEL_COUNT 7
51 #define SPEK_FRAME_SIZE 16
52 #define SPEKTRUM_NEEDED_FRAME_INTERVAL 5000
54 #define SPEKTRUM_BAUDRATE 115200
56 #define SPEKTRUM_MAX_FADE_PER_SEC 40
57 #define SPEKTRUM_FADE_REPORTS_PER_SEC 2
59 static uint8_t spek_chan_shift
;
60 static uint8_t spek_chan_mask
;
61 static bool rcFrameComplete
= false;
62 static bool spekHiRes
= false;
64 // Variables used for calculating a signal strength from satellite fade.
65 // This is time-variant and computed every second based on the fade
66 // count over the last second.
67 static uint32_t spek_fade_last_sec
= 0; // Stores the timestamp of the last second.
68 static uint16_t spek_fade_last_sec_count
= 0; // Stores the fade count at the last second.
69 static uint8_t rssi_channel
; // Stores the RX RSSI channel.
71 static volatile uint8_t spekFrame
[SPEK_FRAME_SIZE
];
73 static rxRuntimeConfig_t
*rxRuntimeConfigPtr
;
76 static IO_t BindPin
= DEFIO_IO(NONE
);
78 #ifdef HARDWARE_BIND_PLUG
79 static IO_t BindPlug
= DEFIO_IO(NONE
);
83 // Receive ISR callback
84 static void spektrumDataReceive(uint16_t c
)
87 timeDelta_t spekTimeInterval
;
88 static timeUs_t spekTimeLast
= 0;
89 static uint8_t spekFramePosition
= 0;
92 spekTimeInterval
= cmpTimeUs(spekTime
, spekTimeLast
);
93 spekTimeLast
= spekTime
;
95 if (spekTimeInterval
> SPEKTRUM_NEEDED_FRAME_INTERVAL
) {
96 spekFramePosition
= 0;
99 if (spekFramePosition
< SPEK_FRAME_SIZE
) {
100 spekFrame
[spekFramePosition
++] = (uint8_t)c
;
101 if (spekFramePosition
== SPEK_FRAME_SIZE
) {
102 rcFrameComplete
= true;
104 rcFrameComplete
= false;
109 static uint32_t spekChannelData
[SPEKTRUM_MAX_SUPPORTED_CHANNEL_COUNT
];
111 uint8_t spektrumFrameStatus(void)
115 uint32_t current_secs
;
117 if (!rcFrameComplete
) {
118 return RX_FRAME_PENDING
;
121 rcFrameComplete
= false;
123 // Fetch the fade count
124 fade
= (spekFrame
[0] << 8) + spekFrame
[1];
125 current_secs
= micros() / 1000 / (1000 / SPEKTRUM_FADE_REPORTS_PER_SEC
);
127 if (spek_fade_last_sec
== 0) {
128 // This is the first frame status received.
129 spek_fade_last_sec_count
= fade
;
130 spek_fade_last_sec
= current_secs
;
131 } else if(spek_fade_last_sec
!= current_secs
) {
132 // If the difference is > 1, then we missed several seconds worth of frames and
133 // should just throw out the fade calc (as it's likely a full signal loss).
134 if((current_secs
- spek_fade_last_sec
) == 1) {
135 if(rssi_channel
!= 0) {
137 spekChannelData
[rssi_channel
] = 2048 - ((fade
- spek_fade_last_sec_count
) * 2048 / (SPEKTRUM_MAX_FADE_PER_SEC
/ SPEKTRUM_FADE_REPORTS_PER_SEC
));
139 spekChannelData
[rssi_channel
] = 1024 - ((fade
- spek_fade_last_sec_count
) * 1024 / (SPEKTRUM_MAX_FADE_PER_SEC
/ SPEKTRUM_FADE_REPORTS_PER_SEC
));
142 spek_fade_last_sec_count
= fade
;
143 spek_fade_last_sec
= current_secs
;
147 for (b
= 3; b
< SPEK_FRAME_SIZE
; b
+= 2) {
148 uint8_t spekChannel
= 0x0F & (spekFrame
[b
- 1] >> spek_chan_shift
);
149 if (spekChannel
< rxRuntimeConfigPtr
->channelCount
&& spekChannel
< SPEKTRUM_MAX_SUPPORTED_CHANNEL_COUNT
) {
150 if(rssi_channel
!= 0 && spekChannel
!= rssi_channel
) {
151 spekChannelData
[spekChannel
] = ((uint32_t)(spekFrame
[b
- 1] & spek_chan_mask
) << 8) + spekFrame
[b
];
156 return RX_FRAME_COMPLETE
;
159 static uint16_t spektrumReadRawRC(const rxRuntimeConfig_t
*rxRuntimeConfig
, uint8_t chan
)
163 if (chan
>= rxRuntimeConfig
->channelCount
) {
168 data
= 988 + (spekChannelData
[chan
] >> 1); // 2048 mode
170 data
= 988 + spekChannelData
[chan
]; // 1024 mode
177 bool spekShouldBind(uint8_t spektrum_sat_bind
)
179 #ifdef HARDWARE_BIND_PLUG
180 BindPlug
= IOGetByTag(IO_TAG(BINDPLUG_PIN
));
181 IOInit(BindPlug
, OWNER_RX
, RESOURCE_INPUT
, 0);
182 IOConfigGPIO(BindPlug
, IOCFG_IPU
);
184 // Check status of bind plug and exit if not active
185 delayMicroseconds(10); // allow configuration to settle
186 if (IORead(BindPlug
)) {
193 spektrum_sat_bind
== SPEKTRUM_SAT_BIND_DISABLED
||
194 spektrum_sat_bind
> SPEKTRUM_SAT_BIND_MAX
197 /* spektrumBind function ported from Baseflight. It's used to bind satellite receiver to TX.
198 * Function must be called immediately after startup so that we don't miss satellite bind window.
199 * Known parameters. Tested with DSMX satellite and DX8 radio. Framerate (11ms or 22ms) must be selected from TX.
200 * 9 = DSMX 11ms / DSMX 22ms
201 * 5 = DSM2 11ms 2048 / DSM2 22ms 1024
203 void spektrumBind(rxConfig_t
*rxConfig
)
206 if (!spekShouldBind(rxConfig
->spektrum_sat_bind
)) {
212 BindPin
= IOGetByTag(IO_TAG(BIND_PIN
));
213 IOInit(BindPin
, OWNER_RX
, RESOURCE_OUTPUT
, 0);
214 IOConfigGPIO(BindPin
, IOCFG_OUT_PP
);
217 IOWrite(BindPin
, true);
219 // Bind window is around 20-140ms after powerup
223 for (i
= 0; i
< rxConfig
->spektrum_sat_bind
; i
++) {
227 // RX line, drive low for 120us
228 IOWrite(BindPin
, false);
229 delayMicroseconds(120);
233 // RX line, drive high for 120us
234 IOWrite(BindPin
, true);
235 delayMicroseconds(120);
239 #ifndef HARDWARE_BIND_PLUG
240 // 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
241 // Don't reset if hardware bind plug is present
242 // Reset only when autoreset is enabled
243 if (rxConfig
->spektrum_sat_bind_autoreset
== 1 && !isMPUSoftReset()) {
244 rxConfig
->spektrum_sat_bind
= 0;
245 saveConfigAndNotify();
250 #endif // SPEKTRUM_BIND
252 bool spektrumInit(const rxConfig_t
*rxConfig
, rxRuntimeConfig_t
*rxRuntimeConfig
)
254 rxRuntimeConfigPtr
= rxRuntimeConfig
;
256 switch (rxConfig
->serialrx_provider
) {
257 case SERIALRX_SPEKTRUM2048
:
260 spek_chan_mask
= 0x07;
262 rxRuntimeConfig
->channelCount
= SPEKTRUM_2048_CHANNEL_COUNT
;
263 rxRuntimeConfig
->rxRefreshRate
= 11000;
265 case SERIALRX_SPEKTRUM1024
:
268 spek_chan_mask
= 0x03;
270 rxRuntimeConfig
->channelCount
= SPEKTRUM_1024_CHANNEL_COUNT
;
271 rxRuntimeConfig
->rxRefreshRate
= 22000;
275 rxRuntimeConfig
->rcReadRawFn
= spektrumReadRawRC
;
276 rxRuntimeConfig
->rcFrameStatusFn
= spektrumFrameStatus
;
278 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_RX_SERIAL
);
284 bool portShared
= telemetryCheckRxPortShared(portConfig
);
286 bool portShared
= false;
289 serialPort_t
*spektrumPort
= openSerialPort(portConfig
->identifier
, FUNCTION_RX_SERIAL
, spektrumDataReceive
, SPEKTRUM_BAUDRATE
, portShared
? MODE_RXTX
: MODE_RX
, SERIAL_NOT_INVERTED
);
293 telemetrySharedPort
= spektrumPort
;
297 rssi_channel
= rxConfig
->rssi_channel
- 1; // -1 because rxConfig->rssi_channel is 1-based and rssi_channel is 0-based.
298 if (rssi_channel
>= rxRuntimeConfig
->channelCount
) {
302 return spektrumPort
!= NULL
;
304 #endif // USE_SERIALRX_SPEKTRUM