3 #if defined(PLATFORM_ESP32) || defined(UNIT_TEST)
14 * Based on OpenVTX implementation - https://github.com/OpenVTx/OpenVTx/blob/master/src/src/mspVtx.c
15 * Original author: Jye Smith.
18 #define FC_QUERY_PERIOD_MS 200 // poll every 200ms
19 #define MSP_VTX_FUNCTION_OFFSET 7
20 #define MSP_VTX_PAYLOAD_OFFSET 11
21 #define MSP_VTX_TIMEOUT_NO_CONNECTION 5000
25 GET_VTX_TABLE_SIZE
= 0,
35 void SendMSPFrameToFC(uint8_t *mspData
);
37 static bool eepromWriteRequired
= false;
38 static bool setRcePitMode
= false;
39 static uint8_t checkingIndex
= 0;
40 static uint8_t pitMode
= 0;
41 static uint8_t power
= 0;
42 static uint8_t channel
= 0;
43 static uint8_t lastState
= STOP_MSPVTX
;
44 static uint8_t mspState
= STOP_MSPVTX
;
46 static void sendCrsfMspToFC(uint8_t *mspFrame
, uint8_t mspFrameSize
)
48 CRSF::SetExtendedHeaderAndCrc(mspFrame
, CRSF_FRAMETYPE_MSP_REQ
, mspFrameSize
, CRSF_ADDRESS_CRSF_RECEIVER
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
49 SendMSPFrameToFC(mspFrame
);
52 static void sendVtxConfigCommand(void)
54 uint8_t vtxConfig
[MSP_REQUEST_LENGTH(0)];
55 CRSF::SetMspV2Request(vtxConfig
, MSP_VTX_CONFIG
, nullptr, 0);
56 sendCrsfMspToFC(vtxConfig
, MSP_REQUEST_FRAME_SIZE(0));
59 static void sendEepromWriteCommand(void)
61 if (!eepromWriteRequired
)
66 uint8_t eepromWrite
[MSP_REQUEST_LENGTH(0)];
67 CRSF::SetMspV2Request(eepromWrite
, MSP_EEPROM_WRITE
, nullptr, 0);
68 sendCrsfMspToFC(eepromWrite
, MSP_REQUEST_FRAME_SIZE(0));
70 eepromWriteRequired
= false;
73 static void clearVtxTable(void)
75 uint8_t payload
[MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
] = {
83 4, // newBand - Band Fatshark
84 4, // newChannel - Channel 4
90 1 // vtxtable should be cleared
93 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
)];
94 CRSF::SetMspV2Request(request
, MSP_SET_VTX_CONFIG
, payload
, MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
);
95 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
));
97 eepromWriteRequired
= true;
100 static void sendRcePitModeCommand(void)
102 uint8_t payload
[4] = {
105 RACE_MODE
, // 25mW Power idx
109 uint8_t request
[MSP_REQUEST_LENGTH(4)];
110 CRSF::SetMspV2Request(request
, MSP_SET_VTX_CONFIG
, payload
, 4);
111 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(4));
113 eepromWriteRequired
= true;
116 static void setVtxTableBand(uint8_t band
)
118 uint8_t payload
[MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
];
121 payload
[1] = BAND_NAME_LENGTH
;
123 for (uint8_t i
= 0; i
< CHANNEL_COUNT
; i
++)
125 payload
[2 + i
] = channelFreqLabelByIdx((band
- 1) * CHANNEL_COUNT
+ i
);
128 payload
[2+CHANNEL_COUNT
] = getBandLetterByIdx(band
- 1);
129 payload
[3+CHANNEL_COUNT
] = IS_FACTORY_BAND
;
130 payload
[4+CHANNEL_COUNT
] = CHANNEL_COUNT
;
132 for (uint8_t i
= 0; i
< CHANNEL_COUNT
; i
++)
134 payload
[(5+CHANNEL_COUNT
) + (i
* 2)] = getFreqByIdx(((band
-1) * CHANNEL_COUNT
) + i
) & 0xFF;
135 payload
[(6+CHANNEL_COUNT
) + (i
* 2)] = (getFreqByIdx(((band
-1) * CHANNEL_COUNT
) + i
) >> 8) & 0xFF;
138 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
)];
139 CRSF::SetMspV2Request(request
, MSP_SET_VTXTABLE_BAND
, payload
, MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
);
140 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
));
142 eepromWriteRequired
= true;
145 static void getVtxTableBand(uint8_t idx
)
147 uint8_t payloadLength
= 1;
148 uint8_t payload
[1] = {idx
};
150 uint8_t request
[MSP_REQUEST_LENGTH(payloadLength
)];
151 CRSF::SetMspV2Request(request
, MSP_VTXTABLE_BAND
, payload
, payloadLength
);
152 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(payloadLength
));
155 static void setVtxTablePowerLevel(uint8_t idx
)
157 uint8_t payload
[MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
];
160 payload
[1] = powerLevelsLut
[idx
- 1] & 0xFF; // powerValue LSB
161 payload
[2] = (powerLevelsLut
[idx
- 1] >> 8) & 0xFF; // powerValue MSB
162 payload
[3] = POWER_LEVEL_LABEL_LENGTH
;
163 payload
[4] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 0];
164 payload
[5] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 1];
165 payload
[6] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 2];
167 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
)];
168 CRSF::SetMspV2Request(request
, MSP_SET_VTXTABLE_POWERLEVEL
, payload
, MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
);
169 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
));
171 eepromWriteRequired
= true;
174 static void getVtxTablePowerLvl(uint8_t idx
)
176 uint8_t payloadLength
= 1;
177 uint8_t payload
[1] = {idx
};
179 uint8_t request
[MSP_REQUEST_LENGTH(payloadLength
)];
180 CRSF::SetMspV2Request(request
, MSP_VTXTABLE_POWERLEVEL
, payload
, payloadLength
);
181 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(payloadLength
));
185 * CRSF frame structure:
186 * <sync/address><length><type><destination><origin><status><MSP_v2_frame><crsf_crc>
187 * MSP V2 over CRSF frame:
188 * <flags><function1><function2><length1><length2><payload1>...<payloadN><msp_crc>
189 * The biggest payload we have is 29 (MSP_VTXTABLE_BAND).
190 * The maximum packet size is 42 bytes which fits well enough under maximum of 64.
191 * Reply always comes with the same function as the request.
194 void mspVtxProcessPacket(uint8_t *packet
)
196 mspVtxConfigPacket_t
*vtxConfigPacket
;
197 mspVtxPowerLevelPacket_t
*powerLevelPacket
;
198 mspVtxBandPacket_t
*bandPacket
;
200 switch (packet
[MSP_VTX_FUNCTION_OFFSET
])
203 vtxConfigPacket
= (mspVtxConfigPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
207 case GET_VTX_TABLE_SIZE
:
209 // Store initially received values. If the VTx Table is correct, only then set these values.
210 pitMode
= vtxConfigPacket
->pitmode
;
211 power
= vtxConfigPacket
->power
;
213 if (power
== RACE_MODE
&& pitMode
!= 1) // If race mode and not already in PIT, force pit mode on boot and set it in BF.
216 setRcePitMode
= true;
219 if (vtxConfigPacket
->lowPowerDisarm
) // Force 0mw on boot because BF doesnt send a low power index.
224 if (power
> NUM_POWER_LEVELS
)
229 channel
= ((vtxConfigPacket
->band
- 1) * 8) + (vtxConfigPacket
->channel
- 1);
230 if (channel
>= FREQ_TABLE_SIZE
)
232 channel
= 27; // F4 5800MHz
235 if (vtxConfigPacket
->bands
== getFreqTableBands() && vtxConfigPacket
->channels
== getFreqTableChannels() && vtxConfigPacket
->powerLevels
== NUM_POWER_LEVELS
)
237 mspState
= CHECK_POWER_LEVELS
;
238 devicesTriggerEvent(EVENT_VTX_CHANGE
);
243 case SET_RCE_PIT_MODE
:
244 setRcePitMode
= false;
245 mspState
= SEND_EEPROM_WRITE
;
246 devicesTriggerEvent(EVENT_VTX_CHANGE
);
248 pitMode
= vtxConfigPacket
->pitmode
;
250 // Set power before freq changes to prevent PLL settling issues and spamming other frequencies.
251 power
= vtxConfigPacket
->power
;
252 vtxSPIPitmode
= pitMode
;
253 vtxSPIPowerIdx
= power
;
255 channel
= ((vtxConfigPacket
->band
- 1) * 8) + (vtxConfigPacket
->channel
- 1);
256 if (channel
< FREQ_TABLE_SIZE
)
258 vtxSPIFrequency
= getFreqByIdx(channel
);
264 case MSP_VTXTABLE_POWERLEVEL
:
265 powerLevelPacket
= (mspVtxPowerLevelPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
269 case CHECK_POWER_LEVELS
:
270 if (powerLevelPacket
->powerValue
== powerLevelsLut
[checkingIndex
] && powerLevelPacket
->powerLabelLength
== POWER_LEVEL_LABEL_LENGTH
) // Check lengths before trying to check content
272 if (memcmp(powerLevelPacket
->label
, powerLevelsLabel
+ checkingIndex
* POWER_LEVEL_LABEL_LENGTH
, sizeof(powerLevelPacket
->label
)) == 0)
275 if (checkingIndex
> NUM_POWER_LEVELS
- 1)
278 mspState
= CHECK_BANDS
;
279 devicesTriggerEvent(EVENT_VTX_CHANGE
);
284 setVtxTablePowerLevel(checkingIndex
+ 1);
289 case MSP_VTXTABLE_BAND
:
290 bandPacket
= (mspVtxBandPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
295 if (bandPacket
->bandNameLength
== BAND_NAME_LENGTH
&& bandPacket
->bandLetter
== getBandLetterByIdx(checkingIndex
) && bandPacket
->isFactoryBand
== IS_FACTORY_BAND
&& bandPacket
->channels
== CHANNEL_COUNT
) // Check lengths before trying to check content
297 if (memcmp(bandPacket
->bandName
, channelFreqLabel
+ checkingIndex
* CHANNEL_COUNT
, sizeof(bandPacket
->bandName
)) == 0)
299 if (memcmp(bandPacket
->channel
, channelFreqTable
+ checkingIndex
* CHANNEL_COUNT
, sizeof(bandPacket
->channel
)) == 0)
302 if (checkingIndex
> getFreqTableBands() - 1)
304 mspState
= (setRcePitMode
? SET_RCE_PIT_MODE
: (eepromWriteRequired
? SEND_EEPROM_WRITE
: MONITORING
));
305 vtxSPIPitmode
= pitMode
;
306 vtxSPIPowerIdx
= power
;
307 vtxSPIFrequency
= getFreqByIdx(channel
);
308 devicesTriggerEvent(EVENT_VTX_CHANGE
);
314 setVtxTableBand(checkingIndex
+ 1);
319 case MSP_SET_VTX_CONFIG
:
320 case MSP_SET_VTXTABLE_BAND
:
321 case MSP_SET_VTXTABLE_POWERLEVEL
:
324 case MSP_EEPROM_WRITE
:
325 mspState
= MONITORING
;
326 devicesTriggerEvent(EVENT_VTX_CHANGE
);
331 static void mspVtxStateUpdate(void)
335 case GET_VTX_TABLE_SIZE
:
336 sendVtxConfigCommand();
338 case CHECK_POWER_LEVELS
:
339 getVtxTablePowerLvl(checkingIndex
+ 1);
342 getVtxTableBand(checkingIndex
+ 1);
344 case SET_RCE_PIT_MODE
:
345 sendRcePitModeCommand();
347 case SEND_EEPROM_WRITE
:
348 sendEepromWriteCommand();
357 void disableMspVtx(void)
359 mspState
= STOP_MSPVTX
;
362 static bool initialize()
364 return OPT_HAS_VTX_SPI
;
369 mspState
= GET_VTX_TABLE_SIZE
;
370 return DURATION_IMMEDIATELY
;
375 if (mspState
== STOP_MSPVTX
)
377 return DURATION_NEVER
;
379 if (lastState
== STOP_MSPVTX
)
381 lastState
= mspState
;
382 return DURATION_IMMEDIATELY
;
384 return DURATION_IGNORE
;
387 static int timeout(void)
389 if (mspState
== STOP_MSPVTX
|| (mspState
!= MONITORING
&& millis() > MSP_VTX_TIMEOUT_NO_CONNECTION
))
391 mspState
= STOP_MSPVTX
;
392 return DURATION_NEVER
;
395 if (hwTimer::running
&& !hwTimer::isTick
)
397 // Only run code during rx free time or when disconnected.
398 return DURATION_IMMEDIATELY
;
401 return FC_QUERY_PERIOD_MS
;
405 device_t MSPVTx_device
= {
406 .initialize
= initialize
,
410 .subscribe
= EVENT_VTX_CHANGE