11 * Based on OpenVTX implementation - https://github.com/OpenVTx/OpenVTx/blob/master/src/src/mspVtx.c
12 * Original author: Jye Smith.
17 #define FC_QUERY_PERIOD_MS 200 // poll every 200ms
18 #define MSP_VTX_FUNCTION_OFFSET 7
19 #define MSP_VTX_PAYLOAD_OFFSET 11
20 #define MSP_VTX_TIMEOUT_NO_CONNECTION 5000
24 GET_VTX_TABLE_SIZE
= 0,
34 void SendMSPFrameToFC(uint8_t *mspData
);
36 static bool eepromWriteRequired
= false;
37 static bool setRcePitMode
= false;
38 static uint8_t checkingIndex
= 0;
39 static uint8_t pitMode
= 0;
40 static uint8_t power
= 0;
41 static uint8_t channel
= 0;
42 static uint8_t mspState
= STOP_MSPVTX
;
44 static void sendCrsfMspToFC(uint8_t *mspFrame
, uint8_t mspFrameSize
)
46 CRSF::SetExtendedHeaderAndCrc(mspFrame
, CRSF_FRAMETYPE_MSP_REQ
, mspFrameSize
, CRSF_ADDRESS_CRSF_RECEIVER
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
47 SendMSPFrameToFC(mspFrame
);
50 static void sendVtxConfigCommand(void)
52 uint8_t vtxConfig
[MSP_REQUEST_LENGTH(0)];
53 CRSF::SetMspV2Request(vtxConfig
, MSP_VTX_CONFIG
, nullptr, 0);
54 sendCrsfMspToFC(vtxConfig
, MSP_REQUEST_FRAME_SIZE(0));
57 static void sendEepromWriteCommand(void)
59 if (!eepromWriteRequired
)
64 uint8_t eepromWrite
[MSP_REQUEST_LENGTH(0)];
65 CRSF::SetMspV2Request(eepromWrite
, MSP_EEPROM_WRITE
, nullptr, 0);
66 sendCrsfMspToFC(eepromWrite
, MSP_REQUEST_FRAME_SIZE(0));
68 eepromWriteRequired
= false;
71 static void clearVtxTable(void)
73 uint8_t payload
[MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
] = {
81 4, // newBand - Band Fatshark
82 4, // newChannel - Channel 4
88 1 // vtxtable should be cleared
91 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
)];
92 CRSF::SetMspV2Request(request
, MSP_SET_VTX_CONFIG
, payload
, MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
);
93 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
));
95 eepromWriteRequired
= true;
98 static void sendRcePitModeCommand(void)
100 uint8_t payload
[4] = {
103 RACE_MODE
, // 25mW Power idx
107 uint8_t request
[MSP_REQUEST_LENGTH(4)];
108 CRSF::SetMspV2Request(request
, MSP_SET_VTX_CONFIG
, payload
, 4);
109 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(4));
111 eepromWriteRequired
= true;
114 static void setVtxTableBand(uint8_t band
)
116 uint8_t payload
[MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
];
119 payload
[1] = BAND_NAME_LENGTH
;
121 for (uint8_t i
= 0; i
< CHANNEL_COUNT
; i
++)
123 payload
[2 + i
] = channelFreqLabelByIdx((band
- 1) * CHANNEL_COUNT
+ i
);
126 payload
[2+CHANNEL_COUNT
] = getBandLetterByIdx(band
- 1);
127 payload
[3+CHANNEL_COUNT
] = IS_FACTORY_BAND
;
128 payload
[4+CHANNEL_COUNT
] = CHANNEL_COUNT
;
130 for (uint8_t i
= 0; i
< CHANNEL_COUNT
; i
++)
132 payload
[(5+CHANNEL_COUNT
) + (i
* 2)] = getFreqByIdx(((band
-1) * CHANNEL_COUNT
) + i
) & 0xFF;
133 payload
[(6+CHANNEL_COUNT
) + (i
* 2)] = (getFreqByIdx(((band
-1) * CHANNEL_COUNT
) + i
) >> 8) & 0xFF;
136 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
)];
137 CRSF::SetMspV2Request(request
, MSP_SET_VTXTABLE_BAND
, payload
, MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
);
138 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
));
140 eepromWriteRequired
= true;
143 static void getVtxTableBand(uint8_t idx
)
145 uint8_t payloadLength
= 1;
146 uint8_t payload
[1] = {idx
};
148 uint8_t request
[MSP_REQUEST_LENGTH(payloadLength
)];
149 CRSF::SetMspV2Request(request
, MSP_VTXTABLE_BAND
, payload
, payloadLength
);
150 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(payloadLength
));
153 static void setVtxTablePowerLevel(uint8_t idx
)
155 uint8_t payload
[MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
];
158 payload
[1] = powerLevelsLut
[idx
- 1] & 0xFF; // powerValue LSB
159 payload
[2] = (powerLevelsLut
[idx
- 1] >> 8) & 0xFF; // powerValue MSB
160 payload
[3] = POWER_LEVEL_LABEL_LENGTH
;
161 payload
[4] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 0];
162 payload
[5] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 1];
163 payload
[6] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 2];
165 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
)];
166 CRSF::SetMspV2Request(request
, MSP_SET_VTXTABLE_POWERLEVEL
, payload
, MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
);
167 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
));
169 eepromWriteRequired
= true;
172 static void getVtxTablePowerLvl(uint8_t idx
)
174 uint8_t payloadLength
= 1;
175 uint8_t payload
[1] = {idx
};
177 uint8_t request
[MSP_REQUEST_LENGTH(payloadLength
)];
178 CRSF::SetMspV2Request(request
, MSP_VTXTABLE_POWERLEVEL
, payload
, payloadLength
);
179 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(payloadLength
));
183 * CRSF frame structure:
184 * <sync/address><length><type><destination><origin><status><MSP_v2_frame><crsf_crc>
185 * MSP V2 over CRSF frame:
186 * <flags><function1><function2><length1><length2><payload1>...<payloadN><msp_crc>
187 * The biggest payload we have is 29 (MSP_VTXTABLE_BAND).
188 * The maximum packet size is 42 bytes which fits well enough under maximum of 64.
189 * Reply always comes with the same function as the request.
192 void mspVtxProcessPacket(uint8_t *packet
)
194 mspVtxConfigPacket_t
*vtxConfigPacket
;
195 mspVtxPowerLevelPacket_t
*powerLevelPacket
;
196 mspVtxBandPacket_t
*bandPacket
;
198 switch (packet
[MSP_VTX_FUNCTION_OFFSET
])
201 vtxConfigPacket
= (mspVtxConfigPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
205 case GET_VTX_TABLE_SIZE
:
207 // Store initially received values. If the VTx Table is correct, only then set these values.
208 pitMode
= vtxConfigPacket
->pitmode
;
209 power
= vtxConfigPacket
->power
;
211 if (power
== RACE_MODE
&& pitMode
!= 1) // If race mode and not already in PIT, force pit mode on boot and set it in BF.
214 setRcePitMode
= true;
217 if (vtxConfigPacket
->lowPowerDisarm
) // Force 0mw on boot because BF doesnt send a low power index.
222 if (power
> NUM_POWER_LEVELS
)
227 channel
= ((vtxConfigPacket
->band
- 1) * 8) + (vtxConfigPacket
->channel
- 1);
228 if (channel
>= FREQ_TABLE_SIZE
)
230 channel
= 27; // F4 5800MHz
233 if (vtxConfigPacket
->bands
== getFreqTableBands() && vtxConfigPacket
->channels
== getFreqTableChannels() && vtxConfigPacket
->powerLevels
== NUM_POWER_LEVELS
)
235 mspState
= CHECK_POWER_LEVELS
;
240 case SET_RCE_PIT_MODE
:
241 setRcePitMode
= false;
242 mspState
= SEND_EEPROM_WRITE
;
244 pitMode
= vtxConfigPacket
->pitmode
;
246 // Set power before freq changes to prevent PLL settling issues and spamming other frequencies.
247 power
= vtxConfigPacket
->power
;
248 vtxSPIPitmode
= pitMode
;
249 vtxSPIPowerIdx
= power
;
251 channel
= ((vtxConfigPacket
->band
- 1) * 8) + (vtxConfigPacket
->channel
- 1);
252 if (channel
< FREQ_TABLE_SIZE
)
254 vtxSPIFrequency
= getFreqByIdx(channel
);
260 case MSP_VTXTABLE_POWERLEVEL
:
261 powerLevelPacket
= (mspVtxPowerLevelPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
265 case CHECK_POWER_LEVELS
:
266 if (powerLevelPacket
->powerValue
== powerLevelsLut
[checkingIndex
] && powerLevelPacket
->powerLabelLength
== POWER_LEVEL_LABEL_LENGTH
) // Check lengths before trying to check content
268 if (memcmp(powerLevelPacket
->label
, powerLevelsLabel
+ checkingIndex
* POWER_LEVEL_LABEL_LENGTH
, sizeof(powerLevelPacket
->label
)) == 0)
271 if (checkingIndex
> NUM_POWER_LEVELS
- 1)
274 mspState
= CHECK_BANDS
;
279 setVtxTablePowerLevel(checkingIndex
+ 1);
284 case MSP_VTXTABLE_BAND
:
285 bandPacket
= (mspVtxBandPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
290 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
292 if (memcmp(bandPacket
->bandName
, channelFreqLabel
+ checkingIndex
* CHANNEL_COUNT
, sizeof(bandPacket
->bandName
)) == 0)
294 if (memcmp(bandPacket
->channel
, channelFreqTable
+ checkingIndex
* CHANNEL_COUNT
, sizeof(bandPacket
->channel
)) == 0)
297 if (checkingIndex
> getFreqTableBands() - 1)
299 mspState
= (setRcePitMode
? SET_RCE_PIT_MODE
: (eepromWriteRequired
? SEND_EEPROM_WRITE
: MONITORING
));
300 vtxSPIPitmode
= pitMode
;
301 vtxSPIPowerIdx
= power
;
302 vtxSPIFrequency
= getFreqByIdx(channel
);
308 setVtxTableBand(checkingIndex
+ 1);
313 case MSP_SET_VTX_CONFIG
:
314 case MSP_SET_VTXTABLE_BAND
:
315 case MSP_SET_VTXTABLE_POWERLEVEL
:
318 case MSP_EEPROM_WRITE
:
319 mspState
= MONITORING
;
324 static void mspVtxStateUpdate(void)
328 case GET_VTX_TABLE_SIZE
:
329 sendVtxConfigCommand();
331 case CHECK_POWER_LEVELS
:
332 getVtxTablePowerLvl(checkingIndex
+ 1);
335 getVtxTableBand(checkingIndex
+ 1);
337 case SET_RCE_PIT_MODE
:
338 sendRcePitModeCommand();
340 case SEND_EEPROM_WRITE
:
341 sendEepromWriteCommand();
350 void disableMspVtx(void)
352 mspState
= STOP_MSPVTX
;
355 static void initialize()
359 mspState
= GET_VTX_TABLE_SIZE
;
363 static int event(void)
365 if (GPIO_PIN_SPI_VTX_NSS
== UNDEF_PIN
)
367 return DURATION_NEVER
;
369 return DURATION_IMMEDIATELY
;
372 static int timeout(void)
374 if (mspState
== STOP_MSPVTX
|| (mspState
!= MONITORING
&& millis() > MSP_VTX_TIMEOUT_NO_CONNECTION
))
376 return DURATION_NEVER
;
379 if (hwTimer::running
&& !hwTimer::isTick
)
381 // Only run code during rx free time or when disconnected.
382 return DURATION_IMMEDIATELY
;
385 return FC_QUERY_PERIOD_MS
;
389 device_t MSPVTx_device
= {
390 .initialize
= initialize
,