2 * This file is part of INAV Project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
6 * You can obtain one at http://mozilla.org/MPL/2.0/.
8 * Alternatively, the contents of this file may be used under the terms
9 * of the GNU General Public License Version 3, as described below:
11 * This file is free software: you may copy, redistribute and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation, either version 3 of the License, or (at your
14 * option) any later version.
16 * This file is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see http://www.gnu.org/licenses/.
32 #if defined(USE_VTX_FFPV) && defined(USE_VTX_CONTROL)
34 #include "build/debug.h"
36 #include "drivers/time.h"
37 #include "drivers/vtx_common.h"
39 #include "common/maths.h"
40 #include "common/utils.h"
42 #include "scheduler/protothreads.h"
45 #include "io/vtx_ffpv24g.h"
46 #include "io/vtx_control.h"
47 #include "io/vtx_string.h"
48 #include "io/serial.h"
51 #define VTX_FFPV_CMD_TIMEOUT_MS 250
52 #define VTX_FFPV_HEARTBEAT_MS 1000
54 #define VTX_FFPV_MIN_BAND (1)
55 #define VTX_FFPV_MAX_BAND (VTX_FFPV_MIN_BAND + VTX_FFPV_BAND_COUNT - 1)
56 #define VTX_FFPV_MIN_CHANNEL (1)
57 #define VTX_FFPV_MAX_CHANNEL (VTX_FFPV_MIN_CHANNEL + VTX_FFPV_CHANNEL_COUNT -1)
59 #define VTX_UPDATE_REQ_NONE 0x00
60 #define VTX_UPDATE_REQ_FREQUENCY 0x01
61 #define VTX_UPDATE_REQ_POWER 0x02
63 typedef struct __attribute__((__packed__
)) ffpvPacket_s
{
74 unsigned updateReqMask
;
84 // Requested VTX state
99 // Comms flags and state
100 ffpvPacket_t sendPkt
;
101 ffpvPacket_t recvPkt
;
106 /*****************************************************************************/
107 const char * const ffpvBandNames
[VTX_FFPV_BAND_COUNT
+ 1] = {
113 const char * ffpvBandLetters
= "-AB";
115 const uint16_t ffpvFrequencyTable
[VTX_FFPV_BAND_COUNT
][VTX_FFPV_CHANNEL_COUNT
] =
117 { 2410, 2430, 2450, 2470, 2370, 2390, 2490, 2510 }, // FFPV 2.4 A
118 { 2414, 2432, 2450, 2468, 2411, 2433, 2453, 2473 }, // FFPV 2.4 A
121 const char * const ffpvChannelNames
[VTX_FFPV_CHANNEL_COUNT
+ 1] = {
122 "-", "1", "2", "3", "4", "5", "6", "7", "8",
125 const char * const ffpvPowerNames
[VTX_FFPV_POWER_COUNT
+ 1] = {
126 "---", "25 ", "200", "500", "800"
129 const unsigned ffpvPowerTable
[VTX_FFPV_POWER_COUNT
] = {
134 /*******************************************************************************/
135 static serialPort_t
* vtxSerialPort
= NULL
;
136 static vtxProtoState_t vtxState
;
138 static uint8_t vtxCalcChecksum(ffpvPacket_t
* pkt
)
140 uint8_t sum
= pkt
->cmd
;
141 for (int i
= 0; i
< 12; i
++) {
147 static bool vtxProtoRecv(void)
149 // Return success instantly if packet is already awaiting processing
150 if (vtxState
.pktReceived
) {
154 uint8_t * bufPtr
= (uint8_t*)&vtxState
.recvPkt
;
155 while (serialRxBytesWaiting(vtxSerialPort
) && !vtxState
.pktReceived
) {
156 const uint8_t c
= serialRead(vtxSerialPort
);
158 if (vtxState
.recvPtr
== 0) {
159 // Wait for sync byte
161 bufPtr
[vtxState
.recvPtr
++] = c
;
165 // Sync byte ok - wait for full packet
166 if (vtxState
.recvPtr
< sizeof(vtxState
.recvPkt
)) {
167 bufPtr
[vtxState
.recvPtr
++] = c
;
170 // Received full packet - do some processing
171 if (vtxState
.recvPtr
== sizeof(vtxState
.recvPkt
)) {
172 // Full packet received - validate packet, make sure it's the one we expect
173 const bool pktValid
= (vtxState
.recvPkt
.header
== 0x0F && vtxState
.recvPkt
.cmd
== vtxState
.sendPkt
.cmd
&& vtxState
.recvPkt
.footer
== 0x00 && vtxState
.recvPkt
.checksum
== vtxCalcChecksum(&vtxState
.recvPkt
));
175 // Reset the receiver state - keep waiting
176 vtxState
.pktReceived
= false;
177 vtxState
.recvPtr
= 0;
179 // Make sure it's not the echo one (half-duplex serial might receive it's own data)
180 else if (memcmp(&vtxState
.recvPkt
.data
, &vtxState
.sendPkt
.data
, sizeof(vtxState
.recvPkt
.data
)) == 0) {
181 vtxState
.pktReceived
= false;
182 vtxState
.recvPtr
= 0;
184 // Valid receive packet
186 vtxState
.pktReceived
= true;
196 static void vtxProtoSend(uint8_t cmd
, const uint8_t * data
)
198 // Craft and send FPV packet
199 vtxState
.sendPkt
.header
= 0x0F;
200 vtxState
.sendPkt
.cmd
= cmd
;
203 memcpy(vtxState
.sendPkt
.data
, data
, sizeof(vtxState
.sendPkt
.data
));
206 ZERO_FARRAY(vtxState
.sendPkt
.data
);
209 vtxState
.sendPkt
.checksum
= vtxCalcChecksum(&vtxState
.sendPkt
);
210 vtxState
.sendPkt
.footer
= 0x00;
213 serialWriteBuf(vtxSerialPort
, (uint8_t *)&vtxState
.sendPkt
, sizeof(vtxState
.sendPkt
));
215 // Reset cmd response state
216 vtxState
.pktReceived
= false;
217 vtxState
.recvPtr
= 0;
220 static void vtxProtoSend_SetFreqency(unsigned freq
)
222 uint8_t data
[12] = {0};
223 data
[0] = freq
& 0xFF;
224 data
[1] = (freq
>> 8) & 0xFF;
225 vtxProtoSend(0x46, data
);
228 static void vtxProtoSend_SetPower(unsigned power
)
230 uint8_t data
[12] = {0};
231 data
[0] = power
& 0xFF;
232 data
[1] = (power
>> 8) & 0xFF;
233 vtxProtoSend(0x50, data
);
236 STATIC_PROTOTHREAD(impl_VtxProtocolThread
)
238 ptBegin(impl_VtxProtocolThread
);
240 // 0: Detect VTX. Dwell here infinitely until we get a valid response from VTX
241 vtxState
.ready
= false;
242 while(!vtxState
.ready
) {
243 // Send capabilities request and wait
244 vtxProtoSend(0x72, NULL
);
245 ptWaitTimeout(vtxProtoRecv(), VTX_FFPV_CMD_TIMEOUT_MS
);
247 // Check if we got a valid response
248 if (vtxState
.pktReceived
) {
249 vtxState
.capabilities
.freqMin
= vtxState
.recvPkt
.data
[0] | (vtxState
.recvPkt
.data
[1] << 8);
250 vtxState
.capabilities
.freqMax
= vtxState
.recvPkt
.data
[2] | (vtxState
.recvPkt
.data
[3] << 8);
251 vtxState
.capabilities
.powerMin
= 0;
252 vtxState
.capabilities
.powerMax
= vtxState
.recvPkt
.data
[4] | (vtxState
.recvPkt
.data
[5] << 8);
253 vtxState
.ready
= true;
257 // 1 : Periodically poll VTX for current channel and power, send updates
258 vtxState
.protoTimeouts
= 0;
259 vtxState
.updateReqMask
= VTX_UPDATE_REQ_NONE
;
261 while(vtxState
.ready
) {
262 // Wait for request for update or time to check liveness
263 ptWaitTimeout(vtxState
.updateReqMask
!= VTX_UPDATE_REQ_NONE
, VTX_FFPV_HEARTBEAT_MS
);
265 if (vtxState
.updateReqMask
!= VTX_UPDATE_REQ_NONE
) {
266 if (vtxState
.updateReqMask
& VTX_UPDATE_REQ_FREQUENCY
) {
267 vtxProtoSend_SetFreqency(vtxState
.request
.freq
);
268 vtxState
.updateReqMask
&= ~VTX_UPDATE_REQ_FREQUENCY
;
269 ptDelayMs(VTX_FFPV_CMD_TIMEOUT_MS
);
271 else if (vtxState
.updateReqMask
& VTX_UPDATE_REQ_POWER
) {
272 vtxProtoSend_SetPower(vtxState
.request
.power
);
273 vtxState
.updateReqMask
&= ~VTX_UPDATE_REQ_POWER
;
276 // Unsupported request - reset
277 vtxState
.updateReqMask
= VTX_UPDATE_REQ_NONE
;
281 // Periodic check for VTX liveness
282 vtxProtoSend(0x76, NULL
);
283 ptWaitTimeout(vtxProtoRecv(), VTX_FFPV_CMD_TIMEOUT_MS
);
285 if (vtxState
.pktReceived
) {
286 // Got a valid state from VTX
287 vtxState
.state
.freq
= (uint16_t)vtxState
.recvPkt
.data
[0] | ((uint16_t)vtxState
.recvPkt
.data
[1] << 8);
288 vtxState
.state
.power
= (uint16_t)vtxState
.recvPkt
.data
[2] | ((uint16_t)vtxState
.recvPkt
.data
[3] << 8);
289 vtxState
.protoTimeouts
= 0;
291 // Check if VTX state matches VTX request
292 if (vtxState
.state
.freq
!= vtxState
.request
.freq
) {
293 vtxState
.updateReqMask
|= VTX_UPDATE_REQ_FREQUENCY
;
296 if (vtxState
.state
.power
!= vtxState
.request
.power
) {
297 vtxState
.updateReqMask
|= VTX_UPDATE_REQ_POWER
;
301 vtxState
.protoTimeouts
++;
305 // Sanity check. If we got more than 3 protocol erros
306 if (vtxState
.protoTimeouts
>= 3) {
307 // Reset ready flag - thread will terminate and restart
308 vtxState
.ready
= false;
316 static void impl_Process(vtxDevice_t
*vtxDevice
, timeUs_t currentTimeUs
)
318 // Glue function betwen VTX VTable and actual driver protothread
320 UNUSED(currentTimeUs
);
322 impl_VtxProtocolThread();
324 // If thread stopped - vtx comms failed - restart thread and re-init VTX comms
325 if (ptIsStopped(ptGetHandle(impl_VtxProtocolThread
))) {
326 ptRestart(ptGetHandle(impl_VtxProtocolThread
));
330 static vtxDevType_e
impl_GetDeviceType(const vtxDevice_t
*vtxDevice
)
336 static bool impl_IsReady(const vtxDevice_t
*vtxDevice
)
338 return vtxDevice
!= NULL
&& vtxSerialPort
!= NULL
&& vtxState
.ready
;
341 static bool impl_DevSetFreq(uint16_t freq
)
343 if (!vtxState
.ready
|| freq
< vtxState
.capabilities
.freqMin
|| freq
> vtxState
.capabilities
.freqMax
) {
347 vtxState
.request
.freq
= freq
;
348 vtxState
.updateReqMask
|= VTX_UPDATE_REQ_FREQUENCY
;
353 static void impl_SetBandAndChannel(vtxDevice_t
* vtxDevice
, uint8_t band
, uint8_t channel
)
357 // Validate band and channel
358 if (band
< VTX_FFPV_MIN_BAND
|| band
> VTX_FFPV_MAX_BAND
|| channel
< VTX_FFPV_MIN_CHANNEL
|| channel
> VTX_FFPV_MAX_CHANNEL
) {
362 if (impl_DevSetFreq(ffpvFrequencyTable
[band
- 1][channel
- 1])) {
363 // Keep track of band/channel data
364 vtxState
.request
.band
= band
;
365 vtxState
.request
.channel
= channel
;
369 static void ffpvSetRFPowerByIndex(uint16_t index
)
372 if (index
< 1 || index
> VTX_FFPV_POWER_COUNT
) {
376 const unsigned power
= ffpvPowerTable
[index
- 1];
377 if (!vtxState
.ready
|| power
< vtxState
.capabilities
.powerMin
|| power
> vtxState
.capabilities
.powerMax
) {
381 vtxState
.request
.power
= power
;
382 vtxState
.request
.powerIndex
= index
;
383 vtxState
.updateReqMask
|= VTX_UPDATE_REQ_POWER
;
386 static void impl_SetPowerByIndex(vtxDevice_t
* vtxDevice
, uint8_t index
)
389 ffpvSetRFPowerByIndex(index
);
392 static void impl_SetPitMode(vtxDevice_t
*vtxDevice
, uint8_t onoff
)
394 // TODO: Not implemented
399 static bool impl_GetBandAndChannel(const vtxDevice_t
*vtxDevice
, uint8_t *pBand
, uint8_t *pChannel
)
401 if (!impl_IsReady(vtxDevice
)) {
405 // if in user-freq mode then report band as zero
406 *pBand
= vtxState
.request
.band
;
407 *pChannel
= vtxState
.request
.channel
;
411 static bool impl_GetPowerIndex(const vtxDevice_t
*vtxDevice
, uint8_t *pIndex
)
413 if (!impl_IsReady(vtxDevice
)) {
417 *pIndex
= vtxState
.request
.powerIndex
;
422 static bool impl_GetPitMode(const vtxDevice_t
*vtxDevice
, uint8_t *pOnOff
)
424 if (!impl_IsReady(vtxDevice
)) {
428 // TODO: Not inplemented
433 static bool impl_GetFreq(const vtxDevice_t
*vtxDevice
, uint16_t *pFreq
)
435 if (!impl_IsReady(vtxDevice
)) {
439 *pFreq
= vtxState
.request
.freq
;
443 static bool impl_GetPower(const vtxDevice_t
*vtxDevice
, uint8_t *pIndex
, uint16_t *pPowerMw
)
445 if (!impl_IsReady(vtxDevice
)) {
449 *pIndex
= vtxState
.request
.powerIndex
;
450 *pPowerMw
= vtxState
.request
.power
;
454 static bool impl_GetOsdInfo(const vtxDevice_t
*vtxDevice
, vtxDeviceOsdInfo_t
* pOsdInfo
)
456 if (!impl_IsReady(vtxDevice
)) {
460 pOsdInfo
->band
= vtxState
.request
.band
;
461 pOsdInfo
->channel
= vtxState
.request
.channel
;
462 pOsdInfo
->frequency
= vtxState
.request
.freq
;
463 pOsdInfo
->powerIndex
= vtxState
.request
.powerIndex
;
464 pOsdInfo
->powerMilliwatt
= vtxState
.request
.power
;
465 pOsdInfo
->bandName
= ffpvBandNames
[vtxState
.request
.band
];
466 pOsdInfo
->bandLetter
= ffpvBandLetters
[vtxState
.request
.band
];
467 pOsdInfo
->channelName
= ffpvChannelNames
[vtxState
.request
.channel
];
468 pOsdInfo
->powerIndexLetter
= '0' + vtxState
.request
.powerIndex
;
472 /*****************************************************************************/
473 static const vtxVTable_t impl_vtxVTable
= {
474 .process
= impl_Process
,
475 .getDeviceType
= impl_GetDeviceType
,
476 .isReady
= impl_IsReady
,
477 .setBandAndChannel
= impl_SetBandAndChannel
,
478 .setPowerByIndex
= impl_SetPowerByIndex
,
479 .setPitMode
= impl_SetPitMode
,
480 .getBandAndChannel
= impl_GetBandAndChannel
,
481 .getPowerIndex
= impl_GetPowerIndex
,
482 .getPitMode
= impl_GetPitMode
,
483 .getFrequency
= impl_GetFreq
,
484 .getPower
= impl_GetPower
,
485 .getOsdInfo
= impl_GetOsdInfo
,
488 static vtxDevice_t impl_vtxDevice
= {
489 .vTable
= &impl_vtxVTable
,
490 .capability
.bandCount
= VTX_FFPV_BAND_COUNT
,
491 .capability
.channelCount
= VTX_FFPV_CHANNEL_COUNT
,
492 .capability
.powerCount
= VTX_FFPV_POWER_COUNT
,
493 .capability
.bandNames
= (char **)ffpvBandNames
,
494 .capability
.channelNames
= (char **)ffpvChannelNames
,
495 .capability
.powerNames
= (char **)ffpvPowerNames
,
498 bool vtxFuriousFPVInit(void)
500 serialPortConfig_t
* portConfig
= findSerialPortConfig(FUNCTION_VTX_FFPV
);
503 portOptions_t portOptions
= 0;
504 portOptions
= portOptions
| (vtxConfig()->halfDuplex
? SERIAL_BIDIR
: SERIAL_UNIDIR
);
505 vtxSerialPort
= openSerialPort(portConfig
->identifier
, FUNCTION_VTX_FFPV
, NULL
, NULL
, 9600, MODE_RXTX
, portOptions
);
508 if (!vtxSerialPort
) {
512 vtxCommonSetDevice(&impl_vtxDevice
);
514 ptRestart(ptGetHandle(impl_VtxProtocolThread
));