/* * Authors (alphabetical order) * - Andre Bernet * - Andreas Weitl * - Bertrand Songis * - Bryan J. Rentoul (Gruvin) * - Cameron Weeks * - Erez Raviv * - Gabriel Birkus * - Jean-Pierre Parisy * - Karl Szmutny * - Michael Blandford * - Michal Hlavinka * - Pat Mackenzie * - Philip Moss * - Rob Thomson * - Romolo Manfredini * - Thomas Husterer * * opentx is based on code named * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, * er9x by Erez Raviv: http://code.google.com/p/er9x/, * and the original (and ongoing) project by * Thomas Husterer, th9x: http://code.google.com/p/th9x/ * * 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" // Enumerate FrSky packet codes #define LINKPKT 0xfe #define USRPKT 0xfd #define A11PKT 0xfc #define A12PKT 0xfb #define A21PKT 0xfa #define A22PKT 0xf9 #define ALRM_REQUEST 0xf8 #define RSSI1PKT 0xf7 #define RSSI2PKT 0xf6 #define RSSI_REQUEST 0xf1 #define START_STOP 0x7e #define BYTESTUFF 0x7d #define STUFF_MASK 0x20 #if defined(TELEMETREZ) #define PRIVATE 0x1B #endif uint8_t frskyRxBuffer[FRSKY_RX_PACKET_SIZE]; // Receive buffer. 9 bytes (full packet), worst case 18 bytes with byte-stuffing (+1) uint8_t frskyTxBuffer[FRSKY_TX_PACKET_SIZE]; // Ditto for transmit buffer #if !defined(CPUARM) uint8_t frskyTxBufferCount = 0; #endif uint8_t frskyStreaming = 0; #if defined(WS_HOW_HIGH) uint8_t frskyUsrStreaming = 0; #endif uint8_t link_counter = 0; FrskyData frskyData; #if defined(FRSKY_HUB) || defined(WS_HOW_HIGH) void checkMinMaxAltitude() { if (TELEMETRY_ALT_BP > frskyData.hub.maxAltitude) frskyData.hub.maxAltitude = TELEMETRY_ALT_BP; if (TELEMETRY_ALT_BP < frskyData.hub.minAltitude) frskyData.hub.minAltitude = TELEMETRY_ALT_BP; } #endif #if defined(VARIO) && !defined(FRSKY_SPORT) && (defined(FRSKY_HUB) || defined(WS_HOW_HIGH)) void evalVario(int16_t altitude_bp, uint16_t altitude_ap) { int32_t varioAltitude_cm = (int32_t)altitude_bp * 100 + (altitude_bp > 0 ? altitude_ap : -altitude_ap); uint8_t varioAltitudeQueuePointer = frskyData.hub.varioAltitudeQueuePointer + 1; if (varioAltitudeQueuePointer >= VARIO_QUEUE_LENGTH) varioAltitudeQueuePointer = 0; frskyData.hub.varioAltitudeQueuePointer = varioAltitudeQueuePointer; frskyData.hub.varioSpeed -= frskyData.hub.varioAltitudeQueue[varioAltitudeQueuePointer] ; frskyData.hub.varioAltitudeQueue[varioAltitudeQueuePointer] = varioAltitude_cm - frskyData.hub.varioAltitude_cm; frskyData.hub.varioAltitude_cm = varioAltitude_cm; frskyData.hub.varioSpeed += frskyData.hub.varioAltitudeQueue[varioAltitudeQueuePointer] ; } #else #define evalVario(...) #endif #if defined(FRSKY_HUB) typedef enum { TS_IDLE = 0, // waiting for 0x5e frame marker TS_DATA_ID, // waiting for dataID TS_DATA_LOW, // waiting for data low byte TS_DATA_HIGH, // waiting for data high byte TS_XOR = 0x80 // decode stuffed byte } TS_STATE; void parseTelemHubByte(uint8_t byte) { static int8_t structPos; static uint8_t lowByte; static TS_STATE state = TS_IDLE; if (byte == 0x5e) { state = TS_DATA_ID; return; } if (state == TS_IDLE) { return; } if (state & TS_XOR) { byte = byte ^ 0x60; state = (TS_STATE)(state - TS_XOR); } if (byte == 0x5d) { state = (TS_STATE)(state | TS_XOR); return; } if (state == TS_DATA_ID) { if (byte > 0x3f) { state = TS_IDLE; } else { structPos = byte*2; state = TS_DATA_LOW; } return; } if (state == TS_DATA_LOW) { lowByte = byte; state = TS_DATA_HIGH; return; } state = TS_IDLE; #if defined(GPS) if ((uint8_t)structPos == offsetof(FrskySerialData, gpsLatitude_bp)) { if (lowByte || byte) frskyData.hub.gpsFix = 1; else if (frskyData.hub.gpsFix > 0 && frskyData.hub.gpsLatitude_bp > 1) frskyData.hub.gpsFix = 0; } else if ((uint8_t)structPos == offsetof(FrskySerialData, gpsLongitude_bp)) { if (lowByte || byte) frskyData.hub.gpsFix = 1; else if (frskyData.hub.gpsFix > 0 && frskyData.hub.gpsLongitude_bp > 1) frskyData.hub.gpsFix = 0; } if ((uint8_t)structPos == offsetof(FrskySerialData, gpsAltitude_bp) || ((uint8_t)structPos >= offsetof(FrskySerialData, gpsAltitude_ap) && (uint8_t)structPos <= offsetof(FrskySerialData, gpsLatitudeNS) && (uint8_t)structPos != offsetof(FrskySerialData, baroAltitude_bp) && (uint8_t)structPos != offsetof(FrskySerialData, baroAltitude_ap))) { // If we don't have a fix, we may discard the value if (frskyData.hub.gpsFix <= 0) return; } #endif #if 0 uint16_t previousValue = *((uint16_t *)(((uint8_t*)&frskyData.hub) + structPos)); #endif ((uint8_t*)&frskyData.hub)[structPos] = lowByte; ((uint8_t*)&frskyData.hub)[structPos+1] = byte; switch ((uint8_t)structPos) { case offsetof(FrskySerialData, rpm): frskyData.hub.rpm *= (uint8_t)60/(g_model.frsky.blades+2); if (frskyData.hub.rpm > frskyData.hub.maxRpm) frskyData.hub.maxRpm = frskyData.hub.rpm; break; case offsetof(FrskySerialData, temperature1): if (frskyData.hub.temperature1 > frskyData.hub.maxTemperature1) frskyData.hub.maxTemperature1 = frskyData.hub.temperature1; break; case offsetof(FrskySerialData, temperature2): if (frskyData.hub.temperature2 > frskyData.hub.maxTemperature2) frskyData.hub.maxTemperature2 = frskyData.hub.temperature2; break; case offsetof(FrskySerialData, current): if(((int16_t)frskyData.hub.current + g_model.frsky.fasOffset)>0) frskyData.hub.current += g_model.frsky.fasOffset; else frskyData.hub.current = 0; if (frskyData.hub.current > frskyData.hub.maxCurrent) frskyData.hub.maxCurrent = frskyData.hub.current; break; case offsetof(FrskySerialData, currentConsumption): // we receive data from openXsensor. stops the calculation of consumption and power frskyData.hub.openXsensor = 1; break; case offsetof(FrskySerialData, volts_ap): #if defined(FAS_BSS) frskyData.hub.vfas = (frskyData.hub.volts_bp * 10 + frskyData.hub.volts_ap); #else frskyData.hub.vfas = ((frskyData.hub.volts_bp * 100 + frskyData.hub.volts_ap * 10) * 21) / 110; #endif /* TODO later if (!frskyData.hub.minVfas || frskyData.hub.minVfas > frskyData.hub.vfas) frskyData.hub.minVfas = frskyData.hub.vfas; */ break; case offsetof(FrskySerialData, baroAltitude_bp): // First received barometer altitude => Altitude offset if (!frskyData.hub.baroAltitudeOffset) frskyData.hub.baroAltitudeOffset = -frskyData.hub.baroAltitude_bp; if (g_model.frsky.varioSource == VARIO_SOURCE_ALTI) { evalVario(frskyData.hub.baroAltitude_bp, 0); } frskyData.hub.baroAltitude_bp += frskyData.hub.baroAltitudeOffset; checkMinMaxAltitude(); break; case offsetof(FrskySerialData, baroAltitude_ap): if (g_model.frsky.varioSource == VARIO_SOURCE_ALTI_PLUS) { evalVario(frskyData.hub.baroAltitude_bp-frskyData.hub.baroAltitudeOffset, frskyData.hub.baroAltitude_ap); } break; #if defined(GPS) case offsetof(FrskySerialData, gpsAltitude_ap): if (!frskyData.hub.gpsAltitudeOffset) frskyData.hub.gpsAltitudeOffset = -frskyData.hub.gpsAltitude_bp; frskyData.hub.gpsAltitude_bp += frskyData.hub.gpsAltitudeOffset; if (!frskyData.hub.baroAltitudeOffset) { if (frskyData.hub.gpsAltitude_bp > frskyData.hub.maxAltitude) frskyData.hub.maxAltitude = frskyData.hub.gpsAltitude_bp; if (frskyData.hub.gpsAltitude_bp < frskyData.hub.minAltitude) frskyData.hub.minAltitude = frskyData.hub.gpsAltitude_bp; } if (!frskyData.hub.pilotLatitude && !frskyData.hub.pilotLongitude) { // First received GPS position => Pilot GPS position getGpsPilotPosition(); } else if (frskyData.hub.gpsDistNeeded || g_menuStack[g_menuStackPtr] == menuTelemetryFrsky) { getGpsDistance(); } break; case offsetof(FrskySerialData, gpsSpeed_bp): // Speed => Max speed if (frskyData.hub.gpsSpeed_bp > frskyData.hub.maxGpsSpeed) frskyData.hub.maxGpsSpeed = frskyData.hub.gpsSpeed_bp; break; #endif case offsetof(FrskySerialData, volts): // Voltage => Cell number + Cell voltage { uint8_t battnumber = ((frskyData.hub.volts & 0x00F0) >> 4); if (battnumber < 12) { if (frskyData.hub.cellsCount < battnumber+1) { frskyData.hub.cellsCount = battnumber+1; } uint8_t cellVolts = (uint8_t)(((((frskyData.hub.volts & 0xFF00) >> 8) + ((frskyData.hub.volts & 0x000F) << 8)))/10); frskyData.hub.cellVolts[battnumber] = cellVolts; if (!frskyData.hub.minCellVolts || cellVolts < frskyData.hub.minCellVolts || battnumber==frskyData.hub.minCellIdx) { frskyData.hub.minCellIdx = battnumber; frskyData.hub.minCellVolts = cellVolts; } } break; } #if defined(GPS) case offsetof(FrskySerialData, hour): frskyData.hub.hour = ((uint8_t)(frskyData.hub.hour + g_eeGeneral.timezone + 24)) % 24; break; #endif case offsetof(FrskySerialData, accelX): case offsetof(FrskySerialData, accelY): case offsetof(FrskySerialData, accelZ): *(int16_t*)(&((uint8_t*)&frskyData.hub)[structPos]) /= 10; break; #if 0 case offsetof(FrskySerialData, gpsAltitude_bp): case offsetof(FrskySerialData, fuelLevel): case offsetof(FrskySerialData, gpsLongitude_bp): case offsetof(FrskySerialData, gpsLatitude_bp): case offsetof(FrskySerialData, gpsCourse_bp): case offsetof(FrskySerialData, day): case offsetof(FrskySerialData, year): case offsetof(FrskySerialData, sec): case offsetof(FrskySerialData, gpsSpeed_ap): case offsetof(FrskySerialData, gpsLongitude_ap): case offsetof(FrskySerialData, gpsLatitude_ap): case offsetof(FrskySerialData, gpsCourse_ap): case offsetof(FrskySerialData, gpsLongitudeEW): case offsetof(FrskySerialData, gpsLatitudeNS): case offsetof(FrskySerialData, varioSpeed): case offsetof(FrskySerialData, power): /* because sent by openXsensor */ case offsetof(FrskySerialData, vfas): case offsetof(FrskySerialData, volts_bp): break; default: *((uint16_t *)(((uint8_t*)&frskyData.hub) + structPos)) = previousValue; break; #endif } } #endif #if defined(WS_HOW_HIGH) void parseTelemWSHowHighByte(uint8_t byte) { if (frskyUsrStreaming < (FRSKY_TIMEOUT10ms*3 - 10)) { ((uint8_t*)&frskyData.hub)[offsetof(FrskySerialData, baroAltitude_bp)] = byte; checkMinMaxAltitude(); if (g_model.frsky.varioSource == VARIO_SOURCE_ALTI) { evalVario(frskyData.hub.baroAltitude_bp, 0); } } else { // At least 100mS passed since last data received ((uint8_t*)&frskyData.hub)[offsetof(FrskySerialData, baroAltitude_bp)+1] = byte; } // baroAltitude_bp unit here is feet! frskyUsrStreaming = FRSKY_TIMEOUT10ms*3; // reset counter } #endif void processFrskyPacket(uint8_t *packet) { // What type of packet? switch (packet[0]) { case LINKPKT: // A1/A2/RSSI values { link_counter += 32; frskyData.analog[0].set(packet[1], g_model.frsky.channels[0].type); frskyData.analog[1].set(packet[2], g_model.frsky.channels[1].type); frskyData.rssi[0].set(packet[3]); frskyData.rssi[1].set(packet[4] / 2); frskyStreaming = FRSKY_TIMEOUT10ms; // reset counter only if valid frsky packets are being detected #if defined(VARIO) uint8_t varioSource = g_model.frsky.varioSource - VARIO_SOURCE_A1; if (varioSource < 2) frskyData.hub.varioSpeed = applyChannelRatio(varioSource, frskyData.analog[varioSource].value); #endif break; } #if defined(FRSKY_HUB) || defined (WS_HOW_HIGH) case USRPKT: // User Data packet uint8_t numBytes = 3 + (packet[1] & 0x07); // sanitize in case of data corruption leading to buffer overflow for (uint8_t i=3; i>1) * (voltage>>1)) / 25; #endif frskyData.hub.currentPrescale += current; if (frskyData.hub.currentPrescale >= 3600) { frskyData.hub.currentConsumption += 1; frskyData.hub.currentPrescale -= 3600; } } if (frskyData.hub.power > frskyData.hub.maxPower) frskyData.hub.maxPower = frskyData.hub.power; } } void telemetryWakeup() { #if defined(PCBSKY9X) rxPdcUsart(processSerialData); // Receive serial data here #endif // Attempt to transmit any waiting Fr-Sky alarm set packets every 50ms (subject to packet buffer availability) static uint8_t frskyTxDelay = 5; if (frskyAlarmsSendState && (--frskyTxDelay == 0)) { frskyTxDelay = 5; // 50ms frskySendNextAlarm(); } #ifndef SIMU #if defined(WS_HOW_HIGH) if (frskyUsrStreaming > 0) { frskyUsrStreaming--; } #endif if (frskyStreaming > 0) { frskyStreaming--; } else { frskyData.rssi[0].set(0); frskyData.rssi[1].set(0); } #endif #if defined(VARIO) if (TELEMETRY_STREAMING() && !IS_FAI_ENABLED()) varioWakeup(); #endif } #if 0 // not used any more bool FRSKY_alarmRaised(uint8_t idx) { for (int i=0; i<2; i++) { if (ALARM_LEVEL(idx, i) != alarm_off) { if (ALARM_GREATER(idx, i)) { if (frskyData.analog[idx].value > g_model.frsky.channels[idx].alarms_value[i]) return true; } else { if (frskyData.analog[idx].value < g_model.frsky.channels[idx].alarms_value[i]) return true; } } } return false; } #endif #if !defined(CPUARM) inline void FRSKY_EnableTXD(void) { frskyTxBufferCount = 0; UCSR0B |= (1 << TXEN0); // enable TX } inline void FRSKY_EnableRXD(void) { UCSR0B |= (1 << RXEN0); // enable RX UCSR0B |= (1 << RXCIE0); // enable Interrupt } #endif void FRSKY_Init(void) { // clear frsky variables resetTelemetry(); #if defined(PCBTARANIS) // TODO #elif defined(PCBSKY9X) startPdcUsartReceive() ; #elif !defined(SIMU) DDRE &= ~(1 << DDE0); // set RXD0 pin as input PORTE &= ~(1 << PORTE0); // disable pullup on RXD0 pin #undef BAUD #define BAUD 9600 #include UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; UCSR0A &= ~(1 << U2X0); // disable double speed operation. // set 8N1 UCSR0B = 0 | (0 << RXCIE0) | (0 << TXCIE0) | (0 << UDRIE0) | (0 << RXEN0) | (0 << TXEN0) | (0 << UCSZ02); UCSR0C = 0 | (1 << UCSZ01) | (1 << UCSZ00); while (UCSR0A & (1 << RXC0)) UDR0; // flush receive buffer // These should be running right from power up on a FrSky enabled '9X. FRSKY_EnableTXD(); // enable FrSky-Telemetry reception FRSKY_EnableRXD(); // enable FrSky-Telemetry reception #endif } void FrskyValueWithMin::set(uint8_t value) { if (this->value == 0) { this->value = value; } else { sum += value; if (link_counter == 0) { this->value = sum / 8; sum = 0; } } if (value && (!min || value < min)) min = value; } void FrskyValueWithMinMax::set(uint8_t value, uint8_t unit) { FrskyValueWithMin::set(value); if (unit != UNIT_VOLTS) { this->value = value; } if (!max || value > max) max = value; } void resetTelemetry() { memclear(&frskyData, sizeof(frskyData)); #if defined(FRSKY_HUB) frskyData.hub.gpsLatitude_bp = 2; frskyData.hub.gpsLongitude_bp = 2; frskyData.hub.gpsFix = -1; #endif #ifdef SIMU frskyData.analog[0].set(120, UNIT_VOLTS); frskyData.analog[1].set(240, UNIT_VOLTS); frskyData.rssi[0].value = 75; frskyData.rssi[1].value = 75; frskyData.hub.fuelLevel = 75; frskyData.hub.rpm = 12000; #if defined(GPS) frskyData.hub.gpsFix = 1; frskyData.hub.gpsLatitude_bp = 4401; frskyData.hub.gpsLatitude_ap = 7710; frskyData.hub.gpsLongitude_bp = 1006; frskyData.hub.gpsLongitude_ap = 8872; frskyData.hub.gpsSpeed_bp = (100 * 250) / 463; getGpsPilotPosition(); frskyData.hub.gpsLatitude_bp = 4401; frskyData.hub.gpsLatitude_ap = 7455; frskyData.hub.gpsLongitude_bp = 1006; frskyData.hub.gpsLongitude_ap = 9533; getGpsDistance(); #endif frskyData.hub.cellsCount = 6; frskyData.hub.gpsAltitude_bp = 50; frskyData.hub.baroAltitude_bp = 50; frskyData.hub.minAltitude = 10; frskyData.hub.maxAltitude = 500; frskyData.hub.accelY = 100; frskyData.hub.temperature1 = -30; frskyData.hub.maxTemperature1 = 100; frskyData.hub.current = 5; frskyData.hub.maxCurrent = 56; #endif }