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
23 GET_VTX_TABLE_SIZE
= 0,
32 void SendMSPFrameToFC(uint8_t *mspData
);
34 static bool eepromWriteRequired
= false;
35 static bool setRcePitMode
= false;
36 static uint8_t checkingIndex
= 0;
37 static uint8_t pitMode
= 0;
38 static uint8_t power
= 0;
39 static uint8_t channel
= 0;
40 static uint8_t mspState
= GET_VTX_TABLE_SIZE
;
42 static void sendCrsfMspToFC(uint8_t *mspFrame
, uint8_t mspFrameSize
)
44 CRSF::SetExtendedHeaderAndCrc(mspFrame
, CRSF_FRAMETYPE_MSP_REQ
, mspFrameSize
, CRSF_ADDRESS_CRSF_RECEIVER
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
45 SendMSPFrameToFC(mspFrame
);
48 static void sendVtxConfigCommand(void)
50 uint8_t vtxConfig
[MSP_REQUEST_LENGTH(0)];
51 CRSF::SetMspV2Request(vtxConfig
, MSP_VTX_CONFIG
, nullptr, 0);
52 sendCrsfMspToFC(vtxConfig
, MSP_REQUEST_FRAME_SIZE(0));
55 static void sendEepromWriteCommand(void)
57 if (!eepromWriteRequired
)
62 uint8_t eepromWrite
[MSP_REQUEST_LENGTH(0)];
63 CRSF::SetMspV2Request(eepromWrite
, MSP_EEPROM_WRITE
, nullptr, 0);
64 sendCrsfMspToFC(eepromWrite
, MSP_REQUEST_FRAME_SIZE(0));
66 eepromWriteRequired
= false;
69 static void clearVtxTable(void)
71 uint8_t payload
[MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
] = {
79 4, // newBand - Band Fatshark
80 4, // newChannel - Channel 4
86 1 // vtxtable should be cleared
89 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
)];
90 CRSF::SetMspV2Request(request
, MSP_SET_VTX_CONFIG
, payload
, MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
);
91 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTX_CONFIG_PAYLOAD_LENGTH
));
93 eepromWriteRequired
= true;
96 static void sendRcePitModeCommand(void)
98 uint8_t payload
[4] = {
101 RACE_MODE
, // 25mW Power idx
105 uint8_t request
[MSP_REQUEST_LENGTH(4)];
106 CRSF::SetMspV2Request(request
, MSP_SET_VTX_CONFIG
, payload
, 4);
107 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(4));
109 eepromWriteRequired
= true;
112 static void setVtxTableBand(uint8_t band
)
114 uint8_t payload
[MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
];
117 payload
[1] = BAND_NAME_LENGTH
;
119 for (uint8_t i
= 0; i
< CHANNEL_COUNT
; i
++)
121 payload
[2 + i
] = channelFreqLabelByIdx((band
- 1) * CHANNEL_COUNT
+ i
);
124 payload
[2+CHANNEL_COUNT
] = getBandLetterByIdx(band
- 1);
125 payload
[3+CHANNEL_COUNT
] = IS_FACTORY_BAND
;
126 payload
[4+CHANNEL_COUNT
] = CHANNEL_COUNT
;
128 for (uint8_t i
= 0; i
< CHANNEL_COUNT
; i
++)
130 payload
[(5+CHANNEL_COUNT
) + (i
* 2)] = getFreqByIdx(((band
-1) * CHANNEL_COUNT
) + i
) & 0xFF;
131 payload
[(6+CHANNEL_COUNT
) + (i
* 2)] = (getFreqByIdx(((band
-1) * CHANNEL_COUNT
) + i
) >> 8) & 0xFF;
134 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
)];
135 CRSF::SetMspV2Request(request
, MSP_SET_VTXTABLE_BAND
, payload
, MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
);
136 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTXTABLE_BAND_PAYLOAD_LENGTH
));
138 eepromWriteRequired
= true;
141 static void getVtxTableBand(uint8_t idx
)
143 uint8_t payloadLength
= 1;
144 uint8_t payload
[1] = {idx
};
146 uint8_t request
[MSP_REQUEST_LENGTH(payloadLength
)];
147 CRSF::SetMspV2Request(request
, MSP_VTXTABLE_BAND
, payload
, payloadLength
);
148 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(payloadLength
));
151 static void setVtxTablePowerLevel(uint8_t idx
)
153 uint8_t payload
[MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
];
156 payload
[1] = powerLevelsLut
[idx
- 1] & 0xFF; // powerValue LSB
157 payload
[2] = (powerLevelsLut
[idx
- 1] >> 8) & 0xFF; // powerValue MSB
158 payload
[3] = POWER_LEVEL_LABEL_LENGTH
;
159 payload
[4] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 0];
160 payload
[5] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 1];
161 payload
[6] = powerLevelsLabel
[((idx
- 1) * POWER_LEVEL_LABEL_LENGTH
) + 2];
163 uint8_t request
[MSP_REQUEST_LENGTH(MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
)];
164 CRSF::SetMspV2Request(request
, MSP_SET_VTXTABLE_POWERLEVEL
, payload
, MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
);
165 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(MSP_SET_VTXTABLE_POWERLEVEL_PAYLOAD_LENGTH
));
167 eepromWriteRequired
= true;
170 static void getVtxTablePowerLvl(uint8_t idx
)
172 uint8_t payloadLength
= 1;
173 uint8_t payload
[1] = {idx
};
175 uint8_t request
[MSP_REQUEST_LENGTH(payloadLength
)];
176 CRSF::SetMspV2Request(request
, MSP_VTXTABLE_POWERLEVEL
, payload
, payloadLength
);
177 sendCrsfMspToFC(request
, MSP_REQUEST_FRAME_SIZE(payloadLength
));
181 * CRSF frame structure:
182 * <sync/address><length><type><destination><origin><status><MSP_v2_frame><crsf_crc>
183 * MSP V2 over CRSF frame:
184 * <flags><function1><function2><length1><length2><payload1>...<payloadN><msp_crc>
185 * The biggest payload we have is 29 (MSP_VTXTABLE_BAND).
186 * The maximum packet size is 42 bytes which fits well enough under maximum of 64.
187 * Reply always comes with the same function as the request.
190 void mspVtxProcessPacket(uint8_t *packet
)
192 mspVtxConfigPacket_t
*vtxConfigPacket
;
193 mspVtxPowerLevelPacket_t
*powerLevelPacket
;
194 mspVtxBandPacket_t
*bandPacket
;
196 switch (packet
[MSP_VTX_FUNCTION_OFFSET
])
199 vtxConfigPacket
= (mspVtxConfigPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
203 case GET_VTX_TABLE_SIZE
:
205 // Store initially received values. If the VTx Table is correct, only then set these values.
206 pitMode
= vtxConfigPacket
->pitmode
;
207 power
= vtxConfigPacket
->power
;
209 if (power
== RACE_MODE
&& pitMode
!= 1) // If race mode and not already in PIT, force pit mode on boot and set it in BF.
212 setRcePitMode
= true;
215 if (vtxConfigPacket
->lowPowerDisarm
) // Force 0mw on boot because BF doesnt send a low power index.
220 if (power
>= NUM_POWER_LEVELS
)
225 channel
= ((vtxConfigPacket
->band
- 1) * 8) + (vtxConfigPacket
->channel
- 1);
226 if (channel
>= FREQ_TABLE_SIZE
)
228 channel
= 27; // F4 5800MHz
231 if (vtxConfigPacket
->bands
== getFreqTableBands() && vtxConfigPacket
->channels
== getFreqTableChannels() && vtxConfigPacket
->powerLevels
== NUM_POWER_LEVELS
)
233 mspState
= CHECK_POWER_LEVELS
;
238 case SET_RCE_PIT_MODE
:
239 setRcePitMode
= false;
240 mspState
= SEND_EEPROM_WRITE
;
242 pitMode
= vtxConfigPacket
->pitmode
;
244 // Set power before freq changes to prevent PLL settling issues and spamming other frequencies.
245 power
= vtxConfigPacket
->power
;
246 vtxSPIPitmode
= pitMode
;
247 vtxSPIPowerIdx
= power
;
249 channel
= ((vtxConfigPacket
->band
- 1) * 8) + (vtxConfigPacket
->channel
- 1);
250 if (channel
< FREQ_TABLE_SIZE
)
252 vtxSPIFrequency
= getFreqByIdx(channel
);
258 case MSP_VTXTABLE_POWERLEVEL
:
259 powerLevelPacket
= (mspVtxPowerLevelPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
263 case CHECK_POWER_LEVELS
:
264 if (powerLevelPacket
->powerValue
== powerLevelsLut
[checkingIndex
] && powerLevelPacket
->powerLabelLength
== POWER_LEVEL_LABEL_LENGTH
) // Check lengths before trying to check content
266 if (memcmp(powerLevelPacket
->label
, powerLevelsLabel
+ checkingIndex
* POWER_LEVEL_LABEL_LENGTH
, sizeof(powerLevelPacket
->label
)) == 0)
269 if (checkingIndex
> NUM_POWER_LEVELS
- 1)
272 mspState
= CHECK_BANDS
;
277 setVtxTablePowerLevel(checkingIndex
+ 1);
282 case MSP_VTXTABLE_BAND
:
283 bandPacket
= (mspVtxBandPacket_t
*)(packet
+ MSP_VTX_PAYLOAD_OFFSET
);
288 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
290 if (memcmp(bandPacket
->bandName
, channelFreqLabel
+ checkingIndex
* CHANNEL_COUNT
, sizeof(bandPacket
->bandName
)) == 0)
292 if (memcmp(bandPacket
->channel
, channelFreqTable
+ checkingIndex
* CHANNEL_COUNT
, sizeof(bandPacket
->channel
)) == 0)
295 if (checkingIndex
> getFreqTableBands() - 1)
297 mspState
= (setRcePitMode
? SET_RCE_PIT_MODE
: (eepromWriteRequired
? SEND_EEPROM_WRITE
: MONITORING
));
298 vtxSPIPitmode
= pitMode
;
299 vtxSPIPowerIdx
= power
;
300 vtxSPIFrequency
= getFreqByIdx(channel
);
306 setVtxTableBand(checkingIndex
+ 1);
311 case MSP_SET_VTX_CONFIG
:
312 case MSP_SET_VTXTABLE_BAND
:
313 case MSP_SET_VTXTABLE_POWERLEVEL
:
316 case MSP_EEPROM_WRITE
:
317 mspState
= MONITORING
;
322 static void mspVtxStateUpdate(void)
326 case GET_VTX_TABLE_SIZE
:
327 sendVtxConfigCommand();
329 case CHECK_POWER_LEVELS
:
330 getVtxTablePowerLvl(checkingIndex
+ 1);
333 getVtxTableBand(checkingIndex
+ 1);
335 case SET_RCE_PIT_MODE
:
336 sendRcePitModeCommand();
338 case SEND_EEPROM_WRITE
:
339 sendEepromWriteCommand();
348 void disableMspVtx(void)
350 mspState
= MSP_STATE_MAX
;
353 static int event(void)
355 if (GPIO_PIN_SPI_VTX_NSS
== UNDEF_PIN
)
357 return DURATION_NEVER
;
359 return DURATION_IMMEDIATELY
;
362 static int timeout(void)
364 if (hwTimer::running
&& !hwTimer::isTick
)
366 // Only run code during rx free time or when disconnected.
367 return DURATION_IMMEDIATELY
;
370 return FC_QUERY_PERIOD_MS
;
374 device_t MSPVTx_device
= {