/* * Copyright (C) OpenTX * * Based on code named * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "opentx.h" #include "telemetry.h" #include "multi.h" enum MultiPacketTypes : uint8_t { MultiStatus = 1, FrSkySportTelemtry, FrSkyHubTelemetry, SpektrumTelemetry, DSMBindPacket, FlyskyIBusTelemetry, ConfigCommand, InputSync, FrskySportPolling }; enum MultiBufferState : uint8_t { NoProtocolDetected, MultiFirstByteReceived, ReceivingMultiProtocol, ReceivingMultiStatus, SpektrumTelemetryFallback, FrskyTelemetryFallback, FrskyTelemetryFallbackFirstByte, FrskyTelemetryFallbackNextBytes, FlyskyTelemetryFallback, MultiStatusOrFrskyData }; #if defined(INTERNAL_MODULE_MULTI) static MultiModuleStatus multiModuleStatus[NUM_MODULES] = { MultiModuleStatus(), MultiModuleStatus() }; static MultiModuleSyncStatus multiSyncStatus[NUM_MODULES] = { MultiModuleSyncStatus(), MultiModuleSyncStatus() }; static uint8_t multiBindStatus[NUM_MODULES] = { MULTI_NORMAL_OPERATION, MULTI_NORMAL_OPERATION }; static MultiBufferState multiTelemetryBufferState[NUM_MODULES]; MultiModuleStatus& getMultiModuleStatus(uint8_t module) { return multiModuleStatus[module]; } MultiModuleSyncStatus& getMultiSyncStatus(uint8_t module) { return multiSyncStatus[module]; } uint8_t getMultiBindStatus(uint8_t module) { return multiBindStatus[module]; } void setMultiBindStatus(uint8_t module, uint8_t bindStatus) { multiBindStatus[module] = bindStatus; } MultiBufferState getMultiTelemetryBufferState(uint8_t module) { return multiTelemetryBufferState[module]; } void setMultiTelemetryBufferState(uint8_t module, MultiBufferState state) { multiTelemetryBufferState[module] = state; } // Use additional telemetry buffer uint8_t intTelemetryRxBuffer[TELEMETRY_RX_PACKET_SIZE]; uint8_t intTelemetryRxBufferCount; #else // !INTERNAL_MODULE_MULTI static MultiModuleStatus multiModuleStatus; static MultiModuleSyncStatus multiSyncStatus; static uint8_t multiBindStatus = MULTI_NORMAL_OPERATION; static MultiBufferState multiTelemetryBufferState; MultiModuleStatus& getMultiModuleStatus(uint8_t) { return multiModuleStatus; } MultiModuleSyncStatus& getMultiSyncStatus(uint8_t) { return multiSyncStatus; } uint8_t getMultiBindStatus(uint8_t) { return multiBindStatus; } void setMultiBindStatus(uint8_t, uint8_t bindStatus) { multiBindStatus = bindStatus; } MultiBufferState getMultiTelemetryBufferState(uint8_t) { return multiTelemetryBufferState; } void setMultiTelemetryBufferState(uint8_t, MultiBufferState state) { multiTelemetryBufferState = state; } #endif // INTERNAL_MODULE_MULTI static MultiBufferState guessProtocol(uint8_t module) { if (g_model.moduleData[module].getMultiProtocol(false) == MODULE_SUBTYPE_MULTI_DSM2) return SpektrumTelemetryFallback; else if (g_model.moduleData[module].getMultiProtocol(false) == MODULE_SUBTYPE_MULTI_FS_AFHDS2A) return FlyskyTelemetryFallback; else return FrskyTelemetryFallback; } static void processMultiStatusPacket(const uint8_t *data, uint8_t module) { MultiModuleStatus& status = getMultiModuleStatus(module); // At least two status packets without bind flag bool wasBinding = status.isBinding(); status.flags = data[0]; status.major = data[1]; status.minor = data[2]; status.revision = data[3]; status.patch = data[4]; status.lastUpdate = get_tmr10ms(); if (wasBinding && !status.isBinding() && getMultiBindStatus(module) == MULTI_BIND_INITIATED) setMultiBindStatus(module, MULTI_BIND_FINISHED); } static void processMultiSyncPacket(const uint8_t *data, uint8_t module) { MultiModuleSyncStatus& status = getMultiSyncStatus(module); status.lastUpdate = get_tmr10ms(); status.interval = data[4]; status.target = data[5]; #if !defined(PPM_PIN_SERIAL) auto oldlag = status.inputLag; (void) oldlag; #endif status.calcAdjustedRefreshRate(data[0] << 8 | data[1], data[2] << 8 | data[3]); #if !defined(PPM_PIN_SERIAL) TRACE("MP ADJ: rest: %d, lag %04d, diff: %04d target: %d, interval: %d, Refresh: %d, intAdjRefresh: %d, adjRefresh %d\r\n", module == EXTERNAL_MODULE ? extmodulePulsesData.dsm2.rest : 0, status.inputLag, oldlag-status.inputLag, status.target, status.interval, status.refreshRate, status.adjustedRefreshRate/50, status.getAdjustedRefreshRate()); #endif } static void processMultiTelemetryPaket(const uint8_t *packet, uint8_t module) { uint8_t type = packet[0]; uint8_t len = packet[1]; const uint8_t *data = packet + 2; // Switch type switch (type) { case MultiStatus: if (len >= 5) processMultiStatusPacket(data, module); break; case DSMBindPacket: if (len >= 10) processDSMBindPacket(module, data); break; case SpektrumTelemetry: // processSpektrumPacket expects data[0] to be the telemetry indicator 0xAA but does not check it, // just send one byte of our header instead if (len >= 17) processSpektrumPacket(data - 1); else TRACE("[MP] Received spektrum telemetry len %d < 17", len); break; case FlyskyIBusTelemetry: if (len >= 28) processFlySkyPacket(data); else TRACE("[MP] Received IBUS telemetry len %d < 28", len); break; case FrSkyHubTelemetry: if (len >= 4) frskyDProcessPacket(data); else TRACE("[MP] Received Frsky HUB telemetry len %d < 4", len); break; case FrSkySportTelemtry: if (len >= 4) sportProcessTelemetryPacket(data); else TRACE("[MP] Received sport telemetry len %d < 4", len); break; case InputSync: if (len >= 6) processMultiSyncPacket(data, module); else TRACE("[MP] Received input sync len %d < 6", len); break; case ConfigCommand: // Just an ack to our command, ignore for now break; #if defined(LUA) case FrskySportPolling: if (len >= 1 && outputTelemetryBuffer.destination == TELEMETRY_ENDPOINT_SPORT && data[0] == outputTelemetryBuffer.sport.physicalId) { TRACE("MP Sending sport data out."); sportSendBuffer(outputTelemetryBuffer.data, outputTelemetryBuffer.size); } break; #endif default: TRACE("[MP] Unkown multi packet type 0x%02X, len %d", type, len); break; } } // sprintf does not work AVR ARM // use a small helper function static void appendInt(char *buf, uint32_t val) { while (*buf) buf++; strAppendUnsigned(buf, val); } #define MIN_REFRESH_RATE 7000 void MultiModuleSyncStatus::calcAdjustedRefreshRate(uint16_t newRefreshRate, uint16_t newInputLag) { // Check how far off we are from our target, positive means we are too slow, negative we are too fast int lagDifference = newInputLag - inputLag; // The refresh rate that we target // Below is least common multiple of MIN_REFRESH_RATE and requested rate uint16_t targetRefreshRate = (uint16_t) (newRefreshRate * ((MIN_REFRESH_RATE / (newRefreshRate - 1)) + 1)); // Overflow, reverse sample if (lagDifference < -targetRefreshRate/2) lagDifference= -lagDifference; // Reset adjusted refresh if rate has changed if (newRefreshRate != refreshRate) { refreshRate = newRefreshRate; adjustedRefreshRate = targetRefreshRate; if (adjustedRefreshRate >= 30000) adjustedRefreshRate /= 2; // Our refresh rate in ps adjustedRefreshRate*=1000; return; } // Caluclate how many samples went into the reported input Lag (*10) int numsamples = interval * 10000 / targetRefreshRate; // Convert lagDifference to ps lagDifference=lagDifference*1000; // Calculate the time we intentionally were late/early if (inputLag > target*10 +30) lagDifference += numsamples*500; else if (inputLag < target*10 - 30) lagDifference -= numsamples*500; // Caculate the time in ps each frame is to slow (positive), fast(negative) int perframeps = lagDifference*10/ numsamples; if (perframeps > 20000) perframeps = 20000; if (perframeps < -20000) perframeps = -20000; adjustedRefreshRate =(adjustedRefreshRate + perframeps); // Safeguards if (adjustedRefreshRate < 6*1000*1000) adjustedRefreshRate = 6*1000*1000; if (adjustedRefreshRate > 30*1000*1000) adjustedRefreshRate = 30*1000*1000; inputLag = newInputLag; } static uint8_t counter; uint16_t MultiModuleSyncStatus::getAdjustedRefreshRate() { if (!isValid() || refreshRate == 0) return 18000; counter = (uint8_t) (counter + 1 % 10); uint16_t rate = (uint16_t) ((adjustedRefreshRate + counter * 50) / 500); // Check how far off we are from our target, positive means we are too slow, negative we are too fast if (inputLag > target*10 +30) return (uint16_t) (rate - 1); else if (inputLag < target*10 - 30) return (uint16_t) (rate + 1); else return rate; } static void prependSpaces(char * buf, int val) { while (*buf) buf++; int k=10000; while(val/k==0 && k > 0) { *buf=' '; buf++; k/= 10; } *buf='\0'; } void MultiModuleSyncStatus::getRefreshString(char *statusText) { if (!isValid()) { return; } strcpy(statusText, "L "); prependSpaces(statusText, inputLag); appendInt(statusText, inputLag); strcat(statusText, "ns R "); prependSpaces(statusText, adjustedRefreshRate/1000); appendInt(statusText, (uint32_t) (adjustedRefreshRate / 1000)); strcat(statusText, "ns"); } void MultiModuleStatus::getStatusString(char *statusText) { if (!isValid()) { #if defined(PCBTARANIS) || defined(PCBHORUS) #if !defined(INTERNAL_MODULE_MULTI) if (IS_INTERNAL_MODULE_ENABLED()) strcpy(statusText, STR_DISABLE_INTERNAL); else #endif #endif strcpy(statusText, STR_MODULE_NO_TELEMETRY); return; } if (!protocolValid()) { strcpy(statusText, STR_PROTOCOL_INVALID); return; } else if (!serialMode()) { strcpy(statusText, STR_MODULE_NO_SERIAL_MODE); return; } else if (!inputDetected()) { strcpy(statusText, STR_MODULE_NO_INPUT); return; } else if(isWaitingforBind()) { strcpy(statusText, STR_MODULE_WAITFORBIND); return; } strcpy(statusText, "V"); appendInt(statusText, major); strcat(statusText, "."); appendInt(statusText, minor); strcat(statusText, "."); appendInt(statusText, revision); strcat(statusText, "."); appendInt(statusText, patch); strcat(statusText, " "); if (isBinding()) strcat(statusText, STR_MODULE_BINDING); } static uint8_t* getRxBuffer(uint8_t moduleIdx) { #if defined(INTERNAL_MODULE_MULTI) if (moduleIdx == INTERNAL_MODULE) return intTelemetryRxBuffer; #endif return telemetryRxBuffer; } static uint8_t& getRxBufferCount(uint8_t moduleIdx) { #if defined(INTERNAL_MODULE_MULTI) if (moduleIdx == INTERNAL_MODULE) return intTelemetryRxBufferCount; #endif return telemetryRxBufferCount; } static void processMultiTelemetryByte(const uint8_t data, uint8_t module) { uint8_t* rxBuffer = getRxBuffer(module); uint8_t& rxBufferCount = getRxBufferCount(module); if (rxBufferCount < TELEMETRY_RX_PACKET_SIZE) { rxBuffer[rxBufferCount++] = data; } else { TRACE("[MP] array size %d error", rxBufferCount); setMultiTelemetryBufferState(module, NoProtocolDetected); } // Length field does not count the header if (rxBufferCount >= 2 && rxBuffer[1] == rxBufferCount - 2) { // debug print the content of the packet #if 0 debugPrintf("[MP] Packet type %02X len 0x%02X: ", rxBuffer[0], rxBuffer[1]); for (int i=0; i<(rxBufferCount+3)/4; i++) { debugPrintf("[%02X%02X %02X%02X] ", rxBuffer[i*4+2], rxBuffer[i*4 + 3], rxBuffer[i*4 + 4], rxBuffer[i*4 + 5]); } debugPrintf("\r\n"); #endif // Packet is complete, process it processMultiTelemetryPaket(rxBuffer, module); setMultiTelemetryBufferState(module, NoProtocolDetected); } } void processMultiTelemetryData(uint8_t data, uint8_t module) { uint8_t* rxBuffer = getRxBuffer(module); uint8_t& rxBufferCount = getRxBufferCount(module); debugPrintf("State: %d, byte received %02X, buflen: %d\r\n", multiTelemetryBufferState, data, rxBufferCount); switch (getMultiTelemetryBufferState(module)) { case NoProtocolDetected: if (data == 'M') { setMultiTelemetryBufferState(module, MultiFirstByteReceived); } else if (data == 0xAA || data == 0x7e) { setMultiTelemetryBufferState(module, guessProtocol(module)); // Process the first byte by the protocol processMultiTelemetryData(data, module); } else { TRACE("[MP] invalid start byte 0x%02X", data); } break; case FrskyTelemetryFallback: setMultiTelemetryBufferState(module, FrskyTelemetryFallbackFirstByte); processFrskyTelemetryData(data); break; case FrskyTelemetryFallbackFirstByte: if (data == 'M') { setMultiTelemetryBufferState(module, MultiStatusOrFrskyData); } else { processFrskyTelemetryData(data); if (data != 0x7e) setMultiTelemetryBufferState(module, FrskyTelemetryFallbackNextBytes); } break; case FrskyTelemetryFallbackNextBytes: processFrskyTelemetryData(data); if (data == 0x7e) { // end of packet or start of new packet setMultiTelemetryBufferState(module, FrskyTelemetryFallbackFirstByte); } break; case FlyskyTelemetryFallback: processFlySkyTelemetryData(data, rxBuffer, rxBufferCount); if (rxBufferCount == 0) { setMultiTelemetryBufferState(module, NoProtocolDetected); } break; case SpektrumTelemetryFallback: processSpektrumTelemetryData(module, data,rxBuffer, rxBufferCount); if (rxBufferCount == 0) { setMultiTelemetryBufferState(module, NoProtocolDetected); } break; case MultiFirstByteReceived: rxBufferCount = 0; if (data == 'P') { setMultiTelemetryBufferState(module, ReceivingMultiProtocol); } else if (data >= 5 && data <= 10) { // Protocol indented for er9x/ersky9, accept only 5-10 as packet length to have // a bit of validation setMultiTelemetryBufferState(module, ReceivingMultiStatus); processMultiTelemetryData(data, module); } else { TRACE("[MP] invalid second byte 0x%02X", data); setMultiTelemetryBufferState(module, NoProtocolDetected); } break; case ReceivingMultiProtocol: processMultiTelemetryByte(data, module); break; case MultiStatusOrFrskyData: // Check len byte if it makes sense for multi if (data >= 5 && data <= 10) { setMultiTelemetryBufferState(module, ReceivingMultiStatus); rxBufferCount = 0; } else { setMultiTelemetryBufferState(module, FrskyTelemetryFallbackNextBytes); processMultiTelemetryData('M', module); } processMultiTelemetryData(data, module); break; case ReceivingMultiStatus: rxBuffer[rxBufferCount++] = data; if (rxBufferCount > 5 && rxBuffer[0] == rxBufferCount-1) { processMultiStatusPacket(rxBuffer+1, module); rxBufferCount = 0; setMultiTelemetryBufferState(module, NoProtocolDetected); } if (rxBufferCount > 10) { // too long ignore TRACE("Overlong multi status packet detected ignoring, wanted %d", rxBuffer[0]); rxBufferCount =0; setMultiTelemetryBufferState(module, NoProtocolDetected); } break; } }