VTX SPI output calibration routine and initial PWM value config (#2515)
[ExpressLRS.git] / src / lib / MSPVTX / devMSPVTX.cpp
blob7771b880b0938840bc9bf7a85966030d5702c29a
1 #include "targets.h"
2 #include "common.h"
3 #include "devMSPVTX.h"
4 #include "devVTXSPI.h"
5 #include "freqTable.h"
6 #include "CRSF.h"
7 #include "hwTimer.h"
9 /**
10 * Created by phobos-
11 * Based on OpenVTX implementation - https://github.com/OpenVTx/OpenVTx/blob/master/src/src/mspVtx.c
12 * Original author: Jye Smith.
13 **/
15 #ifdef HAS_MSP_VTX
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
22 typedef enum
24 GET_VTX_TABLE_SIZE = 0,
25 CHECK_POWER_LEVELS,
26 CHECK_BANDS,
27 SET_RCE_PIT_MODE,
28 SEND_EEPROM_WRITE,
29 MONITORING,
30 STOP_MSPVTX,
31 MSP_STATE_MAX
32 } mspVtxState_e;
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)
61 return;
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] = {
74 0, // idx LSB
75 0, // idx MSB
76 3, // 25mW Power idx
77 0, // pitmode
78 0, // lowPowerDisarm
79 0, // pitModeFreq LSB
80 0, // pitModeFreq MSB
81 4, // newBand - Band Fatshark
82 4, // newChannel - Channel 4
83 0, // newFreq LSB
84 0, // newFreq MSB
85 6, // newBandCount
86 8, // newChannelCount
87 5, // newPowerCount
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] = {
101 channel, // idx LSB
102 0, // idx MSB
103 RACE_MODE, // 25mW Power idx
104 1 // pitmode
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];
118 payload[0] = band;
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];
157 payload[0] = idx;
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])
200 case MSP_VTX_CONFIG:
201 vtxConfigPacket = (mspVtxConfigPacket_t *)(packet + MSP_VTX_PAYLOAD_OFFSET);
203 switch (mspState)
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.
213 pitMode = 1;
214 setRcePitMode = true;
217 if (vtxConfigPacket->lowPowerDisarm) // Force 0mw on boot because BF doesnt send a low power index.
219 power = 1;
222 if (power > NUM_POWER_LEVELS)
224 power = 3; // 25 mW
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;
236 break;
238 clearVtxTable();
239 break;
240 case SET_RCE_PIT_MODE:
241 setRcePitMode = false;
242 mspState = SEND_EEPROM_WRITE;
243 case MONITORING:
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);
256 break;
258 break;
260 case MSP_VTXTABLE_POWERLEVEL:
261 powerLevelPacket = (mspVtxPowerLevelPacket_t *)(packet + MSP_VTX_PAYLOAD_OFFSET);
263 switch (mspState)
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)
270 checkingIndex++;
271 if (checkingIndex > NUM_POWER_LEVELS - 1)
273 checkingIndex = 0;
274 mspState = CHECK_BANDS;
276 break;
279 setVtxTablePowerLevel(checkingIndex + 1);
280 break;
282 break;
284 case MSP_VTXTABLE_BAND:
285 bandPacket = (mspVtxBandPacket_t *)(packet + MSP_VTX_PAYLOAD_OFFSET);
287 switch (mspState)
289 case CHECK_BANDS:
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)
296 checkingIndex++;
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);
304 break;
308 setVtxTableBand(checkingIndex + 1);
309 break;
311 break;
313 case MSP_SET_VTX_CONFIG:
314 case MSP_SET_VTXTABLE_BAND:
315 case MSP_SET_VTXTABLE_POWERLEVEL:
316 break;
318 case MSP_EEPROM_WRITE:
319 mspState = MONITORING;
320 break;
324 static void mspVtxStateUpdate(void)
326 switch (mspState)
328 case GET_VTX_TABLE_SIZE:
329 sendVtxConfigCommand();
330 break;
331 case CHECK_POWER_LEVELS:
332 getVtxTablePowerLvl(checkingIndex + 1);
333 break;
334 case CHECK_BANDS:
335 getVtxTableBand(checkingIndex + 1);
336 break;
337 case SET_RCE_PIT_MODE:
338 sendRcePitModeCommand();
339 break;
340 case SEND_EEPROM_WRITE:
341 sendEepromWriteCommand();
342 break;
343 case MONITORING:
344 break;
345 default:
346 break;
350 void disableMspVtx(void)
352 mspState = STOP_MSPVTX;
355 static void initialize()
357 if (OPT_HAS_VTX_SPI)
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;
383 } else {
384 mspVtxStateUpdate();
385 return FC_QUERY_PERIOD_MS;
389 device_t MSPVTx_device = {
390 .initialize = initialize,
391 .start = nullptr,
392 .event = event,
393 .timeout = timeout
396 #endif