diff --git a/FollowMe.cfg b/FollowMe.cfg new file mode 100644 index 0000000..f072d55 --- /dev/null +++ b/FollowMe.cfg @@ -0,0 +1,71 @@ +#define DEFAULT_AcftType 1 // [0..15] default aircraft-type: glider +#define DEFAULT_GeoidSepar 40 // [m] +#define DEFAULT_CONbaud 115200 +#define DEFAULT_PPSdelay 100 +#define DEFAULT_FreqPlan 0 + +// #define WITH_HELTEC // HELTEC module: PCB LED on GPI025 +// #define WITH_TTGO // TTGO module: PCB LED on GPIO2, GPIO25 free to use as DAC2 output +// #define WITH_TBEAM // T-Beam module +// #define WITH_TBEAM_V10 // T-Beam module +// #define WITH_JACEK // JACEK ESP32 OGN-Tracker +// #define WITH_M5_JACEK // JACEK M5 ESP32 OGN-Tracker +#define WITH_FollowMe // by Avionix + +// #define WITH_ILI9341 // 320x240 M5stack +// #define WITH_ST7789 // IPS 240x240 ST7789 +// #define WITH_TFT_LCD // TFT LCD +// #define WITH_OLED // OLED display on the I2C: some TTGO modules are without OLED display +// #define WITH_OLED2 // 2nd OLED display, I2C address next higher +#define WITH_U8G2_OLED // I2C OLED through the U8g2 library +#define WITH_U8G2_SH1106 + +#define WITH_RFM95 // RF chip selection: both HELTEC and TTGO use sx1276 which is same as RFM95 + +// #define WITH_SLEEP // with software sleep mode controlled by the long-press on the button + +// #define WITH_AXP // with AXP192 power controller (T-BEAM V1.0) +#define WITH_BQ // with BQ24295 power controller (FollowMe) + +// #define WITH_LED_RX +// #define WITH_LED_TX + +#define WITH_GPS_ENABLE // use GPS_ENABLE control line to turn the GPS ON/OFF +#define WITH_GPS_PPS // use the PPS signal from GPS for precise time-sync. +#define WITH_GPS_CONFIG // attempt to configure higher GPS baud rate and airborne mode + +// #define WITH_GPS_UBX // GPS understands UBX +#define WITH_GPS_MTK // GPS understands MTK +// #define WITH_GPS_SRF +// #define WITH_MAVLINK + +// #define WITH_GPS_UBX_PASS // to pass directly UBX packets to/from GPS +// #define WITH_GPS_NMEA_PASS // to pass directly NMEA to/from GPS + +// #define WITH_BMP180 // BMP180 pressure sensor +// #define WITH_BMP280 // BMP280 pressure sensor +#define WITH_BME280 // BMP280 with humidity (but still works with BMP280) +// #define WITH_MS5607 // MS5607 pressure sensor + +#define WITH_PFLAA // PFLAU and PFLAA for compatibility with XCsoar and LK8000 +// #define WITH_POGNT +#define WITH_LOOKOUT + +#define WITH_CONFIG // interpret the console input: $POGNS to change parameters + +// #define WITH_BEEPER // with digital buzzer +// #define WITH_SOUND // with analog sound produced by DAC on pin 25 + +// #define WITH_KNOB +// #define WITH_VARIO + +#define WITH_SD // use the SD card in SPI mode and FAT file system +#define WITH_SPIFFS // use SPIFFS file system in Flash +#define WITH_LOG // log own positions and other received to SPIFFS and possibly to uSD + +#define WITH_BT_SPP // Bluetooth serial port for smartphone/tablet link +// #define WITH_WIFI // attempt to connect to the wifi router for uploading the log files +// #define WITH_SPIFFS_LOG // log transmitted and received packets to SPIFFS + +// #define WITH_ENCRYPT // Encrypt (optionally) the position + diff --git a/T-Beam_v10.cfg b/T-Beam_v10.cfg new file mode 100644 index 0000000..d14132d --- /dev/null +++ b/T-Beam_v10.cfg @@ -0,0 +1,71 @@ +#define DEFAULT_AcftType 1 // [0..15] default aircraft-type: glider +#define DEFAULT_GeoidSepar 40 // [m] +#define DEFAULT_CONbaud 115200 +#define DEFAULT_PPSdelay 100 +#define DEFAULT_FreqPlan 0 + +// #define WITH_HELTEC // HELTEC module: PCB LED on GPI025 +// #define WITH_TTGO // TTGO module: PCB LED on GPIO2, GPIO25 free to use as DAC2 output +// #define WITH_TBEAM // T-Beam module +#define WITH_TBEAM_V10 // T-Beam module +// #define WITH_JACEK // JACEK ESP32 OGN-Tracker +// #define WITH_M5_JACEK // JACEK M5 ESP32 OGN-Tracker +// #define WITH_FollowMe // by Avionix + +// #define WITH_ILI9341 // 320x240 M5stack +// #define WITH_ST7789 // IPS 240x240 ST7789 +// #define WITH_TFT_LCD // TFT LCD +// #define WITH_OLED // OLED display on the I2C: some TTGO modules are without OLED display +// #define WITH_OLED2 // 2nd OLED display, I2C address next higher +// #define WITH_U8G2_OLED // I2C OLED through the U8g2 library +// #define WITH_U8G2_SH1106 + +#define WITH_RFM95 // RF chip selection: both HELTEC and TTGO use sx1276 which is same as RFM95 + +// #define WITH_SLEEP // with software sleep mode controlled by the long-press on the button + +#define WITH_AXP // with AXP192 power controller (T-BEAM V1.0) +// #define WITH_BQ // with BQ24295 power controller (FollowMe) + +// #define WITH_LED_RX +// #define WITH_LED_TX + +// #define WITH_GPS_ENABLE // use GPS_ENABLE control line to turn the GPS ON/OFF +#define WITH_GPS_PPS // use the PPS signal from GPS for precise time-sync. +#define WITH_GPS_CONFIG // attempt to configure higher GPS baud rate and airborne mode + +#define WITH_GPS_UBX // GPS understands UBX +// #define WITH_GPS_MTK // GPS understands MTK +// #define WITH_GPS_SRF +// #define WITH_MAVLINK + +// #define WITH_GPS_UBX_PASS // to pass directly UBX packets to/from GPS +// #define WITH_GPS_NMEA_PASS // to pass directly NMEA to/from GPS + +// #define WITH_BMP180 // BMP180 pressure sensor +// #define WITH_BMP280 // BMP280 pressure sensor +#define WITH_BME280 // BMP280 with humidity (but still works with BMP280) +// #define WITH_MS5607 // MS5607 pressure sensor + +#define WITH_PFLAA // PFLAU and PFLAA for compatibility with XCsoar and LK8000 +// #define WITH_POGNT +#define WITH_LOOKOUT + +#define WITH_CONFIG // interpret the console input: $POGNS to change parameters + +// #define WITH_BEEPER // with digital buzzer +// #define WITH_SOUND // with analog sound produced by DAC on pin 25 + +// #define WITH_KNOB +// #define WITH_VARIO + +// #define WITH_SD // use the SD card in SPI mode and FAT file system +#define WITH_SPIFFS // use SPIFFS file system in Flash +#define WITH_LOG // log own positions and other received to SPIFFS and possibly to uSD + +#define WITH_BT_SPP // Bluetooth serial port for smartphone/tablet link +// #define WITH_WIFI // attempt to connect to the wifi router for uploading the log files +// #define WITH_SPIFFS_LOG // log transmitted and received packets to SPIFFS + +// #define WITH_ENCRYPT // Encrypt (optionally) the position + diff --git a/main/atmosphere.h b/main/atmosphere.h index c58cc8c..0914981 100644 --- a/main/atmosphere.h +++ b/main/atmosphere.h @@ -1,3 +1,6 @@ +#ifndef __ATMOSPHERE_H__ +#define __ATMOSPHERE_H__ + #include #include @@ -56,3 +59,5 @@ class Atmosphere } ; + +#endif // __ATMOSPHERE_H__ diff --git a/main/axp192.h b/main/axp192.h new file mode 100644 index 0000000..eb33b91 --- /dev/null +++ b/main/axp192.h @@ -0,0 +1,265 @@ +#ifndef __AXP192_H__ +#define __AXP192_H__ + +#include + +class AXP192 +{ private: + static const uint8_t AXP192_ADDR = 0x34; // possible I2C addresses + + // registers + static const uint8_t REG_STATUS = 0x00; // bit #2 = charge/discharge, bit #0 = power-on triggered by Vbus/ACin + static const uint8_t REG_MODE_CHGSTATUS = 0x01; // charge mode and status + static const uint8_t REG_ID = 0x03; + static const uint8_t REG_LDO234_DC23_CTL = 0x12; // [bit mask] control outputs ON/OFF + static const uint8_t REG_DC1_VOLTAGE = 0x26; // [25mV] controls DC1 voltage + static const uint8_t REG_OFF_CTL = 0x32; // power-off, battery check, LED control + static const uint8_t REG_CHARGE_1 = 0x33; // target voltage, current + static const uint8_t REG_CHARGE_2 = 0x34; + static const uint8_t REG_BACKUP_CHG = 0x35; // backup battery charge + static const uint8_t REG_POK_SET = 0x36; // press-key parameters + + static const uint8_t REG_INTEN1 = 0x40; // IRQ enable + static const uint8_t REG_INTEN2 = 0x41; + static const uint8_t REG_INTEN3 = 0x42; + static const uint8_t REG_INTEN4 = 0x43; + static const uint8_t REG_INTEN5 = 0x4A; + + static const uint8_t REG_INTSTS1 = 0x44; // IRQ status + static const uint8_t REG_INTSTS2 = 0x45; + static const uint8_t REG_INTSTS3 = 0x46; + static const uint8_t REG_INTSTS4 = 0x47; + static const uint8_t REG_INTSTS5 = 0x4D; + + static const uint8_t REG_VBUS_VOLT_H8 = 0x5A; // [1.7mV] + static const uint8_t REG_VBUS_VOLT_L4 = 0x5B; + static const uint8_t REG_VBUS_CURR_H8 = 0x5C; // [0.375mA] + static const uint8_t REG_VBUS_CURR_L4 = 0x5D; + static const uint8_t REG_TEMP_H8 = 0x5E; // [-144.7..264.8/0.1]degC + static const uint8_t REG_TEMP_L4 = 0x5F; + + static const uint8_t REG_BAT_VOLT_H8 = 0x78; // [0.0..4504.5/1.1]mV + static const uint8_t REG_BAT_VOLT_L4 = 0x79; + static const uint8_t REG_BAT_INP_CURR_H8 = 0x7A; // [0.5mA] + static const uint8_t REG_BAT_INP_CURR_L5 = 0x7B; + static const uint8_t REG_BAT_OUT_CURR_H8 = 0x7C; // [0.5mA] + static const uint8_t REG_BAT_OUT_CURR_L5 = 0x7D; + + static const uint8_t REG_ADC_EN1 = 0x82; // battery voltage enabled by default + static const uint8_t REG_ADC_EN2 = 0x83; // internal temperature enabled by default + static const uint8_t REG_ADC_SPEED = 0x84; // 25Hz by default + static const uint8_t REG_ADC_RANGE = 0x85; + + static const uint8_t REG_BAT_INP_CHG = 0xB0; + static const uint8_t REG_BAT_OUT_CHG = 0xB4; + static const uint8_t REG_BAT_MON = 0xB8; + + static const uint8_t AXP192_ID = 0x03; // ID for the AXP192 + + public: + static const uint8_t OUT_DCDC1 = 0; // supply outputs + static const uint8_t OUT_DCDC3 = 1; + static const uint8_t OUT_LDO2 = 2; + static const uint8_t OUT_LDO3 = 3; + static const uint8_t OUT_DCDC2 = 4; + static const uint8_t OUT_EXTEN = 6; + + static const uint16_t ADC_TEMP = 0x8000; // ADC sampling inputs + static const uint16_t ADC_BAT_VOLT = 0x0080; + static const uint16_t ADC_BAT_CURR = 0x0040; + static const uint16_t ADC_VBUS_VOLT = 0x0008; + static const uint16_t ADC_VBUS_CURR = 0x0004; + + static const uint32_t AXP202_PEK_LONGPRESS_IRQ = 0x00010000; + static const uint32_t AXP202_PEK_SHORTPRESS_IRQ = 0x00020000; + + public: + uint8_t Bus; // which I2C bus + uint8_t ADDR; // detected I2C address + uint8_t ID; + + public: + uint8_t Error; // error on the I2C bus (0=no error) + + uint8_t checkID(void) // check ID, to make sure the AXP192 is reachable + { ADDR=0; + Error=I2C_Read(Bus, AXP192_ADDR, REG_ID, ID); + if( (!Error) && (ID==AXP192_ID) ) { ADDR=AXP192_ADDR; return 0; } + return 1; } // 0 => no error and correct ID + + uint8_t readStatus(void) + { uint8_t Byte=0; Error=I2C_Read(Bus, ADDR, REG_STATUS, Byte); return Byte; } + + uint8_t setPOK(uint8_t Byte=0xDC) + { Error=I2C_Write(Bus, ADDR, REG_POK_SET, Byte); + return Error; } + + uint8_t setPowerOutput(uint8_t Channel, bool ON=1) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_LDO234_DC23_CTL, Byte); if(Error) return Error; + uint8_t Mask=1; Mask<<=Channel; + if(ON) Byte |= Mask; + else Byte &= ~Mask; + Error=I2C_Write(Bus, ADDR, REG_LDO234_DC23_CTL, Byte); + return Error; } + + uint8_t setDCDC1(uint16_t mV) // [mV] set voltage on DCDC1 output + { if(mV>3500) mV=3500; + else if(mV<700) mV=700; + uint8_t Byte = (mV-700+12)/25; + Error=I2C_Write(Bus, ADDR, REG_DC1_VOLTAGE, Byte); + return Error; } + + uint8_t ShutDown(void) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_OFF_CTL, Byte); if(Error) return Error; + Byte |= 0x80; + Error=I2C_Write(Bus, ADDR, REG_OFF_CTL, Byte); + return Error; } + + uint8_t setLED(uint8_t Mode) // 0=OFF, 1=1Hz, 2=4Hz, 3=ON, 4=reflect charging status + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_OFF_CTL, Byte); if(Error) return Error; + if(Mode<4) + { Byte |= 0x08; // control LED by software + Byte &= 0xCF; + Byte |= Mode<<4; } // set LED state: 0, 1, 2, 3 + else Byte &= 0xF7; // control LED by hardware + Error=I2C_Write(Bus, ADDR, REG_OFF_CTL, Byte); + return Error; } + + uint8_t enableADC(uint16_t Mask) + { Error=I2C_Write(Bus, ADDR, REG_ADC_EN1, Mask ); if(Error) return Error; + Error=I2C_Write(Bus, ADDR, REG_ADC_EN2, Mask>>8); return Error; } + + uint16_t readH8L4(uint8_t Reg) // read two-byte H8:L4 value + { uint8_t Byte[2]; Error=I2C_Read(Bus, ADDR, Reg, Byte, 2); if(Error) return 0; + uint16_t Word = Byte[0]; Word<<=4; Word|=Byte[1]&0x0F; return Word; } + + uint16_t readVbusVoltage(void) + { uint16_t Volt=readH8L4(REG_VBUS_VOLT_H8); return (Volt*17+5)/10; } // [1mV] + + uint16_t readVbusCurrent(void) + { uint16_t Curr=readH8L4(REG_VBUS_CURR_H8); return (Curr*15+20)/40; } // [mA] + + uint16_t readBatteryVoltage(void) + { uint16_t Volt=readH8L4(REG_BAT_VOLT_H8); return (Volt*11+5)/10; } // [mV] + + // uint16_t readVbusVoltage(void) + // { uint8_t H8; Error=I2C_Read(Bus, ADDR, REG_VBUS_VOLT_H8, H8); if(Error) return 0; + // uint8_t L4; Error=I2C_Read(Bus, ADDR, REG_VBUS_VOLT_L4, L4); if(Error) return 0; + // uint16_t Volt=H8; Volt<<=4; Volt|=L4&0x0F; return (Volt*17+5)/10; } // [mV] + + // uint16_t readVbusCurrent(void) + // { uint8_t H8; Error=I2C_Read(Bus, ADDR, REG_VBUS_CURR_H8, H8); if(Error) return 0; + // uint8_t L4; Error=I2C_Read(Bus, ADDR, REG_VBUS_CURR_L4, L4); if(Error) return 0; + // uint16_t Curr=H8; Curr<<=4; Curr|=L4&0x0F; return (Curr*15+20)/40; } // [1mA] + + // uint16_t readBatteryVoltage(void) + // { uint8_t H8; Error=I2C_Read(Bus, ADDR, REG_BAT_VOLT_H8, H8); if(Error) return 0; + // uint8_t L4; Error=I2C_Read(Bus, ADDR, REG_BAT_VOLT_L4, L4); if(Error) return 0; + // uint16_t Volt=H8; Volt<<=4; Volt|=L4&0x0F; return (Volt*11+5)/10; } // [1mV] + + int16_t readTemperature(void) + { int16_t Temp=readH8L4(REG_TEMP_H8); return Temp-1447; } // [0.1degC] + + // int16_t readTemperature(void) + // { uint8_t H8; Error=I2C_Read(Bus, ADDR, REG_TEMP_H8, H8); if(Error) return 0; + // uint8_t L4; Error=I2C_Read(Bus, ADDR, REG_TEMP_L4, L4); if(Error) return 0; + // int16_t Temp=H8; Temp<<=4; Temp|=L4&0x0F; return Temp-1447; } // [0.1degC] + + uint16_t readH8L5(uint8_t Reg) + { uint8_t Byte[2]; Error=I2C_Read(Bus, ADDR, Reg, Byte, 2); if(Error) return 0; + uint16_t Word = Byte[0]; Word<<=5; Word|=Byte[1]&0x01F; return Word; } + + uint16_t readBatteryInpCurrent(void) + { uint16_t Curr=readH8L5(REG_BAT_INP_CURR_H8); return Curr/2; } // [mA] + + uint16_t readBatteryOutCurrent(void) + { uint16_t Curr=readH8L5(REG_BAT_OUT_CURR_H8); return Curr/2; } // [mA] + + // uint16_t readBatteryInpCurrent(void) + // { uint8_t H8; Error=I2C_Read(Bus, ADDR, REG_BAT_INP_CURR_H8, H8); if(Error) return 0; + // uint8_t L5; Error=I2C_Read(Bus, ADDR, REG_BAT_INP_CURR_L5, L5); if(Error) return 0; + // uint16_t Curr=H8; Curr<<=5; Curr|=L5&0x1F; return Curr/2; } // [1mA] + + // uint16_t readBatteryOutCurrent(void) + // { uint8_t H8; Error=I2C_Read(Bus, ADDR, REG_BAT_OUT_CURR_H8, H8); if(Error) return 0; + // uint8_t L5; Error=I2C_Read(Bus, ADDR, REG_BAT_OUT_CURR_L5, L5); if(Error) return 0; + // uint16_t Curr=H8; Curr<<=5; Curr|=L5&0x1F; return Curr/2; } // [1mA] + + uint16_t read32bit(uint8_t Reg) + { uint8_t Byte[4]; Error=I2C_Read(Bus, ADDR, Reg, Byte, 4); if(Error) return 0; + uint32_t Word=Byte[0]; + for(uint8_t Idx=1; Idx<4; Idx++) + { Word<<=8; Word|=Byte[Idx]; } + return Word; } + + uint32_t readBatteryInpCharge(void) // [0x8000/25 mAs] + { uint32_t Charge = read32bit(REG_BAT_INP_CHG); return Charge; } + + uint32_t readBatteryOutCharge(void) // [0x8000/25 mAs] + { uint32_t Charge = read32bit(REG_BAT_OUT_CHG); return Charge; } + + uint8_t setBatMon(uint8_t Byte=0x80) + { Error=I2C_Write(Bus, ADDR, REG_BAT_MON, Byte); return Error; } + + uint8_t enableBatMon(void) { return setBatMon(0x80); } + uint8_t disableBatMon(void) { return setBatMon(0x00); } + uint8_t stopBatMon(void) { return setBatMon(0xC0); } + uint8_t clearBatMon(void) { return setBatMon(0xA0); } + + bool isBatteryConnected(void) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_MODE_CHGSTATUS, Byte); if(Error) return 0; + return Byte&0x20; } // 1=battery connected, 0=no battery connected + + bool isBatteryCharging(void) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_MODE_CHGSTATUS, Byte); if(Error) return 0; + return Byte&0x40; } // 1=charging, 0=charge finished or not charging + + bool isBatteryActive(void) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_MODE_CHGSTATUS, Byte); if(Error) return 0; + return Byte&0x08; } // 1=active + + bool isChargeNotEnough(void) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_MODE_CHGSTATUS, Byte); if(Error) return 0; + return Byte&0x04; } // 1=not enough charging current + + uint8_t enableIRQ(uint32_t IrqMask, bool Enable=1) + { for(uint8_t Idx=0; Idx<4; Idx++) + { uint8_t Mask = IrqMask; + if(Mask) + { uint8_t Byte; + Error=I2C_Read(Bus, ADDR, REG_INTEN1+Idx, Byte); // if(Error) continue; + if(Enable) Byte |= Mask; + else Byte &= ~Mask; + Error=I2C_Write(Bus, ADDR, REG_INTEN1+Idx, Byte); // if(Error) continue; + } + IrqMask>>=8; + } + return Error; } + + bool readLongPressIRQ(void) + { uint8_t Byte=0; + Error=I2C_Read(Bus, ADDR, REG_INTSTS1+2, Byte); if(Error) return 0; + return Byte&0x01; } + + bool readShortPressIRQ(void) + { uint8_t Byte=0; + Error=I2C_Read(Bus, ADDR, REG_INTSTS1+2, Byte); if(Error) return 0; + return Byte&0x02; } + + uint8_t clearIRQ(void) + { uint8_t Byte=0xFF; + for(uint8_t Idx=0; Idx<4; Idx++) + { Error=I2C_Write(Bus, ADDR, REG_INTSTS1+Idx, Byte); } + Error=I2C_Write(Bus, ADDR, REG_INTSTS5, Byte); + return Error; } + +} ; + +#endif // __AXP192_H__ diff --git a/main/bq24295.h b/main/bq24295.h new file mode 100644 index 0000000..4431ba6 --- /dev/null +++ b/main/bq24295.h @@ -0,0 +1,72 @@ +#ifndef __BQ24295_H__ +#define __BQ24295_H__ + +#include + +class BQ24295 +{ private: + static const uint8_t BQ24295_ADDR = 0x6B; // possible I2C addresses + + static const uint8_t REG_SOURCE = 0x00; + static const uint8_t REG_POWERON = 0x01; + static const uint8_t REG_CHG_CURR = 0x02; // charging current + static const uint8_t REG_PRE_CHG = 0x03; // pre-/post- charge current + static const uint8_t REG_CHG_VOLT = 0x04; + static const uint8_t REG_TIMER_CTRL = 0x05; + static const uint8_t REG_BOOST = 0x06; + static const uint8_t REG_MISC = 0x07; + static const uint8_t REG_STATUS = 0x08; // + static const uint8_t REG_FAULT = 0x09; + static const uint8_t REG_ID = 0x0A; // reads 0x23 but should read 0xC0 + + static const uint8_t BQ24295_ID = 0xC0; // ID for the BQ24295, but the chip returns 0x23 ? + + public: + uint8_t Bus; // which I2C bus + uint8_t ADDR; // detected I2C address + uint8_t ID; + + public: + uint8_t Error; // error on the I2C bus (0=no error) + + uint8_t readReg(uint8_t Reg) + { uint8_t Byte=0; Error=I2C_Read(Bus, ADDR, Reg, Byte); return Byte; } + + uint8_t readStatus(void) { return readReg(REG_STATUS); } // VVCCDPTS VV=Vbus status, CC=Charge status, D=DPM, P=Power-Good, T=Thermal reg., S=Vsys-min reg. + uint8_t readFault (void) { return readReg(REG_FAULT); } // WOCCBrNN W=Watchdog, O=OTG, CC=charge fault, NN=NTC(temperature) + uint8_t readSource(void) { return readReg(REG_SOURCE); } // + uint8_t readID (void) { return readReg(REG_ID); } + + uint8_t checkID(void) // check ID, to make sure the BQ24295 is reachable + { ADDR=0; + Error=I2C_Read(Bus, BQ24295_ADDR, REG_ID, ID); + if( (!Error) /* && (ID==BQ24295_ID) */ ) { ADDR=BQ24295_ADDR; return 0; } + return 1; } // 0 => no error and correct ID + + uint8_t writeSource(uint8_t Byte=0x05) // disable, 3.88V, 1.5A + { Error=I2C_Write(Bus, ADDR, REG_SOURCE, Byte); + return Error; } + + uint8_t writePowerON(uint8_t Byte=0x3F) // OTG enable, charge enable, 3.7V + { Error=I2C_Write(Bus, ADDR, REG_POWERON, Byte); + return Error; } + + uint8_t writeChargeCurr(uint8_t Byte=0x00) // 512mA + { Error=I2C_Write(Bus, ADDR, REG_CHG_CURR, Byte); + return Error; } + + uint8_t writePreCharge(uint8_t Byte=0x00) // 128mA, 128mA + { Error=I2C_Write(Bus, ADDR, REG_PRE_CHG, Byte); + return Error; } + + uint8_t writeChargeVolt(uint8_t Byte=0x9A) // 4.112V, 3.0V, 100mV + { Error=I2C_Write(Bus, ADDR, REG_CHG_VOLT, Byte); + return Error; } + + uint8_t writeTimerCtrl(uint8_t Byte=0x8C) // disable watchdog + { Error=I2C_Write(Bus, ADDR, REG_TIMER_CTRL, Byte); + return Error; } + +} ; + +#endif // __BQ24295_H__ diff --git a/main/config.h b/main/config.h index b895eef..6d60999 100644 --- a/main/config.h +++ b/main/config.h @@ -1,19 +1,31 @@ #define DEFAULT_AcftType 1 // [0..15] default aircraft-type: glider #define DEFAULT_GeoidSepar 40 // [m] #define DEFAULT_CONbaud 115200 -#define DEFAULT_PPSdelay 80 +#define DEFAULT_PPSdelay 100 #define DEFAULT_FreqPlan 0 // #define WITH_HELTEC // HELTEC module: PCB LED on GPI025 -#define WITH_TTGO // TTGO module: PCB LED on GPIO2, GPIO25 free to use as DAC2 output +// #define WITH_TTGO // TTGO module: PCB LED on GPIO2, GPIO25 free to use as DAC2 output // #define WITH_TBEAM // T-Beam module +#define WITH_TBEAM_V10 // T-Beam module +// #define WITH_JACEK // JACEK ESP32 OGN-Tracker +// #define WITH_M5_JACEK // JACEK M5 ESP32 OGN-Tracker // #define WITH_FollowMe // by Avionix -#define WITH_RFM95 -// #define WITH_RFM69 - -#define WITH_OLED // OLED display on the I2C: some TTGO modules are without OLED display +// #define WITH_ILI9341 // 320x240 M5stack +// #define WITH_ST7789 // IPS 240x240 ST7789 +// #define WITH_TFT_LCD // TFT LCD +// #define WITH_OLED // OLED display on the I2C: some TTGO modules are without OLED display // #define WITH_OLED2 // 2nd OLED display, I2C address next higher +// #define WITH_U8G2_OLED // I2C OLED through the U8g2 library +// #define WITH_U8G2_SH1106 + +#define WITH_RFM95 // RF chip selection: both HELTEC and TTGO use sx1276 which is same as RFM95 + +// #define WITH_SLEEP // with software sleep mode controlled by the long-press on the button + +#define WITH_AXP // with AXP192 power controller (T-BEAM V1.0) +// #define WITH_BQ // with BQ24295 power controller (FollowMe) // #define WITH_LED_RX // #define WITH_LED_TX @@ -21,6 +33,7 @@ // #define WITH_GPS_ENABLE // use GPS_ENABLE control line to turn the GPS ON/OFF #define WITH_GPS_PPS // use the PPS signal from GPS for precise time-sync. #define WITH_GPS_CONFIG // attempt to configure higher GPS baud rate and airborne mode + #define WITH_GPS_UBX // GPS understands UBX // #define WITH_GPS_MTK // GPS understands MTK // #define WITH_GPS_SRF @@ -31,20 +44,28 @@ // #define WITH_BMP180 // BMP180 pressure sensor // #define WITH_BMP280 // BMP280 pressure sensor -// #define WITH_BME280 // BMP280 with humidity (but still works with BMP280) +#define WITH_BME280 // BMP280 with humidity (but still works with BMP280) // #define WITH_MS5607 // MS5607 pressure sensor #define WITH_PFLAA // PFLAU and PFLAA for compatibility with XCsoar and LK8000 +// #define WITH_POGNT +#define WITH_LOOKOUT #define WITH_CONFIG // interpret the console input: $POGNS to change parameters -// #define WITH_BEEPER +#define WITH_BEEPER // with digital buzzer +// #define WITH_SOUND // with analog sound produced by DAC on pin 25 + +// #define WITH_KNOB +// #define WITH_VARIO // #define WITH_SD // use the SD card in SPI mode and FAT file system -// #define WITH_SPIFFS // use SPIFFS file system in Flash -// #define WITH_LOG // log own positions and other received to SPIFFS and possibly to uSD +#define WITH_SPIFFS // use SPIFFS file system in Flash +#define WITH_LOG // log own positions and other received to SPIFFS and possibly to uSD #define WITH_BT_SPP // Bluetooth serial port for smartphone/tablet link // #define WITH_WIFI // attempt to connect to the wifi router for uploading the log files // #define WITH_SPIFFS_LOG // log transmitted and received packets to SPIFFS +// #define WITH_ENCRYPT // Encrypt (optionally) the position + diff --git a/main/ctrl.cpp b/main/ctrl.cpp index f6ecddf..c588f79 100644 --- a/main/ctrl.cpp +++ b/main/ctrl.cpp @@ -6,12 +6,14 @@ #include #include "esp_system.h" +// #include "esp_sleep.h" #include "hal.h" -#include "rf.h" #include "sens.h" +#include "rf.h" #include "ctrl.h" +#include "proc.h" #include "log.h" #include "gps.h" @@ -19,10 +21,17 @@ #include "timesync.h" #include "format.h" +#include "disp_oled.h" +#include "disp_lcd.h" + +// #include "ymodem.h" + // #define DEBUG_PRINT static char Line[128]; +FIFO KeyBuffer; + // ======================================================================================================================== void PrintTasks(void (*CONS_UART_Write)(char)) @@ -53,338 +62,6 @@ void PrintTasks(void (*CONS_UART_Write)(char)) // ======================================================================================================================== -#ifdef WITH_OLED -int OLED_DisplayStatus(uint32_t Time, uint8_t LineIdx=0) -{ Format_String(Line , "OGN Tx/Rx "); - Format_HHMMSS(Line+10, Time); - OLED_PutLine(LineIdx++, Line); - Parameters.Print(Line); - OLED_PutLine(LineIdx++, Line); - return 0; } - -int OLED_DisplayPosition(GPS_Position *GPS=0, uint8_t LineIdx=2) -{ if(GPS && GPS->isValid()) - { Line[0]=' '; - Format_SignDec(Line+1, GPS->Latitude /60, 6, 4); Line[9]=' '; - Format_UnsDec (Line+10, GPS->Altitude /10, 5, 0); Line[15]='m'; - OLED_PutLine(LineIdx , Line); - Format_SignDec(Line, GPS->Longitude/60, 7, 4); - Format_SignDec(Line+10, GPS->ClimbRate, 4, 1); - OLED_PutLine(LineIdx+1, Line); - Format_UnsDec (Line , GPS->Speed, 4, 1); Format_String(Line+5, "m/s "); - Format_UnsDec (Line+10, GPS->Heading, 4, 1); Line[15]='^'; - OLED_PutLine(LineIdx+2, Line); - Format_String(Line, "0D/00sat DOP00.0"); - Line[0]+=GPS->FixMode; Format_UnsDec(Line+3, GPS->Satellites, 2); - Format_UnsDec(Line+12, (uint16_t)GPS->HDOP, 3, 1); - OLED_PutLine(LineIdx+3, Line); - } - else { OLED_PutLine(LineIdx, 0); OLED_PutLine(LineIdx+1, 0); OLED_PutLine(LineIdx+2, 0); OLED_PutLine(LineIdx+3, 0); } - if(GPS && GPS->isDateValid()) - { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; - Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; - Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; Line[9]=' '; } - else Format_String(Line, " "); - if(GPS && GPS->isTimeValid()) - { Format_UnsDec (Line+10, (uint16_t)GPS->Hour, 2, 0); - Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); - Format_UnsDec (Line+14, (uint16_t)GPS->Sec, 2, 0); - } else Line[10]=0; - OLED_PutLine(LineIdx+4, Line); - Line[0]=0; - if(GPS && GPS->hasBaro) - { Format_String(Line , "0000.0hPa 00000m"); - Format_UnsDec(Line , GPS->Pressure/40, 5, 1); - Format_UnsDec(Line+10, GPS->StdAltitude/10, 5, 0); } - OLED_PutLine(LineIdx+5, Line); - return 0; } -#endif - -#ifdef WITH_U8G2 - -void OLED_PutLine(u8g2_t *OLED, uint8_t LineIdx, const char *Line) -{ if(Line==0) return; -#ifdef DEBUG_PRINT - xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "OLED_PutLine( ,"); - Format_UnsDec(CONS_UART_Write, (uint16_t)LineIdx); - CONS_UART_Write(','); - Format_String(CONS_UART_Write, Line); - Format_String(CONS_UART_Write, ")\n"); - xSemaphoreGive(CONS_Mutex); -#endif - // u8g2_SetFont(OLED, u8g2_font_5x8_tr); - u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); - u8g2_DrawStr(OLED, 0, (LineIdx+1)*8, Line); -} - -void OLED_DrawStatus(u8g2_t *OLED, uint32_t Time, uint8_t LineIdx=0) -{ Format_String(Line , "OGN Tx/Rx "); - Format_HHMMSS(Line+10, Time); Line[16]=0; - OLED_PutLine(OLED, LineIdx++, Line); - Parameters.Print(Line); Line[16]=0; - OLED_PutLine(OLED, LineIdx++, Line); } - -void OLED_DrawPosition(u8g2_t *OLED, GPS_Position *GPS=0, uint8_t LineIdx=2) -{ if(GPS && GPS->isValid()) - { Line[0]=' '; - Format_SignDec(Line+1, GPS->Latitude /60, 6, 4); Line[9]=' '; - Format_UnsDec (Line+10, GPS->Altitude /10, 5, 0); Line[15]='m'; - OLED_PutLine(OLED, LineIdx , Line); - Format_SignDec(Line, GPS->Longitude/60, 7, 4); - Format_SignDec(Line+10, GPS->ClimbRate, 4, 1); - OLED_PutLine(OLED, LineIdx+1, Line); - Format_UnsDec (Line , GPS->Speed, 4, 1); Format_String(Line+5, "m/s "); - Format_UnsDec (Line+10, GPS->Heading, 4, 1); Line[15]='^'; - OLED_PutLine(OLED, LineIdx+2, Line); - Format_String(Line, "0D/00sat DOP00.0"); - Line[0]+=GPS->FixMode; Format_UnsDec(Line+3, GPS->Satellites, 2); - Format_UnsDec(Line+12, (uint16_t)GPS->HDOP, 3, 1); - OLED_PutLine(OLED, LineIdx+3, Line); - } - // else { OLED_PutLine(OLED, LineIdx, 0); OLED_PutLine(OLED, LineIdx+1, 0); OLED_PutLine(LineIdx+2, 0); OLED_PutLine(LineIdx+3, 0); } - if(GPS && GPS->isDateValid()) - { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; - Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; - Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; Line[9]=' '; } - else Format_String(Line, " "); - if(GPS && GPS->isTimeValid()) - { Format_UnsDec (Line+10, (uint16_t)GPS->Hour, 2, 0); - Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); - Format_UnsDec (Line+14, (uint16_t)GPS->Sec, 2, 0); - } else Line[10]=0; - OLED_PutLine(OLED, LineIdx+4, Line); - Line[0]=0; - if(GPS && GPS->hasBaro) - { Format_String(Line , "0000.0hPa 00000m"); - Format_UnsDec(Line , GPS->Pressure/40, 5, 1); - Format_UnsDec(Line+10, GPS->StdAltitude/10, 5, 0); } - OLED_PutLine(OLED, LineIdx+5, Line); -} - -void OLED_DrawGPS(u8g2_t *OLED, GPS_Position *GPS=0) // GPS time, position, altitude -{ // u8g2_SetFont(OLED, u8g2_font_ncenB14_tr); - u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line - uint8_t Len=0; - Len+=Format_String(Line+Len, "GPS "); - if(GPS && GPS->isValid()) - { Line[Len++]='0'+GPS->FixMode; Line[Len++]='D'; Line[Len++]='/'; - Len+=Format_UnsDec(Line+Len, GPS->Satellites, 1); - Len+=Format_String(Line+Len, "sat DOP"); - Len+=Format_UnsDec(Line+Len, (uint16_t)GPS->HDOP, 2, 1); } - else - { Len+=Format_String(Line+Len, "(no lock)"); } - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 12, Line); - if(GPS && GPS->isDateValid()) - { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; - Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; - Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; - } else Format_String(Line, " . . "); - if(GPS && GPS->isTimeValid()) - { Format_UnsDec (Line+ 9, (uint16_t)GPS->Hour, 2, 0); Line[11]=':'; - Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); Line[14]=':'; - Format_UnsDec (Line+15, (uint16_t)GPS->Sec, 2, 0); - } else Format_String(Line+9, " : : "); - Line[17]=0; - u8g2_DrawStr(OLED, 0, 24, Line); - Len=0; - Len+=Format_String(Line+Len, "Lat: "); - if(GPS && GPS->isValid()) - { Len+=Format_SignDec(Line+Len, GPS->Latitude /6, 7, 5); - Line[Len++]=0xB0; } - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 36, Line); - Len=0; - Len+=Format_String(Line+Len, "Lon: "); - if(GPS && GPS->isValid()) - { Len+=Format_SignDec(Line+Len, GPS->Longitude /6, 8, 5); - Line[Len++]=0xB0; } - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 48, Line); - Len=0; - Len+=Format_String(Line+Len, "Alt: "); - if(GPS && GPS->isValid()) - { Len+=Format_SignDec(Line+Len, GPS->Altitude, 4, 1); - Line[Len++]='m'; } - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 60, Line); -} - -void OLED_DrawRF(u8g2_t *OLED) // RF -{ u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines. 12 pixels/line - uint8_t Len=0; -#ifdef WITH_RFM69 - Len+=Format_String(Line+Len, "RFM69"); // Type of RF chip used - if(isTxTypeHW()) Line[Len++]='H'; - Line[Len++]='W'; -#endif -#ifdef WITH_RFM95 - Len+=Format_String(Line+Len, "RFM95"); -#endif -#ifdef WITH_SX1272 - Len+=Format_String(Line+Len, "SX1272"); -#endif - Line[Len++]=':'; - Len+=Format_SignDec(Line+Len, (int16_t)Parameters.getTxPower()); // Tx power - Len+=Format_String(Line+Len, "dBm"); - Line[Len++]=' '; - Len+=Format_SignDec(Line+Len, (int32_t)Parameters.RFchipFreqCorr, 2, 1); // frequency correction - Len+=Format_String(Line+Len, "ppm"); - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 12, Line); - Len=0; - Len+=Format_String(Line+Len, "Rx:"); // - Len+=Format_SignDec(Line+Len, -5*TRX.averRSSI, 2, 1); // noise level seen by the receiver - Len+=Format_String(Line+Len, "dBm "); - Len+=Format_UnsDec(Line+Len, RX_OGN_Count64); // received packet/min - Len+=Format_String(Line+Len, "/min"); - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 24, Line); - Len=0; - Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); // RF chip internal temperature (not calibrated) - // Line[Len++]=0xB0; - // Line[Len++]='C'; - Len+=Format_String(Line+Len, "\260C RxFIFO:"); - Len+=Format_UnsDec(Line+Len, RF_RxFIFO.Full()); // how many packets wait in the RX queue - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 36, Line); - // u8g2_DrawStr(OLED, 0, 48, RF_FreqPlan.getPlanName()); - Len=0; - Len+=Format_String(Line+Len, RF_FreqPlan.getPlanName()); // name of the frequency plan - Line[Len++]=' '; - Len+=Format_UnsDec(Line+Len, (uint16_t)(RF_FreqPlan.getCenterFreq()/100000), 3, 1); // center frequency - Len+=Format_String(Line+Len, "MHz"); - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 48, Line); - -} - -void OLED_DrawBaro(u8g2_t *OLED, GPS_Position *GPS=0) -{ u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line - uint8_t Len=0; -#ifdef WITH_BMP180 - Len+=Format_String(Line+Len, "BMP180 "); -#endif -#ifdef WITH_BMP280 - Len+=Format_String(Line+Len, "BMP280 "); -#endif -#ifdef WITH_BME280 - Len+=Format_String(Line+Len, "BME280 "); -#endif -#ifdef WITH_MS5607 - Len+=Format_String(Line+Len, "MS5607 "); -#endif - Len+=Format_UnsDec(Line+Len, GPS->Pressure/4, 5, 2); - Len+=Format_String(Line+Len, "Pa"); - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 12, Line); - Len=0; - Len+=Format_UnsDec(Line+Len, GPS->StdAltitude, 5, 1); - Len+=Format_String(Line+Len, "m "); - Len+=Format_SignDec(Line+Len, GPS->ClimbRate, 2, 1); - Len+=Format_String(Line+Len, "m/s"); - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 24, Line); - Len=0; - Len+=Format_SignDec(Line+Len, GPS->Temperature, 2, 1); - Line[Len++]=0xB0; - Line[Len++]='C'; - Line[Len++]=' '; - Len+=Format_SignDec(Line+Len, GPS->Humidity, 2, 1); - Line[Len++]='%'; - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 36, Line); -} - -void OLED_DrawSystem(u8g2_t *OLED) -{ - u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line - uint8_t Len=0; - Len+=Format_String(Line+Len, "GPS "); -#ifdef WITH_GPS_UBX - Len+=Format_String(Line+Len, "UBX "); -#endif -#ifdef WITH_GPS_MTK - Len+=Format_String(Line+Len, "MTK "); -#endif -#ifdef WITH_GPS_SRF - Len+=Format_String(Line+Len, "SRF "); -#endif - Len+=Format_UnsDec(Line+Len, GPS_getBaudRate(), 1); - Len+=Format_String(Line+Len, "bps"); - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 12, Line); - - Len=0; -#ifdef WITH_RFM69 - Len+=Format_String(Line+Len, "RFM69 v"); // Type of RF chip used - if(isTxTypeHW()) Line[Len++]='H'; - Line[Len++]='W'; -#endif -#ifdef WITH_RFM95 - Len+=Format_String(Line+Len, "RFM95 v"); -#endif -#ifdef WITH_SX1272 - Len+=Format_String(Line+Len, "SX1272 v"); -#endif - Len+=Format_Hex(Line+Len, TRX.chipVer); - Line[Len++]=' '; - Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); - Line[Len++]=0xB0; - Line[Len++]='C'; - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 24, Line); - - Len=0; -#ifdef WITH_BMP180 - Len+=Format_String(Line+Len, "BMP180 0x"); - Len+=Format_Hex(Line+Len, Baro.ADDR); -#endif -#ifdef WITH_BMP280 - Len+=Format_String(Line+Len, "BMP280 0x"); - Len+=Format_Hex(Line+Len, Baro.ADDR); -#endif -#ifdef WITH_BME280 - Len+=Format_String(Line+Len, "BME280 0x"); - Len+=Format_Hex(Line+Len, Baro.ADDR); -#endif -#ifdef WITH_MS5607 - Len+=Format_String(Line+Len, "MS5607 0x"); - Len+=Format_Hex(Line+Len, Baro.ADDR); -#endif - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 36, Line); - -#ifdef WITH_SD - Len=0; - Len += Format_String(Line+Len, "SD "); - if(SD_isMounted()) - { Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectors()); - Line[Len++]='x'; - Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectorSize()*5/512, 2, 1); - Len+=Format_String(Line+Len, "KB"); } - else - { Len+=Format_String(Line+Len, "none"); } - Line[Len]=0; - u8g2_DrawStr(OLED, 0, 60, Line); - -#endif - -} - -void OLED_DrawID(u8g2_t *OLED) -{ u8g2_SetFont(OLED, u8g2_font_9x15_tr); - strcpy(Line, "ID: "); Parameters.Print(Line+4); Line[14]=0; - u8g2_DrawStr(OLED, 0, 20, Line); - u8g2_SetFont(OLED, u8g2_font_10x20_tr); -#ifdef WITH_FollowMe - u8g2_DrawStr(OLED, 8, 40, "FollowMe868"); - u8g2_DrawStr(OLED, 16, 60, "by AVIONIX"); -#endif -} - -#endif // ======================================================================================================================== @@ -457,6 +134,21 @@ static void ProcessNMEA(void) // process a valid NMEA that got to the consol #endif } +static uint8_t Verbose=0; +static uint32_t VerboseSuspendTime=0; +const uint32_t VerboseSuspendTimeout=60; + +static void CheckCtrlV(void) +{ if(VerboseSuspendTime==0) return; + uint32_t Time = TimeSync_Time()-VerboseSuspendTime; + if(Time>VerboseSuspendTimeout) { Parameters.Verbose=Verbose; VerboseSuspendTime=0; } +} + +static void ProcessCtrlV(void) +{ if(VerboseSuspendTime) { Parameters.Verbose=Verbose; VerboseSuspendTime=0; return; } + Verbose = Parameters.Verbose; VerboseSuspendTime=TimeSync_Time(); Parameters.Verbose=0; +} + static void ProcessCtrlC(void) // print system state to the console { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Parameters.Print(Line); @@ -532,13 +224,107 @@ static void ProcessCtrlL(void) // print syste #endif } +void SleepIn(void) +{ xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "Sleep-in\n"); + xSemaphoreGive(CONS_Mutex); + +#ifdef WITH_GPS_UBX +#ifdef WITH_GPA_ENA + GPS_DISABLE(); +#endif + UBX_RXM_PMREQ PMREQ; + PMREQ.duration = 0; + PMREQ.flags = 0x01; + UBX_RxMsg::Send(0x02, 0x41, GPS_UART_Write, (uint8_t *)(&PMREQ), sizeof(PMREQ)); +#endif + +#ifdef WITH_GPS_MTK +#ifdef WITH_GPA_ENA + GPS_DISABLE(); + Format_String(GPS_UART_Write, "$PMTK225,4*2F\r\n"); // backup mode: 7uA +#else + Format_String(GPS_UART_Write, "$PMTK161,0*28\r\n"); // standby mode: 1mA +#endif +#endif + +#ifdef WITH_OLED + OLED_DisplayON(0); +#endif + +#ifdef WITH_U8G2_OLED + u8g2_SetPowerSave(&U8G2_OLED, 1); +#endif + +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + LCD_SetBacklightLevel(0); +#endif + + PowerMode=0; + vTaskDelay(1000); } + +void SleepOut(void) +{ +#ifdef WITH_GPS_ENABLE + GPS_DISABLE(); +#endif + PowerMode=2; +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + LCD_SetBacklightLevel(6); +#endif +#ifdef WITH_U8G2_OLED + u8g2_SetPowerSave(&U8G2_OLED, 0); +#endif +#ifdef WITH_OLED + OLED_DisplayON(1); +#endif + +#ifdef WITH_GPS_UBX +#ifdef WITH_GPS_ENABLE + GPS_ENABLE(); +#endif + Format_String(GPS_UART_Write, "\n"); +#endif + +#ifdef WITH_GPS_MTK +#ifdef WITH_GPS_ENABLE + GPS_ENABLE(); +#else + Format_String(GPS_UART_Write, "$PMTK161,1*29\r\n"); +#endif +#endif + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "Sleep-out\n"); + xSemaphoreGive(CONS_Mutex); } + +#ifdef WITH_SLEEP +static TickType_t LowBatt_Time=0; + +static void LowBatt_Watch(void) // check battery voltage +{ uint16_t BattVolt = BatteryVoltage>>8; // [mV] + if(BattVolt>=3250 || BattVolt<=700 ) { LowBatt_Time=0; return; } + int16_t BattRate = (BatteryVoltageRate+128)>>8; // [mv/sec] + if(BattRate>0) { LowBatt_Time=0; return; } + TickType_t Now=xTaskGetTickCount(); + if(LowBatt_Time==0) { LowBatt_Time=Now; return; } + Now-=LowBatt_Time; + if(Now>=30000) // if low battery and voltage dropping persists for 30sec + { SleepIn(); // + Sleep(); // enter sleep + SleepOut(); // wake up + LowBatt_Time=0; } // reset time counter +} +#endif + static void ProcessInput(void) { for( ; ; ) { uint8_t Byte; int Err=CONS_UART_Read(Byte); if(Err<=0) break; // get byte from console, if none: exit the loop #ifndef WITH_GPS_UBX_PASS - if(Byte==0x03) ProcessCtrlC(); // if Ctrl-C received - if(Byte==0x0C) ProcessCtrlL(); // if Ctrl-C received + if(Byte==0x03) ProcessCtrlC(); // if Ctrl-C received: print parameters + if(Byte==0x0C) ProcessCtrlL(); // if Ctrl-L received: list log files + if(Byte==0x16) ProcessCtrlV(); // if Ctrl-L received: suspend (verbose) printout if(Byte==0x18) esp_restart() ; // if Ctrl-X received then restart + // if(Byte==0x19) Shutdown(); // if Ctrl-Y receiver: shutdown #endif NMEA.ProcessByte(Byte); // pass the byte through the NMEA processor if(NMEA.isComplete()) // if complete NMEA: @@ -560,56 +346,164 @@ static void ProcessInput(void) // ======================================================================================================================== -const uint8_t OLED_Pages = 6; -static uint8_t OLED_Page = 1; - extern "C" void vTaskCTRL(void* pvParameters) -{ uint32_t PrevTime=0; +{ + uint32_t PrevTime=0; GPS_Position *PrevGPS=0; - for( ; ; ) + for( ; ; ) // { ProcessInput(); // process console input - +#ifdef WITH_SLEEP +#if defined(WITH_FollowMe) || defined(WITH_TBEAM) + LowBatt_Watch(); +#endif +#endif vTaskDelay(1); // +#ifdef WITH_AXP + bool PowerOffRequest = AXP.readLongPressIRQ() /* || AXP.readShortPressIRQ() */ ; + if(PowerOffRequest) + { PowerMode=0; + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "Power-Off Request\n"); + xSemaphoreGive(CONS_Mutex); + // vTaskDelay(1000); + AXP.setLED(4); +#ifdef WITH_OLED + OLED_DisplayON(0); +#endif +#ifdef WITH_U8G2_OLED + u8g2_SetPowerSave(&U8G2_OLED, 1); +#endif +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + LCD_SetBacklightLevel(0); +#endif + AXP.setPowerOutput(AXP.OUT_LDO2, 0); // turn off RFM power + AXP.setPowerOutput(AXP.OUT_LDO3, 0); // turn off GPS power + AXP.setPowerOutput(AXP.OUT_DCDC1, 0); + // AXP.setPowerOutput(AXP.OUT_DCDC2, 0); + // AXP.setPowerOutput(AXP.OUT_DCDC3, 0); + // AXP.setPowerOutput(AXP.OUT_EXTEN, 0); + AXP.ShutDown(); + vTaskDelay(1000); +// #define PIN_AXP_IRQ GPIO_NUM_35 +// esp_sleep_enable_ext0_wakeup(PIN_AXP_IRQ, 0); // 1 = High, 0 = Low +// esp_deep_sleep_start(); + } + bool ShortPress = AXP.readShortPressIRQ(); + if(ShortPress) + { KeyBuffer.Write(0x04); +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "AXP short-press\n"); + xSemaphoreGive(CONS_Mutex); +#endif + AXP.clearIRQ(); } +#endif + LED_TimerCheck(1); // update the LED flashes #ifdef WITH_BEEPER Play_TimerCheck(); // update the LED flashes #endif - bool PageChange=0; - int32_t PressRelease=Button_TimerCheck(); + + int32_t PressRelease=Button_TimerCheck(); // 0 = no change +// #ifdef DEBUG_PRINT if(PressRelease!=0) { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "PressRelease = "); + Format_String(CONS_UART_Write, "Button: "); Format_SignDec(CONS_UART_Write, PressRelease); Format_String(CONS_UART_Write, "ms\n"); xSemaphoreGive(CONS_Mutex); } +// #endif if(PressRelease>0) - { if(PressRelease<=300) // short button push: switch pages - { OLED_Page++; if(OLED_Page>=OLED_Pages) OLED_Page=0; - PageChange=1; } - else if(PressRelease<=2000) // long button push: some page action + { if(PressRelease<=700) // short button push: switch pages + { KeyBuffer.Write(0x01); } + else if(PressRelease<=3000) // longer button push: some page action + { KeyBuffer.Write(0x41); } + else // very long push { } } uint32_t Time=TimeSync_Time(); - GPS_Position *GPS = GPS_getPosition(); bool TimeChange = Time!=PrevTime; + uint32_t Sec = (Time-1)%60; + GPS_Position *GPS = GPS_getPosition(Sec); bool GPSchange = GPS!=PrevGPS; if( (!TimeChange) && (!GPSchange) ) continue; PrevTime=Time; PrevGPS=GPS; -#ifdef WITH_OLED - if(Button_SleepRequest) - { OLED_DisplayON(0); } - else - { esp_err_t StatErr=ESP_OK; - esp_err_t PosErr=ESP_OK; - if(TimeChange) - { StatErr = OLED_DisplayStatus(Time, 0); } - if(GPSchange) - { PosErr = OLED_DisplayPosition(GPS, 2); } - } + if(TimeChange) CheckCtrlV(); + +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + if(TimeChange) + { Format_String(CONS_UART_Write, "TimeChange: "); + // Format_HHMMSS(CONS_UART_Write, Sec); + Format_HHMMSS(CONS_UART_Write, Time); + Format_String(CONS_UART_Write, "\n"); } + if(GPSchange && GPS) + { Format_String(CONS_UART_Write, "GPSchange: "); + GPS->PrintLine(Line); + Format_String(CONS_UART_Write, Line); } + xSemaphoreGive(CONS_Mutex); +#endif + +#ifdef WITH_BQ +#ifdef DEBUG_PRINT + if(TimeChange) + { uint16_t Batt = BatterySense(2); + // uint8_t ID = BQ.readID(); + // uint8_t Status = BQ.readStatus(); + // uint8_t Fault = BQ.readFault(); + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "BQ24295:"); + for(uint8_t Reg=0; Reg<=10; Reg++) + { uint8_t Val=BQ.readReg(Reg); CONS_UART_Write(' '); Format_Hex(CONS_UART_Write, Val); } + // Format_Hex(CONS_UART_Write, ID); + // CONS_UART_Write('/'); + // Format_Hex(CONS_UART_Write, Status); + // CONS_UART_Write('/'); + // Format_Hex(CONS_UART_Write, Fault); + Format_String(CONS_UART_Write, " Batt="); + Format_UnsDec(CONS_UART_Write, Batt, 4, 3); + Format_String(CONS_UART_Write, "V\n"); + xSemaphoreGive(CONS_Mutex); } +#endif +#endif + +#ifdef WITH_AXP +// #ifdef DEBUG_PRINT + if(TimeChange) + { uint16_t Batt=AXP.readBatteryVoltage(); // [mV] + uint16_t InpCurr=AXP.readBatteryInpCurrent(); // [mA] + uint16_t OutCurr=AXP.readBatteryOutCurrent(); // [mA] + uint16_t Vbus=AXP.readVbusVoltage(); // [mV] + uint16_t VbusCurr=AXP.readVbusCurrent(); // [mA] + int16_t Temp=AXP.readTemperature(); // [0.1degC] + uint32_t InpCharge=AXP.readBatteryInpCharge(); + uint32_t OutCharge=AXP.readBatteryOutCharge(); + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "AXP192: Batt="); + Format_UnsDec(CONS_UART_Write, Batt, 4, 3); + Format_String(CONS_UART_Write, "V, "); + Format_UnsDec(CONS_UART_Write, InpCurr, 4, 3); + Format_String(CONS_UART_Write, "/"); + Format_UnsDec(CONS_UART_Write, OutCurr, 4, 3); + Format_String(CONS_UART_Write, "A, USB: "); + Format_UnsDec(CONS_UART_Write, Vbus, 4, 3); + Format_String(CONS_UART_Write, "V, "); + Format_UnsDec(CONS_UART_Write, VbusCurr, 4, 3); + Format_String(CONS_UART_Write, "A, Charge="); + Format_UnsDec(CONS_UART_Write, ((InpCharge<<12)+562)/1125, 2, 1); + Format_String(CONS_UART_Write, "-"); + Format_UnsDec(CONS_UART_Write, ((OutCharge<<12)+562)/1125, 2, 1); + Format_String(CONS_UART_Write, "mAh, Temp="); + Format_SignDec(CONS_UART_Write, Temp, 2, 1); + Format_String(CONS_UART_Write, "degC\n"); + xSemaphoreGive(CONS_Mutex); } +// #endif +#endif + #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); if(TimeChange) @@ -622,26 +516,6 @@ void vTaskCTRL(void* pvParameters) Format_String(CONS_UART_Write, "\n"); } xSemaphoreGive(CONS_Mutex); #endif -#endif // WITH_OLED - -#ifdef WITH_U8G2 - if(Button_SleepRequest) - { u8g2_SetPowerSave(&U8G2_OLED, 0); } - else if(TimeChange || PageChange) - { u8g2_ClearBuffer(&U8G2_OLED); - switch(OLED_Page) - { case 2: OLED_DrawGPS (&U8G2_OLED, GPS); break; - case 3: OLED_DrawRF (&U8G2_OLED); break; - case 4: OLED_DrawBaro (&U8G2_OLED, GPS); break; - case 1: OLED_DrawID (&U8G2_OLED); break; - case 5: OLED_DrawSystem(&U8G2_OLED); break; - default: - { OLED_DrawStatus(&U8G2_OLED, Time, 0); - OLED_DrawPosition(&U8G2_OLED, GPS, 2); } - } - u8g2_SendBuffer(&U8G2_OLED); - } -#endif #ifdef DEBUG_PRINT // in debug mode print the parameters and state every 60sec if((Time%60)!=0) continue; diff --git a/main/ctrl.h b/main/ctrl.h index 7b0187a..867a0e4 100644 --- a/main/ctrl.h +++ b/main/ctrl.h @@ -1,5 +1,8 @@ +#include "fifo.h" #include "hal.h" +extern FIFO KeyBuffer; + #ifdef __cplusplus extern "C" #endif diff --git a/main/disp.cpp b/main/disp.cpp new file mode 100644 index 0000000..2f759f4 --- /dev/null +++ b/main/disp.cpp @@ -0,0 +1,182 @@ +#include + +#include +#include +#include +#include + +#include "esp_system.h" +// #include "esp_sleep.h" + +#include "hal.h" + +#include "sens.h" +#include "rf.h" +#include "ctrl.h" +#include "proc.h" +#include "log.h" + +#include "gps.h" +#include "ubx.h" +#include "timesync.h" +#include "format.h" + +#include "disp_oled.h" +#include "disp_lcd.h" + +#ifdef WITH_U8G2_OLED +const uint8_t DISP_Pages = 6; +static uint8_t DISP_Page = 1; +#endif +#if defined(WITH_ST7789) || defined(WITH_ILI9341) +const uint8_t DISP_Pages = 9; +static uint8_t DISP_Page = 0; +#endif + + +extern "C" +void vTaskDISP(void* pvParameters) +{ +#ifdef WITH_U8G2_OLED + u8g2_ClearBuffer(&U8G2_OLED); + OLED_DrawLogo(&U8G2_OLED); + u8g2_SendBuffer(&U8G2_OLED); +#endif +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + // LCD_Start(); + LCD_LogoPage_Draw(); + LCD_SetBacklightLevel(6); // backlight level +#endif + + uint32_t PrevTime=0; + GPS_Position *PrevGPS=0; + for( ; ; ) // + { +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + if(PowerMode==0) + { vTaskDelay(200); LCD_SetBacklightLevel(0); continue; } +#endif +#ifdef WITH_U8G2_OLED + if(PowerMode==0) + { vTaskDelay(200); /* u8g2_SetPowerSave(&U8G2_OLED, 1); */ continue; } +#endif + vTaskDelay(1); // + + bool PageChange = 0; + uint8_t Key = 0; + KeyBuffer.Read(Key); // read key pressed from the buffer + if(Key) PageChange=1; // page-change on any key + + uint32_t Time=TimeSync_Time(); + bool TimeChange = Time!=PrevTime; // did time change = a new second ? + uint32_t Sec = (Time-1)%60; + GPS_Position *GPS = GPS_getPosition(Sec); + bool GPSchange = GPS!=PrevGPS; // did GPS data change = new position ? + if(!PageChange) // if no page change was requested + { if( (!TimeChange) && (!GPSchange) ) continue; + PrevTime=Time; PrevGPS=GPS; } + +#if defined(WITH_U8G2_OLED) || defined(WITH_ST7789) || defined(WITH_ILI9341) + if(PageChange) DISP_Page++; if(DISP_Page>=DISP_Pages) DISP_Page=0; +#endif + +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + static uint8_t LCD_Backlight = 8*16+8; + const uint8_t LCD_BacklightLimit=4*16+8; // lower limit for the backlight +#ifdef WITH_AXP + uint16_t Vbus=AXP.readVbusVoltage(); // external supply (USB) voltage + if(PageChange || Vbus>=4000) LCD_Backlight=8*16+8; // high backlight on page-change or when charging +#else + if(PageChange) LCD_Backlight=8*16+8; // high backlight on page change +#endif + switch(DISP_Page) + { case 0: if(PageChange) LCD_LogoPage_Draw(Time, GPS); // logo with basic information + LCD_LogoPage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 1: if(PageChange) LCD_GPSpage_Draw(Time, GPS); // GPS data + LCD_GPSpage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 2: if(PageChange) LCD_RFpage_Draw(Time, GPS); // RF data + LCD_RFpage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 3: if(PageChange) LCD_BaroPage_Draw(Time, GPS); // Baro data + LCD_BaroPage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 4: if(PageChange) LCD_BattPage_Draw(Time, GPS); // Battery + LCD_BattPage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 5: if(PageChange) LCD_ParmPage_Draw(Time, GPS); // ID and parameters + LCD_ParmPage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 6: if(PageChange) LCD_RelayPage_Draw(Time, GPS); // RELAY list + LCD_RelayPage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 7: if(PageChange) LCD_LookPage_Draw(Time, GPS); // Look targets + LCD_LookPage_Update(Time, GPS, TimeChange, GPSchange); + break; + case 8: if(PageChange) LCD_SysPage_Draw(Time, GPS); // System overview + LCD_SysPage_Update(Time, GPS, TimeChange, GPSchange); + break; + } + if(TimeChange) // on each new second + { LCD_SetBacklightLevel(LCD_Backlight/16); // + if(LCD_Backlight>LCD_BacklightLimit) LCD_Backlight--; } // reduce backlight a lttle if above minimum +#endif // if WITH_ST7789 or WITH_ILI9341 + +#ifdef WITH_OLED + // if(Button_SleepRequest) + // { OLED_DisplayON(0); } + // else + { esp_err_t StatErr=ESP_OK; + esp_err_t PosErr=ESP_OK; + if(TimeChange) + { StatErr = OLED_DisplayStatus(Time, 0); } + if(GPSchange) + { PosErr = OLED_DisplayPosition(GPS, 2); } + } +#endif // WITH_OLED + +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + if(TimeChange) + { Format_String(CONS_UART_Write, "TimeChange: "); + // Format_SignDec(CONS_UART_Write, StatErr); + Format_String(CONS_UART_Write, "\n"); } + if(GPSchange) + { Format_String(CONS_UART_Write, "GPSchange: "); + // Format_SignDec(CONS_UART_Write, PosErr); + Format_String(CONS_UART_Write, "\n"); } + xSemaphoreGive(CONS_Mutex); +#endif + +#ifdef WITH_U8G2_OLED + // if(Button_SleepRequest) + // { u8g2_SetPowerSave(&U8G2_OLED, 0); } + // else + if(PageChange || ( GPS?GPSchange:TimeChange) ) + { u8g2_ClearBuffer(&U8G2_OLED); + // if(Look.WarnLevel) + // { OLED_DrawTrafWarn(&U8G2_OLED, GPS); } + // else + { switch(DISP_Page) + { case 2: OLED_DrawGPS (&U8G2_OLED, GPS); break; + case 3: OLED_DrawRF (&U8G2_OLED); break; + case 4: OLED_DrawBaro (&U8G2_OLED, GPS); break; + case 1: OLED_DrawID (&U8G2_OLED); break; + case 5: OLED_DrawSystem(&U8G2_OLED); break; + // case 6: OLED_DrawRelay (&U8G2_OLED, GPS); break; + // case 7: OLED_DrawLookout(&U8G2_OLED, GPS); break; + case 0: OLED_DrawBattery(&U8G2_OLED); break; + // case 9: OLED_DrawTrafWarn(&U8G2_OLED, GPS); break; + // default: + // { OLED_DrawStatus(&U8G2_OLED, Time, 0); + // OLED_DrawPosition(&U8G2_OLED, GPS, 2); } + } + } + OLED_DrawStatusBar(&U8G2_OLED, GPS); + u8g2_SendBuffer(&U8G2_OLED); + } +#endif + + } +} diff --git a/main/disp.h b/main/disp.h new file mode 100644 index 0000000..1914b5a --- /dev/null +++ b/main/disp.h @@ -0,0 +1,7 @@ +#include "hal.h" + +#ifdef __cplusplus + extern "C" +#endif + void vTaskDISP(void* pvParameters); + diff --git a/main/disp_lcd.cpp b/main/disp_lcd.cpp new file mode 100644 index 0000000..eb3a7d3 --- /dev/null +++ b/main/disp_lcd.cpp @@ -0,0 +1,713 @@ +#include + +#include +#include +#include +#include + +// #include "esp_system.h" +// #include "esp_sleep.h" + +#include "hal.h" + +#include "sens.h" +#include "rf.h" +#include "ctrl.h" +#include "proc.h" +// #include "log.h" + +#include "gps.h" +// #include "ubx.h" +// #include "timesync.h" +#include "format.h" + +// #include "ymodem.h" + +// #define DEBUG_PRINT + +// static char Line[128]; + +// ======================================================================================================================== + +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + +#include "st7789.h" +#include "lcd_battery.h" + +// Embedded images +// #ifdef WITH_M5_JACEK +// extern const uint8_t OGN_logo_jpg[] asm("_binary_OGN_logo_320x240_jpg_start"); +// extern const uint8_t OGN_logo_end[] asm("_binary_OGN_logo_320x240_jpg_end"); +// const int OGN_logo_size = OGN_logo_end-OGN_logo_jpg; +// #else +extern const uint8_t OGN_logo_jpg[] asm("_binary_OGN_logo_240x240_jpg_start"); +extern const uint8_t OGN_logo_end[] asm("_binary_OGN_logo_240x240_jpg_end"); +const int OGN_logo_size = OGN_logo_end-OGN_logo_jpg; +// #endif + +// extern const uint8_t Club_logo_jpg[] asm("_binary_AP_logo_240x240_jpg_start"); +// extern const uint8_t Club_logo_end[] asm("_binary_AP_logo_240x240_jpg_end"); +// const int Club_logo_size = Club_logo_end-Club_logo_jpg; + +#ifdef FOR_CKL +extern const uint8_t Club_logo_jpg[] asm("_binary_CKL_logo_240x240_jpg_start"); +extern const uint8_t Club_logo_end[] asm("_binary_CKL_logo_240x240_jpg_end"); +const int Club_logo_size = Club_logo_end-Club_logo_jpg; +#endif + +// uint8_t LCD_Backlight = 8*16; + +static void LCD_UpdateTime(uint32_t Time, GPS_Position *GPS, bool Redraw=0) +{ static char Msg[2][10] = { { 0 }, { 0 } }; + static uint16_t Back[2] = { 0, 0 }; + static bool Idx = 0; + Back[Idx] = RGB565_LIGHTRED; + if(GPS && GPS->isTimeValid()) + { if(GPS->isDateValid()) { Time=GPS->getUnixTime(); Back[Idx]=RGB565_GREEN; } + else { Time=GPS->getDayTime(); Back[Idx]=RGB565_GREENYELLOW; } + if(GPS->FracSec>=50) Time++; } + uint8_t Len=Format_HHcMMcSS(Msg[Idx], Time); Msg[Idx][Len]=0; + bool Redo = Redraw || Back[Idx]!=Back[Idx^1]; + if(Redo) LCD_DrawString(Msg[Idx], LCD_WIDTH-LCD_StringWidth(Msg[Idx])-4, LCD_HEIGHT-LCD_FontHeight(), RGB565_BLACK, Back[Idx]); + else LCD_UpdateString(Msg[Idx], Msg[Idx^1], LCD_WIDTH-LCD_StringWidth(Msg[Idx])-4, LCD_HEIGHT-LCD_FontHeight(), RGB565_BLACK, Back[Idx]); + Idx^=1; } + +static LCD_BattSymb BattSymb; + +static uint8_t BattCapacity(uint16_t mVolt) +{ if(mVolt>=4100) return 100; // 4.1V or above => full capacity + if(mVolt<=3600) return 0; // 3.6V or below => zero capacity + return (mVolt-3600+2)/5; } // linear dependence (simplified) + +static void LCD_UpdateBattery(bool Redraw=0) +{ // static char Volt[2][8] = { { 0 }, { 0 } }; + // static uint16_t Back[2] = { 0, 0 }; + static uint16_t BattCol[2] = { 0, 0 }; + static bool Idx = 0; + uint16_t mVolt = (BatteryVoltage+128)>>8; + // uint16_t Voltage = (mVolt+5)/10; // [0.01V] + uint8_t Capacity = BattCapacity(mVolt); // [mv] => [%] +#ifdef WITH_AXP + static char Curr[2][8] = { { 0 }, { 0 } }; + uint16_t InpCurr=AXP.readBatteryInpCurrent(); // [mA] + uint16_t OutCurr=AXP.readBatteryOutCurrent(); // [mA] + int16_t Current = InpCurr-OutCurr; + uint8_t Charging = Current>0; +#endif +/* + Back[Idx] = RGB565_LIGHTRED; + if(Voltage>=350) Back[Idx] = RGB565_YELLOW; + if(Voltage>=365) Back[Idx] = RGB565_GREENYELLOW; + if(Voltage>=375) Back[Idx] = RGB565_GREEN; + if(Voltage>=410) Back[Idx] = RGB565_CYAN; + if(Voltage>=420) Back[Idx] = RGB565_LIGHTBLUE; + if(Voltage>=425) Back[Idx] = RGB565_MAGENTA; + Format_UnsDec(Volt[Idx], Voltage, 3, 2); Volt[Idx][4]='V'; Volt[Idx][5]=0; + bool Redo = Redraw || Back[Idx]!=Back[Idx^1]; + if(Redo) LCD_DrawString(Volt[Idx], 4, LCD_HEIGHT-LCD_FontHeight(), RGB565_BLACK, Back[Idx]); + else LCD_UpdateString(Volt[Idx], Volt[Idx^1], 4, LCD_HEIGHT-LCD_FontHeight(), RGB565_BLACK, Back[Idx]); +*/ + uint8_t BattLev=(Capacity+10)/20; +#ifdef WITH_AXP + static uint8_t DispLev = 0; + if(Charging) { DispLev++; if(DispLev>5) DispLev = BattLev?BattLev-1:0; } + else { DispLev = BattLev; } +#else + uint8_t &DispLev = BattLev; +#endif + + // static uint8_t Level = 0; + const uint16_t LevelCol[6] = { RGB565_RED, RGB565_RED, RGB565_ORANGE, RGB565_YELLOW, RGB565_GREENYELLOW, RGB565_GREEN }; + // const uint16_t LevelCol[6] = { RGB565_RED, RGB565_RED, RGB565_DARKORANGE, RGB565_DARKYELLOW, RGB565_DARKGREENYELLOW, RGB565_DARKGREEN }; + BattSymb.setLevel(DispLev); +#ifdef PRINT_DEBUG + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "LCD_UpdateBattery() "); + Format_Hex(CONS_UART_Write, BattSymb.CellMap); + CONS_UART_Write('/'); + Format_Hex(CONS_UART_Write, BattSymb.Flags); + Format_String(CONS_UART_Write, "\n"); + xSemaphoreGive(CONS_Mutex); +#endif + BattCol[Idx]=LevelCol[BattLev]; + if(Redraw || BattCol[Idx]!=BattCol[Idx^1]) + { BattSymb.Xpos=0; BattSymb.Ypos=LCD_HEIGHT-BattSymb.Width; + BattSymb.FrameCol=RGB565_DARKGREY; BattSymb.FillCol=RGB565_LIGHTGREY; BattSymb.CellCol=BattCol[Idx]; + if(BattSymb.CellCol==RGB565_RED) BattSymb.FrameCol=RGB565_RED; + BattSymb.Draw(); } + else + BattSymb.Update(); + // Level+=1; if(Level>5) Level=0; + +#ifndef FOR_CKL +#ifdef WITH_AXP + strcpy(Curr[Idx], " mA "); + Format_SignDec(Curr[Idx], Current, 3); + bool Redo = Redraw || Curr[Idx][0]!=Curr[Idx^1][0]; + if(Redo) + LCD_DrawString(Curr[Idx], 0, LCD_HEIGHT-LCD_FontHeight()-LCD_FontHeight(tft_Dejavu18), RGB565_BLACK, RGB565_WHITE, tft_Dejavu18); + else + LCD_UpdateString(Curr[Idx], Curr[Idx^1], 0, LCD_HEIGHT-LCD_FontHeight()-LCD_FontHeight(tft_Dejavu18), RGB565_BLACK, RGB565_WHITE, tft_Dejavu18); +#endif +#endif + Idx^=1; } + +static void LCD_UpdateRX(bool Redraw=0) +{ static char Count[2][12] = { { 0 }, { 0 } }; + static char Noise[2][12] = { { 0 }, { 0 } }; + static bool Idx = 0; + uint8_t Len=0; + Len+=Format_String(Count[Idx]+Len, " Rx:"); // + Len+=Format_UnsDec(Count[Idx]+Len, RX_OGN_Count64); // received packet/min + Len+=Format_String(Count[Idx]+Len, "/min"); + Count[Idx][Len]=0; + if(Redraw) LCD_DrawString(Count[Idx], LCD_WIDTH-LCD_StringWidth(Count[Idx], tft_Dejavu18), 4, RGB565_BLACK, RGB565_WHITE, tft_Dejavu18); + else LCD_UpdateString(Count[Idx], Noise[Idx^1], LCD_WIDTH-LCD_StringWidth(Count[Idx], tft_Dejavu18), 4, RGB565_BLACK, RGB565_WHITE, tft_Dejavu18); + Len=0; + Len+=Format_String(Noise[Idx]+Len, " "); + Len+=Format_SignDec(Noise[Idx]+Len, -5*TRX.averRSSI, 4, 1); // noise level seen by the receiver + Len+=Format_String(Noise[Idx]+Len, "dBm"); + Noise[Idx][Len]=0; + if(Redraw) LCD_DrawString(Noise[Idx], LCD_WIDTH-LCD_StringWidth(Noise[Idx], tft_Dejavu18), 4+LCD_FontHeight(tft_Dejavu18), RGB565_BLACK, RGB565_WHITE, tft_Dejavu18); + else LCD_UpdateString(Noise[Idx], Noise[Idx^1], LCD_WIDTH-LCD_StringWidth(Noise[Idx], tft_Dejavu18), 4+LCD_FontHeight(tft_Dejavu18), RGB565_BLACK, RGB565_WHITE, tft_Dejavu18); + Idx^=1; } + +static void LCD_UpdateGPS(GPS_Position *GPS, bool Redraw=0) +{ static char Sat[2][8] = { { 0 }, { 0 } }; + static uint16_t Back[2] = { 0, 0 }; + // static char SNR[2][8] = { { 0 }, { 0 } }; + static char Alt[2][8] = { { 0 }, { 0 } }; + static bool Idx = 0; + Back[Idx] = RGB565_LIGHTRED; + if(GPS) + { uint16_t Sats = 0; + if(GPS->isValid()) Sats = GPS->Satellites; + if(Sats>=5) Back[Idx] = RGB565_GREEN; + else if(Sats>=4) Back[Idx] = RGB565_GREENYELLOW; + else if(Sats>=3) Back[Idx] = RGB565_YELLOW; + if(GPS->Sec&3) { Format_UnsDec(Sat[Idx], Sats, 2); Format_String(Sat[Idx]+2, "sat"); } + else { Format_UnsDec(Sat[Idx], (GPS_SatSNR+2)/4, 2); Format_String(Sat[Idx]+2, "dB "); } + Sat[Idx][5]=0; } + else // no data from GPS ? + { Format_String(Sat[Idx], "GPS ?"); Sat[Idx][5]=0; } + bool Redo = Redraw || Back[Idx]!=Back[Idx^1]; + if(Redo) LCD_DrawString(Sat[Idx], 0, 0, RGB565_BLACK, Back[Idx]); + else LCD_UpdateString(Sat[Idx], Sat[Idx^1], 0, 0, RGB565_BLACK, Back[Idx]); + if(GPS && GPS->isValid()) + { int32_t Altitude=GPS->Altitude; if(Altitude<0) Altitude=0; Altitude=(Altitude+5)/10; + uint8_t Len=Format_UnsDec(Alt[Idx], (uint32_t)Altitude); + Len+=Format_String(Alt[Idx]+Len, "m "); + Alt[Idx][Len]=0; +#ifdef FOR_CKL + if(Redraw) LCD_DrawString(Alt[Idx], LCD_WIDTH-6*16, 0, RGB565_BLACK, RGB565_WHITE); + else LCD_UpdateString(Alt[Idx], Alt[Idx^1], LCD_WIDTH-6*16, 0, RGB565_BLACK, RGB565_WHITE); +#else + if(Redraw) LCD_DrawString(Alt[Idx], 0, LCD_FontHeight(), RGB565_BLACK, RGB565_WHITE); + else LCD_UpdateString(Alt[Idx], Alt[Idx^1], 0, LCD_FontHeight(), RGB565_BLACK, RGB565_WHITE); +#endif + } + else + { } + Idx^=1; } + +static void LCD_UpdatePosition(GPS_Position *GPS, bool Redraw=0) +{ static bool Idx = 0; + int PosY = 0; + + static char Lock[2][20] = { { 0 }, { 0 } }; + if(GPS) + { strcpy(Lock[Idx], "0/00sat/00dB/00.0 "); + Lock[Idx][0] = '0'+GPS->FixQuality; + Format_UnsDec(Lock[Idx]+2, GPS->Satellites, 2); + Format_UnsDec(Lock[Idx]+8, (GPS_SatSNR+2)/4, 2); + Format_UnsDec(Lock[Idx]+13, GPS->HDOP, 3, 1); } + else + { strcpy(Lock[Idx], "No data from GPS "); } + if(Redraw) LCD_DrawString(Lock[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else LCD_UpdateString(Lock[Idx], Lock[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + static char Time[2][18] = { { 0 }, { 0 } }; + static char Lat [2][18] = { { 0 }, { 0 } }; + static char Lon [2][18] = { { 0 }, { 0 } }; + static char Alt [2][18] = { { 0 }, { 0 } }; + static char Vrt [2][18] = { { 0 }, { 0 } }; + static char Spd [2][18] = { { 0 }, { 0 } }; + static char Trk [2][18] = { { 0 }, { 0 } }; + static char Trn [2][18] = { { 0 }, { 0 } }; + strcpy(Time[Idx], "00.00.0000 000000"); + strcpy(Lat[Idx], "Lat: __._____ "); + strcpy(Lon[Idx], "Lon: ___._____ "); + strcpy(Alt[Idx], "Alt: ____._m "); + strcpy(Vrt[Idx], "Vrt: __._ m/s "); + strcpy(Spd[Idx], "Spd: __._ m/s "); + strcpy(Trk[Idx], "Trk: ___._ deg "); + strcpy(Trn[Idx], "Trn: __._ deg/s "); + if(GPS) + { if(GPS->isTimeValid()) + { Format_UnsDec (Time[Idx]+11, (uint16_t)GPS->Hour, 2, 0); + Format_UnsDec (Time[Idx]+13, (uint16_t)GPS->Min, 2, 0); + Format_UnsDec (Time[Idx]+15, (uint16_t)GPS->Sec, 2, 0); } + if(GPS->isDateValid()) + { Format_UnsDec (Time[Idx] , (uint16_t)GPS->Day, 2, 0); + Format_UnsDec (Time[Idx]+ 3, (uint16_t)GPS->Month, 2, 0); + Format_UnsDec (Time[Idx]+ 6, (uint16_t)GPS->Year , 4, 0); } + Format_SignDec(Lat[Idx]+6, GPS->Latitude /6, 7, 5); + Format_SignDec(Lon[Idx]+5, GPS->Longitude/6, 8, 5); + Format_SignDec(Alt[Idx]+6, GPS->Altitude, 5, 1); + Format_UnsDec (Spd[Idx]+5, GPS->Speed, 3, 1); + Format_SignDec(Vrt[Idx]+5, GPS->ClimbRate, 3, 1); + Format_UnsDec (Trk[Idx]+5, GPS->Heading, 4, 1); + Format_SignDec(Trn[Idx]+5, GPS->TurnRate, 3, 1); } + if(Redraw) + LCD_DrawString(Time[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Time[Idx], Time[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw || Lat[Idx][6]!=Lat[Idx^1][6]) + LCD_DrawString(Lat[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Lat[Idx], Lat[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw || Lon[Idx][5]!=Lon[Idx^1][5]) + LCD_DrawString(Lon[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Lon[Idx], Lon[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw || Alt[Idx][6]!=Alt[Idx^1][6]) + LCD_DrawString(Alt[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Alt[Idx], Alt[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw || Vrt[Idx][5]!=Vrt[Idx^1][5]) + LCD_DrawString(Vrt[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Vrt[Idx], Vrt[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw) + LCD_DrawString(Spd[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Spd[Idx], Spd[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw) + LCD_DrawString(Trk[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Trk[Idx], Trk[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + if(Redraw || Trn[Idx][5]!=Trn[Idx^1][5]) + LCD_DrawString(Trn[Idx], 4, PosY, RGB565_BLACK, RGB565_WHITE); + else + LCD_UpdateString(Trn[Idx], Trn[Idx^1], 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + Idx^=1; } + +static void LCD_UpdateSys(bool Redraw=0) +{ if(!Redraw) return; + int PosY = 2; + char Line[24]; + uint8_t Len=0; + Len+=Format_String(Line+Len, "GPS "); +#ifdef WITH_GPS_UBX + Len+=Format_String(Line+Len, "UBX "); +#endif +#ifdef WITH_GPS_MTK + Len+=Format_String(Line+Len, "MTK "); +#endif +#ifdef WITH_GPS_SRF + Len+=Format_String(Line+Len, "SRF "); +#endif + Len+=Format_UnsDec(Line+Len, GPS_getBaudRate(), 1); + Len+=Format_String(Line+Len, "bps"); + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + Len=0; +#ifdef WITH_RFM69 + Len+=Format_String(Line+Len, "RFM69 v"); // Type of RF chip used + if(Parameters.isTxTypeHW()) Line[Len++]='H'; + Line[Len++]='W'; +#endif +#ifdef WITH_RFM95 + Len+=Format_String(Line+Len, "RFM95 v"); +#endif +#ifdef WITH_SX1272 + Len+=Format_String(Line+Len, "SX1272 v"); +#endif + Len+=Format_Hex(Line+Len, TRX.chipVer); + // Line[Len++]=' '; + // Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); + // Line[Len++]=0xB0; + // Line[Len++]='C'; + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + Len=0; +#ifdef WITH_BMP180 + Len+=Format_String(Line+Len, "BMP180 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif +#ifdef WITH_BMP280 + Len+=Format_String(Line+Len, "BMP280 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif +#ifdef WITH_BME280 + Len+=Format_String(Line+Len, "BME280 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif +#ifdef WITH_MS5607 + Len+=Format_String(Line+Len, "MS5607 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + Len=0; +#ifdef WITH_ST7789 + Len+=Format_String(Line+Len, "ST7789 240x240"); +#endif +#ifdef WITH_ILI9341 + Len+=Format_String(Line+Len, "ILI9341 320x240"); +#endif + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + +#ifdef WITH_SPIFFS + Len=0; + Len+=Format_String(Line+Len, "SPIFFS "); + size_t Total, Used; + if(SPIFFS_Info(Total, Used)==0) // get the SPIFFS usage summary + { Len+=Format_UnsDec(Line+Len, (Total-Used)/1024); + Len+=Format_String(Line+Len, "/"); + Len+=Format_UnsDec(Line+Len, Total/1024); + Len+=Format_String(Line+Len, "kB"); } + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); +#endif + +#ifdef WITH_SD + Len=0; + Len+=Format_String(Line+Len, "SD "); + if(SD_isMounted()) + { Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectors()); + Line[Len++]='x'; + Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectorSize()*5/512, 2, 1); + Len+=Format_String(Line+Len, "KB"); } + else + { Len+=Format_String(Line+Len, "none"); } + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); +#endif + + +} + +static void LCD_UpdateRF(bool Redraw=0) +{ static bool Idx = 0; + if(!Redraw) return; + + int PosY = 2; + char Line[24]; + uint8_t Len=0; +#ifdef WITH_RFM69 + Len+=Format_String(Line+Len, "RFM69"); // Type of RF chip used + if(Parameters.isTxTypeHW()) Line[Len++]='H'; + Line[Len++]='W'; +#endif +#ifdef WITH_RFM95 + Len+=Format_String(Line+Len, "RFM95"); +#endif +#ifdef WITH_SX1272 + Len+=Format_String(Line+Len, "SX1272"); +#endif + Line[Len++]=':'; Line[Len++]=' '; + Len+=Format_SignDec(Line+Len, (int16_t)Parameters.getTxPower()); // Tx power + Len+=Format_String(Line+Len, "dBm"); + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + Len=0; + Len+=Format_UnsDec(Line+Len, (uint16_t)(RF_FreqPlan.getCenterFreq()/100000), 3, 1); // center frequency + Len+=Format_String(Line+Len, "MHz"); + Line[Len++]=' '; + Len+=Format_String(Line+Len, RF_FreqPlan.getPlanName()); // name of the frequency plan + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + Len=0; + Len+=Format_SignDec(Line+Len, (int32_t)Parameters.RFchipFreqCorr, 2, 1); // frequency correction + Len+=Format_String(Line+Len, "ppm"); + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + + Idx^=1; } + +static void LCD_UpdatePower(bool Redraw=0) +{ static bool Idx = 0; + + static char USB[2][20] = { { 0 }, { 0 } }; + + Idx^=1; } + +static void LCD_UpdateParameters(bool Redraw=0) +{ if(!Redraw) return; + char Line[32]; + int PosY = 2; + uint8_t Len=Format_String(Line, "ID="); + Line[Len++]=HexDigit(Parameters.AcftType); Line[Len++]=':'; + Line[Len++]=HexDigit(Parameters.AddrType); Line[Len++]=':'; + Len+=Format_Hex(Line+Len, Parameters.Address, 6); + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); + + for(uint8_t Idx=0; Idx(LCD_HEIGHT-2*LCD_FontHeight())) break; + const char *Value = Parameters.InfoParmValue(Idx); if(Value[0]==0) continue; + uint8_t Len=Format_String(Line, OGN_Packet::InfoParmName(Idx)); + Line[Len++]='='; + Len+=Format_String(Line+Len, Value); + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); } + +} + +static void LCD_UpdateRelayList(bool Redraw=0) +{ static uint8_t PrevLines=0; + if(Redraw) PrevLines=0; + char Line[24]; + int PosY = 2; + uint8_t Lines=0; + for( uint8_t Idx=0; Idx(LCD_HEIGHT-2*LCD_FontHeight())) break; + OGN_RxPacket *Packet = RelayQueue.Packet+Idx; if(Packet->Rank==0) continue; + uint8_t Len=0; + Line[Len++]='0'+Packet->Packet.Header.AddrType; + Line[Len++]=':'; + Len+=Format_Hex(Line+Len, Packet->Packet.Header.Address, 6); + Line[Len++]=' '; + Len+=Format_Hex(Line+Len, Packet->Rank); + Line[Len++]=' '; + Line[Len++]=':'; + Len+=Format_UnsDec(Line+Len, Packet->Packet.Position.Time, 2); + Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, RGB565_WHITE); + PosY+=LCD_FontHeight(); Lines++; } + for(uint8_t Line=Lines; Line(LCD_HEIGHT-2*LCD_FontHeight())) break; + LCD_DrawBox(0, PosY, LCD_WIDTH, LCD_FontHeight(), RGB565_WHITE); + PosY+=LCD_FontHeight(); } + PrevLines=Lines; } + +static void LCD_UpdateLookList(bool Redraw=0) +{ static uint8_t PrevLines=0; + if(Redraw) PrevLines=0; + char Line[24]; + int PosY = 2; + uint8_t Lines=0; + for( uint8_t Idx=0; Idx(LCD_HEIGHT-2*LCD_FontHeight())) break; + const LookOut_Target *Tgt = Look.Target+Idx; if(!Tgt->Alloc) continue; + uint16_t Bkg = RGB565_GREEN; + uint8_t Len=0; + Len+=Format_Hex(Line+Len, Tgt->ID, 7); + Line[Len++]=' '; + if(Tgt->DistMargin) continue; + uint8_t Warn = Tgt->WarnLevel; + if(Warn>=3) Bkg = RGB565_LIGHTRED; + else if(Warn==2) Bkg = RGB565_ORANGE; + else if(Warn==1) Bkg = RGB565_YELLOW; + Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin>>1, 2); + Line[Len++]='s'; Line[Len++]=' '; + // Len+=Format_UnsDec(Line+Len, ((Tgt->MissDist>>1)+50)/100, 2, 1); + Len+=Format_UnsDec(Line+Len, ((Tgt->HorDist>>1)+50)/100, 2, 1); + Line[Len++]='k'; Line[Len++]='m'; Line[Len++]=' '; Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, Bkg); + PosY+=LCD_FontHeight(); Lines++; } + for( uint8_t Idx=0; Idx(LCD_HEIGHT-2*LCD_FontHeight())) break; + const LookOut_Target *Tgt = Look.Target+Idx; if(!Tgt->Alloc) continue; + uint16_t Bkg = RGB565_GREEN; + uint8_t Len=0; + Len+=Format_Hex(Line+Len, Tgt->ID, 7); + Line[Len++]=' '; + if(Tgt->DistMargin==0) continue; + Line[Len++]=' '; Line[Len++]=' '; Line[Len++]=' '; Line[Len++]=' '; Line[Len++]=' '; + Len+=Format_UnsDec(Line+Len, ((Tgt->HorDist>>1)+50)/100, 2, 1); + Line[Len++]='k'; Line[Len++]='m'; Line[Len++]=' '; Line[Len]=0; + LCD_DrawString(Line, 4, PosY, RGB565_BLACK, Bkg); + PosY+=LCD_FontHeight(); Lines++; } + for(uint8_t Line=Lines; Line(LCD_HEIGHT-2*LCD_FontHeight())) break; + LCD_DrawBox(0, PosY, LCD_WIDTH, LCD_FontHeight(), RGB565_WHITE); + PosY+=LCD_FontHeight(); } + PrevLines=Lines; } + +static void LCD_DrawID(void) +{ const uint8_t *Font = tft_Dejavu18; + char ID[16]; + uint8_t Len=0; + ID[Len++]=HexDigit(Parameters.AcftType); ID[Len++]=':'; + ID[Len++]=HexDigit(Parameters.AddrType); ID[Len++]=':'; + Len+=Format_Hex(ID+Len, Parameters.Address, 6); + ID[Len]=0; + int PosX = LCD_WIDTH -LCD_StringWidth(ID, Font) -4; + int PosY = LCD_HEIGHT -LCD_FontHeight(Font) -LCD_FontHeight(); + LCD_DrawTranspString(ID, PosX, PosY, RGB565_BLUE, Font); + +} + +// ------------------------------------------------------------------------------------------------------------------------ + +void LCD_LogoPage_Draw(void) // Logo page: initial background draw +{ if(LCD_WIDTH!=240) LCD_ClearDisplay(RGB565_WHITE); +#ifdef FOR_CKL + LCD_DrawJPEG(Club_logo_jpg, Club_logo_size, (LCD_WIDTH-240)/2, 0); +#else +// #ifdef WITH_M5_JACEK +// LCD_DrawJPEG( OGN_logo_jpg, OGN_logo_size, (LCD_WIDTH-320)/2, 0); +// #else + LCD_DrawJPEG( OGN_logo_jpg, OGN_logo_size, (LCD_WIDTH-240)/2, 0); +// #endif + LCD_DrawID(); +#endif +} + +void LCD_LogoPage_Draw(uint32_t Time, GPS_Position *GPS) // Logo page: initial draw with Time/GPS data +{ LCD_LogoPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); +#ifndef FOR_CKL + LCD_UpdateRX(1); +#endif + LCD_UpdateGPS(GPS, 1); } + +void LCD_LogoPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) // Logo page: update the data in the corners +{ if(TimeChange) + { LCD_UpdateTime(Time, GPS); + LCD_UpdateBattery(); +#ifndef FOR_CKL + LCD_UpdateRX(); +#endif + } + if(GPSchange) { LCD_UpdateGPS(GPS); } +} + +void LCD_GPSpage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_GPSpage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_GPSpage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); + LCD_UpdatePosition(GPS, 1); } + +void LCD_GPSpage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } + if(GPSchange) LCD_UpdatePosition(GPS); } + + +void LCD_RFpage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_RFpage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_RFpage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); + LCD_UpdateRF(1); } + +void LCD_RFpage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } + if(TimeChange) LCD_UpdateRF(); } + + +void LCD_BattPage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_BattPage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_BattPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); +} + +void LCD_BattPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } +} + + +void LCD_ParmPage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_ParmPage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_ParmPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); + LCD_UpdateParameters(1); } + +void LCD_ParmPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } + LCD_UpdateParameters(); } + + +void LCD_BaroPage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_BaroPage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_BaroPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); +} + +void LCD_BaroPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } +} + + +void LCD_SysPage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_SysPage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_SysPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); + LCD_UpdateSys(1); } + +void LCD_SysPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } + if(TimeChange) LCD_UpdateSys(); } + + +void LCD_LookPage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_LookPage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_LookPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); + LCD_UpdateLookList(1); } + +void LCD_LookPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } + if(GPSchange) LCD_UpdateLookList(); } + + +void LCD_RelayPage_Draw(void) +{ LCD_ClearDisplay(RGB565_WHITE); } + +void LCD_RelayPage_Draw(uint32_t Time, GPS_Position *GPS) +{ LCD_RelayPage_Draw(); + LCD_UpdateTime(Time, GPS, 1); LCD_UpdateBattery(1); + LCD_UpdateRelayList(1); } + +void LCD_RelayPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange) +{ if(TimeChange) { LCD_UpdateTime(Time, GPS); LCD_UpdateBattery(); } + if(GPSchange) LCD_UpdateRelayList(); } + +// ------------------------------------------------------------------------------------------------------------------------ + +#endif + +// ======================================================================================================================== diff --git a/main/disp_lcd.h b/main/disp_lcd.h new file mode 100644 index 0000000..d1a7104 --- /dev/null +++ b/main/disp_lcd.h @@ -0,0 +1,41 @@ +#if defined(WITH_ST7789) || defined(WITH_ILI9341) + +#include "st7789.h" + +void LCD_LogoPage_Draw(void); +void LCD_LogoPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_LogoPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_GPSpage_Draw(void); +void LCD_GPSpage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_GPSpage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_RFpage_Draw(void); +void LCD_RFpage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_RFpage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_BattPage_Draw(void); +void LCD_BattPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_BattPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_ParmPage_Draw(void); +void LCD_ParmPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_ParmPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_BaroPage_Draw(void); +void LCD_BaroPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_BaroPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_SysPage_Draw(void); +void LCD_SysPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_SysPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_LookPage_Draw(void); +void LCD_LookPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_LookPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +void LCD_RelayPage_Draw(void); +void LCD_RelayPage_Draw(uint32_t Time, GPS_Position *GPS); +void LCD_RelayPage_Update(uint32_t Time, GPS_Position *GPS, bool TimeChange, bool GPSchange); + +#endif diff --git a/main/disp_oled.cpp b/main/disp_oled.cpp new file mode 100644 index 0000000..10d4b28 --- /dev/null +++ b/main/disp_oled.cpp @@ -0,0 +1,635 @@ +#include + +#include +#include +#include +#include + +// #include "esp_system.h" +// #include "esp_sleep.h" + +#include "hal.h" + +#include "sens.h" +#include "rf.h" +#include "ctrl.h" +#include "proc.h" +// #include "log.h" + +#include "gps.h" +// #include "ubx.h" +// #include "timesync.h" +#include "format.h" + +static char Line[128]; + +// ======================================================================================================================== + +#ifdef WITH_OLED + +#include "disp_oled.h" + +int OLED_DisplayStatus(uint32_t Time, uint8_t LineIdx) +{ Format_String(Line , "OGN Tx/Rx "); + Format_HHMMSS(Line+10, Time); + OLED_PutLine(LineIdx++, Line); + Parameters.Print(Line); + OLED_PutLine(LineIdx++, Line); + return 0; } + +int OLED_DisplayPosition(GPS_Position *GPS=0, uint8_t LineIdx=2) +{ if(GPS && GPS->isValid()) + { Line[0]=' '; + Format_SignDec(Line+1, GPS->Latitude /60, 6, 4); Line[9]=' '; + Format_UnsDec (Line+10, GPS->Altitude /10, 5, 0); Line[15]='m'; + OLED_PutLine(LineIdx , Line); + Format_SignDec(Line, GPS->Longitude/60, 7, 4); + Format_SignDec(Line+10, GPS->ClimbRate, 4, 1); + OLED_PutLine(LineIdx+1, Line); + Format_UnsDec (Line , GPS->Speed, 4, 1); Format_String(Line+5, "m/s "); + Format_UnsDec (Line+10, GPS->Heading, 4, 1); Line[15]='^'; + OLED_PutLine(LineIdx+2, Line); + Format_String(Line, "0D/00sat DOP00.0"); + Line[0]+=GPS->FixMode; Format_UnsDec(Line+3, GPS->Satellites, 2); + Format_UnsDec(Line+12, (uint16_t)GPS->HDOP, 3, 1); + OLED_PutLine(LineIdx+3, Line); + } + else { OLED_PutLine(LineIdx, 0); OLED_PutLine(LineIdx+1, 0); OLED_PutLine(LineIdx+2, 0); OLED_PutLine(LineIdx+3, 0); } + if(GPS && GPS->isDateValid()) + { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; + Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; + Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; Line[9]=' '; } + else Format_String(Line, " "); + if(GPS && GPS->isTimeValid()) + { Format_UnsDec (Line+10, (uint16_t)GPS->Hour, 2, 0); + Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); + Format_UnsDec (Line+14, (uint16_t)GPS->Sec, 2, 0); + } else Line[10]=0; + OLED_PutLine(LineIdx+4, Line); + Line[0]=0; + if(GPS && GPS->hasBaro) + { Format_String(Line , "0000.0hPa 00000m"); + Format_UnsDec(Line , GPS->Pressure/40, 5, 1); + Format_UnsDec(Line+10, GPS->StdAltitude/10, 5, 0); } + OLED_PutLine(LineIdx+5, Line); + return 0; } +#endif + +// ======================================================================================================================== + +#ifdef WITH_U8G2_OLED + +void OLED_DrawLogo(u8g2_t *OLED) // draw logo and hardware options in software +{ + u8g2_DrawCircle(OLED, 96, 32, 30, U8G2_DRAW_ALL); + u8g2_DrawCircle(OLED, 96, 32, 34, U8G2_DRAW_UPPER_RIGHT); + u8g2_DrawCircle(OLED, 96, 32, 38, U8G2_DRAW_UPPER_RIGHT); + // u8g2_SetFont(OLED, u8g2_font_open_iconic_all_4x_t); + // u8g2_DrawGlyph(OLED, 64, 32, 0xF0); + u8g2_SetFont(OLED, u8g2_font_ncenB14_tr); + u8g2_DrawStr(OLED, 74, 31, "OGN"); + u8g2_SetFont(OLED, u8g2_font_8x13_tr); + u8g2_DrawStr(OLED, 69, 43, "Tracker"); + +#ifdef WITH_FollowMe + u8g2_DrawStr(OLED, 0, 16 ,"FollowMe"); +#endif +#ifdef WITH_TTGO + u8g2_DrawStr(OLED, 0, 16 ,"TTGO"); +#endif +#ifdef WITH_HELTEC + u8g2_DrawStr(OLED, 0, 16 ,"HELTEC"); +#endif +#if defined(WITH_TBEAM) || defined(WITH_TBEAM_V10) + u8g2_DrawStr(OLED, 0, 16 ,"T-BEAM"); +#endif + +#ifdef WITH_GPS_MTK + u8g2_DrawStr(OLED, 0, 28 ,"MTK GPS"); +#endif +#ifdef WITH_GPS_UBX + u8g2_DrawStr(OLED, 0, 28 ,"UBX GPS"); +#endif +#ifdef WITH_GPS_SRF + u8g2_DrawStr(OLED, 0, 28 ,"SRF GPS"); +#endif + +#ifdef WITH_RFM95 + u8g2_DrawStr(OLED, 0, 40 ,"RFM95"); +#endif +#ifdef WITH_RFM69 + u8g2_DrawStr(OLED, 0, 40 ,"RFM69"); +#endif + +#ifdef WITH_BMP180 + u8g2_DrawStr(OLED, 0, 52 ,"BMP180"); +#endif +#ifdef WITH_BMP280 + u8g2_DrawStr(OLED, 0, 52 ,"BMP280"); +#endif +#ifdef WITH_BME280 + u8g2_DrawStr(OLED, 0, 52 ,"BME280"); +#endif + +#ifdef WITH_BT_SPP + u8g2_DrawStr(OLED, 0, 64 ,"BT SPP"); +#endif +} + +void OLED_PutLine(u8g2_t *OLED, uint8_t LineIdx, const char *Line) +{ if(Line==0) return; +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "OLED_PutLine( ,"); + Format_UnsDec(CONS_UART_Write, (uint16_t)LineIdx); + CONS_UART_Write(','); + Format_String(CONS_UART_Write, Line); + Format_String(CONS_UART_Write, ")\n"); + xSemaphoreGive(CONS_Mutex); +#endif + // u8g2_SetFont(OLED, u8g2_font_5x8_tr); + u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); + u8g2_DrawStr(OLED, 0, (LineIdx+1)*8, Line); +} + +void OLED_DrawStatus(u8g2_t *OLED, uint32_t Time, uint8_t LineIdx=0) +{ Format_String(Line , "OGN Tx/Rx "); + Format_HHMMSS(Line+10, Time); Line[16]=0; + OLED_PutLine(OLED, LineIdx++, Line); + Parameters.Print(Line); Line[16]=0; + OLED_PutLine(OLED, LineIdx++, Line); } + +void OLED_DrawPosition(u8g2_t *OLED, GPS_Position *GPS=0, uint8_t LineIdx=2) +{ if(GPS && GPS->isValid()) + { Line[0]=' '; + Format_SignDec(Line+1, GPS->Latitude /60, 6, 4); Line[9]=' '; + Format_UnsDec (Line+10, GPS->Altitude /10, 5, 0); Line[15]='m'; + OLED_PutLine(OLED, LineIdx , Line); + Format_SignDec(Line, GPS->Longitude/60, 7, 4); + Format_SignDec(Line+10, GPS->ClimbRate, 4, 1); + OLED_PutLine(OLED, LineIdx+1, Line); + Format_UnsDec (Line , GPS->Speed, 4, 1); Format_String(Line+5, "m/s "); + Format_UnsDec (Line+10, GPS->Heading, 4, 1); Line[15]='^'; + OLED_PutLine(OLED, LineIdx+2, Line); + Format_String(Line, "0D/00sat DOP00.0"); + Line[0]+=GPS->FixMode; Format_UnsDec(Line+3, GPS->Satellites, 2); + Format_UnsDec(Line+12, (uint16_t)GPS->HDOP, 3, 1); + OLED_PutLine(OLED, LineIdx+3, Line); + } + // else { OLED_PutLine(OLED, LineIdx, 0); OLED_PutLine(OLED, LineIdx+1, 0); OLED_PutLine(LineIdx+2, 0); OLED_PutLine(LineIdx+3, 0); } + if(GPS && GPS->isDateValid()) + { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; + Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; + Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; Line[9]=' '; } + else Format_String(Line, " "); + if(GPS && GPS->isTimeValid()) + { Format_UnsDec (Line+10, (uint16_t)GPS->Hour, 2, 0); + Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); + Format_UnsDec (Line+14, (uint16_t)GPS->Sec, 2, 0); + } else Line[10]=0; + OLED_PutLine(OLED, LineIdx+4, Line); + Line[0]=0; + if(GPS && GPS->hasBaro) + { Format_String(Line , "0000.0hPa 00000m"); + Format_UnsDec(Line , GPS->Pressure/40, 5, 1); + Format_UnsDec(Line+10, GPS->StdAltitude/10, 5, 0); } + OLED_PutLine(OLED, LineIdx+5, Line); +} + +void OLED_DrawGPS(u8g2_t *OLED, GPS_Position *GPS) // GPS time, position, altitude +{ // u8g2_SetFont(OLED, u8g2_font_ncenB14_tr); + u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line + uint8_t Len=0; +/* + Len+=Format_String(Line+Len, "GPS "); + if(GPS && GPS->isValid()) + { Line[Len++]='0'+GPS->FixMode; Line[Len++]='D'; Line[Len++]='/'; + Len+=Format_UnsDec(Line+Len, GPS->Satellites, 1); + Len+=Format_String(Line+Len, "sat DOP"); + Len+=Format_UnsDec(Line+Len, (uint16_t)GPS->HDOP, 2, 1); } + else + { Len+=Format_String(Line+Len, "(no lock)"); } + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 12, Line); +*/ + if(GPS && GPS->isDateValid()) + { Format_UnsDec (Line , (uint16_t)GPS->Day, 2, 0); Line[2]='.'; + Format_UnsDec (Line+ 3, (uint16_t)GPS->Month, 2, 0); Line[5]='.'; + Format_UnsDec (Line+ 6, (uint16_t)GPS->Year , 2, 0); Line[8]=' '; + } else Format_String(Line, " . . "); + if(GPS && GPS->isTimeValid()) + { Format_UnsDec (Line+ 9, (uint16_t)GPS->Hour, 2, 0); Line[11]=':'; + Format_UnsDec (Line+12, (uint16_t)GPS->Min, 2, 0); Line[14]=':'; + Format_UnsDec (Line+15, (uint16_t)GPS->Sec, 2, 0); + } else Format_String(Line+9, " : : "); + Line[17]=0; + u8g2_DrawStr(OLED, 0, 24, Line); + + Len=0; + Len+=Format_String(Line+Len, "Lat: "); + if(GPS && GPS->isValid()) + { Len+=Format_SignDec(Line+Len, GPS->Latitude /6, 7, 5); + Line[Len++]=0xB0; } + else Len+=Format_String(Line+Len, "---.-----"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 36, Line); + Len=0; + Len+=Format_String(Line+Len, "Lon: "); + if(GPS && GPS->isValid()) + { Len+=Format_SignDec(Line+Len, GPS->Longitude /6, 8, 5); + Line[Len++]=0xB0; } + else Len+=Format_String(Line+Len, "----.-----"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 48, Line); + Len=0; + Len+=Format_String(Line+Len, "Alt: "); + if(GPS && GPS->isValid()) + { Len+=Format_SignDec(Line+Len, GPS->Altitude, 4, 1); + Line[Len++]='m'; } + else Len+=Format_String(Line+Len, "-----.-"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 60, Line); +} + +void OLED_DrawRF(u8g2_t *OLED) // RF +{ u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines. 12 pixels/line + uint8_t Len=0; +#ifdef WITH_RFM69 + Len+=Format_String(Line+Len, "RFM69"); // Type of RF chip used + if(Parameters.isTxTypeHW()) Line[Len++]='H'; + Line[Len++]='W'; +#endif +#ifdef WITH_RFM95 + Len+=Format_String(Line+Len, "RFM95"); +#endif +#ifdef WITH_SX1272 + Len+=Format_String(Line+Len, "SX1272"); +#endif + Line[Len++]=':'; + Len+=Format_SignDec(Line+Len, (int16_t)Parameters.getTxPower()); // Tx power + Len+=Format_String(Line+Len, "dBm"); + Line[Len++]=' '; + Len+=Format_SignDec(Line+Len, (int32_t)Parameters.RFchipFreqCorr, 2, 1); // frequency correction + Len+=Format_String(Line+Len, "ppm"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 24, Line); + Len=0; + Len+=Format_String(Line+Len, "Rx:"); // + Len+=Format_SignDec(Line+Len, -5*TRX.averRSSI, 2, 1); // noise level seen by the receiver + Len+=Format_String(Line+Len, "dBm "); + Len+=Format_UnsDec(Line+Len, RX_OGN_Count64); // received packet/min + Len+=Format_String(Line+Len, "/min"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 36, Line); + Len=0; + Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); // RF chip internal temperature (not calibrated) + // Line[Len++]=0xB0; + // Line[Len++]='C'; + Len+=Format_String(Line+Len, "\260C RxFIFO:"); + Len+=Format_UnsDec(Line+Len, RF_RxFIFO.Full()); // how many packets wait in the RX queue + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 48, Line); + // u8g2_DrawStr(OLED, 0, 48, RF_FreqPlan.getPlanName()); + Len=0; + Len+=Format_String(Line+Len, RF_FreqPlan.getPlanName()); // name of the frequency plan + Line[Len++]=' '; + Len+=Format_UnsDec(Line+Len, (uint16_t)(RF_FreqPlan.getCenterFreq()/100000), 3, 1); // center frequency + Len+=Format_String(Line+Len, "MHz"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 60, Line); +} + +void OLED_DrawRelay(u8g2_t *OLED, GPS_Position *GPS) +{ u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); + uint8_t Len=Format_String(Line, "Relay queue :"); + if(GPS && GPS->Sec>=0) Len+=Format_UnsDec(Line+Len, (uint16_t)(GPS->Sec), 2); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 24, Line); + uint8_t LineIdx=1; + for( uint8_t Idx=0; Idx *Packet = RelayQueue.Packet+Idx; if(Packet->Rank==0) continue; + uint8_t Len=0; + Line[Len++]='0'+Packet->Packet.Header.AddrType; + Line[Len++]=':'; + Len+=Format_Hex(Line+Len, Packet->Packet.Header.Address, 6); + Line[Len++]=' '; + Len+=Format_Hex(Line+Len, Packet->Rank); + Line[Len++]=' '; + Line[Len++]=':'; + Len+=Format_UnsDec(Line+Len, Packet->Packet.Position.Time, 2); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, (LineIdx+3)*8, Line); + LineIdx++; if(LineIdx>=8) break; + } +} + +void OLED_DrawLookout(u8g2_t *OLED, GPS_Position *GPS) +{ u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); + uint8_t Len=Format_String(Line, "Look "); + if(Look.WarnLevel) + { const LookOut_Target *Tgt = Look.Target+Look.WorstTgtIdx; + Len+=Format_Hex(Line+Len, Tgt->ID, 7); + Line[Len++]='/'; + Line[Len++]='0'+Tgt->WarnLevel; + Line[Len++]=' '; + Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin>>1); + Line[Len++]='s'; + } + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 24, Line); + uint8_t LineIdx=1; + for( uint8_t Idx=0; IdxAlloc) continue; + uint8_t Len=0; + Len+=Format_Hex(Line+Len, Tgt->ID, 7); + Line[Len++]=' '; + if(Tgt->DistMargin) + { Len+=Format_UnsDec(Line+Len, Tgt->HorDist>>1); + Line[Len++]='m'; } + else + { Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin>>1); + Line[Len++]='s'; + Line[Len++]=' '; + Len+=Format_UnsDec(Line+Len, Tgt->MissDist>>1); + Line[Len++]='m'; } + Line[Len]=0; + u8g2_DrawStr(OLED, 0, (LineIdx+3)*8, Line); + LineIdx++; if(LineIdx>=8) break; + } +} + +void OLED_DrawTrafWarn(u8g2_t *OLED, GPS_Position *GPS) +{ u8g2_SetFont(OLED, u8g2_font_9x15_tr); + if(Look.WarnLevel==0) return; + const LookOut_Target *Tgt = Look.Target+Look.WorstTgtIdx; + uint8_t Len=0; + Len+=Format_Hex(Line+Len, Tgt->ID, 7); + Line[Len++]='/'; + Line[Len++]='0'+Tgt->WarnLevel; + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 30, Line); + Len=0; + Len+=Format_UnsDec(Line+Len, Tgt->TimeMargin*5, 2, 1); + Line[Len++]='s'; + Line[Len++]=' '; + Len+=Format_UnsDec(Line+Len, Tgt->MissDist*5, 2, 1); + Line[Len++]='m'; + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 45, Line); + Len=0; + Len+=Format_UnsDec(Line+Len, Tgt->getRelHorSpeed()*5, 2, 1); + Line[Len++]='m'; + Line[Len++]='/'; + Line[Len++]='s'; + Line[Len++]=' '; + Len+=Format_UnsDec(Line+Len, Tgt->HorDist*5, 2, 1); + Line[Len++]='m'; + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 60, Line); +} + +void OLED_DrawBaro(u8g2_t *OLED, GPS_Position *GPS) +{ u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line + uint8_t Len=0; +#ifdef WITH_BMP180 + Len+=Format_String(Line+Len, "BMP180 "); +#endif +#ifdef WITH_BMP280 + Len+=Format_String(Line+Len, "BMP280 "); +#endif +#ifdef WITH_BME280 + Len+=Format_String(Line+Len, "BME280 "); +#endif +#ifdef WITH_MS5607 + Len+=Format_String(Line+Len, "MS5607 "); +#endif + if(GPS && GPS->hasBaro) + { Len+=Format_UnsDec(Line+Len, GPS->Pressure/4, 5, 2); + Len+=Format_String(Line+Len, "Pa "); } + else Len+=Format_String(Line+Len, "----.--Pa "); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 24, Line); + Len=0; + if(GPS && GPS->hasBaro) + { Len+=Format_SignDec(Line+Len, GPS->StdAltitude, 5, 1); + Len+=Format_String(Line+Len, "m "); + Len+=Format_SignDec(Line+Len, GPS->ClimbRate, 2, 1); + Len+=Format_String(Line+Len, "m/s "); } + else Len+=Format_String(Line+Len, "-----.-m --.-m/s "); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 36, Line); + Len=0; + if(GPS && GPS->hasBaro) + { Len+=Format_SignDec(Line+Len, GPS->Temperature, 2, 1); + Line[Len++]=0xB0; + Line[Len++]='C'; + Line[Len++]=' '; + Len+=Format_SignDec(Line+Len, GPS->Humidity, 2, 1); + Line[Len++]='%'; } + else Len+=Format_String(Line+Len, "---.- C --.-% "); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 48, Line); +} + +static uint8_t BattCapacity(uint16_t mVolt) +{ if(mVolt>=4100) return 100; + if(mVolt<=3600) return 0; + return (mVolt-3600+2)/5; } + +void OLED_DrawBattery(u8g2_t *OLED) +{ uint8_t Cap=BattCapacity(BatteryVoltage>>8); + // u8g2_SetFont(OLED, u8g2_font_battery19_tn); + // u8g2_DrawGlyph(OLED, 120, 60, '0'+(Cap+10)/20); + + // u8g2_SetFont(OLED, u8g2_font_6x10_tr); + // u8g2_DrawStr(OLED, 0, 24, Line); + + // u8g2_DrawStr(OLED, 0, 24, "Battery"); + + u8g2_SetFont(OLED, u8g2_font_9x15_tr); + + strcpy(Line, " %"); + if(Cap>=100) Format_UnsDec(Line, Cap, 3); + else if(Cap>=10) Format_UnsDec(Line+1, Cap, 2); + else Line[2]='0'+Cap; + u8g2_DrawStr (OLED, 16, 32, Line); + u8g2_DrawFrame(OLED, 12, 20, 42, 14); + u8g2_DrawBox (OLED, 8, 23, 4, 8); + + strcpy(Line, " . V"); + Format_UnsDec(Line, BatteryVoltage>>8, 4, 3); + u8g2_DrawStr(OLED, 0, 48, Line); + + strcpy(Line, " . mV/min "); + Format_SignDec(Line, (600*BatteryVoltageRate+128)>>8, 3, 1); + u8g2_DrawStr(OLED, 0, 60, Line); + +#ifdef WITH_BQ + // uint8_t Status = BQ.readStatus(); + // uint8_t State = (Status>>4)&0x03; + // const char *StateName[4] = { "Not charging", "Pre-charge", "Fast charge", "Full" } ; + // u8g2_SetFont(OLED, u8g2_font_amstrad_cpc_extended_8r); + // u8g2_SetFont(OLED, u8g2_font_6x10_tr); + // u8g2_DrawStr(OLED, 0, 50, StateName[State]); + // u8g2_DrawStr(OLED, 0, 60, Status&0x04?"Power-is-Good":"Power-is-not-Good"); + +/* print BQ registers for debug + u8g2_SetFont(OLED, u8g2_font_6x10_tr); + for(uint8_t Reg=0; Reg<=10; Reg++) + { uint8_t Val = BQ.readReg(Reg); + Format_Hex(Line+3*Reg, Val); Line[3*Reg+2]=' '; } + Line[33]=0; + u8g2_DrawStr(OLED, 0, 60, Line+15); + Line[15]=0; u8g2_DrawStr(OLED, 0, 50, Line); +*/ +#endif +} + +void OLED_DrawStatusBar(u8g2_t *OLED, GPS_Position *GPS) +{ static bool Odd=0; + uint8_t Cap=BattCapacity(BatteryVoltage>>8); + uint8_t BattLev = (Cap+10)/20; + uint8_t Charging = 0; +#ifdef WITH_BQ + uint8_t Status = BQ.readStatus(); + Charging = (Status>>4)&0x03; + static uint8_t DispLev = 0; + if(Charging==1 || Charging==2) { DispLev++; if(DispLev>5) DispLev = BattLev?BattLev-1:0; } + else { DispLev = BattLev; } +#else + uint8_t &DispLev = BattLev; +#endif + if(BattLev==0 && !Charging && Odd) // when battery is empty, then flash it at 0.5Hz + { } + else + { u8g2_SetFont(OLED, u8g2_font_battery19_tn); + u8g2_SetFontDirection(OLED, 3); + u8g2_DrawGlyph(OLED, 24, 10, '0'+DispLev); + u8g2_SetFontDirection(OLED, 0); } + Odd=!Odd; + + // u8g2_SetFont(OLED, u8g2_font_5x7_tr); + // u8g2_SetFont(OLED, u8g2_font_5x8_tr); + static uint8_t Sec=0; + u8g2_SetFont(OLED, u8g2_font_6x12_tr); + // strcpy(Line, "[ %] --sat --:--"); + strcpy(Line, "--sat --:--Z"); + if(GPS && GPS->isTimeValid()) + { Format_UnsDec (Line+6, (uint16_t)GPS->Hour, 2, 0); Line[8]=':'; + Format_UnsDec (Line+9, (uint16_t)GPS->Min, 2, 0); + } else Format_String(Line+6, "--:--"); + if(GPS) + { if(Sec) + { Format_UnsDec(Line, (uint16_t)GPS->Satellites, 2); memcpy(Line+2, "sat", 3); } + else + { Format_UnsDec(Line, (uint16_t)(GPS_SatSNR+2)/4, 2); memcpy(Line+2, "dB ", 3);} + } + else Format_String(Line, "--sat"); + u8g2_DrawStr(OLED, 40, 10, Line); + Sec++; if(Sec>=3) Sec=0; } + +void OLED_DrawSystem(u8g2_t *OLED) +{ + u8g2_SetFont(OLED, u8g2_font_7x13_tf); // 5 lines, 12 pixels/line + uint8_t Len=0; + Len+=Format_String(Line+Len, "GPS "); +#ifdef WITH_GPS_UBX + Len+=Format_String(Line+Len, "UBX "); +#endif +#ifdef WITH_GPS_MTK + Len+=Format_String(Line+Len, "MTK "); +#endif +#ifdef WITH_GPS_SRF + Len+=Format_String(Line+Len, "SRF "); +#endif + Len+=Format_UnsDec(Line+Len, GPS_getBaudRate(), 1); + Len+=Format_String(Line+Len, "bps"); + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 24, Line); + + Len=0; +#ifdef WITH_RFM69 + Len+=Format_String(Line+Len, "RFM69 v"); // Type of RF chip used + if(Parameters.isTxTypeHW()) Line[Len++]='H'; + Line[Len++]='W'; +#endif +#ifdef WITH_RFM95 + Len+=Format_String(Line+Len, "RFM95 v"); +#endif +#ifdef WITH_SX1272 + Len+=Format_String(Line+Len, "SX1272 v"); +#endif + Len+=Format_Hex(Line+Len, TRX.chipVer); + Line[Len++]=' '; + Len+=Format_SignDec(Line+Len, (int16_t)TRX.chipTemp); + Line[Len++]=0xB0; + Line[Len++]='C'; + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 36, Line); + + Len=0; +#ifdef WITH_BMP180 + Len+=Format_String(Line+Len, "BMP180 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif +#ifdef WITH_BMP280 + Len+=Format_String(Line+Len, "BMP280 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif +#ifdef WITH_BME280 + Len+=Format_String(Line+Len, "BME280 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif +#ifdef WITH_MS5607 + Len+=Format_String(Line+Len, "MS5607 0x"); + Len+=Format_Hex(Line+Len, Baro.ADDR); +#endif + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 48, Line); + +#ifdef WITH_SPIFFS + Len=0; + Len+=Format_String(Line+Len, "SPIFFS "); + size_t Total, Used; + if(SPIFFS_Info(Total, Used)==0) // get the SPIFFS usage summary + { Len+=Format_UnsDec(Line+Len, (Total-Used)/1024); + Len+=Format_String(Line+Len, "/"); + Len+=Format_UnsDec(Line+Len, Total/1024); + Len+=Format_String(Line+Len, "kB"); } + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 60, Line); +#endif +/* +#ifdef WITH_SD + Len=0; + Len+=Format_String(Line+Len, "SD "); + if(SD_isMounted()) + { Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectors()); + Line[Len++]='x'; + Len+=Format_UnsDec(Line+Len, (uint32_t)SD_getSectorSize()*5/512, 2, 1); + Len+=Format_String(Line+Len, "KB"); } + else + { Len+=Format_String(Line+Len, "none"); } + Line[Len]=0; + u8g2_DrawStr(OLED, 0, 60, Line); +#endif +*/ +} + +void OLED_DrawID(u8g2_t *OLED) +{ u8g2_SetFont(OLED, u8g2_font_9x15_tr); + strcpy(Line, "ID "); Parameters.Print(Line+3); Line[13]=0; + u8g2_DrawStr(OLED, 0, 28, Line); + // u8g2_SetFont(OLED, u8g2_font_10x20_tr); +#ifdef WITH_FollowMe + u8g2_SetFont(OLED, u8g2_font_7x13_tf); + u8g2_DrawStr(OLED, 15, 44, "FollowMe868"); + u8g2_DrawStr(OLED, 20, 56, "by AVIONIX"); +#endif + u8g2_SetFont(OLED, u8g2_font_5x8_tr); + u8g2_DrawStr(OLED, 96, 62, "v0.0.0"); +} + +#endif + +// ======================================================================================================================== diff --git a/main/disp_oled.h b/main/disp_oled.h new file mode 100644 index 0000000..8d4a632 --- /dev/null +++ b/main/disp_oled.h @@ -0,0 +1,25 @@ + +#ifdef WITH_OLED + +int OLED_DisplayStatus(uint32_t Time, uint8_t LineIdx=0); +int OLED_DisplayPosition(GPS_Position *GPS=0, uint8_t LineIdx=2); + +#endif + +#ifdef WITH_U8G2_OLED + +void OLED_DrawLogo(u8g2_t *OLED); +void OLED_DrawStatus(u8g2_t *OLED, uint32_t Time, uint8_t LineIdx=0); +void OLED_DrawPosition(u8g2_t *OLED, GPS_Position *GPS=0, uint8_t LineIdx=2); +void OLED_DrawGPS(u8g2_t *OLED, GPS_Position *GPS=0); +void OLED_DrawRF(u8g2_t *OLED); +void OLED_DrawRelay(u8g2_t *OLED, GPS_Position *GPS=0); +void OLED_DrawLookout(u8g2_t *OLED, GPS_Position *GPS=0); +void OLED_DrawTrafWarn(u8g2_t *OLED, GPS_Position *GPS=0); +void OLED_DrawBaro(u8g2_t *OLED, GPS_Position *GPS=0); +void OLED_DrawBattery(u8g2_t *OLED); +void OLED_DrawStatusBar(u8g2_t *OLED, GPS_Position *GPS); +void OLED_DrawSystem(u8g2_t *OLED); +void OLED_DrawID(u8g2_t *OLED); + +#endif diff --git a/main/format.cpp b/main/format.cpp index 46fe1a6..8b07c17 100644 --- a/main/format.cpp +++ b/main/format.cpp @@ -69,6 +69,22 @@ void Format_Hex( void (*Output)(char), uint32_t Word ) { Format_Hex(Output, (uint8_t)(Word>>24)); Format_Hex(Output, (uint8_t)(Word>>16)); Format_Hex(Output, (uint8_t)(Word>>8)); Format_Hex(Output, (uint8_t)Word); } +void Format_MAC( void (*Output)(char), uint8_t *MAC, uint8_t Len) +{ for(uint8_t Idx=0; Idx +#define WITH_AUTOCR + char HexDigit(uint8_t Val); void Format_Bytes ( void (*Output)(char), const uint8_t *Bytes, uint8_t Len); @@ -14,6 +16,7 @@ void Format_String( void (*Output)(char), const char *String, uint8_t MinLen, void Format_Hex( void (*Output)(char), uint8_t Byte ); void Format_Hex( void (*Output)(char), uint16_t Word ); void Format_Hex( void (*Output)(char), uint32_t Word ); +void Format_MAC( void (*Output)(char), uint8_t *MAC, uint8_t Len=6); void Format_UnsDec ( void (*Output)(char), uint16_t Value, uint8_t MinDigits=1, uint8_t DecPoint=0); void Format_SignDec( void (*Output)(char), int16_t Value, uint8_t MinDigits=1, uint8_t DecPoint=0); @@ -34,8 +37,19 @@ uint8_t Format_Hex( char *Output, uint8_t Byte ); uint8_t Format_Hex( char *Output, uint16_t Word ); uint8_t Format_Hex( char *Output, uint32_t Word ); uint8_t Format_Hex( char *Output, uint32_t Word, uint8_t Digits); +uint8_t Format_Hex( char *Output, uint64_t Word ); +// uint8_t Format_Hex( char *Output, uint64_t Word, uint8_t Digits); +template + uint8_t Format_Hex( char *Output, Type Word, uint8_t Digits) +{ for(uint8_t Idx=Digits; Idx>0; ) + { Output[--Idx]=HexDigit(Word&0x0F); + Word>>=4; } + return Digits; } + +uint8_t Format_HHcMMcSS(char *Out, uint32_t Time); uint8_t Format_HHMMSS(char *Out, uint32_t Time); +void Format_HHMMSS(void (*Output)(char), uint32_t Time); uint8_t Format_Latitude (char *Out, int32_t Lat); // [1/600000deg] => DDMM.MMMMs uint8_t Format_Longitude(char *Out, int32_t Lon); // [1/600000deg] => DDDMM.MMMMs @@ -49,60 +63,60 @@ int16_t Read_Dec3(const char *Inp); // convert three digit decimal n int16_t Read_Dec4(const char *Inp); // convert three digit decimal number into an integer template - int8_t Read_Hex(Type &Int, const char *Inp) // convert variable number of digits hexadecimal number into an integer - { Int=0; int8_t Len=0; - if(Inp==0) return 0; - for( ; ; ) + int8_t Read_Hex(Type &Int, const char *Inp, uint8_t MaxDig=0) // convert variable number of digits hexadecimal number into an integer + { if(Inp==0) return 0; + if(MaxDig==0) MaxDig=2*sizeof(Type); + Int=0; int8_t Len=0; + for( ; MaxDig; MaxDig--) { int8_t Dig=Read_Hex1(Inp[Len]); if(Dig<0) break; Int = (Int<<4) + Dig; Len++; } return Len; } // return number of characters read - template - int8_t Read_UnsDec(Type &Int, const char *Inp) // convert variable number of digits unsigned decimal number into an integer - { Int=0; int8_t Len=0; - if(Inp==0) return 0; - for( ; ; ) - { int8_t Dig=Read_Dec1(Inp[Len]); if(Dig<0) break; - Int = 10*Int + Dig; Len++; } - return Len; } // return number of characters read +template + int8_t Read_UnsDec(Type &Int, const char *Inp) // convert variable number of digits unsigned decimal number into an integer + { Int=0; int8_t Len=0; + if(Inp==0) return 0; + for( ; ; ) + { int8_t Dig=Read_Dec1(Inp[Len]); if(Dig<0) break; + Int = 10*Int + Dig; Len++; } + return Len; } // return number of characters read - template - int8_t Read_SignDec(Type &Int, const char *Inp) // convert signed decimal number into in16_t or int32_t - { Int=0; int8_t Len=0; - if(Inp==0) return 0; - char Sign=Inp[0]; - if((Sign=='+')||(Sign=='-')) Len++; - Len+=Read_UnsDec(Int, Inp+Len); if(Sign=='-') Int=(-Int); - return Len; } // return number of characters read +template + int8_t Read_SignDec(Type &Int, const char *Inp) // convert signed decimal number into in16_t or int32_t + { Int=0; int8_t Len=0; + if(Inp==0) return 0; + char Sign=Inp[0]; + if((Sign=='+')||(Sign=='-')) Len++; + Len+=Read_UnsDec(Int, Inp+Len); if(Sign=='-') Int=(-Int); + return Len; } // return number of characters read - template - int8_t Read_Int(Type &Value, const char *Inp) - { Value=0; int8_t Len=0; - if(Inp==0) return 0; - char Sign=Inp[0]; int8_t Dig; - if((Sign=='+')||(Sign=='-')) Len++; - if((Inp[Len]=='0')&&(Inp[Len+1]=='x')) - { Len+=2; Dig=Read_Hex(Value, Inp+Len); } - else - { Dig=Read_UnsDec(Value, Inp+Len); } - if(Dig<=0) return Dig; - Len+=Dig; - if(Sign=='-') Value=(-Value); return Len; } - - template - int8_t Read_Float1(Type &Value, const char *Inp) // read floating point, take just one digit after decimal point - { Value=0; int8_t Len=0; - if(Inp==0) return 0; - char Sign=Inp[0]; int8_t Dig; - if((Sign=='+')||(Sign=='-')) Len++; - Len+=Read_UnsDec(Value, Inp+Len); Value*=10; - if(Inp[Len]!='.') goto Ret; - Len++; - Dig=Read_Dec1(Inp[Len]); if(Dig<0) goto Ret; - Value+=Dig; Len++; - Dig=Read_Dec1(Inp[Len]); if(Dig>=5) Value++; - Ret: if(Sign=='-') Value=(-Value); return Len; } +template + int8_t Read_Int(Type &Value, const char *Inp) + { Value=0; int8_t Len=0; + if(Inp==0) return 0; + char Sign=Inp[0]; int8_t Dig; + if((Sign=='+')||(Sign=='-')) Len++; + if((Inp[Len]=='0')&&(Inp[Len+1]=='x')) + { Len+=2; Dig=Read_Hex(Value, Inp+Len); } + else + { Dig=Read_UnsDec(Value, Inp+Len); } + if(Dig<=0) return Dig; + Len+=Dig; + if(Sign=='-') Value=(-Value); return Len; } +template + int8_t Read_Float1(Type &Value, const char *Inp) // read floating point, take just one digit after decimal point + { Value=0; int8_t Len=0; + if(Inp==0) return 0; + char Sign=Inp[0]; int8_t Dig; + if((Sign=='+')||(Sign=='-')) Len++; + Len+=Read_UnsDec(Value, Inp+Len); Value*=10; + if(Inp[Len]!='.') goto Ret; + Len++; + Dig=Read_Dec1(Inp[Len]); if(Dig<0) goto Ret; + Value+=Dig; Len++; + Dig=Read_Dec1(Inp[Len]); if(Dig>=5) Value++; + Ret: if(Sign=='-') Value=(-Value); return Len; } int8_t Read_LatDDMMSS(int32_t &Lat, const char *Inp); int8_t Read_LonDDMMSS(int32_t &Lon, const char *Inp); diff --git a/main/gps.cpp b/main/gps.cpp index 77f1911..adaa2a5 100644 --- a/main/gps.cpp +++ b/main/gps.cpp @@ -1,9 +1,10 @@ + #include #include #include "hal.h" #include "gps.h" -// #include "ctrl.h" +#include "ctrl.h" #include "nmea.h" #include "ubx.h" @@ -23,6 +24,7 @@ #ifdef DEBUG_PRINT static char Line[128]; +static void CONS_HexDump(char Byte) { Format_Hex(CONS_UART_Write, (uint8_t)Byte); } #endif // ---------------------------------------------------------------------------- @@ -37,13 +39,16 @@ static UBX_RxMsg UBX; // UBX messages catcher static MAV_RxMsg MAV; // MAVlink message catcher #endif -uint16_t GPS_PosPeriod = 0; +uint16_t GPS_PosPeriod = 0; // [0.01s] time between succecive GPS readouts + +// uint8_t GPS_PowerMode = 2; // 0=shutdown, 1=reduced power, 2=normal const uint8_t PosPipeIdxMask = GPS_PosPipeSize-1; -static GPS_Position Position[GPS_PosPipeSize]; // GPS position pipe static uint8_t PosIdx; // Pipe index, increments with every GPS position received +static GPS_Position Position[GPS_PosPipeSize]; // GPS position pipe -static TickType_t Burst_TickCount; // [msec] TickCount when the data burst from GPS started +static TickType_t PPS_Tick; // [msec] System Tick when the PPS arrived +static TickType_t Burst_Tick; // [msec] System Tick when the data burst from GPS started uint32_t GPS_TimeSinceLock; // [sec] time since the GPS has a lock uint32_t GPS_FatTime = 0; // [sec] UTC date/time in FAT format @@ -52,18 +57,20 @@ static TickType_t Burst_TickCount; // [msec] TickCount when the data bur int32_t GPS_Longitude = 0; // int16_t GPS_GeoidSepar= 0; // [0.1m] uint16_t GPS_LatCosine = 3000; // + uint32_t GPS_Random = 0x12345678; // random number from the LSB of the GPS data + uint16_t GPS_SatSNR = 0; // [0.25dB] Status GPS_Status; static union { uint8_t Flags; struct - { bool Spare:1; // - bool Active:1; // has started - bool GxRMC:1; // GPRMC or GNRMC registered + { bool GxRMC:1; // GPRMC or GNRMC registered bool GxGGA:1; // GPGGA or GNGGA registered bool GxGSA:1; // GPGSA or GNGSA registered - bool Complete:1; // all GPS data is supplied and thus ready for processing + bool Spare:1; + bool Active:1; // has started and data from the GPS is flowing + bool Complete:1; // all GPS data we need is supplied and thus ready for processing } ; } GPS_Burst; // for the autobaud on the GPS port @@ -77,7 +84,65 @@ uint32_t GPS_getBaudRate (void) { return BaudRate[BaudRateIdx]; } uint32_t GPS_nextBaudRate(void) { BaudRateIdx++; if(BaudRateIdx>=BaudRates) BaudRateIdx=0; return GPS_getBaudRate(); } const uint32_t GPS_TargetBaudRate = 57600; // BaudRate[4]; // [bps] must be one of the baud rates known by the autbaud -const uint8_t GPS_TargetDynModel = 7; // for UBX GPS's: 6 = airborne with >1g, 7 = with >2g +// const uint8_t GPS_TargetDynModel = 7; // for UBX GPS's: 6 = airborne with >1g, 7 = with >2g + +static char GPS_Cmd[64]; + +// ---------------------------------------------------------------------------- + +static uint16_t SatSNRsum = 0; +static uint8_t SatSNRcount = 0; + +struct GPS_Sat // store GPS satellite data in single 32-bit word +{ union + { uint32_t Word; + struct + { uint16_t Azim: 9; // [deg] + uint8_t Elev: 7; // [deg] + uint8_t SNR: 7; // [dB/Hz] + uint16_t PRN: 9; // [1..96] GPS:1..32, SBAS:33..64, GNSS:65..96 + } ; + } ; +} ; + +static void ProcessGSV(NMEA_RxMsg &GSV) // process GxGSV to extract satellite data +{ +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, (const char *)GSV.Data, 0, GSV.Len); + Format_String(CONS_UART_Write, " ("); + Format_UnsDec(CONS_UART_Write, (uint16_t)GSV.Parms); + Format_String(CONS_UART_Write, ")\n"); + xSemaphoreGive(CONS_Mutex); +#endif + if(!GSV.isGPGSV()) return; // for now, only the GPS satellites, before we learn to mix the others in + if(GSV.Parms<3) return; + int8_t Pkts=Read_Dec1((const char *)GSV.ParmPtr(0)); if(Pkts<0) return; + int8_t Pkt =Read_Dec1((const char *)GSV.ParmPtr(1)); if(Pkt <0) return; + int8_t Sats=Read_Dec2((const char *)GSV.ParmPtr(2)); + if(Sats<0) Sats=Read_Dec1((const char *)GSV.ParmPtr(2)); + if(Sats<0) return; + for( int Parm=3; Parm10) return; // [ms] filter out difference away from 1.00sec - TimeSync_HardPPS(TickCount); + PPS_Tick = xTaskGetTickCount(); // [ms] TickCount now + TickType_t Delta = PPS_Tick-PrevTickCount; // [ms] time difference to the previous PPS + PrevTickCount = PPS_Tick; // [ms] + if(abs((int)Delta-1000)>=20) return; // [ms] filter out difference away from 1.00sec + TimeSync_HardPPS(PPS_Tick); // [ms] synchronize the UTC time to the PPS at given Tick #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60); + Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60, 2); CONS_UART_Write('.'); Format_UnsDec(CONS_UART_Write, TimeSync_msTime(),3); Format_String(CONS_UART_Write, " -> PPS\n"); xSemaphoreGive(CONS_Mutex); #endif GPS_Status.PPS=1; - LED_PCB_Flash(50); + LED_PCB_Flash(100); // uint8_t Sec=GPS_Sec; Sec++; if(Sec>=60) Sec=0; GPS_Sec=Sec; // GPS_UnixTime++; // #ifdef WITH_MAVLINK @@ -158,12 +223,12 @@ static void GPS_LockEnd(void) // called when GPS looses a // ---------------------------------------------------------------------------- static void GPS_BurstStart(void) // when GPS starts sending the data on the serial port -{ Burst_TickCount=xTaskGetTickCount(); +{ Burst_Tick=xTaskGetTickCount(); #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60); + Format_UnsDec(CONS_UART_Write, TimeSync_Time(Burst_Tick)%60, 2); CONS_UART_Write('.'); - Format_UnsDec(CONS_UART_Write, TimeSync_msTime(),3); + Format_UnsDec(CONS_UART_Write, TimeSync_msTime(Burst_Tick), 3); Format_String(CONS_UART_Write, " -> GPS_BurstStart() GPS:"); Format_Hex(CONS_UART_Write, GPS_Status.Flags); Format_String(CONS_UART_Write, "\n"); @@ -196,34 +261,71 @@ static void GPS_BurstStart(void) // wh CFG_MSG.msgID = 0x04; // ID for GSA UBX_RxMsg::Send(0x06, 0x01, GPS_UART_Write, (uint8_t *)(&CFG_MSG), sizeof(CFG_MSG)); } +#endif +#ifdef WITH_GPS_MTK + if(Parameters.NavRate) + { strcpy(GPS_Cmd, "$PMTK220,"); // MTK command to change the navigation rate + uint8_t Len = strlen(GPS_Cmd); + uint16_t OneSec = 1000; + Len += Format_UnsDec(GPS_Cmd+Len, OneSec/Parameters.NavRate); + Len += NMEA_AppendCheck(GPS_Cmd, Len); + GPS_Cmd[Len++]='\r'; + GPS_Cmd[Len++]='\n'; + GPS_Cmd[Len]=0; + Format_String(GPS_UART_Write, GPS_Cmd, Len, 0); + GPS_Status.ModeConfig=1; + } + if(Parameters.NavMode) + { strcpy(GPS_Cmd, "$PMTK886,"); // MTK command to change the navigation mode + uint8_t Len = strlen(GPS_Cmd); + GPS_Cmd[Len++]='0'+Parameters.NavMode; + Len += NMEA_AppendCheck(GPS_Cmd, Len); + GPS_Cmd[Len++]='\r'; + GPS_Cmd[Len++]='\n'; + GPS_Cmd[Len]=0; + Format_String(GPS_UART_Write, GPS_Cmd, Len, 0); + GPS_Status.ModeConfig=1; + } #endif } if(!GPS_Status.BaudConfig) // if GPS baud config is not done yet { // Format_String(CONS_UART_Write, "CFG_PRT query...\n"); #ifdef WITH_GPS_UBX - // uint8_t UART1_Port=1; - // UBX_RxMsg::Send(0x06, 0x00, GPS_UART_Write, &UART1_Port, 1); // send the query for the port config to have a template configuration packet + UBX_CFG_PRT CFG_PRT; // send in blind the config message for the UART + CFG_PRT.portID=1; + CFG_PRT.reserved1=0x00; + CFG_PRT.txReady=0x0000; + CFG_PRT.mode=0x08D0; + CFG_PRT.baudRate=GPS_TargetBaudRate; + CFG_PRT.inProtoMask=3; + CFG_PRT.outProtoMask=3; + CFG_PRT.flags=0x0000; + CFG_PRT.reserved2=0x0000; + UBX_RxMsg::Send(0x06, 0x00, GPS_UART_Write, (uint8_t*)(&CFG_PRT), sizeof(CFG_PRT)); +#ifdef DEBUG_PRINT + Format_String(CONS_UART_Write, "GPS <- CFG_PRT: "); + UBX_RxMsg::Send(0x06, 0x00, CONS_HexDump, (uint8_t*)(&CFG_PRT), sizeof(CFG_PRT)); + Format_String(CONS_UART_Write, "\n"); +#endif UBX_RxMsg::Send(0x06, 0x00, GPS_UART_Write); // send the query for the port config to have a template configuration packet #endif #ifdef WITH_GPS_MTK - char GPS_Cmd[36]; - strcpy(GPS_Cmd, "$PMTK251,"); // MTK command to change the baud rate - uint8_t Len = strlen(GPS_Cmd); - Len += Format_UnsDec(GPS_Cmd+Len, GPS_TargetBaudRate); - Len += NMEA_AppendCheck(GPS_Cmd, Len); - GPS_Cmd[Len++]='\r'; // this is apparently needed but it should not, as ESP32 does auto-CR ?? - GPS_Cmd[Len++]='\n'; - GPS_Cmd[Len]=0; - Format_String(GPS_UART_Write, GPS_Cmd, Len, 0); + { strcpy(GPS_Cmd, "$PMTK251,"); // MTK command to change the baud rate + uint8_t Len = strlen(GPS_Cmd); + Len += Format_UnsDec(GPS_Cmd+Len, GPS_TargetBaudRate); + Len += NMEA_AppendCheck(GPS_Cmd, Len); + GPS_Cmd[Len++]='\r'; // this is apparently needed but it should not, as ESP32 does auto-CR ?? + GPS_Cmd[Len++]='\n'; + GPS_Cmd[Len]=0; + Format_String(GPS_UART_Write, GPS_Cmd, Len, 0); } #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "GPS <- "); Format_String(CONS_UART_Write, GPS_Cmd, Len, 0); xSemaphoreGive(CONS_Mutex); #endif -#endif +#endif // WITH_GPS_MTK #ifdef WITH_GPS_SRF - char GPS_Cmd[36]; strcpy(GPS_Cmd, "$PSRF100,1,"); // SiRF command to change the baud rate Len = strlen(GPS_Cmd); Len += Format_UnsDec(GPS_Cmd+Len, GPS_TargetBaudRate); @@ -234,7 +336,7 @@ static void GPS_BurstStart(void) // wh GPS_Cmd[Len++]='\n'; GPS_Cmd[Len]=0; Format_String(GPS_UART_Write, GPS_Cmd, Len, 0); -#endif +#endif // WITH_GPS_SRF } QueryWait=60; } @@ -243,6 +345,18 @@ static void GPS_BurstStart(void) // wh #endif // WITH_GPS_CONFIG } +static void GPS_Random_Update(uint8_t Bit) +{ GPS_Random = (GPS_Random<<1) | (Bit&1); } + +static void GPS_Random_Update(GPS_Position *Pos) +{ if(Position==0) return; + GPS_Random_Update(Pos->Altitude); + GPS_Random_Update(Pos->Speed); + GPS_Random_Update(Pos->Latitude); + GPS_Random_Update(Pos->Longitude); + if(Pos->hasBaro) GPS_Random_Update(Pos->Pressure); + XorShift32(GPS_Random); } + static void GPS_BurstComplete(void) // when GPS has sent the essential data for position fix { #ifdef WITH_MAVLINK @@ -262,14 +376,15 @@ static void GPS_BurstComplete(void) // wh xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60, 2); CONS_UART_Write('.'); - Format_UnsDec(CONS_UART_Write, TimeSync_msTime(),3); + Format_UnsDec(CONS_UART_Write, TimeSync_msTime(), 3); Format_String(CONS_UART_Write, " -> GPS_BurstComplete() GPS:"); Format_Hex(CONS_UART_Write, GPS_Status.Flags); - Format_String(CONS_UART_Write, "\nGPS"); - CONS_UART_Write('0'+PosIdx); CONS_UART_Write(':'); CONS_UART_Write(' '); + Format_String(CONS_UART_Write, "\nGPS["); + CONS_UART_Write('0'+PosIdx); CONS_UART_Write(']'); CONS_UART_Write(' '); Format_String(CONS_UART_Write, Line); xSemaphoreGive(CONS_Mutex); #endif + GPS_Random_Update(Position+PosIdx); if(Position[PosIdx].hasGPS) // GPS position data complete { Position[PosIdx].isReady=1; // mark this record as ready for processing => producing packets for transmission if(Position[PosIdx].isTimeValid()) // if time is valid already @@ -277,7 +392,15 @@ static void GPS_BurstComplete(void) // wh { uint32_t UnixTime=Position[PosIdx].getUnixTime(); GPS_FatTime=Position[PosIdx].getFatTime(); #ifndef WITH_MAVLINK // with MAVlink we sync. with the SYSTEM_TIME message - TimeSync_SoftPPS(Burst_TickCount, UnixTime, Parameters.PPSdelay); + int32_t msDiff = Position[PosIdx].FracSec; + if(msDiff>=50) { msDiff-=100; UnixTime++; } // [0.01s] + msDiff*=10; // [ms] + // if(abs(msDiff)<=200) // if (almost) full-second burst + { // TickType_t PPS_Age = Burst_Tick-PPS_Tick; + // if(PPS_Age>10000) TimeSync_SoftPPS(Burst_Tick, UnixTime, Parameters.PPSdelay); + // else TimeSync_SetTime(Burst_Tick-Parameters.PPSdelay, UnixTime); + TimeSync_SoftPPS(Burst_Tick, UnixTime, msDiff+Parameters.PPSdelay); + } #endif } } @@ -305,7 +428,7 @@ static void GPS_BurstComplete(void) // wh if(!Position[PrevIdx2].isValid()) break; TimeDiff = Position[PosIdx].calcTimeDiff(Position[PrevIdx2]); PrevIdx=PrevIdx2; } - TimeDiff=Position[PosIdx].calcDifferences(Position[PrevIdx]); + TimeDiff=Position[PosIdx].calcDifferentials(Position[PrevIdx]); #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "calcDiff() => "); @@ -317,7 +440,7 @@ static void GPS_BurstComplete(void) // wh Format_String(CONS_UART_Write, "s\n"); xSemaphoreGive(CONS_Mutex); #endif - LED_PCB_Flash(100); } + LED_PCB_Flash(200); } } else // complete but no valid lock { if(GPS_TimeSinceLock) { GPS_LockEnd(); GPS_TimeSinceLock=0; } @@ -339,13 +462,13 @@ static void GPS_BurstComplete(void) // wh if(Period>0) GPS_PosPeriod = (Period+GPS_PosPipeSize/2)/(GPS_PosPipeSize-1); #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write,"GPS"); - CONS_UART_Write('0'+PosIdx); CONS_UART_Write(':'); CONS_UART_Write(' '); + Format_String(CONS_UART_Write,"GPS["); + CONS_UART_Write('0'+PosIdx); CONS_UART_Write(']'); CONS_UART_Write(' '); Format_UnsDec(CONS_UART_Write, (uint16_t)Position[PosIdx].Sec, 2); CONS_UART_Write('.'); Format_UnsDec(CONS_UART_Write, (uint16_t)Position[PosIdx].FracSec, 2); Format_String(CONS_UART_Write, "s "); - Format_SignDec(CONS_UART_Write, Period, 3, 2); + Format_UnsDec(CONS_UART_Write, GPS_PosPeriod, 3, 2); Format_String(CONS_UART_Write, "s\n"); xSemaphoreGive(CONS_Mutex); #endif @@ -367,15 +490,15 @@ static void GPS_BurstEnd(void) // wh // ---------------------------------------------------------------------------- GPS_Position *GPS_getPosition(uint8_t &BestIdx, int16_t &BestRes, int8_t Sec, int8_t Frac) // return GPS position closest to the given Sec.Frac -{ int16_t TargetTime = Frac+(int16_t)Sec*100; +{ int16_t TargetTime = Frac+(int16_t)Sec*100; // target time including the seconds BestIdx=0; BestRes=0x7FFF; - for(uint8_t Idx=0; IdxisReady) continue; - int16_t Diff = TargetTime - (Pos->FracSec + (int16_t)Pos->Sec*100); - if(Diff<(-3000)) Diff+=6000; - else if(Diff>3000) Diff-=6000; - if(fabs(Diff)isReady) continue; // skip those not-ready yet + int16_t Diff = TargetTime - (Pos->FracSec + (int16_t)Pos->Sec*100); // difference from the target time + if(Diff<(-3000)) Diff+=6000; // wrap-around 60 sec + else if(Diff>=3000) Diff-=6000; + if(fabs(Diff) "); Format_Bytes(CONS_UART_Write, NMEA.Data, 6); CONS_UART_Write(' '); Format_Hex(CONS_UART_Write, GPS_Burst.Flags); @@ -429,7 +544,7 @@ static void GPS_NMEA(void) // wh if( NMEA.isP() || NMEA.isGxRMC() || NMEA.isGxGGA() || NMEA.isGxGSA() || NMEA.isGPTXT() ) // we would need to patch the GGA here for the GPS which does not calc. nor correct for GeoidSepar #endif - { // if(CONS_UART_Free()>=128) + { if(Parameters.Verbose) { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, (const char *)NMEA.Data, 0, NMEA.Len); Format_String(CONS_UART_Write, "\n"); @@ -460,7 +575,7 @@ static void DumpUBX(void) static void GPS_UBX(void) // when GPS gets an UBX packet { GPS_Status.UBX=1; GPS_Status.BaudConfig = (GPS_getBaudRate() == GPS_TargetBaudRate); - LED_PCB_Flash(2); + LED_PCB_Flash(10); // DumpUBX(); // Position[PosIdx].ReadUBX(UBX); #ifdef WITH_GPS_UBX_PASS @@ -477,7 +592,7 @@ static void GPS_UBX(void) { class UBX_CFG_PRT *CFG = (class UBX_CFG_PRT *)UBX.Word; // create pointer to the packet content #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "TaskGPS: CFG_PRT\n"); + Format_String(CONS_UART_Write, "CFG_PRT: "); DumpUBX(); Format_Hex(CONS_UART_Write, CFG->portID); CONS_UART_Write(':'); @@ -504,14 +619,15 @@ static void GPS_UBX(void) { class UBX_CFG_NAV5 *CFG = (class UBX_CFG_NAV5 *)UBX.Word; #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "TaskGPS: CFG_NAV5 "); + Format_String(CONS_UART_Write, "CFG_NAV5: "); Format_Hex(CONS_UART_Write, CFG->dynModel); Format_String(CONS_UART_Write, "\n"); xSemaphoreGive(CONS_Mutex); #endif - if(CFG->dynModel==GPS_TargetDynModel) GPS_Status.ModeConfig=1; // dynamic model = 6 => Airborne with >1g acceleration + // if(CFG->dynModel==GPS_TargetDynModel) GPS_Status.ModeConfig=1; // dynamic model = 6 => Airborne with >1g acceleration + if(CFG->dynModel==Parameters.NavMode) GPS_Status.ModeConfig=1; // dynamic model = 6 => Airborne with >1g acceleration else - { CFG->dynModel=GPS_TargetDynModel; CFG->mask = 0x01; // + { CFG->dynModel=Parameters.NavMode; CFG->mask = 0x01; // UBX.RecalcCheck(); // reclaculate the check sum UBX.Send(GPS_UART_Write); // send this UBX packet } @@ -529,7 +645,7 @@ static void GPS_UBX(void) xSemaphoreGive(CONS_Mutex); /* if(UBX.Byte[0]==0x06 && UBX.Byte[1]==0x00 && UBX.ID==0) // negative ACK to CFG-PRT - { static char GPS_Cmd[36]; + { strcpy(GPS_Cmd, "$PUBX,41,1,0007,0003,"); // $PUBX command to change the baud rate uint8_t Len = strlen(GPS_Cmd); Len += Format_UnsDec(GPS_Cmd+Len, GPS_TargetBaudRate); @@ -582,7 +698,7 @@ static uint64_t MAV_getUnixTime(void) // [m static void GPS_MAV(void) // when GPS gets an MAV packet { TickType_t TickCount=xTaskGetTickCount(); GPS_Status.MAV=1; - LED_PCB_Flash(2); + LED_PCB_Flash(10); GPS_Status.BaudConfig = (GPS_getBaudRate() == GPS_TargetBaudRate); uint8_t MsgID = MAV.getMsgID(); uint64_t UnixTime_ms = MAV_getUnixTime(); // get the time from the MAVlink message @@ -615,7 +731,7 @@ static void GPS_MAV(void) // wh uint32_t UnixTime = UnixTime_ms/1000; // [ s] Unix Time uint32_t UnixFrac = UnixTime_ms-(uint64_t)UnixTime*1000; // [ms] Second fraction of the Unix time MAV_TimeOfs_ms=UnixTime_ms-SysTime->time_boot_ms; // [ms] difference between the Unix Time and the Ardupilot time-since-boot - TimeSync_SoftPPS(TickCount-UnixFrac, UnixTime, 70); + TimeSync_SoftPPS(TickCount-UnixFrac, UnixTime, Parameters.PPSdelay); #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "MAV_SYSTEM_TIME: "); @@ -736,9 +852,9 @@ void vTaskGPS(void* pvParameters) GPS_Status.Flags = 0; // PPS_TickCount=0; - Burst_TickCount=0; + Burst_Tick=0; - vTaskDelay(5); + vTaskDelay(5); // put some initial delay for lighter startup load xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "TaskGPS:"); @@ -789,6 +905,7 @@ void vTaskGPS(void* pvParameters) uint16_t MaxBytesPerTick = 1+(GPS_getBaudRate()+2500)/5000; for( ; ; ) // loop over bytes in the GPS UART buffer { uint8_t Byte; int Err=GPS_UART_Read(Byte); if(Err<=0) break; // get Byte from serial port, if no bytes then break this loop + // CONS_UART_Write(Byte); // copy the GPS output to console (for debug only) Bytes++; LineIdle=0; // if there was a byte: restart idle counting NMEA.ProcessByte(Byte); // process through the NMEA interpreter @@ -827,23 +944,40 @@ void vTaskGPS(void* pvParameters) #endif */ if(LineIdle==0) // if any bytes were received ? - { if(!GPS_Burst.Active) GPS_BurstStart(); // burst started + { if(!GPS_Burst.Active) GPS_BurstStart(); // if not already started then declare burst started GPS_Burst.Active=1; - if( (!GPS_Burst.Complete) && (GPS_Burst.GxGGA && GPS_Burst.GxRMC && GPS_Burst.GxGSA) ) - { GPS_Burst.Complete=1; GPS_BurstComplete(); } + if( (!GPS_Burst.Complete) && (GPS_Burst.GxGGA && GPS_Burst.GxRMC && GPS_Burst.GxGSA) ) // if GGA+RMC+GSA received + { GPS_Burst.Complete=1; GPS_BurstComplete(); } // declare burst complete } - else if(LineIdle>=GPS_BurstTimeout) // if GPS sends no more data for 10 time ticks - { if(GPS_Burst.Active) // if still in burst - { if(!GPS_Burst.Complete) GPS_BurstComplete(); - GPS_BurstEnd(); } // burst just ended + else if(LineIdle>=GPS_BurstTimeout) // if GPS sends no more data for GPS_BurstTimeout ticks + { if(GPS_Burst.Active) // if burst was active + { if(!GPS_Burst.Complete && GPS_Burst.GxGGA && GPS_Burst.GxRMC) GPS_BurstComplete(); // if not complete yet, then declare burst complete + GPS_BurstEnd(); } // declare burst ended else if(LineIdle>=1500) // if idle for more than 1.5 sec { GPS_Status.Flags=0; } - GPS_Burst.Flags=0; + GPS_Burst.Flags=0; // clear all flags: active and complete } if(NoValidData>=2000) // if no valid data from GPS for 1sec { GPS_Status.Flags=0; GPS_Burst.Flags=0; // assume GPS state is unknown uint32_t NewBaudRate = GPS_nextBaudRate(); // switch to the next baud rate + if(PowerMode>0) + { +#ifdef WITH_GPS_UBS +#ifdef WITH_GPS_ENABLE + GPS_ENABLE(); +#endif + GPS_UART_Write('\n'); +#endif +#ifdef WITH_GPS_MTK +#ifdef WITH_GPS_ENABLE + GPS_DISABLE(); + vTaskDelay(1); + GPS_ENABLE(); +#endif + GPS_UART_Write('\n'); +#endif + } xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "TaskGPS: "); Format_UnsDec(CONS_UART_Write, NewBaudRate); @@ -854,3 +988,5 @@ void vTaskGPS(void* pvParameters) } } } + + diff --git a/main/gps.h b/main/gps.h index 5a90cd2..d95ca7d 100644 --- a/main/gps.h +++ b/main/gps.h @@ -9,6 +9,8 @@ const uint8_t GPS_PosPipeSize = 4; // number of GPS positions held in a pipe +// extern uint8_t GPS_PowerMode; // 0=shutdown, 1=reduced, 2=normal + extern uint32_t GPS_FatTime; // [2 sec] UTC time in FAT format (for FatFS) extern int32_t GPS_Altitude; // [0.1m] altitude (height above Geoid) extern int32_t GPS_Latitude; // [0.0001/60 deg] @@ -16,7 +18,9 @@ extern int32_t GPS_Longitude; // [0.0001/60 deg] extern int16_t GPS_GeoidSepar; // [0.1m] extern uint16_t GPS_LatCosine; // [1.0/(1<<12)] Latitude Cosine for distance calculations extern uint32_t GPS_TimeSinceLock; // [sec] time since GPS has a valid lock +extern uint32_t GPS_Random; // random number produced from the GPS data extern uint16_t GPS_PosPeriod; // [0.01sec] how often (which period) the GPS/MAV is sending the positions +extern uint16_t GPS_SatSNR; // [0.25dB] average SNR for satellites being used for navigation typedef union { uint8_t Flags; @@ -26,8 +30,8 @@ typedef union bool MAV:1; // got at least one valid MAV message bool PPS:1; // got at least one PPS signal bool BaudConfig:1; // baudrate is configured - bool ModeConfig:1; // mode is configured - bool :1; // + bool ModeConfig:1; // navigation mode is configured + bool RateConfig:1; // navigation rate is configured bool :1; // } ; } Status; diff --git a/main/hal.cpp b/main/hal.cpp index 97aa37f..602d3c4 100644 --- a/main/hal.cpp +++ b/main/hal.cpp @@ -15,6 +15,10 @@ #include "esp_system.h" #include "esp_freertos_hooks.h" +#ifdef WITH_SLEEP +#include "esp_sleep.h" +#endif + #include "nvs.h" #include "nvs_flash.h" @@ -43,15 +47,41 @@ #include "fifo.h" #endif +#ifdef WITH_SOUND +#include "intmath.h" +#include "driver/i2s.h" +#endif + #ifdef WITH_OLED #include "ssd1306.h" #include "font8x8_basic.h" #endif -#ifdef WITH_U8G2 +#ifdef WITH_U8G2_OLED #include "u8g2.h" #endif +#ifdef WITH_TFT_LCD +extern "C" { +#include "tftspi.h" +#include "tft.h" + } +#endif + +#if defined(WITH_ST7789) || defined(WITH_ILI9341) +#include "st7789.h" +#endif + +#ifdef WITH_AXP +#include "axp192.h" +#endif + +#ifdef WITH_BQ +#include "bq24295.h" +#endif + +uint8_t PowerMode = 2; // 0=sleep/minimal power, 1=comprimize, 2=full power + // ====================================================================================================== /* The HELTEC AUtomation board WiFi LoRa 32 with sx1278 (RFM95) @@ -101,7 +131,7 @@ UART2 pins: 16 = GPIO16 = RxD -> taken by OLED ? 17 = GPIO17 = TxD -T-Beam board pinout: +T-Beam board pinout and docs: http://tinymicros.com/wiki/TTGO_T-Beam HPD13A = RF chip ? 23 = RST (?) @@ -125,70 +155,94 @@ PSRM32 = SDIO ? */ /* -GPIO HELTEC TTGO JACEK T-Beam FollowMe Restrictions +GPIO HELTEC TTGO JACEK M5_JACEK T-Beam T-Beamv10 FollowMe Restrictions - 0 Button - 1 CONS/TxD CONS/TxD CONS/TxD CONS/TxD Console/Program - 2 SD/MISO . LED Bootstrap: LOW to enter UART download mode - 3 CONS/RxD CONS/RxD CONS/RxD CONS/RxD Console/Program - 4 OLED/SDA OLED/SDA ADC/CS Beeper PER/RST - 5 RF/SCK RF/SCK RF/SCK RF/SCK RF/CS - 6 SD/CLK - 7 SD/DATA0 - 8 SD/DATA1 - 9 SD/DATA2 -10 SD/DATA3 -11 SD/CMD -12 GPS/RxD GPS/RxD SD/CS GPS/RxD SD/MISO JTAG/TDI Bootstrap: select output voltage to power the flash chip -13 GPS/Ena GPS/Ena SD/SCK SD/MOSI JTAG/TCK -14 RF/RST RF/RST Beeper LED SD/CLK JTAG/TMS -15 OLED/SCL OLED/SCL SD/MOSI GPS/TxD SD/CS JTAG/TDO -16 OLED/RST OLED/RST RF/IRQ GPS/Tx -17 Beeper Beeper RF/RST GPS/Rx -18 RF/CS RF/CS RF/MISO RF/CS RF/SCK -19 RF/MISO RF/MISO RF/MOSI RF/MISO RF/MISO -20 -21 LED RF/CS I2C/SDA I2C/SDA -22 PWR/ON I2C/SCL I2C/CLK -23 PWR/LDO RF/RST RF/MOSI -24 -25 LED DAC2 . . TT/RX0 -26 RF/IRQ RF/IRQ SCL RF/IRQ BMX/INT1 -27 RF/MOSI RF/MOSI SDA TT/TX0 -28 -29 -30 -31 -32 GPS/TxD . TT/BOOT -33 OLED/RST . GPS/WAKE -34 GPS/PPS GPS/PPS GPS/RxD GPS/PPS -35 GPS/TxD GPS/TxD GPS/PPS BAT/Sense RF/IRQ -36 BAT/Sense BAT/Sense -37 -38 -39 Button + 0 Button Button . LCD/DC LCD/DC Can lock the boot, better not pull it down + 1 CONS/TxD CONS/TxD CONS/TxD CONS/TxD CONS/TxD CONS/TxD Console/Program + 2 SD/MISO . LCD/MOSI LCD/MOSI LED/DBG Bootstrap: LOW to enter UART download mode + 3 CONS/RxD CONS/RxD CONS/RxD CONS/RxD CONS/RxD CONS/RxD Console/Program + 4 OLED/SDA OLED/SDA ADC/CS LCD/BCKL LCD/BCKL PERF/RST + 5 RF/SCK RF/SCK RF/SCK GPS/ANT RF/SCK RF/SCK RF/CS + 6 SD/CLK SD/CLK SD/CLK + 7 SD/DATA0 + 8 SD/DATA1 + 9 SD/DATA2 +10 SD/DATA3 +11 DC/CMD SD/CMD SD/CMD +12 GPS/RxD GPS/RxD SD/CS GPS/RxD GPS/TxD SD/MISO HSPI/MISO JTAG/TDI Bootstrap: select output voltage to power the flash chip +13 GPS/Ena GPS/Ena SD/SCK LCD/SCL LCD/CLK LCD/CLK SD/MOSI HSPI/MOSI JTAG/TCK +14 RF/RST RF/RST Beeper ? LED/PCB . SD/CLK HSPI/CLK JTAG/TMS +15 OLED/SCL OLED/SCL SD/MOSI GPS/TxD . HSPI/CS0 JTAG/TDO +16 OLED/RST OLED/RST RF/IRQ GPS/Tx RAM/CS GPS/TX U2_RXD +17 Beeper Beeper RF/RST GPS/Rx Beeper GPS/RX U2_TXD +18 RF/CS RF/CS RF/MISO RF/CS RF/SCK RF/CS VSPI/CLK +19 RF/MISO RF/MISO RF/MOSI RF/MISO RF/MISO RF/MISO RF/MISO VSPI/MISO +20 not listed +21 LED? RF/CS I2C/SCL I2C/SDA I2C/SDA I2C/SDA VSPI/QUADHP +22 . PWR/ON I2C/SDA I2C/CLK I2C/SCL VSPI/QUADWP +23 . PWR/LDO RF/RST RF/MOSI RF/RST RF/MOSI VSPI/MOSI +24 not listed +25 LED Speaker . Speaker Speaker Speaker TT/RX +26 RF/IRQ RF/IRQ SCL RF/IRQ RF/IRQ RF/IRQ PWR/Good +27 RF/MOSI RF/MOSI SDA RF/MOSI RF/MOSI TT/TX +28 not listed +29 not listed +30 not listed +31 not listed +32 . . GPS/TxD ? . TT/RST XTAL +33 . . OLED/RST ? . GPS/EN XTAL +34 GPS/PPS GPS/PPS GPS/RxD KNOB/Sense GPS/TxD GPS/PPS +35 GPS/TxD GPS/TxD GPS/PPS BAT/Sense AXP/IRQ RF/IRQ +36 . . BAT/Sense BAT/Ext. . Vbat/Sense +37 . . GPS/PPS +38 . . Button Button +39 . . Button Button Button */ #ifdef WITH_TTGO -#define PIN_LED_PCB GPIO_NUM_2 // status LED on the PCB: 2, GPIO25 is DAC2 +#define PIN_LED_PCB GPIO_NUM_2 // status LED on the PCB #endif + #ifdef WITH_HELTEC #define PIN_LED_PCB GPIO_NUM_25 // status LED on the PCB: 25, GPIO25 is DAC2 #endif -#ifdef WITH_TBEAM -#define PIN_LED_PCB GPIO_NUM_14 // status LED on the PCB: 14, posisbly inverted -#define PIN_LED_PCB_INV + +#if defined(WITH_TBEAM) // || defined(WITH_TBEAM_V10) +#define PIN_LED_PCB GPIO_NUM_14 // blue LED on the PCB: 14 +// #define PIN_LED_PCB_INV #endif + #ifdef WITH_FollowMe #define PIN_LED_PCB GPIO_NUM_2 // debug LED #define PIN_LED_TX GPIO_NUM_15 +#ifdef WITH_BQ +#define PIN_POWER_GOOD GPIO_NUM_26 +#endif #endif // #define PIN_LED_TX GPIO_NUM_?? // #define PIN_LED_RX GPIO_NUM_?? +#ifdef WITH_JACEK +#define PIN_RFM_RST GPIO_NUM_17 // Reset +#define PIN_RFM_IRQ GPIO_NUM_16 // packet done on receive or transmit +#define PIN_RFM_SS GPIO_NUM_21 // SPI chip-select +#define PIN_RFM_SCK GPIO_NUM_5 // SPI clock +#define PIN_RFM_MISO GPIO_NUM_18 // SPI MISO +#define PIN_RFM_MOSI GPIO_NUM_19 // SPI MOSI +#endif // JACEK + +#ifdef WITH_M5_JACEK +#define PIN_RFM_RST GPIO_NUM_13 // Reset +#define PIN_RFM_IRQ GPIO_NUM_35 // Packet done on receive or transmit +#define PIN_RFM_SS GPIO_NUM_12 // SPI chip-select +#define PIN_RFM_SCK GPIO_NUM_18 // SPI clock (same as LCD) +#define PIN_RFM_MISO GPIO_NUM_19 // SPI MISO (save as LCD) +#define PIN_RFM_MOSI GPIO_NUM_23 // SPI MOSI (save as LCD) +#endif // M5_JACEK + #if defined(WITH_HELTEC) || defined(WITH_TTGO) #define PIN_RFM_RST GPIO_NUM_14 // Reset #define PIN_RFM_IRQ GPIO_NUM_26 // packet done on receive or transmit @@ -207,6 +261,15 @@ GPIO HELTEC TTGO JACEK T-Beam FollowMe Restrictions #define PIN_RFM_MOSI GPIO_NUM_27 // SPI MOSI #endif // TBEAM +#ifdef WITH_TBEAM_V10 +#define PIN_RFM_RST GPIO_NUM_23 // Reset 23 +#define PIN_RFM_IRQ GPIO_NUM_26 // packet done on receive or transmit +#define PIN_RFM_SS GPIO_NUM_18 // SPI chip-select +#define PIN_RFM_SCK GPIO_NUM_5 // SPI clock +#define PIN_RFM_MISO GPIO_NUM_19 // SPI MISO +#define PIN_RFM_MOSI GPIO_NUM_27 // SPI MOSI +#endif // TBEAM + #ifdef WITH_FollowMe // #define PIN_RFM_RST GPIO_NUM_32 // Reset #define PIN_RFM_IRQ GPIO_NUM_35 // 39 // packet done on receive or transmit @@ -216,23 +279,66 @@ GPIO HELTEC TTGO JACEK T-Beam FollowMe Restrictions #define PIN_RFM_MOSI GPIO_NUM_23 // SPI MOSI #endif // FollowMe -#define RFM_SPI_HOST VSPI_HOST // or HSPI_HOST +#define RFM_SPI_HOST VSPI_HOST // or H or VSPI_HOST ? +#define RFM_SPI_DMA 1 // DMA channel #define RFM_SPI_SPEED 4000000 // [Hz] 4MHz SPI clock rate for RF chip +#ifdef WITH_ST7789 +#ifdef WITH_TBEAM +#define LCD_PIN_MOSI GPIO_NUM_2 // SDA +#define LCD_PIN_MISO GPIO_NUM_NC // MISO not connected +#define LCD_PIN_CLK GPIO_NUM_13 // SCL +#endif // TBEAM +#ifdef WITH_TBEAM_V10 +#define LCD_PIN_MOSI GPIO_NUM_2 // 13 // SDA +#define LCD_PIN_MISO GPIO_NUM_NC // MISO not connected +#define LCD_PIN_CLK GPIO_NUM_13 // 14 // SCL +// #define LCD_PIN_RST GPIO_NUM_15 // NC // RST +#endif // TBEAM v1.0 +#define LCD_SPI_SPEED 26000000 // [Hz] +#define LCD_SPI_HOST HSPI_HOST // VSPI_HOST or HSPI_HOST +#define LCD_SPI_DMA 2 // DMA channel +#define LCD_SPI_MODE 3 // for ST7789 with CS tied to LOW +#endif + +#ifdef WITH_JACEK +#define PIN_GPS_TXD GPIO_NUM_32 +#define PIN_GPS_RXD GPIO_NUM_34 +#define PIN_GPS_PPS GPIO_NUM_35 +#endif // JACEK + +#ifdef WITH_M5_JACEK +#define PIN_GPS_TXD GPIO_NUM_17 +#define PIN_GPS_RXD GPIO_NUM_16 +#define PIN_GPS_PPS GPIO_NUM_26 +#endif // M5_JACEK + +#ifdef WITH_TBEAM_V10 +#define PIN_AXP_IRQ GPIO_NUM_35 +#endif + #if defined(WITH_HELTEC) || defined(WITH_TTGO) - // VK2828U GN-801 GG-1802 MAVlink -#define PIN_GPS_TXD GPIO_NUM_12 // green green blue green -#define PIN_GPS_RXD GPIO_NUM_35 // blue yellow green yellow -#define PIN_GPS_PPS GPIO_NUM_34 // white blue yellow -#define PIN_GPS_ENA GPIO_NUM_13 // yellow white + // VK2828U GN-801 MAVlink +#define PIN_GPS_TXD GPIO_NUM_13 // green green green +#define PIN_GPS_RXD GPIO_NUM_35 // blue yellow yellow +#define PIN_GPS_PPS GPIO_NUM_34 // white blue +// #define PIN_GPS_RXD GPIO_NUM_38 // for a new HELTEC +// #define PIN_GPS_PPS GPIO_NUM_37 // for a new HELTEC +// #define PIN_GPS_ENA GPIO_NUM_13 // yellow white #endif // HELTEC || TTGO // Note: I had a problem with GPS ENABLE on GPIO13, thus I tied the enable wire to 3.3V for the time being. #ifdef WITH_TBEAM -#define PIN_GPS_TXD GPIO_NUM_15 // UBX GPS with only UART +#define PIN_GPS_TXD GPIO_NUM_15 #define PIN_GPS_RXD GPIO_NUM_12 -#endif +#endif // TBEAM + +#ifdef WITH_TBEAM_V10 +#define PIN_GPS_TXD GPIO_NUM_12 +#define PIN_GPS_RXD GPIO_NUM_34 +#define PIN_GPS_PPS GPIO_NUM_37 +#endif // TBEAM v1.0 #ifdef WITH_FollowMe // L80 GPS with PPS, Enable and Reset #define PIN_GPS_TXD GPIO_NUM_17 @@ -254,6 +360,18 @@ GPIO HELTEC TTGO JACEK T-Beam FollowMe Restrictions #define PIN_AERO_RXD GPIO_NUM_27 #endif +uint8_t BARO_I2C = (uint8_t)I2C_BUS; + +#ifdef WITH_JACEK +#define PIN_I2C_SCL GPIO_NUM_26 // SCL pin +#define PIN_I2C_SDA GPIO_NUM_27 // SDA pin +#endif // JACEK + +#ifdef WITH_M5_JACEK +#define PIN_I2C_SCL GPIO_NUM_22 // SCL pin +#define PIN_I2C_SDA GPIO_NUM_21 // SDA pin +#endif // M5_JACEK + #if defined(WITH_HELTEC) || defined(WITH_TTGO) #define PIN_I2C_SCL GPIO_NUM_15 // SCL pin #define PIN_I2C_SDA GPIO_NUM_4 // SDA pin @@ -261,7 +379,7 @@ GPIO HELTEC TTGO JACEK T-Beam FollowMe Restrictions #define PIN_OLED_RST GPIO_NUM_16 // OLED RESET: low-active #endif // HELTEC || TTGO -#ifdef WITH_TBEAM // T-Beam +#if defined(WITH_TBEAM) || defined(WITH_TBEAM_V10) // T-Beam #define PIN_I2C_SCL GPIO_NUM_22 // SCL pin => this way the pin pattern fits the BMP280 module #define PIN_I2C_SDA GPIO_NUM_21 // SDA pin #define OLED_I2C_ADDR 0x3C // I2C address of the OLED display @@ -274,17 +392,50 @@ GPIO HELTEC TTGO JACEK T-Beam FollowMe Restrictions // #define PIN_OLED_RST GPIO_NUM_15 // OLED RESET: low-active #endif -uint8_t BARO_I2C = (uint8_t)I2C_BUS; +#if defined(WITH_HELTEC) || defined(WITH_TTGO) +#define PIN_OLED_RST GPIO_NUM_16 // OLED RESET: low-active +#endif -#ifdef WITH_TBEAM -#define PIN_BEEPER GPIO_NUM_4 +#ifdef WITH_JACEK + +#define PIN_OLED_RST GPIO_NUM_33 // OLED RESET: low-active +#define OLED_I2C_ADDR 0x3C // I2C address of the OLED display + +#define PIN_POWER GPIO_NUM_22 // power to GPS, RF, ... +#define PIN_POWER_LDO GPIO_NUM_23 // LOW=low power LDO, HIGH=higher power LDO + +#define PIN_SD_CS GPIO_NUM_12 // SD card interface in SPI mode +#define PIN_SD_SCK GPIO_NUM_13 +#define PIN_SD_MISO GPIO_NUM_2 +#define PIN_SD_MOSI GPIO_NUM_15 +#define SD_SPI_DMA 2 + +#define PIN_BEEPER GPIO_NUM_14 + +#endif // JACEK + +#if defined(WITH_TBEAM) || defined(WITH_TBEAM_V10) +#define PIN_BEEPER GPIO_NUM_25 // same as DAC #endif // TBEAM #if defined(WITH_HELTEC) || defined(WITH_TTGO) #define PIN_BEEPER GPIO_NUM_17 #endif // HELTEC || TTGO -#if !defined(WITH_OLED) && !defined(WITH_U8G2) && !defined(WITH_BMP180) && !defined(WITH_BMP280) && !defined(WITH_BME280) +#ifdef WITH_M5_JACEK + +#define PIN_GPS_ANT GPIO_NUM_5 // internal(H) or external(L) GPS antenna + +// #define PIN_SD_CS GPIO_NUM_4 // SD card interface in SPI mode +// #define PIN_SD_SCK GPIO_NUM_18 +// #define PIN_SD_MISO GPIO_NUM_19 +// #define PIN_SD_MOSI GPIO_NUM_23 + +#define PIN_BEEPER GPIO_NUM_25 // DAC + +#endif // M5_JACEK + +#if !defined(WITH_AXP) && !defined(WITH_BQ) && !defined(WITH_OLED) && !defined(WITH_U8G2_OLED) && !defined(WITH_BMP180) && !defined(WITH_BMP280) && !defined(WITH_BME280) #undef PIN_I2C_SCL #undef PIN_I2C_SDA #endif @@ -294,14 +445,37 @@ uint8_t BARO_I2C = (uint8_t)I2C_BUS; #define PIN_SD_MOSI GPIO_NUM_13 #define PIN_SD_SCK GPIO_NUM_14 #define PIN_SD_CS GPIO_NUM_15 +#define SD_SPI_DMA 2 #endif // FollowMe -#ifdef WITH_FollowMe -#define PIN_BUTTON GPIO_NUM_39 -#else +#ifdef WITH_M5_JACEK // the three buttons below the LCD + +#define PIN_BUTTON_L GPIO_NUM_37 // Left +#define PIN_BUTTON GPIO_NUM_38 // Center +#define PIN_BUTTON_R GPIO_NUM_39 // Right + +#endif + +#if defined(WITH_FollowMe) // || defined(WITH_TBEAM) || defined(WITH_TBEAM_V10) +#define PIN_BUTTON GPIO_NUM_39 // or 38 ? +#endif + +#if defined(WITH_TBEAM_V10) +#define PIN_BUTTON GPIO_NUM_38 +#endif + +#ifdef WITH_JACEK +#define PIN_BUTTON GPIO_NUM_38 +#endif + +#if defined(WITH_TTGO) || defined(WITH_HELTEC) #define PIN_BUTTON GPIO_NUM_0 #endif +#ifndef PIN_BUTTON +#define PIN_BUTTON GPIO_NUM_39 +#endif + // ====================================================================================================== // 48-bit unique ID of the chip @@ -332,6 +506,25 @@ uint8_t MAV_Seq=0; // sequence number for MAVlink message sen FlashParameters Parameters; //-------------------------------------------------------------------------------------------------------- +// Power control + +#ifdef WITH_JACEK +void POWER_Dir (void) { gpio_set_direction(PIN_POWER, GPIO_MODE_OUTPUT); } +void POWER_On (void) { gpio_set_level(PIN_POWER, 0); } // +void POWER_Off (void) { gpio_set_level(PIN_POWER, 1); } +void POWER_LDO_Dir(void) { gpio_set_direction(PIN_POWER_LDO, GPIO_MODE_OUTPUT); } +void POWER_LDO_On (void) { gpio_set_level(PIN_POWER_LDO, 1); } // +void POWER_LDO_Off(void) { gpio_set_level(PIN_POWER_LDO, 0); } +#endif + +#ifdef WITH_M5_JACEK +void GPS_ANT_Dir (void) { gpio_set_direction(PIN_GPS_ANT, GPIO_MODE_OUTPUT); } +void GPS_ANT_Sel (uint8_t Antenna=1) { gpio_set_level(PIN_GPS_ANT, Antenna); } // 1=internal, 0=external +#endif + +#ifdef PIN_POWER_GOOD +bool Power_isGood(void) { return gpio_get_level(PIN_POWER_GOOD); } +#endif //-------------------------------------------------------------------------------------------------------- // Status LED on the PCB @@ -345,7 +538,7 @@ void LED_PCB_Off (void) { gpio_set_level(PIN_LED_PCB, 1); } void LED_PCB_On (void) { gpio_set_level(PIN_LED_PCB, 1); } void LED_PCB_Off (void) { gpio_set_level(PIN_LED_PCB, 0); } #endif -#else +#else // if LED on the PCB is absent, just make dummy calls void LED_PCB_Dir (void) { } void LED_PCB_On (void) { } void LED_PCB_Off (void) { } @@ -608,6 +801,7 @@ void GPS_UART_SetBaudrate(int BaudRate) { uart_set_baudrate(GPS_UART, B void GPS_DISABLE(void) { gpio_set_level(PIN_GPS_ENA, 0); } void GPS_ENABLE (void) { gpio_set_level(PIN_GPS_ENA, 1); } #endif + #ifdef PIN_GPS_PPS bool GPS_PPS_isOn(void) { return gpio_get_level(PIN_GPS_PPS); } #endif @@ -616,24 +810,25 @@ bool GPS_PPS_isOn(void) { return gpio_get_level(PIN_GPS_PPS); } // RF chip #ifdef PIN_RFM_RST // if reset pin declared for the RF chip -inline void RFM_RESET_Dir (void) { gpio_set_direction(PIN_RFM_RST, GPIO_MODE_OUTPUT); } -inline void RFM_RESET_Set (bool High) { gpio_set_level(PIN_RFM_RST, High); } +void RFM_RESET_SetInput (void) { gpio_set_direction(PIN_RFM_RST, GPIO_MODE_INPUT); } +void RFM_RESET_SetOutput (void) { gpio_set_direction(PIN_RFM_RST, GPIO_MODE_OUTPUT); } +void RFM_RESET_SetLevel (uint8_t High) { gpio_set_level(PIN_RFM_RST, High&1); } #ifdef WITH_RFM95 // for RFM95 reset is low-active -void RFM_RESET(uint8_t On) { RFM_RESET_Set(~On); } +void RFM_RESET(uint8_t On) { if(On&1) { RFM_RESET_SetOutput(); RFM_RESET_SetLevel(0); } else RFM_RESET_SetInput(); } #endif #ifdef WITH_RFM69 // for RFM69 reset is high-active -void RFM_RESET(uint8_t On) { RFM_RESET_Set(On); } +void RFM_RESET(uint8_t On) { RFM_RESET_SetLevel(On); } #endif #else // if no reset pin declared for the RF chip, then make an empty call -inline void RFM_RESET_Dir (void) { } +inline void RFM_RESET_SetOutput (void) { } void RFM_RESET(uint8_t On) { } #endif // PIN_RFM_RST -inline void RFM_IRQ_Dir (void) { gpio_set_direction(PIN_RFM_IRQ, GPIO_MODE_INPUT); } - bool RFM_IRQ_isOn(void) { return gpio_get_level(PIN_RFM_IRQ); } +void RFM_IRQ_SetInput(void) { gpio_set_direction(PIN_RFM_IRQ, GPIO_MODE_INPUT); } +bool RFM_IRQ_isOn(void) { return gpio_get_level(PIN_RFM_IRQ); } static spi_device_handle_t RFM_SPI; @@ -643,7 +838,104 @@ void RFM_TransferBlock(uint8_t *Data, uint8_t Len) Trans.tx_buffer = Data; Trans.rx_buffer = Data; Trans.length = 8*Len; - esp_err_t ret = spi_device_polling_transmit(RFM_SPI, &Trans); } + // esp_err_t ret = spi_device_polling_transmit(RFM_SPI, &Trans); } + esp_err_t ret = spi_device_transmit(RFM_SPI, &Trans); } + +//-------------------------------------------------------------------------------------------------------- + +#ifdef WITH_TFT_LCD + +static void TFT_LCD_Init(void) +{ TFT_PinsInit(); + + spi_lobo_device_handle_t spi; + + spi_lobo_bus_config_t buscfg = + { .mosi_io_num=PIN_NUM_MOSI, // set SPI MOSI pin +#ifdef PIN_NUM_MISO + .miso_io_num = PIN_NUM_MISO, // set SPI MISO pin +#else + .miso_io_num = -1, +#endif + .sclk_io_num = PIN_NUM_CLK, // set SPI CLK pin + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = 6*1024 }; + + spi_lobo_device_interface_config_t devcfg = + { .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, +#ifdef PIN_NUM_CS + .mode = 0, +#else + .mode = 3, +#endif + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = 8000000, // Initial clock out at 8 MHz + .spics_io_num = -1, // we will use external CS pin +#ifdef PIN_NUM_CS + .spics_ext_io_num = PIN_NUM_CS, // external CS pin +#else + .spics_ext_io_num = -1, +#endif + .flags=LB_SPI_DEVICE_HALFDUPLEX, // ALWAYS SET to HALF DUPLEX MODE!! for display spi + .pre_cb = 0, + .post_cb = 0, + .selected = 0, + }; + + max_rdclock = 4000000; + vTaskDelay(100); + +#ifdef WITH_M5_JACEK + esp_err_t ret=spi_lobo_bus_add_device(TFT_VSPI_HOST, &buscfg, &devcfg, &spi); +#endif +#ifdef WITH_TBEAM + esp_err_t ret=spi_lobo_bus_add_device(TFT_VSPI_HOST, &buscfg, &devcfg, &spi); +#endif + // assert(ret==ESP_OK); + // printf("SPI: display device added to spi bus (%d)\r\n", SPI_BUS); + disp_spi = spi; + + // ret = spi_lobo_device_select(spi, 1); + // assert(ret==ESP_OK); + // ret = spi_lobo_device_deselect(spi); + // assert(ret==ESP_OK); + + TFT_display_init(); + // max_rdclock = find_rd_speed(); + + font_rotate = 0; + text_wrap = 0; + font_transparent = 0; + font_forceFixed = 0; + gray_scale = 0; + // disp_select(); + // spi_lobo_device_select(spi, 1); + TFT_invertDisplay(INVERT_ON); + TFT_setGammaCurve(DEFAULT_GAMMA_CURVE); + // spi_lobo_device_deselect(spi); + // disp_deselect(); + // TFT_setRotation(LANDSCAPE); + TFT_setRotation(PORTRAIT); + TFT_setFont(DEFAULT_FONT, NULL); + TFT_resetclipwin(); + + TFT_fillScreen(TFT_GREEN); + // TFT_fillScreen(TFT_WHITE); + // TFT_fillScreen(TFT_BLACK); + TFT_resetclipwin(); + + TFT_drawRect(20, 20, 200, 200, TFT_CYAN); + + TFT_print("OGN-Tracker", CENTER, CENTER); + +} + +#endif // WITH_TFT_LCD //-------------------------------------------------------------------------------------------------------- // BEEPER @@ -653,7 +945,8 @@ void RFM_TransferBlock(uint8_t *Data, uint8_t Len) static ledc_timer_config_t LEDC_Timer = { speed_mode : LEDC_HIGH_SPEED_MODE, // timer mode - { duty_resolution : LEDC_TIMER_8_BIT }, // resolution of PWM duty: 0..255 + duty_resolution : LEDC_TIMER_8_BIT, // resolution of PWM duty: 0..255 +// { duty_resolution : LEDC_TIMER_8_BIT, } // resolution of PWM duty: 0..255 timer_num : LEDC_TIMER_0, // timer index freq_hz : 880 // frequency of PWM signal } ; @@ -737,6 +1030,99 @@ void Play_TimerCheck(void) // every ms serve the note play #endif +//-------------------------------------------------------------------------------------------------------- +// SOUND + +#ifdef WITH_SOUND + +// extern const uint8_t Sound_737_Traffic_u8[] asm("_binary_737_Traffic_u8_start"); +// extern const uint8_t Sound_737_Traffic_end[] asm("_binary_737_Traffic_u8_end"); +// const int Sound_737_Traffic_size = Sound_737_Traffic_end-Sound_737_Traffic_u8; + +// extern const uint8_t Sound_737_Sink_Rate_u8[] asm("_binary_737_Sink_Rate_u8_start"); +// extern const uint8_t Sound_737_Sink_Rate_end[] asm("_binary_737_Sink_Rate_u8_end"); +// const int Sound_737_Sink_Rate_size = Sound_737_Sink_Rate_end-Sound_737_Sink_Rate_u8; + +// extern const uint8_t Sound_737_Whoop_Whoop_u8[] asm("_binary_737_Whoop_Whoop_u8_start"); +// extern const uint8_t Sound_737_Whoop_Whoop_end[] asm("_binary_737_Whoop_Whoop_u8_end"); +// const int Sound_737_Whoop_Whoop_size = Sound_737_Whoop_Whoop_end-Sound_737_Whoop_Whoop_u8; + +// const uint32_t Sound_SampleRate = 16000; +const int Sound_BuffSize = 256; + +esp_err_t Sound_Init(void) +{ + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), + .sample_rate = Sound_SampleRate, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // I2S_CHANNEL_FMT_ALL_RIGHT or I2S_CHANNEL_FMT_ONLY_RIGHT ? + .communication_format = I2S_COMM_FORMAT_I2S_MSB, + .intr_alloc_flags = 0, + .dma_buf_count = 4, + .dma_buf_len = Sound_BuffSize, + .use_apll = 1, + .tx_desc_auto_clear = 1, + .fixed_mclk = 0 }; + i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); // install and start i2s driver + // i2s_set_pin(I2S_NUM_0, 0); + i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); // init DAC pad + // i2s_set_sample_rates(I2S_NUM_0, Sound_SampleRate); + // i2s_set_clk(I2S_NUM_0, Sound_SampleRate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO); + return ESP_OK; } + +static uint16_t Sound_Buffer[Sound_BuffSize]; + +int Sound_Play(const uint16_t *Data, int Len) +{ size_t Written=0; + i2s_write(I2S_NUM_0, Data, Len<<1, &Written, portMAX_DELAY); + return Written>>1; } + +void Sound_PlayU8(const uint8_t *Data, uint16_t Len) +{ int BuffIdx=0; + for( ; Len--; ) + { if(BuffIdx>=Sound_BuffSize) { Sound_Play(Sound_Buffer, Sound_BuffSize); BuffIdx=0; } + uint16_t DAC = (*Data++); DAC<<=8; + Sound_Buffer[BuffIdx++] = DAC; } + if(BuffIdx) { Sound_Play(Sound_Buffer, BuffIdx); BuffIdx=0; } +} + +void Sound_PlayS8(const int8_t *Data, uint16_t Len, uint8_t Vol) +{ if(Vol>8) Vol=8; + int BuffIdx=0; + for( ; Len--; ) + { if(BuffIdx>=Sound_BuffSize) { Sound_Play(Sound_Buffer, Sound_BuffSize); BuffIdx=0; } + int16_t DAC = (*Data++); if(DAC&0x80) DAC|=0xFF00; DAC<<=Vol; DAC+=0x8000; + Sound_Buffer[BuffIdx++] = DAC; } + if(BuffIdx) { Sound_Play(Sound_Buffer, BuffIdx); BuffIdx=0; } +} + +void Sound_PlaySilence(uint16_t Len) +{ int BuffIdx=0; + for( ; Len--; ) + { if(BuffIdx>=Sound_BuffSize) { Sound_Play(Sound_Buffer, Sound_BuffSize); BuffIdx=0; } + Sound_Buffer[BuffIdx++] = 0x8000; } + if(BuffIdx) { Sound_Play(Sound_Buffer, BuffIdx); BuffIdx=0; } +} + +static uint16_t Sound_Phase=0; + +void Sound_Beep(int16_t Freq, uint16_t Len, int16_t Ampl) +{ int BuffIdx=0; + for( ; Len--; ) + { if(BuffIdx>=Sound_BuffSize) { Sound_Play(Sound_Buffer, Sound_BuffSize); BuffIdx=0; } // if buffer full then play it + int16_t Sig = IntSine(Sound_Phase)>>16; // 16-bit sine + // int16_t Sig = Isin(Sound_Phase); // 13-bit, +/-4096 range sine + Sound_Phase += Freq; // increment the Phase + // Sig = ((int32_t)Sig*Ampl+0x1000)>>13; // multiply by requested Amplitude + Sig>>=1; + uint16_t DAC = 0x8000+Sig; // DAC = (DAC&0xFF00) | (DAC>>8); + Sound_Buffer[BuffIdx++] = DAC; } + if(BuffIdx) { Sound_Play(Sound_Buffer, BuffIdx); BuffIdx=0; } +} + +#endif + //-------------------------------------------------------------------------------------------------------- // OLED display @@ -833,7 +1219,7 @@ esp_err_t OLED_Clear(uint8_t DispIdx) return espRc; } #endif -#ifdef WITH_U8G2 +#ifdef WITH_U8G2_OLED static i2c_cmd_handle_t U8G2_Cmd; @@ -913,79 +1299,23 @@ static uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t a u8g2_t U8G2_OLED; -void U8G2_DrawLogo(u8g2_t *OLED) // draw logo and hardware options in software -{ - u8g2_DrawCircle(OLED, 96, 32, 30, U8G2_DRAW_ALL); - u8g2_DrawCircle(OLED, 96, 32, 34, U8G2_DRAW_UPPER_RIGHT); - u8g2_DrawCircle(OLED, 96, 32, 38, U8G2_DRAW_UPPER_RIGHT); - // u8g2_SetFont(OLED, u8g2_font_open_iconic_all_4x_t); - // u8g2_DrawGlyph(OLED, 64, 32, 0xF0); - u8g2_SetFont(OLED, u8g2_font_ncenB14_tr); - u8g2_DrawStr(OLED, 74, 31, "OGN"); - u8g2_SetFont(OLED, u8g2_font_8x13_tr); - u8g2_DrawStr(OLED, 69, 43, "Tracker"); - -#ifdef WITH_FollowMe - u8g2_DrawStr(OLED, 0, 16 ,"FollowMe"); -#endif -#ifdef WITH_TTGO - u8g2_DrawStr(OLED, 0, 16 ,"TTGO"); -#endif -#ifdef WITH_HELTEC - u8g2_DrawStr(OLED, 0, 16 ,"HELTEC"); -#endif -#ifdef WITH_TBEAM - u8g2_DrawStr(OLED, 0, 16 ,"T-BEAM"); -#endif - -#ifdef WITH_GPS_MTK - u8g2_DrawStr(OLED, 0, 28 ,"MTK GPS"); -#endif -#ifdef WITH_GPS_UBX - u8g2_DrawStr(OLED, 0, 28 ,"UBX GPS"); -#endif -#ifdef WITH_GPS_SRF - u8g2_DrawStr(OLED, 0, 28 ,"SRF GPS"); -#endif - -#ifdef WITH_RFM95 - u8g2_DrawStr(OLED, 0, 40 ,"RFM95"); -#endif -#ifdef WITH_RFM69 - u8g2_DrawStr(OLED, 0, 40 ,"RFM69"); -#endif - -#ifdef WITH_BMP180 - u8g2_DrawStr(OLED, 0, 52 ,"BMP180"); -#endif -#ifdef WITH_BMP280 - u8g2_DrawStr(OLED, 0, 52 ,"BMP280"); -#endif -#ifdef WITH_BME280 - u8g2_DrawStr(OLED, 0, 52 ,"BME280"); -#endif - -#ifdef WITH_BT_SPP - u8g2_DrawStr(OLED, 0, 64 ,"BT SPP"); -#endif -} - void U8G2_Init(void) { -#ifdef WITH_FollowMe +#ifdef WITH_U8G2_SH1106 u8g2_Setup_sh1106_i2c_128x64_noname_f(&U8G2_OLED, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb); #else u8g2_Setup_ssd1306_i2c_128x64_noname_f(&U8G2_OLED, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb); #endif u8x8_SetI2CAddress(&U8G2_OLED.u8x8, OLED_I2C_ADDR); u8g2_InitDisplay(&U8G2_OLED); +#ifdef WITH_U8G2_FLIP + u8g2_SetDisplayRotation(&U8G2_OLED, U8G2_R2); // flip the display +#endif u8g2_SetPowerSave(&U8G2_OLED, 0); - u8g2_ClearBuffer(&U8G2_OLED); - // u8g2_DrawBox (&U8G2_OLED, 0, 26, 80, 6); - // u8g2_DrawFrame(&U8G2_OLED, 0, 26, 100, 6); - U8G2_DrawLogo(&U8G2_OLED); - u8g2_SendBuffer(&U8G2_OLED); + // u8g2_ClearBuffer(&U8G2_OLED); + // U8G2_DrawLogo(&U8G2_OLED); + // u8g2_SendBuffer(&U8G2_OLED); } #endif @@ -1019,12 +1349,14 @@ esp_err_t SD_Mount(void) static esp_err_t SD_Init(void) { Host = SDSPI_HOST_DEFAULT(); + // Host.max_freq_khz = SDMMC_FREQ_PROBING; + SlotConfig = SDSPI_SLOT_CONFIG_DEFAULT(); SlotConfig.gpio_miso = PIN_SD_MISO; SlotConfig.gpio_mosi = PIN_SD_MOSI; SlotConfig.gpio_sck = PIN_SD_SCK; SlotConfig.gpio_cs = PIN_SD_CS; - SlotConfig.dma_channel = 2; // otherwise it conflicts with RFM SPI + SlotConfig.dma_channel = SD_SPI_DMA; // otherwise it conflicts with RFM SPI or LCD SPI return SD_Mount(); } // ESP_OK => all good, ESP_FAIL => failed to mount file system, other => failed to init. the SD card #endif // WITH_SD @@ -1072,12 +1404,30 @@ void LED_TimerCheck(uint8_t Ticks) #endif } -bool Button_SleepRequest = 0; +// bool Button_SleepRequest = 0; -uint32_t Button_PressTime=0; // [ms] counts for how long the button is kept pressed -uint32_t Button_ReleaseTime=0; +#ifdef WITH_SLEEP +static bool SleepPending = 0; +extern void SleepIn(void); +extern void SleepOut(void); -const int8_t Button_FilterTime = 20; // [ms] +void Sleep(void) +{ Format_String(CONS_UART_Write, "Sleep...\n"); +#ifdef WITH_LONGPRESS_SLEEP + gpio_wakeup_enable(PIN_BUTTON, GPIO_INTR_LOW_LEVEL); // _NEGEDGE ? +#endif + esp_sleep_enable_gpio_wakeup(); + vTaskDelay(100); + esp_light_sleep_start(); + gpio_wakeup_enable(PIN_BUTTON, GPIO_INTR_DISABLE); + Format_String(CONS_UART_Write, "Wake up !\n"); } + +#endif + +static uint32_t Button_PressTime=0; // [ms] counts for how long the button is kept pressed +static uint32_t Button_ReleaseTime=0; + +const int8_t Button_FilterTime = 20; // [ms] anti-glitch filter width const int8_t Button_FilterThres = Button_FilterTime/2; static int8_t Button_Filter=(-Button_FilterTime); @@ -1099,26 +1449,27 @@ static int8_t Button_Filter=(-Button_FilterTime); // #endif // } + static uint32_t Button_keptPressed(uint8_t Ticks) { uint32_t ReleaseTime=0; - Button_PressTime+=Ticks; + Button_PressTime+=Ticks; // count for how long the button is kept pressed // Button_SleepRequest = Button_PressTime>=30000; // [ms] setup SleepRequest if button pressed for >= 4sec - // if(Button_PressTime>=32000) - // { Format_String(CONS_UART_Write, "Sleep in 2 sec\n"); - // vTaskDelay(2000); - // Sleep(); } - if(Button_ReleaseTime) +#ifdef WITH_LONGPRESS_SLEEP + if(!SleepPending && Button_PressTime>=4000) + { SleepIn(); SleepPending=1; } +#endif + if(Button_ReleaseTime) // if release-time counter non-zero { // Format_String(CONS_UART_Write, "Button pressed: released for "); // Format_UnsDec(CONS_UART_Write, Button_ReleaseTime, 4, 3); // Format_String(CONS_UART_Write, "sec\n"); - ReleaseTime=Button_ReleaseTime; + ReleaseTime=Button_ReleaseTime; // then return the release time Button_ReleaseTime=0; } return ReleaseTime; } // [ms] when button was pressed, return the release time static uint32_t Button_keptReleased(uint8_t Ticks) { uint32_t PressTime=0; - Button_ReleaseTime+=Ticks; - if(Button_PressTime) + Button_ReleaseTime+=Ticks; // count release time + if(Button_PressTime) // if pressed-time non-zero { // Format_String(CONS_UART_Write, "Button released: pressed for "); // Format_UnsDec(CONS_UART_Write, Button_PressTime, 4, 3); // Format_String(CONS_UART_Write, "sec\n"); @@ -1126,7 +1477,13 @@ static uint32_t Button_keptReleased(uint8_t Ticks) // { Format_String(CONS_UART_Write, "Sleep in 2 sec\n"); // vTaskDelay(2000); // Sleep(); } - PressTime=Button_PressTime; + PressTime=Button_PressTime; // return the pressed-time +#ifdef WITH_SLEEP + if(SleepPending) + { Sleep(); + SleepOut(); + SleepPending=0; } +#endif Button_PressTime=0; } return PressTime; } // [ms] when button is released, return the press time @@ -1136,15 +1493,15 @@ int32_t Button_TimerCheck(uint8_t Ticks) #ifdef PIN_BUTTON // CONS_UART_Write(Button_isPressed()?'^':'_'); if(Button_isPressed()) - { Button_Filter+=Ticks; if(Button_Filter>Button_FilterTime) Button_Filter=Button_FilterTime; - if(Button_Filter>=Button_FilterThres) - { uint32_t ReleaseTime=Button_keptPressed(Ticks); + { Button_Filter+=Ticks; if(Button_Filter>Button_FilterTime) Button_Filter=Button_FilterTime; // increment and saturate the counter + if(Button_Filter>=Button_FilterThres) // if above the threshold + { uint32_t ReleaseTime=Button_keptPressed(Ticks); // count for how long it is being kept pressed if(ReleaseTime) PressReleaseTime=(-(int32_t)ReleaseTime);; } } else - { Button_Filter-=Ticks; if(Button_Filter<(-Button_FilterTime)) Button_Filter=(-Button_FilterTime); - if(Button_Filter<=(-Button_FilterThres)) - { uint32_t PressTime=Button_keptReleased(Ticks); + { Button_Filter-=Ticks; if(Button_Filter<(-Button_FilterTime)) Button_Filter=(-Button_FilterTime); // decrement and saturate + if(Button_Filter<=(-Button_FilterThres)) // if below the threshold + { uint32_t PressTime=Button_keptReleased(Ticks); // count for how long it is being kept released if(PressTime) PressReleaseTime=PressTime; } } #endif @@ -1163,15 +1520,26 @@ void vApplicationTickHook(void) // RTOS timer tick hook */ //-------------------------------------------------------------------------------------------------------- -// ADC +// AXP192 +#ifdef WITH_AXP +AXP192 AXP; +#endif + +#ifdef WITH_BQ +BQ24295 BQ; +#endif + +//-------------------------------------------------------------------------------------------------------- +// ADC static esp_adc_cal_characteristics_t *ADC_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t)); #ifdef WITH_TBEAM -static adc1_channel_t ADC_channel = ADC1_GPIO35_CHANNEL; +static adc1_channel_t ADC_Chan_Batt = ADC1_GPIO35_CHANNEL; +static adc1_channel_t ADC_Chan_Knob = ADC1_GPIO34_CHANNEL; #else -static adc1_channel_t ADC_channel = ADC1_GPIO36_CHANNEL; +static adc1_channel_t ADC_Chan_Batt = ADC1_GPIO36_CHANNEL; #endif static const adc_atten_t ADC_atten = ADC_ATTEN_DB_11; static const adc_unit_t ADC_unit = ADC_UNIT_1; @@ -1181,22 +1549,39 @@ static int ADC_Init(void) { // if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) // Check TP is burned into eFuse // if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) // Check Vref is burned into eFuse adc1_config_width(ADC_WIDTH_BIT_12); - adc1_config_channel_atten(ADC_channel, ADC_atten); + adc1_config_channel_atten(ADC_Chan_Batt, ADC_atten); esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_unit, ADC_atten, ADC_WIDTH_BIT_12, ADC_Vref, ADC_characs); // calibrate ADC1 return 0; } +#ifdef WITH_AXP +uint16_t BatterySense(int Samples) +{ return AXP.readBatteryVoltage(); } // [mV] +#else uint16_t BatterySense(int Samples) { uint32_t RawVoltage=0; for( int Idx=0; Idx=Bias) Volt-=Bias; + return Volt; } // [mV] +#endif +#ifdef WITH_TBEAM +uint16_t KnobSense(int Samples) +{ uint16_t RawVoltage=0; + for( int Idx=0; Idx inline uint8_t I2C_Write(uint8_t Bus, uint8_t Addr, uint8_t Reg, Type &Object, uint8_t Wait=10) { return I2C_Write(Bus, Addr, Reg, (uint8_t *)&Object, sizeof(Type), Wait); } @@ -163,5 +192,22 @@ template uint8_t I2C_Restart(uint8_t Bus); uint16_t BatterySense(int Samples=4); // [mV] +#ifdef WITH_TBEAM +uint16_t KnobSense (int Samples=4); // [ADC] +#endif + +#ifdef WITH_BQ +#include "bq24295.h" +extern BQ24295 BQ; +#endif + +#ifdef WITH_AXP +#include "axp192.h" +extern AXP192 AXP; +#endif + +#ifdef WITH_SLEEP +void Sleep(void); +#endif #endif // __HAL_H__ diff --git a/main/knob.h b/main/knob.h new file mode 100644 index 0000000..d44c142 --- /dev/null +++ b/main/knob.h @@ -0,0 +1,11 @@ +#ifdef WITH_KNOB + +extern volatile uint8_t KNOB_Tick; + +#ifdef __cplusplus + extern "C" +#endif +void vTaskKNOB(void* pvParameters); + +#endif + diff --git a/main/lcd_battery.h b/main/lcd_battery.h new file mode 100644 index 0000000..83c9047 --- /dev/null +++ b/main/lcd_battery.h @@ -0,0 +1,50 @@ +#include +#include "st7789.h" + +class LCD_BattSymb +{ public: + static const uint8_t Width = 24; + static const uint8_t Border = 2; + static const uint8_t Cells = 5; + static const uint8_t CellWidth = Width-Border*4; + static const uint8_t CellLength = CellWidth*3/4; + static const uint8_t Length = (CellLength+Border)*Cells+Border*3; + static const uint8_t TipLen = Border*2; + int16_t Xpos, Ypos; + int16_t FrameCol, CellCol, FillCol; + uint8_t CellMap; // which cells are to be displayed + uint8_t Flags; // which cells and other elements are displayed + + public: + LCD_BattSymb() { CellMap=0; Xpos=0; Ypos=0; FillCol=RGB565_LIGHTGREY; CellCol=RGB565_DARKGREEN; FrameCol=RGB565_BLACK; Flags=0; } + + void setLevel(uint8_t Level) + { CellMap=0; uint8_t Mask=1; + if(Level>Cells) Level=Cells; + for(uint8_t Cell=0; Cell + +#include +#include +#include + +#include "intmath.h" + +// #define DEBUG_PRINT + +#include "relpos.h" + +// ======================================================================================================= + +class LookOut_Target +{ public: + uint32_t ID; // ID of the target = aircraft ID + Acft_RelPos Pos; // Position relative to the reference Lat/Lon/Alt + int8_t Pred; // [0.5sec] amount of time by which this position has been predicted/extrapolated + uint8_t GpsPrec; // GPS position error including prediction + + union + { uint8_t Flags; // flags + struct + { bool isMoving :1; // is a moving target + bool Alloc :1; // is allocated or not (a free slot, where a new target can go into) + // bool Reported :1; // this target has already been reported with $PFLAA + } ; + } ; + + union + { uint32_t Rank; // rank: lowest means shorter time margin, shorter distance margin thus bigger thread + struct + { uint16_t DistMargin; // [0.5m] remaining safety margin: if positive, then considered no thread at all + uint8_t TimeMargin; // [0.5s] time to target (if no distance margin left) + uint8_t WarnLevel; // assigned warning level: 0, 1, 2 or 3 + } ; + } ; + + int16_t dX; // [0.5m] relative position of target + int16_t dY; // [0.5m] + int16_t dZ; // [0.5m] + + int16_t Vx; // [0.5m/s] relative speed of target + int16_t Vy; // [0.5m/s] + int16_t Vz; // [0.5m/s] + + // int16_t Ax; // [1/16m/s^2] relative acceleration of target + // int16_t Ay; // [1/16m/s^2] + + uint16_t HorDist; // [0.5m] relltive hor. distance to target + int16_t MissTime; // [0.5s] estimated closest approach time + uint16_t MissDist; // [0.5m] estimated closest approach distance + + public: + void Clear(void) { Pred=0; Flags=0; HorDist=0; MissDist=0; Rank=0xFFFF; } + + // uint16_t HorRelSpeed(void) const { } + + uint32_t DistSqr(void) const { return (int32_t)dX*dX + (int32_t)dY*dY + (int32_t)dZ*dZ; } // [0.25m^2] Distance-square to the Target + uint32_t VelSqr (void) const { return (int32_t)Vx*Vx + (int32_t)Vy*Vy + (int32_t)Vz*Vz; } // [0.5m/s^2] Relative velocity square of the Target + + void calcVel(Acft_RelPos &RefPos) // calc. relative target velocity against a reference position + { Pos.getSpeedVector(Vx, Vy); // get horizontal speed vector from Speed and Heading + int16_t rVx, rVy; RefPos.getSpeedVector(rVx, rVy); // same for the reference position + Vx -= rVx; Vy -=rVy; // difference + Vz = Pos.Climb-RefPos.Climb; } // vertical different + + int16_t getBearing (void) const { return IntAtan2(dY, dX); } // bearing to the target + uint16_t getHorDist (void) const { return Acft_RelPos::FastDistance(dX, dY); } // relative horizontal distance to the target + uint16_t getRelHorSpeed(void) const { return Acft_RelPos::FastDistance(Vx, Vy); } // relative horiz. speed of the target + + void Print(void) const + { printf("%08lX/%+5.1fs/%7.1fm/%7.1fm/%5.1fs/%5.1fm/%+5.1fs/w%d", (long int)ID, + 0.5*Pred, 0.5*DistMargin, 0.5*HorDist, 0.5*TimeMargin, 0.5*MissDist, 0.5*MissTime, WarnLevel); + // printf(" [%+7.1f,%+7.1f,%+7.1f]m [%+5.1f,%+5.1f,%+5.1f]m/s", 0.5*dX, 0.5*dY, 0.5*dZ, 0.5*Vx, 0.5*Vy, 0.5*Vz); + // printf(" [%+5.2f,%+5.2f]m/s^-2", 0.0625*Ax, 0.0625*Ay); + Pos.Print(); } + + uint8_t Print(char *output) + { uint8_t Len=0; + + return Len; } + + uint8_t WritePFLAA(char *NMEA) + { uint8_t Len=0; + Len+=Format_String(NMEA+Len, "$PFLAA,"); // sentence name and alarm-level (but no alarms for trackers) + NMEA[Len++]='0'+WarnLevel; + NMEA[Len++]=','; + Len+=Format_SignDec(NMEA+Len, dX/2); + NMEA[Len++]=','; + Len+=Format_SignDec(NMEA+Len, dY/2); + NMEA[Len++]=','; + Len+=Format_SignDec(NMEA+Len, dZ/2); // [m] relative altitude + NMEA[Len++]=','; + NMEA[Len++]='0'+((ID>>24)&0x03); // address-type (3=OGN) + NMEA[Len++]=','; + uint32_t Addr = ID&0xFFFFFF; // [24-bit] address + Len+=Format_Hex(NMEA+Len, (uint8_t)(Addr>>16)); // 24-bit address: RND, ICAO, FLARM, OGN + Len+=Format_Hex(NMEA+Len, (uint16_t)Addr); + NMEA[Len++]=','; + Len+=Format_UnsDec(NMEA+Len, (225*Pos.Heading+0x800)>>12, 4, 1); // [deg] heading (by GPS) + NMEA[Len++]=','; + Len+=Format_SignDec(NMEA+Len, (225*Pos.Turn+0x800)>>12, 2, 1); // [deg/sec] turn rate + NMEA[Len++]=','; + Len+=Format_UnsDec(NMEA+Len, 5*Pos.Speed, 2, 1); // [approx. m/s] ground speed + NMEA[Len++]=','; + Len+=Format_SignDec(NMEA+Len, 5*Pos.Climb, 2, 1); // [m/s] climb/sink rate + NMEA[Len++]=','; + NMEA[Len++]=HexDigit(ID>>26); // [0..F] aircraft-type: 1=glider, 2=tow plane, etc. + Len+=NMEA_AppendCheckCRNL(NMEA, Len); + NMEA[Len]=0; + return Len; } // return number of formatted characters + +} ; + +// ======================================================================================================= + +class LookOut +{ public: + uint32_t ID; // ID of me (own aircraft) + Acft_RelPos Pos; // Position relative to the reference Lat/Lon/Alt + int8_t Pred; // [0.5sec] amount of time by which position has been predicted/extrapolated + + union + { uint8_t Flags; + struct + { uint8_t GpsPrec :6; // GPS position precision + bool isMoving :1; // own position moving + bool hasPosition :1; // own position is valid + } ; + } ; + + uint8_t WarnLevel; // highest warning level of all the targets + uint8_t WeakestIdx; // index for the weakest target (or a not allocated target) + uint32_t WeakestRank; // rank of the weakest target + + uint8_t Targets; // [aircrafts] actual number of targets monitored + uint8_t WorstTgtIdx; // [] most dangereous target + uint8_t WorstTgtTime; // [0.5s] time to closest approach + + uint8_t RefTime; // [sec] ref. T for the local T,X,Y,Z coord. system + int16_t LatCos; // [2^-12] + // ref. X,Y,Z for the local T,X,Y,Z coord. system + int32_t RefLat; // [1/60000deg] + int32_t RefLon; // [1/60000deg] + int32_t RefAlt; // [m] + + const static uint8_t MaxTargets = 32; // maximum number of targets + LookOut_Target Target[MaxTargets]; // array of Targets + + const static int32_t DistRange = 7000; // [m] drop immediately anything beyond this distance + const static int16_t MinHorizSepar = 30; // [m] minimum horizontal separation + const static int16_t MinVertSepar = 20; // [m] minimum vertical separation + const static int16_t WarnTime = 20; // [sec] target warning prior to impact + + Acft_RelPos PredMe, PredTgt; // for temporary storage for predictions. + + char Line[80]; // for printing + + public: + + void Clear(void) + { Flags=0; ID=0; Pos.Clear(); Pred=0; + Targets=0; WeakestIdx=0; WeakestRank=0xFFFFFFFF; + WorstTgtIdx=0; WorstTgtTime=0xFF; + for(uint8_t Idx=0; IdxgetBearing()-Pos.Heading; } + + uint8_t WritePOGNA(char *NMEA, const LookOut_Target *Tgt) // Alert NMEA centence + { uint8_t Len=0; + Len+=Format_String(NMEA+Len, "$POGNA,"); + + Len+=NMEA_AppendCheckCRNL(NMEA, Len); + NMEA[Len]=0; + return Len; } + + void PrintPFLA(void) // print (for debug) $PFLAU and PFLAA + { WritePFLAU(Line); printf("%s", Line); + for(uint8_t Idx=0; Idx0) Tgt = Target + WorstTgtIdx; + uint8_t Len=0; + Len+=Format_String(NMEA+Len, "$PFLAU,"); + Len+=Format_UnsDec(NMEA+Len, Targets, 1); // number of targets received + NMEA[Len++]=','; + NMEA[Len++]='0'+hasPosition; // TX status + NMEA[Len++]=','; + NMEA[Len++]='0'+hasPosition; // GPS status: 0=no fix, 1=on the ground, 2=airborne + NMEA[Len++]=','; + NMEA[Len++]='1'; // power status: one could monitor the supply + NMEA[Len++]=','; + NMEA[Len++]='0'+WarnLevel; // Warning level: 0..3 + NMEA[Len++]=','; + if(Tgt) // [deg] relative bearing: -180..+180 + { Len+=Format_SignDec(NMEA+Len, ((int32_t)getRelBearing(Tgt)*45+0x1000)>>13, 1); } + NMEA[Len++]=','; + NMEA[Len++]='0'+((WarnLevel>0)<<1); // alarm-type: 0=none, 2=aircraft, 3=obstacle/zone/terrain + NMEA[Len++]=','; + if(Tgt) // [m] relative vertical distance + { Len+=Format_SignDec(NMEA+Len, (Tgt->dZ)>>1, 1); } + NMEA[Len++]=','; + if(Tgt) // [m] relative horizontal distance + { Len+=Format_UnsDec(NMEA+Len, (Tgt->HorDist)>>1, 1); } + if(Tgt) // ID + { NMEA[Len++]=','; + Len+=Format_Hex(NMEA+Len, Tgt->ID); } + Len+=NMEA_AppendCheckCRNL(NMEA, Len); + NMEA[Len]=0; + return Len; } + + void Print(void) const + { if(!hasPosition) return; + printf("Ref: %02d: [%+10.6f, %+11.6f]deg %ldm\n", RefTime, 0.0001/60*RefLat, 0.0001/60*RefLon, (long int)RefAlt); + printf("%08lX/%+5.1fs/ Margin/ HorDist/Margin/ Miss/ Miss/w%d", (long int)ID, 0.5*Pred, WarnLevel); Pos.Print(); + for(uint8_t Idx=0; IdxAlloc) Tgt->Print(); + } + } + + template + int32_t Start(OGNx_Packet &Me) + { Clear(); + ID = Me.getAddressAndType() | ((uint32_t)Me.Position.AcftType<<26) ; + RefTime = Me.Position.Time; + RefLat = Me.DecodeLatitude(); + RefLon = Me.DecodeLongitude(); + RefAlt = Me.DecodeAltitude(); + LatCos = Icos(GPS_Position::calcLatAngle16(RefLat)); + Pred=0; + return Pos.Read(Me, RefTime, RefLat, RefLon, RefAlt, LatCos, DistRange); } + + template + const LookOut_Target *ProcessOwn(OGNx_Packet &Me) // process my own position + { // printf("ProcessOwn() ... entry\n"); + if(hasPosition) // in my position is valid + { Pred=0; + if(Pos.Read(Me, RefTime, RefLat, RefLon, RefAlt, LatCos, DistRange)<0) // read the new position + { hasPosition = Start(Me)>=0; } // if this fails, attempt to start from the new position + } + else + { hasPosition = Start(Me)>=0; + } + + if(hasPosition) // if already started + { AdjustRefTime(Pos.T); // adjust time ref. point if needed + AdjustRefAlt(); // adjust vertical ref. altitude if needed + AdjustRefLatLon(Me); } // adjuest horizontal Lat/Lon position if needed. + + WarnLevel=0; + Targets=0; + WorstTgtIdx=0; + WorstTgtTime=0xFF; + for(uint8_t Idx=0; IdxAlloc) continue; // skip empty slots + if(Tgt->DistMargin==0) // those with no safety margin + { while(Tgt->Pos.T<=(Pos.T-4)) // bring closer in time to my (new) position + { Tgt->Pos.StepFwd2secs(); Tgt->Pred+=4; } + } + uint8_t Warn=calcTarget(Tgt); // (re)calculate the target + if(Warn) + { if(Warn>WarnLevel) WarnLevel=Warn; + if(Tgt->TimeMarginTimeMargin; WorstTgtIdx=Idx; } + } + Targets++; } + // printf("ProcessOwn() ... exit\n"); + // if(Targets==0) return 0; // return NULL if no targets are tracked + LookOut_Target *Tgt = Target+WorstTgtIdx; + if( (!Tgt->Alloc) || (Tgt->DistMargin>0) ) return 0; // return NULL if target is not a thread + return Tgt; } // return the pointer to the most dangerous target + + template + const LookOut_Target *ProcessTarget(OGNx_Packet &Packet) // process positions of other aircrafts + { // printf("ProcessTarget(%d) ... entry\n", WeakestIdx); + LookOut_Target *New = Target+WeakestIdx; // get a free or lowest rank slot + New->Clear(); + if(New->Pos.Read(Packet, RefTime, RefLat, RefLon, RefAlt, LatCos, DistRange)<0) return 0; // calculate the position against the reference position + uint32_t ID = Packet.getAddressAndType() | ((uint32_t)Packet.Position.AcftType<<26) ; // get ID + New->ID = ID; // set ID of this position + // printf("ProcessTarget() ... %08X\n", ID); + uint8_t OldIdx; + for(OldIdx=0; OldIdxTarget[WeakestIdx].Pos.T) return Target+OldIdx; // if position is not really older than stop processing this (not new) position + Target[OldIdx].Alloc=0; } // mark old position as "not allocated" + + New->Alloc=1; // mark this position as allocated + + AdjustRefTime(New->Pos.T); // possibly adjust the time reference after this new position time + // printf("ProcessTarget() ... AdjustRefTime()\n"); + + if(Pos.T<=(New->Pos.T-4)) // bring my position closer in time + { Pos.StepFwd2secs(); Pred+=4; } + + uint8_t Warn=calcTarget(New); // calculate the safety margin for the target + if(Warn>WarnLevel) WarnLevel=Warn; + // printf("ProcessTarget() ... calc()\n"); + + uint8_t MaxIdx=WeakestIdx; uint16_t Max=New->Rank; // look for the lowest rank position on the list + for( uint8_t Idx=MaxIdx; ; ) // go over targets + { Idx++; if(Idx>=MaxTargets) Idx=0; + if(Idx==WeakestIdx) break; // end the loop when back at New + LookOut_Target &Tgt = Target[Idx]; + if(!Tgt.Alloc) { MaxIdx=Idx; break; } // if unallocated target found: stop the search + if(Tgt.Rank==0xFFFF) { MaxIdx=Idx; break; } // if abs. weakest target found: stop the search + if(Tgt.Rank>=Max) { Max=Tgt.Rank; MaxIdx=Idx; } // if weaker rank found: note it + } + WeakestIdx=MaxIdx; // tqke the weakest slot for the nest time + + return New; } + + uint8_t calcTarget(LookOut_Target *Tgt) // calculate the savety margin for the (new) target + { + Tgt->TimeMargin=0xFF; // initially set inf. time margin + Tgt->WarnLevel=0; // warning level=0 + Tgt->MissTime=0; + Tgt->MissDist=0; + uint16_t Margin = calcVertMargin(Tgt); // [0.5m] calc. vertical margin + if(Margin==0) Margin = calcHorizMargin(Tgt); // [0.5m] if vertical margin iz zero then get horizontal margin + Tgt->DistMargin = Margin; // [0.5m] + if(Margin>0) // if there is still safety margin, no more calc. (dealloc. ?) + { // Tgt->TimeMargin = Margin/; + return 0; } // return warning level = 0 + // at this point the distance is possibly small enough so we may hit the target +#ifdef DEBUG_PRINT + printf("calcTarget(0x%08X) ... no abs. safety margin\n", Tgt->ID); +#endif + Tgt->calcVel(Pos); // calculate relative velocity + // uint32_t RelVelSqr = Tgt->VelSqr(); // [0.25(m/s)^2] velocity square + int16_t dT = Pos.T - Tgt->Pos.T; // [0.5s] we need to recalc. the distance if the target time is not same as mine + if(dT) // [0.5s] if time difference is non-zero + { int16_t Vx,Vy,Vz; // [0.5m/s] Target speed vector + Tgt->Pos.getSpeedVector(Vx, Vy); Vz=Tgt->Pos.Climb; // [0.5m/s] + Tgt->dX += (dT*Vx)>>1; // update Target relative distance + Tgt->dY += (dT*Vy)>>1; + Tgt->dZ += (dT*Vz)>>1; + Tgt->HorDist = Acft_RelPos::FastDistance(Tgt->dX, Tgt->dY); } // update Target horizontal distance + // uint32_t RelDistSqr = Tgt->DistSqr(); // [0.25m^2] distance square + // uint32_t WarnTimeSqr = (uint32_t)WarnTime*WarnTime; // [s] warning time square + // printf("calcTarget(0x%08X) ...\n", Tgt->ID); + // printf("RelDistSqr = %3.1fm^ RelVelSqr=%3.1f(m/s)^2 Time=%ds\n", 0.25*RelDistSqr, 0.25*RelVelSqr, RelVelSqr ? IntSqrt(RelDistSqr/RelVelSqr):0xFFFF); + // if((RelVelSqr*WarnTimeSqr)<=RelDistSqr) return 0; + uint16_t RelVel = Acft_RelPos::FastDistance(Tgt->Vx, Tgt->Vy, Tgt->Vz); // [0.5m/s] + uint16_t MinMissDist = 4*RelVel+Pos.Error + Tgt->Pos.Error + 2*MinHorizSepar; // [0.5m] +#ifdef DEBUG_PRINT + printf("Target: [%+4.1f, %+4.1f, %+4.1f] = %3.1fm/s MinMissDist=%3.1fm\n", + 0.5*Tgt->Vx, 0.5*Tgt->Vy, 0.5*Tgt->Vz, 0.5*RelVel, 0.5*MinMissDist); +#endif + + PredMe=Pos; PredTgt=Tgt->Pos; // copy my position and the target to temporary variables + int16_t TimeMargin = PredMe.StepTillMinSepar(PredTgt, MinMissDist, 2*(WarnTime+2)); // predict when minimum separation is reached +#ifdef DEBUG_PRINT + printf("StepTillMinSepar(0x%08X, %3.1fm, %1ds) => %+4.1fs\n", Tgt->ID, 0.5*MinMissDist, WarnTime+2, 0.5*TimeMargin); +#endif + Tgt->TimeMargin=TimeMargin; // store the time margin till minimum separation + Tgt->MissTime=TimeMargin; + if(TimeMargin>(2*WarnTime)) return 0; // if time-to-margin longer than warning time then return no warning + Tgt->WarnLevel=1; // otherwise set already the first warning level + if(TimeMargin>WarnTime) return Tgt->WarnLevel; // is time-to-margin longer than half the warning time, then stop calculations here, return 1st warning level + +#ifdef DEBUG_PRINT + printf("Me :"); PredMe.Print(); + printf("Tgt:"); PredTgt.Print(); +#endif + for(uint8_t Count=3; Count; Count--) + { int16_t MissTime = PredMe.MissTime(PredTgt, WarnTime); +#ifdef DEBUG_PRINT + printf("%d: MissTime = %+4.1fs\n", Count, 0.5*MissTime); +#endif + if(abs(MissTime)<=1) break; + PredMe.StepFwd(MissTime); + PredTgt.StepFwd(PredMe.T-PredTgt.T); } +#ifdef DEBUG_PRINT + printf("Me :"); PredMe.Print(); + printf("Tgt:"); PredTgt.Print(); +#endif + Tgt->MissDist = PredMe.FastDistance(PredTgt); + Tgt->MissTime = PredMe.T-Pos.T; +#ifdef DEBUG_PRINT + printf("MissTime = %+4.1f, MissDist = %4.1f\n", 0.5*Tgt->MissTime, 0.5*Tgt->MissDist); +#endif + if( (Tgt->MissTime<0) || (Tgt->MissTime>(2*WarnTime)) || (Tgt->MissDist>MinMissDist) ) Tgt->WarnLevel=0; + else if(Tgt->MissDist<(2*MinHorizSepar)) { Tgt->WarnLevel=2; if(Tgt->MissTime<(2*WarnTime/3)) Tgt->WarnLevel=3; } +#ifdef DEBUG_PRINT + printf("calcTarget(%08X) V=[%+5.1f, %+5.1f, %+5.1f]m/s D=[%+7.1f, %+7.1f, %+7.1f]m MissTime=%5.1fsec MissDist=%6.1fm\n", + Tgt->ID, 0.5*Tgt->Vx, 0.5*Tgt->Vy, 0.5*Tgt->Vz, 0.5*Tgt->dX, 0.5*Tgt->dY, 0.5*Tgt->dZ, 0.5*Tgt->MissTime, 0.5*Tgt->MissDist); +#endif + return Tgt->WarnLevel; } + + uint16_t calcVertMargin(LookOut_Target *Tgt) // calculate vertical savety margin + { Tgt->dZ = Tgt->Pos.Z - Pos.Z; // [0.5m] relative vertical distance + Tgt->Vz = Tgt->Pos.Climb - Pos.Climb; // [0.5ms/s] relative vertical speed + int16_t VertError = Pos.Error+Tgt->Pos.Error; VertError+=VertError/2; // [0.5m] est. total vertical error + VertError += 2*MinVertSepar; // [0.5m] + if(abs(Tgt->dZ)<=VertError) return 0; // if vertical distance less than margin required: return zero margin + if(Tgt->dZ>0) { if(Tgt->Vz>=0) return Tgt->dZ-VertError; } // if target is higher and is climbing return relative vertical distance + else { if(Tgt->Vz<=0) return -Tgt->dZ-VertError; } // if target is lower and is falling, return like above + int16_t dT = Tgt->Pos.T - Pos.T; // [0.5sec] time diff. in measured positions + int32_t MaxAlt = ( (int32_t)Tgt->Vz * (2*(WarnTime+4)+abs(dT)) )>>1; // [0.5m] max. altitude change within the warning time + MaxAlt = abs(MaxAlt) + VertError; // [0.5m] + // printf("calcVertMargin() dAlt=%+4.1f dT=%+4.1f Climb=%+4.1f MaxAlt=%+4.1f\n", 0.5*Tgt->dZ, 0.5*dT, 0.5*Tgt->Vz, 0.5*MaxAlt); + if(Tgt->dZ>0) // if target is above + { if(Tgt->dZdZ-MaxAlt); } // [0.5m] + else // if target is below + { if((-Tgt->dZ)dZ-MaxAlt); } // [0.5m] + } // return the vertical margin: if positive: we are safe, if zero: we are too close + + uint16_t calcHorizMargin(LookOut_Target *Tgt) + { Tgt->dX = Tgt->Pos.X - Pos.X; // [0.5m] relative distance + Tgt->dY = Tgt->Pos.Y - Pos.Y; // [0.5m] + Tgt->HorDist = Acft_RelPos::FastDistance(Tgt->dX, Tgt->dY); // [0.5m] estimate horizontal distance + int16_t HorError = Pos.Error+Tgt->Pos.Error; // [0.5m] sum GPS error from me and the target + HorError += 2*MinHorizSepar; // [0.5m] add the min. separation required + int16_t dT = abs(Tgt->Pos.T - Pos.T); // [0.5m] time difference between my data and target data + int32_t MaxDistT = ((int32_t)Tgt->Pos.Speed * (2*(WarnTime+4)+dT))>>1; // [0.5m] possible distance covered by the target + int32_t MaxDistM = ((int32_t) Pos.Speed * (2*(WarnTime+4)+dT))>>1; // [0.5m] possible distance covered by me + int32_t MaxDist = MaxDistT + MaxDistM + HorError; // [0.5m] add horizontal separation + if(MaxDistHorDist) return Tgt->HorDist-MaxDist; // [0.5m] return the (positive) difference: we are safe + return 0; } // zero-margin => bad ! + + void AdjustRefTime(int16_t TimeDelta) // adjust the time reference point + { if(TimeDelta<(2*12)) return; // in more than 10sec into the future from the current reference + TimeDelta/=2; + RefTime+=TimeDelta; if(RefTime>=60) RefTime-=60; // shift time reference + Pos.T-=2*TimeDelta; // shift the relative time on my own position + if(Pos.T<(-2*60)) hasPosition=0; // if older than 60sec declare "no position" + for(uint8_t Idx=0; Idx + void AdjustRefLatLon(OGNx_Packet &Me) // shift the horizontal reference point when we get too far off + { if( (fabs(Pos.X)<(2*1000)) && (fabs(Pos.Y)<(2*500)) ) return; + int32_t LatDist, LonDist; + if(Me.calcDistanceVector(LatDist, LonDist, RefLat, RefLon, LatCos, DistRange)<0) { return; } + RefLat = Me.DecodeLatitude(); + RefLon = Me.DecodeLongitude(); + LatDist*=2; + LonDist*=2; + Pos.X -= LatDist; + Pos.Y -= LonDist; + for(uint8_t Idx=0; Idx0) + { if(Parameters.SaveToFlash) Parameters.WriteToNVS(); } + } +#endif + #ifdef WITH_BT_SPP { int32_t Err=BT_SPP_Init(); // start BT SPP // #ifdef DEBUG_PRINT @@ -54,16 +65,25 @@ void app_main(void) #ifdef WITH_LOG xTaskCreate(vTaskLOG , "LOG", 2560, 0, tskIDLE_PRIORITY+1, 0); #endif - xTaskCreate(vTaskPROC, "PROC", 4096, 0, tskIDLE_PRIORITY+3, 0); + xTaskCreate(vTaskPROC, "PROC", 2048, 0, tskIDLE_PRIORITY+3, 0); xTaskCreate(vTaskGPS, "GPS", 2048, 0, tskIDLE_PRIORITY+4, 0); #if defined(WITH_BMP180) || defined(WITH_BMP280) || defined(WITH_BME280) || defined(WITH_MS5607) xTaskCreate(vTaskSENS, "SENS", 2048, 0, tskIDLE_PRIORITY+4, 0); #endif +#ifdef WITH_KNOB + xTaskCreate(vTaskKNOB, "KNOB", 2048, 0, tskIDLE_PRIORITY+3, 0); +#endif #ifdef WITH_AERO xTaskCreate(vTaskAERO, "AERO", 2048, 0, tskIDLE_PRIORITY+4, 0); #endif #ifdef WITH_WIFI xTaskCreate(vTaskWIFI, "WIFI", 4096, 0, tskIDLE_PRIORITY+2, 0); +#endif +#if defined(WITH_OLED) || defined(WITH_U8G2_OLED) || defined(WITH_ST7789) || defined(WITH_ILI9341) + xTaskCreate(vTaskDISP, "DISP", 2048, 0, tskIDLE_PRIORITY+2, 0); +#endif +#ifdef WITH_SOUND + xTaskCreate(vTaskSOUND, "SOUND", 2048, 0, tskIDLE_PRIORITY+3, 0); #endif // xTaskCreate(vTaskCTRL, "CTRL", 1536, 0, tskIDLE_PRIORITY+2, 0); vTaskCTRL(0); // run directly the CTRL task, instead of creating a separate one. diff --git a/main/nmea.h b/main/nmea.h index c85272e..94c71cd 100644 --- a/main/nmea.h +++ b/main/nmea.h @@ -11,7 +11,7 @@ inline uint8_t NMEA_AppendCheckCRNL(char *NMEA, uint8_t Len) { return NMEA_Appen class NMEA_RxMsg // receiver for the NMEA sentences { public: - static const uint8_t MaxLen=96; // maximum length + static const uint8_t MaxLen=104; // maximum length static const uint8_t MaxParms=24; // maximum number of parameters (commas) uint8_t Data[MaxLen]; // the message itself uint8_t Len; // number of bytes @@ -141,7 +141,7 @@ inline uint8_t NMEA_AppendCheckCRNL(char *NMEA, uint8_t Len) { return NMEA_Appen if(Data[4]!='G') return 0; return Data[5]=='A'; } - uint8_t isGPGSA(void) const // GPS satellite data + uint8_t isGPGSA(void) const // { if(!isGP()) return 0; if(Data[3]!='G') return 0; if(Data[4]!='S') return 0; @@ -159,6 +159,24 @@ inline uint8_t NMEA_AppendCheckCRNL(char *NMEA, uint8_t Len) { return NMEA_Appen if(Data[4]!='S') return 0; return Data[5]=='A'; } + uint8_t isGxGSV(void) const // GPS satellite data + { if(!isGx()) return 0; + if(Data[3]!='G') return 0; + if(Data[4]!='S') return 0; + return Data[5]=='V'; } + + uint8_t isGPGSV(void) const // GPS satellite data + { if(!isGP()) return 0; + if(Data[3]!='G') return 0; + if(Data[4]!='S') return 0; + return Data[5]=='V'; } + + uint8_t isGNGSV(void) const // GPS satellite data + { if(!isGN()) return 0; + if(Data[3]!='G') return 0; + if(Data[4]!='S') return 0; + return Data[5]=='V'; } + uint8_t isGPTXT(void) const // GPS satellite data { if(!isGP()) return 0; if(Data[3]!='T') return 0; diff --git a/main/ogn.h b/main/ogn.h index 9096a4e..df47c03 100644 --- a/main/ogn.h +++ b/main/ogn.h @@ -22,9 +22,13 @@ #include "format.h" +#include "ognconv.h" + #include "ogn1.h" // OGN v1 #include "ogn2.h" // OGN v2 +#include "atmosphere.h" + // --------------------------------------------------------------------------------------------------------------------- template @@ -607,8 +611,9 @@ template void cleanTime(uint8_t Time) // clean up slots of given Time { for(int Idx=0; Idx=60) clean(Idx); } } @@ -624,15 +629,15 @@ template uint8_t Print(char *Out) { uint8_t Len=0; - for(uint8_t Idx=0; Idx0; // read time and check if same as the GGA says @@ -964,7 +1022,7 @@ class GPS_Position else if(TimeDiff>=3000) TimeDiff-=6000; return TimeDiff; } // [0.01s] - int16_t calcDifferences(GPS_Position &RefPos) // calculate climb rate and turn rate with an earlier reference position + int16_t calcDifferentials(GPS_Position &RefPos) // calculate climb rate and turn rate with an earlier reference position { ClimbRate=0; TurnRate=0; if(RefPos.FixQuality==0) return 0; int16_t TimeDiff = calcTimeDiff(RefPos); @@ -974,19 +1032,29 @@ class GPS_Position ClimbRate = Altitude-RefPos.Altitude; if(hasBaro && RefPos.hasBaro && (abs(Altitude-StdAltitude)<2500) ) { ClimbRate = StdAltitude-RefPos.StdAltitude; } - if(TimeDiff==100) + Accel = Speed-RefPos.Speed; + if(TimeDiff==20) + { ClimbRate*=5; + TurnRate *=5; + Accel *=5; } + else if(TimeDiff==50) + { ClimbRate*=2; + TurnRate *=2; + Accel *=2; } + else if(TimeDiff==100) { } else if(TimeDiff==200) { ClimbRate=(ClimbRate+1)>>1; - TurnRate=(TurnRate+1)>>1; } - else + TurnRate=( TurnRate+1)>>1; + Accel =( Accel +1)>>1; } + else if(TimeDiff!=0) { ClimbRate = ((int32_t)ClimbRate*100)/TimeDiff; - TurnRate = ((int32_t)TurnRate *100)/TimeDiff; } + TurnRate = ((int32_t) TurnRate*100)/TimeDiff; + Accel = ((int32_t) Accel *100)/TimeDiff; } return TimeDiff; } // [0.01s] void Write(MAV_GPS_RAW_INT *MAV) const -// { MAV->time_usec = (int64_t)1000000*getUnixTime()+10000*FracSec; - { MAV->time_usec = getUnixTime_ms()*1000; // (int64_t)1000000*getUnixTime()+10000*FracSec; + { MAV->time_usec = (int64_t)1000000*getUnixTime()+10000*FracSec; MAV->lat = ((int64_t)50*Latitude+1)/3; MAV->lon = ((int64_t)50*Longitude+1)/3; MAV->alt = 100*Altitude; @@ -1080,12 +1148,10 @@ class GPS_Position if(hasBaro) { Packet.EncodeTemperature(Temperature); Packet.Status.Pressure = (Pressure+16)>>5; - Packet.EncodeHumidity(Humidity); - } + Packet.EncodeHumidity(Humidity); } else { Packet.Status.Pressure = 0; - Packet.clrHumidity(); - } + Packet.clrHumidity(); } } // uint8_t getFreqPlan(void) const // get the frequency plan from Lat/Lon: 1 = Europe + Africa, 2 = USA/CAnada, 3 = Australia + South America, 4 = New Zeeland @@ -1120,12 +1186,37 @@ class GPS_Position else Packet.clrBaro(); //or no-baro if pressure sensor data not there } - void calcExtrapolation(int32_t &Lat, int32_t &Lon, int32_t &Alt, int16_t &Head, int32_t dTime) const // extrapolate GPS position by a fraction of a second + void Extrapolate(int32_t dTime) // [0.01sec] extrapolate the position by dTime + { int16_t dSpeed = ((int32_t)Accel*dTime)/100; + Speed += dSpeed/2; + int16_t HeadAngle = ((int32_t)Heading<<12)/225; // [cordic] heading angle + int16_t TurnAngle = (((dTime*TurnRate)/25)<<9)/225; // [cordic] + HeadAngle += TurnAngle/2; + int32_t LatSpeed = ((int32_t)Speed*Icos(HeadAngle)+0x800)>>12; // [0.1m/s] + int32_t LonSpeed = ((int32_t)Speed*Isin(HeadAngle)+0x800)>>12; // [0.1m/s] + HeadAngle += TurnAngle-TurnAngle/2; + Speed += dSpeed-dSpeed/2; if(Speed<0) Speed=0; + Latitude += calcLatitudeExtrapolation (dTime, LatSpeed); + Longitude += calcLongitudeExtrapolation(dTime, LonSpeed); + int32_t dAlt = calcAltitudeExtrapolation(dTime); // [0.1m] + Altitude += dAlt; // [0.1m] + if(hasBaro) + { StdAltitude += dAlt; // [0.1m] + Pressure += 4000*dAlt/Atmosphere::PressureLapseRate(Pressure/4, Temperature); } // [0.25Pa] ([Pa], [0.1degC]) + Heading += (dTime*TurnRate)/100; // [0.1deg] + if(Heading<0) Heading+=3600; else if(Heading>=3600) Heading-=3600; // [0.1deg] + int16_t fTime = FracSec+dTime; // [0.01sec] + while(fTime>=100) { incrTimeDate(); fTime-=100; } + while(fTime< 0) { decrTimeDate(); fTime+=100; } + FracSec=fTime; } + + // extrapolate GPS position by a fraction of a second + void calcExtrapolation(int32_t &Lat, int32_t &Lon, int32_t &Alt, int16_t &Head, int32_t dTime) const // [0.01sec] { int16_t HeadAngle = ((int32_t)Heading<<12)/225; // [] int16_t TurnAngle = (((dTime*TurnRate)/25)<<9)/225; // [] HeadAngle += TurnAngle; - int32_t LatSpeed = ((int32_t)Speed*Icos(HeadAngle))>>12; // [0.1m/s] - int32_t LonSpeed = ((int32_t)Speed*Isin(HeadAngle))>>12; // [0.1m/s] + int32_t LatSpeed = ((int32_t)Speed*Icos(HeadAngle)+0x800)>>12; // [0.1m/s] + int32_t LonSpeed = ((int32_t)Speed*Isin(HeadAngle)+0x800)>>12; // [0.1m/s] Lat = Latitude + calcLatitudeExtrapolation (dTime, LatSpeed); Lon = Longitude + calcLongitudeExtrapolation(dTime, LonSpeed); Alt = Altitude + calcAltitudeExtrapolation(dTime); @@ -1135,15 +1226,15 @@ class GPS_Position int32_t calcAltitudeExtrapolation(int32_t Time) const // [0.01s] { return Time*ClimbRate/100; } // [0.1m] - int32_t calcLatitudeExtrapolation(int32_t Time, int32_t LatSpeed) const // [0.01s] - { return (Time*LatSpeed*177)>>15; } // [0.1m] + int32_t calcLatitudeExtrapolation(int32_t Time, int32_t LatSpeed) const // [0.01s] [0.1m/s] + { return (Time*LatSpeed*177+0x4000)>>15; } // [0.1m] int32_t calcLongitudeExtrapolation(int32_t Time, int32_t LonSpeed) const // [0.01s] { int16_t LatCosine = calcLatCosine(calcLatAngle16(Latitude)); return calcLongitudeExtrapolation(Time, LonSpeed, LatCosine); } int32_t calcLongitudeExtrapolation(int32_t Time, int32_t LonSpeed, int16_t LatCosine) const // [0.01s] - { return (((int32_t)Time*LonSpeed*177)>>3)/LatCosine; } + { return ((((int32_t)Time*LonSpeed*177+4)>>3))/LatCosine; } // static int32_t calcLatDistance(int32_t Lat1, int32_t Lat2) // [m] distance along latitude // { return ((int64_t)(Lat2-Lat1)*0x2f684bda+0x80000000)>>32; } @@ -1173,7 +1264,7 @@ class GPS_Position // // printf("Latitude=%+d, LatAngle=%04X LatCos=%08X\n", Latitude, (uint16_t)LatAngle, LatCos); // return ((int64_t)Dist*LatCos+0x40000000)>>31; } // distance corrected by the latitude cosine - void calcLatitudeCosine(void) + void calcLatitudeCosine(void) { int16_t LatAngle = calcLatAngle16(Latitude); LatitudeCosine = calcLatCosine(LatAngle); } @@ -1288,10 +1379,13 @@ class GPS_Position // printf("%s => [%d]\n", Seq, Params); return Params; } + uint32_t getDayTime(void) const + { return Times60((uint32_t)(Times60((uint16_t)Hour) + Min)) + Sec; } // this appears to save about 100 bytes of code + // return (uint32_t)Hour*SecsPerHour + (uint16_t)Min*SecsPerMin + Sec; } // compared to this line + uint32_t getUnixTime(void) const // return the Unix timestamp (tested 2000-2037) { uint16_t Days = DaysSinceYear2000() + DaysSimce1jan(); - return Times60(Times60(Times24((uint32_t)(Days+10957)))) + Times60((uint32_t)(Times60((uint16_t)Hour) + Min)) + Sec; } // this appears to save about 100 bytes of code - // return (uint32_t)(Days+10957)*SecsPerDay + (uint32_t)Hour*SecsPerHour + (uint16_t)Min*SecsPerMin + Sec; } // compared to this line + return Times60(Times60(Times24((uint32_t)(Days+10957)))) + getDayTime(); } uint32_t getFatTime(void) const // return timestamp in FAT format { uint16_t Date = ((uint16_t)(Year+20)<<9) | ((uint16_t)Month<<5) | Day; @@ -1319,10 +1413,6 @@ class GPS_Position setUnixTime(Time); FracSec = (Time_ms-(uint64_t)Time*1000)/10; } - uint64_t getUnixTime_ms(void) const - { return (uint64_t)getUnixTime()*1000 + (uint32_t)FracSec*10; } - - private: static const uint32_t SecsPerMin = 60; diff --git a/main/ogn1.h b/main/ogn1.h index 1b372b3..5fa8bf1 100644 --- a/main/ogn1.h +++ b/main/ogn1.h @@ -85,7 +85,8 @@ class OGN1_Packet // Packet structure for the OGN tracker struct { unsigned int Pulse : 8; // [bpm] // pilot: heart pulse rate unsigned int Oxygen : 7; // [%] // pilot: oxygen level in the blood - unsigned int FEScurr : 5; // [A] // FES current + unsigned int SatSNR : 5; // [dB] // average SNR of GPS signals + // unsigned int FEScurr : 5; // [A] // FES current unsigned int RxRate : 4; // [/min] // log2 of received packet rate unsigned int Time : 6; // [sec] // same as in the position packet unsigned int FixQuality: 2; @@ -98,7 +99,7 @@ class OGN1_Packet // Packet structure for the OGN tracker unsigned int Satellites: 4; // [ ] unsigned int Firmware : 8; // [ ] // firmware version unsigned int Hardware : 8; // [ ] // hardware version - unsigned int TxPower : 4; // [dBm] // RF trancmitter power + unsigned int TxPower : 4; // [dBm] // RF trancmitter power (offset = 4) unsigned int ReportType: 4; // [0] // 0 for the status report unsigned int Voltage : 8; // [1/64V] VR // supply/battery voltage } Status; @@ -138,10 +139,10 @@ class OGN1_Packet // Packet structure for the OGN tracker // void recvBytes(const uint8_t *SrcPacket) { memcpy(Byte(), SrcPacket, Bytes); } // load data bytes e.g. from a demodulator - static const uint8_t InfoParmNum = 12; // [int] number of info-parameters and their names + static const uint8_t InfoParmNum = 14; // [int] number of info-parameters and their names static const char *InfoParmName(uint8_t Idx) { static const char *Name[InfoParmNum] = { "Pilot", "Manuf", "Model", "Type", "SN", "Reg", "ID", "Class", - "Task" , "Base" , "ICE" , "PilotID" } ; + "Task" , "Base" , "ICE" , "PilotID", "Hard", "Soft" } ; return IdxICAO_address = HeaderWord&0x03FFFFFF; MAV->lat = ((int64_t)50*DecodeLatitude()+1)/3; MAV->lon = ((int64_t)50*DecodeLongitude()+1)/3; @@ -241,7 +242,6 @@ class OGN1_Packet // Packet structure for the OGN tracker MAV->tslc = 0; MAV->emiter_type = 0; } */ - void Encode(MAV_ADSB_VEHICLE *MAV) { MAV->ICAO_address = Header.Address; MAV->lat = ((int64_t)50*DecodeLatitude()+1)/3; // convert coordinates to [1e-7deg] @@ -465,8 +465,8 @@ class OGN1_Packet // Packet structure for the OGN tracker Msg[Len++] = ' '; Msg[Len++] = '!'; Msg[Len++] = 'W'; - Msg[Len++] = '0'+(Lat+5)/10; - Msg[Len++] = '0'+(Lon+5)/10; + Msg[Len++] = '0'+Lat/10; + Msg[Len++] = '0'+Lon/10; Msg[Len++] = '!'; Msg[Len++] = ' '; Msg[Len++] = 'i'; Msg[Len++] = 'd'; Len+=Format_Hex(Msg+Len, ((uint32_t)Position.AcftType<<26) | ((uint32_t)Header.AddrType<<24) | Header.Address); @@ -727,6 +727,9 @@ class OGN1_Packet // Packet structure for the OGN tracker // -------------------------------------------------------------------------------------------------------------- + void Encrypt (const uint32_t Key[4]) { XXTEA_Encrypt(Data, 4, Key, 8); } // encrypt with given Key + void Decrypt (const uint32_t Key[4]) { XXTEA_Decrypt(Data, 4, Key, 8); } // decrypt with given Key + void Whiten (void) { TEA_Encrypt_Key0(Data, 8); TEA_Encrypt_Key0(Data+2, 8); } // whiten the position void Dewhiten(void) { TEA_Decrypt_Key0(Data, 8); TEA_Decrypt_Key0(Data+2, 8); } // de-whiten the position diff --git a/main/ogn2.h b/main/ogn2.h index edf7775..dfa8512 100644 --- a/main/ogn2.h +++ b/main/ogn2.h @@ -40,13 +40,13 @@ class OGN2_Packet // Packet structure for the OGN tracker unsigned int Relay : 1; // 0 = direct packet, 1 = relayed packet unsigned int Parity : 1; // parity takes into account bits 0..27 thus only the 28 lowest bits unsigned int NonPos : 1; // 0 = position packet, 1 = other information like status - unsigned int Auth : 2; // Authentication: 00 = no auth. 01 = auth. this packet, 10/11 = auth response + // unsigned int Auth : 2; // Authentication: 00 = no auth. 01 = auth. this packet, 10/11 = auth response // Auth:NonPos: 000 = position, 010 = position, to be followed by crypto-response, // 101 = response #0, 111 = response #1 // 001 = non-position: status, info, etc. // 100 = ???, 110 = ???, 011 = ??? - // unsigned int NonOGN : 1; // 0 = OGN packet, 1 = other systems, like MAVlink - // unsigned int Encrypted : 1; // packet is encrypted + unsigned int NonOGN : 1; // 0 = OGN packet, 1 = other systems, like MAVlink + unsigned int Encrypted : 1; // packet is encrypted unsigned int Emergency : 1; // aircraft in emergency (not used for now) } Header ; @@ -89,7 +89,8 @@ class OGN2_Packet // Packet structure for the OGN tracker unsigned int Pulse : 8; // [bpm] // pilot: heart pulse rate unsigned int Oxygen : 7; // [%] // pilot: oxygen level in the blood - unsigned int FEScurr : 5; // [A] // + // unsigned int FEScurr : 5; // [A] // + unsigned int SatSNR : 5; // [dB] // average SNR of GPS signals unsigned int RxRate : 4; // [/min] // log2 of received packet rate unsigned int Time : 6; // [sec] // same as in the position packet unsigned int FixQuality: 2; @@ -115,10 +116,10 @@ class OGN2_Packet // Packet structure for the OGN tracker uint8_t *Byte(void) const { return (uint8_t *)&HeaderWord; } // packet as bytes uint32_t *Word(void) const { return (uint32_t *)&HeaderWord; } // packet as words - static const uint8_t InfoParmNum = 12; // [int] number of info-parameters and their names + static const uint8_t InfoParmNum = 14; // [int] number of info-parameters and their names static const char *InfoParmName(uint8_t Idx) { static const char *Name[InfoParmNum] = { "Pilot", "Manuf", "Model", "Type", "SN", "Reg", "ID", "Class", - "Task" , "Base" , "ICE" , "PilotID" } ; + "Task" , "Base" , "ICE" , "PilotID", "Hard", "Soft" } ; return Idx>5) ^ (Y<<2)) + ((Y>>3) ^ (Z<<4))) ^ ((Sum^Y) + (Key[(P&3)^E] ^ Z))); } + +void XXTEA_Encrypt(uint32_t *Data, uint8_t Words, const uint32_t Key[4], uint8_t Loops) +{ const uint32_t Delta = 0x9e3779b9; + uint32_t Sum = 0; + uint32_t Z = Data[Words-1]; uint32_t Y; + for( ; Loops; Loops--) + { Sum += Delta; + uint8_t E = (Sum>>2)&3; + for (uint8_t P=0; P<(Words-1); P++) + { Y = Data[P+1]; + Z = Data[P] += XXTEA_MX(E, Y, Z, P, Sum, Key); } + Y = Data[0]; + Z = Data[Words-1] += XXTEA_MX(E, Y, Z, Words-1, Sum, Key); + } +} + +void XXTEA_Decrypt(uint32_t *Data, uint8_t Words, const uint32_t Key[4], uint8_t Loops) +{ const uint32_t Delta = 0x9e3779b9; + uint32_t Sum = Loops*Delta; + uint32_t Y = Data[0]; uint32_t Z; + for( ; Loops; Loops--) + { uint8_t E = (Sum>>2)&3; + for (uint8_t P=Words-1; P; P--) + { Z = Data[P-1]; + Y = Data[P] -= XXTEA_MX(E, Y, Z, P, Sum, Key); } + Z = Data[Words-1]; + Y = Data[0] -= XXTEA_MX(E, Y, Z, 0, Sum, Key); + Sum -= Delta; + } +} + // ============================================================================================== void XorShift32(uint32_t &Seed) // simple random number generator @@ -202,5 +241,31 @@ void xorshift64(uint64_t &Seed) // ============================================================================================== +const static unsigned char MapAscii85[86] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; + +const static uint8_t UnmapAscii85[128] = +{ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 62, 85, 63, 64, 65, 66, 85, 67, 68, 69, 70, 85, 71, 85, 85, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 85, 72, 73, 74, 75, 76, + 77, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 85, 85, 85, 78, 79, + 80, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 81, 82, 83, 84, 85 }; + +uint8_t EncodeAscii85(char *Ascii, uint32_t Word) +{ for( uint8_t Idx=5; Idx; ) + { uint32_t Div = Word/85; + Idx--; + Ascii[Idx]=MapAscii85[Word-Div*85]; + Word=Div; } + Ascii[5]=0; + return 5; } + +uint8_t DecodeAscii85(uint32_t &Word, const char *Ascii) +{ Word=0; + for( uint8_t Idx=0; Idx<5; Idx++) + { char Char = Ascii[Idx]; if(Char<=0) return 0; + uint8_t Dig = UnmapAscii85[(uint8_t)Char]; + if(Dig>=85) return 0; + Word = Word*85+Dig; } + return 5; } + // ============================================================================================== diff --git a/main/ognconv.h b/main/ognconv.h index e43d5ff..7b931ef 100644 --- a/main/ognconv.h +++ b/main/ognconv.h @@ -73,7 +73,13 @@ void TEA_Decrypt (uint32_t* Data, const uint32_t *Key, int Loops); void TEA_Encrypt_Key0 (uint32_t* Data, int Loops); void TEA_Decrypt_Key0 (uint32_t* Data, int Loops); +void XXTEA_Encrypt(uint32_t *Data, uint8_t Words, const uint32_t Key[4], uint8_t Loops); +void XXTEA_Decrypt(uint32_t *Data, uint8_t Words, const uint32_t Key[4], uint8_t Loops); + void XorShift32(uint32_t &Seed); // simple random number generator void xorshift64(uint64_t &Seed); +uint8_t EncodeAscii85( char *Ascii, uint32_t Word ); // Encode 32-bit Word into 5-char Ascii-85 string +uint8_t DecodeAscii85(uint32_t &Word, const char *Ascii); // Decode 5-char Ascii-85 to 32-bit Word + #endif // __OGNCONV_H__ diff --git a/main/parameters.h b/main/parameters.h index 00752fe..fc34642 100644 --- a/main/parameters.h +++ b/main/parameters.h @@ -14,6 +14,10 @@ #include "nvs.h" #endif +#ifdef WITH_SAMD21 +#include "flashsize.h" +#endif + #ifdef WITH_STM32 #include "stm32f10x_flash.h" #include "flashsize.h" @@ -29,8 +33,8 @@ class FlashParameters { uint32_t AcftID; // identification: Private:AcftType:AddrType:Address - must be different for every tracker struct { uint32_t Address:24; // address (ID) - uint8_t AddrType:2; - uint8_t AcftType:4; + uint8_t AddrType:2; // 0=RND, 1=ICAO, 2=FLR, 3=OGN + uint8_t AcftType:4; // 1=glider, 2=towplane, 3=helicopter, etc. bool NoTrack:1; // unused bool Stealth:1; // unused } ; @@ -44,15 +48,19 @@ class FlashParameters int16_t PressCorr; // [0.25Pa] pressure correction for the baro union - { uint8_t Flags; + { uint16_t Flags; struct { bool SaveToFlash:1; // Save parameters from the config file to Flash bool hasBT:1; // has BT interface on the console bool BT_ON:1; // BT on after power up bool manGeoidSepar:1; // GeoidSepar is manually configured as the GPS or MAVlink are not able to deliver it + bool Encrypt:1; // encrypt the position + uint8_t NavMode:3; // GPS navigation mode/model + uint8_t NavRate:2; // [Hz] + uint8_t Verbose:2; // + int8_t TimeCorr:4; // [sec] it appears for ArduPilot you need to correct time by 3 seconds } ; - } ; // - int8_t TimeCorr; // [sec] it appears for ArduPilot you need to correct time by 3 seconds + } ; // int16_t GeoidSepar; // [0.1m] Geoid-Separation, apparently ArduPilot MAVlink does not give this value (although present in the format) // or it could be a problem of some GPSes @@ -60,7 +68,7 @@ class FlashParameters uint8_t FreqPlan; // force given frequency hopping plan static const uint8_t InfoParmLen = 16; // [char] max. size of an infp-parameter - static const uint8_t InfoParmNum = 12; // [int] number of info-parameters + static const uint8_t InfoParmNum = 14; // [int] number of info-parameters char *InfoParmValue(uint8_t Idx) { return IdxADDR.reg = ((uint32_t)Addr)>>1; + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER; + while (!NVMCTRL->INTFLAG.bit.READY) { } + } + + void EraseSize(volatile uint32_t *Addr, uint32_t Size=1024) const // erase multiple pages for given size + { for( ; ; ) + { if(Size==0) break; + ErasePage4x64(Addr); // + if(Size<256) break; + Addr+=64; Size-=256; } + } + + int WritePage64(volatile uint32_t *Addr, const uint32_t *Data, uint32_t Words) const + { // NVMCTRL->ADDR.reg = ((uint32_t)Addr)>>1; + NVMCTRL->CTRLB.bit.MANW = 1; // disable Automatic Page Write + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; // execute Page Buffer Clear + while (NVMCTRL->INTFLAG.bit.READY == 0) { } + uint32_t Idx=0; + for(Idx=0; (Idx<16) && (IdxCTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP; // execute Write Page + while (NVMCTRL->INTFLAG.bit.READY == 0) { } + return Idx; } + + int WriteSize(volatile uint32_t *Addr, const uint32_t *Data, uint32_t Words) + { for( ; ; ) + { int Len = WritePage64(Addr, Data, Words); + Addr+=Len; Data+=Len; Words-=Len; if(Words==0) break; } + return 0; } + + int8_t WriteToFlash(volatile uint32_t *Addr=0) // write parameters to Flash + { if(Addr==0) Addr = DefaultFlashAddr(); + setCheckSum(); + const uint32_t Words=sizeof(FlashParameters)/sizeof(uint32_t); + EraseSize(Addr, Words); + WriteSize(Addr, (const uint32_t *)this, Words); + if(calcCheckSum(Addr, Words)!=0) return -1; // verify check-sum in Flash + return 0; } +#endif // WITH_SAMD21 + #ifdef WITH_STM32 +/* uint32_t static CheckSum(const uint32_t *Word, uint32_t Words) // calculate check-sum of pointed data { uint32_t Check=CheckInit; for(uint32_t Idx=0; Idx=0) && (IdxWarnLevel; + // uint16_t DistMargin = Tgt->DistMargin; // [0.5m] + uint16_t TimeMargin = Tgt->TimeMargin; // [0.5s] + uint16_t HorDist = Tgt->HorDist; // [0.5] + uint16_t Bearing = Tgt->getBearing(); // + int16_t RelBearing = Look.getRelBearing(Tgt); + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "Traffic: "); + CONS_UART_Write('#'); + CONS_UART_Write('0'+WarnLevel); + CONS_UART_Write(' '); + // Format_Hex(CONS_UART_Write, Bearing); + // CONS_UART_Write(' '); + uint16_t DirIdx = (Bearing+0x800)>>12; DirIdx&=0x0F; + Format_String(CONS_UART_Write, Dir[DirIdx]); + CONS_UART_Write(' '); + uint16_t RelDirIdx = (RelBearing+0x1000)>>13; RelDirIdx&=0x07; + Format_String(CONS_UART_Write, RelDir[RelDirIdx]); + CONS_UART_Write(' '); + Format_UnsDec(CONS_UART_Write, (uint16_t)(HorDist/2)); + Format_String(CONS_UART_Write, "m "); + Format_UnsDec(CONS_UART_Write, (uint16_t)(TimeMargin/2)); + Format_String(CONS_UART_Write, "s\n"); + xSemaphoreGive(CONS_Mutex); + // SoundMsg("Traffic"); +} #endif +#endif + +// static uint16_t PrevBattVolt = 0; // [mV] +static Delay BatteryVoltagePipe; +uint32_t BatteryVoltage = 0; // [1/256 mV] low-pass filtered battery voltage + int32_t BatteryVoltageRate = 0; // [1/256 mV/sec] low-pass filtered battery voltage rise/drop rate static char Line[128]; // for printing out to the console, etc. @@ -31,12 +75,12 @@ static LDPC_Decoder Decoder; // decoder and error corrector for the OGN #ifdef WITH_LOG static int SPIFFSlog(OGN_RxPacket *Packet, uint32_t Time) -{ OGN_LogPacket *LogPacket = LOG_FIFO.getWrite(); if(LogPacket==0) return -1; - LogPacket->Packet = Packet->Packet; +{ OGN_LogPacket *LogPacket = LOG_FIFO.getWrite(); if(LogPacket==0) return -1; // allocate new packet in the LOG_FIFO + LogPacket->Packet = Packet->Packet; // copy the packet LogPacket->Flags=0x80; LogPacket->setTime(Time); LogPacket->setCheck(); - LOG_FIFO.Write(); + LOG_FIFO.Write(); // finalize the write return 1; } static int SPIFFSlog(OGN_TxPacket *Packet, uint32_t Time) @@ -52,13 +96,13 @@ static int SPIFFSlog(OGN_TxPacket *Packet, uint32_t Time) // --------------------------------------------------------------------------------------------------------------------------------------- -#ifdef WITH_ESP32 -const uint8_t RelayQueueSize = 32; -#else -const uint8_t RelayQueueSize = 16; -#endif +// #ifdef WITH_ESP32 +// const uint8_t RelayQueueSize = 32; +// #else +// const uint8_t RelayQueueSize = 16; +// #endif -static OGN_PrioQueue RelayQueue; // received packets and candidates to be relayed +OGN_PrioQueue RelayQueue; // received packets and candidates to be relayed #ifdef DEBUG_PRINT static void PrintRelayQueue(uint8_t Idx) // for debug @@ -78,7 +122,8 @@ static bool GetRelayPacket(OGN_TxPacket *Packet) // prepare a p uint8_t Idx=RelayQueue.getRand(RX_Random); // get weight-random packet from the relay queue if(RelayQueue.Packet[Idx].Rank==0) return 0; // should not happen ... memcpy(Packet->Packet.Byte(), RelayQueue[Idx]->Byte(), OGN_Packet::Bytes); // copy the packet - Packet->Packet.Header.Relay=1; // increment the relay count (in fact we only do single relay) + Packet->Packet.Header.Relay=1; // increment the relay count (in fact we only do single relay) + // Packet->Packet.calcAddrParity(); Packet->Packet.Whiten(); Packet->calcFEC(); // whiten and calc. the FEC code => packet ready for transmission // PrintRelayQueue(Idx); // for debug RelayQueue.decrRank(Idx); // reduce the rank of the packet selected for relay @@ -153,20 +198,60 @@ static void ReadStatus(OGN_Packet &Packet) #ifdef WITH_ESP32 // Packet.clrTemperature(); - uint32_t Battery = BatterySense(); // [mV] - Packet.EncodeVoltage(((Battery*64)+500)/1000); // [1/64V] + + uint16_t BattVolt = BatterySense(); // [mV] measure battery voltage + if(BatteryVoltage>0) + { // int32_t PrevVolt = BatteryVoltage; + int32_t Rate = ((uint32_t)BattVolt<<8) - BatteryVoltage; // [1/256 mV] + BatteryVoltage += (Rate+32)>>6; // [1/256 mV] low-pass battery voltage measurement + uint16_t Volt = (BatteryVoltage+16)>>5; + int16_t Diff = Volt-BatteryVoltagePipe.Input(Volt); + BatteryVoltageRate = Diff; } + // BatteryVoltageRate = BatteryVoltage - PrevVolt; } + // int16_t BattVoltDiff = BattVolt - PrevBattVolt; + // int32_t Diff = ((int32_t)BattVoltDiff<<8) - BatteryVoltageRate; + // BatteryVoltageRate += Diff/256; } + // BatteryVoltageRate = (((int32_t)BattVoltDiff<<8) + BatteryVoltageRate*255 + 128)>>8; } + else + { BatteryVoltage = BattVolt<<8; + // PrevBattVolt = BattVolt; + BatteryVoltagePipe.Clear(BattVolt<<3); + BatteryVoltageRate = 0; } + // PrevBattVolt = BattVolt; + Packet.EncodeVoltage(((BatteryVoltage>>2)+500)/1000); // [1/64V] encode into the status packet + +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, "Battery: "); + // Format_UnsDec(CONS_UART_Write, BattVolt); + // CONS_UART_Write(' '); + // Format_UnsDec(CONS_UART_Write, PrevBattVolt); + // CONS_UART_Write(' '); + // Format_UnsDec(CONS_UART_Write, BatteryVoltage, 2); + // CONS_UART_Write(' '); + // Format_SignDec(CONS_UART_Write, BatteryVoltageRate, 2); + // CONS_UART_Write(' '); + Format_UnsDec(CONS_UART_Write, (10*BatteryVoltage+128)>>8, 5, 4); + Format_String(CONS_UART_Write, "V "); + Format_SignDec(CONS_UART_Write, (600*BatteryVoltageRate+128)>>8, 3, 1); + Format_String(CONS_UART_Write, "mV/min\n"); + xSemaphoreGive(CONS_Mutex); +#endif #endif if(Packet.Status.Pressure==0) Packet.EncodeTemperature(TRX.chipTemp*10); // [0.1degC] Packet.Status.RadioNoise = TRX.averRSSI; // [-0.5dBm] write radio noise to the status packet - Packet.Status.TxPower = Parameters.getTxPower()-4; + uint8_t TxPower = Parameters.getTxPower()-4; + if(TxPower>15) TxPower=15; + Packet.Status.TxPower = TxPower; uint16_t RxRate = RX_OGN_Count64+1; uint8_t RxRateLog2=0; RxRate>>=1; while(RxRate) { RxRate>>=1; RxRateLog2++; } Packet.Status.RxRate = RxRateLog2; - { uint8_t Len=0; + if(Parameters.Verbose) + { uint8_t Len=0; Len+=Format_String(Line+Len, "$POGNR,"); // NMEA report: radio status Len+=Format_UnsDec(Line+Len, RF_FreqPlan.Plan); // which frequency plan Line[Len++]=','; @@ -201,10 +286,6 @@ static void ReadStatus(OGN_Packet &Packet) Format_String(Log_Write, Line, Len); // send the NMEA out to the log file xSemaphoreGive(Log_Mutex); } #endif - -// #ifdef WITH_FollowMe -// Format_String(ADSB_UART_Write, "ADS-B UART test\n"); -// #endif } } @@ -244,10 +325,13 @@ static void ProcessRxPacket(OGN_RxPacket *RxPacket, uint8_t RxPacket if(DistOK) { RxPacket->calcRelayRank(GPS_Altitude/10); // calculate the relay-rank (priority for relay) OGN_RxPacket *PrevRxPacket = RelayQueue.addNew(RxPacketIdx); - uint8_t Len=RxPacket->WritePOGNT(Line); // print on the console as $POGNT - xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, Line, 0, Len); - xSemaphoreGive(CONS_Mutex); +#ifdef WITH_POGNT + if(Parameters.Verbose) + { uint8_t Len=RxPacket->WritePOGNT(Line); // print on the console as $POGNT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, Line, 0, Len); + xSemaphoreGive(CONS_Mutex); } +#endif // Len=RxPacket->Packet.WriteAPRS(Line, RxTime); // print on the console as APRS message // xSemaphoreTake(CONS_Mutex, portMAX_DELAY); // Format_String(CONS_UART_Write, Line, 0, Len); @@ -256,9 +340,9 @@ static void ProcessRxPacket(OGN_RxPacket *RxPacket, uint8_t RxPacket const LookOut_Target *Tgt=Look.ProcessTarget(RxPacket->Packet); // process the received target postion if(Tgt) Warn=Tgt->WarnLevel; // remember warning level of this target #ifdef WITH_BEEPER - if(KNOB_Tick>12) Play(Play_Vol_1 | Play_Oct_2 | (7+2*Warn), 3+16*Warn); // if Knob>12 => make a beep for every received packet + if(KNOB_Tick>12) Play(Play_Vol_1 | Play_Oct_2 | (7+2*Warn), 3+16*Warn); #endif -#else // WITH_LOOKOUT +#else // if not WITH_LOOKOUT #ifdef WITH_BEEPER if(KNOB_Tick>12) Play(Play_Vol_1 | Play_Oct_2 | 7, 3); // if Knob>12 => make a beep for every received packet #endif @@ -266,7 +350,7 @@ static void ProcessRxPacket(OGN_RxPacket *RxPacket, uint8_t RxPacket #ifdef WITH_LOG bool Signif = PrevRxPacket!=0; if(!Signif) Signif=OGN_isSignif(&(RxPacket->Packet), &(PrevRxPacket->Packet)); - if(Signif) SPIFFSlog(RxPacket, RxTime); + if(Signif) SPIFFSlog(RxPacket, RxTime); // log only significant packets #endif #ifdef WITH_SDLOG if(Log_Free()>=128) @@ -275,22 +359,23 @@ static void ProcessRxPacket(OGN_RxPacket *RxPacket, uint8_t RxPacket xSemaphoreGive(Log_Mutex); } #endif #ifdef WITH_PFLAA - Len=RxPacket->WritePFLAA(Line, Warn, LatDist, LonDist, RxPacket->Packet.DecodeAltitude()-GPS_Altitude/10); // print on the console - xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, Line, 0, Len); - xSemaphoreGive(CONS_Mutex); + if( Parameters.Verbose // print PFLAA on the console for received packets +#ifdef WITH_LOOKOUT + && (!Tgt) +#endif + ) + { uint8_t Len=RxPacket->WritePFLAA(Line, Warn, LatDist, LonDist, RxPacket->Packet.DecodeAltitude()-GPS_Altitude/10); + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, Line, 0, Len); + xSemaphoreGive(CONS_Mutex); } #endif #ifdef WITH_MAVLINK MAV_ADSB_VEHICLE MAV_RxReport; RxPacket->Packet.Encode(&MAV_RxReport); MAV_RxMsg::Send(sizeof(MAV_RxReport), MAV_Seq++, MAV_SysID, MAV_COMP_ID_ADSB, MAV_ID_ADSB_VEHICLE, (const uint8_t *)&MAV_RxReport, GPS_UART_Write); -#ifdef DEBUG_PRINT - xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "MAV_ADSB_VEHICLE sent. ID="); - Format_Hex(CONS_UART_Write, MAV_RxReport.ICAO_address); - CONS_UART_Write('\r'); CONS_UART_Write('\n'); - xSemaphoreGive(CONS_Mutex); -#endif + // xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + // MAV_RxMsg::Send(sizeof(MAV_RxReport), MAV_Seq++, MAV_SysID, MAV_COMP_ID_ADSB, MAV_ID_ADSB_VEHICLE, (const uint8_t *)&MAV_RxReport, CONS_UART_Write); + // xSemaphoreGive(CONS_Mutex); #endif } } @@ -354,7 +439,7 @@ void vTaskPROC(void* pvParameters) { vTaskDelay(1); RFM_RxPktData *RxPkt = RF_RxFIFO.getRead(); // check for new received packets - if(RxPkt) + if(RxPkt) // if there is a new received packet { #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); @@ -367,11 +452,11 @@ void vTaskPROC(void* pvParameters) xSemaphoreGive(CONS_Mutex); #endif DecodeRxPacket(RxPkt); // decode and process the received packet - RF_RxFIFO.Read(); } + RF_RxFIFO.Read(); } // remove this packet from the queue static uint32_t PrevSlotTime=0; // remember previous time slot to detect a change uint32_t SlotTime = TimeSync_Time(); // time slot - if(TimeSync_msTime()<300) SlotTime--; // lasts up to 0.300sec after the PPS + if(TimeSync_msTime()<340) SlotTime--; // lasts up to 0.300sec after the PPS if(SlotTime==PrevSlotTime) continue; // stil same time slot, go back to RX processing PrevSlotTime=SlotTime; // new slot started @@ -380,13 +465,13 @@ void vTaskPROC(void* pvParameters) #ifdef WITH_MAVLINK GPS_Position *Position = GPS_getPosition(BestIdx, BestResid, (SlotTime-1)%60, 0); #else - GPS_Position *Position = GPS_getPosition(BestIdx, BestResid, SlotTime%60, 0); + GPS_Position *Position = GPS_getPosition(BestIdx, BestResid, SlotTime%60, 0); // get GPS position which isReady #endif #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "getPos() => "); + Format_String(CONS_UART_Write, "getPos("); Format_UnsDec(CONS_UART_Write, SlotTime%60, 2); - CONS_UART_Write(' '); + Format_String(CONS_UART_Write, ") => "); Format_UnsDec(CONS_UART_Write, (uint16_t)BestIdx); CONS_UART_Write(':'); Format_SignDec(CONS_UART_Write, BestResid, 3, 2); @@ -395,12 +480,16 @@ void vTaskPROC(void* pvParameters) #endif // GPS_Position *Position = GPS_getPosition(); if(Position) Position->EncodeStatus(StatPacket.Packet); // encode GPS altitude and pressure/temperature/humidity - if( Position && Position->isReady && (!Position->Sent) && Position->isReady && Position->isValid() ) + else { StatPacket.Packet.Status.FixQuality=0; StatPacket.Packet.Status.Satellites=0; } // or lack of the GPS lock + { uint8_t SatSNR = (GPS_SatSNR+2)/4; + if(SatSNR>8) { SatSNR-=8; if(SatSNR>31) SatSNR=31; } + else { SatSNR=0; } + StatPacket.Packet.Status.SatSNR = SatSNR; } + if( Position && Position->isReady && (!Position->Sent) && Position->isValid() ) { int16_t AverSpeed=GPS_AverageSpeed(); // [0.1m/s] average speed, including the vertical speed if(Parameters.FreqPlan==0) RF_FreqPlan.setPlan(Position->Latitude, Position->Longitude); // set the frequency plan according to the GPS position else RF_FreqPlan.setPlan(Parameters.FreqPlan); -/* #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60); @@ -409,7 +498,6 @@ void vTaskPROC(void* pvParameters) Format_String(CONS_UART_Write, " -> Sent\n"); xSemaphoreGive(CONS_Mutex); #endif -*/ PosTime=Position->getUnixTime(); PosPacket.Packet.HeaderWord=0; PosPacket.Packet.Header.Address = Parameters.Address; // set address @@ -420,12 +508,17 @@ void vTaskPROC(void* pvParameters) #endif PosPacket.Packet.calcAddrParity(); // parity of (part of) the header if(BestResid==0) Position->Encode(PosPacket.Packet); // encode position/altitude/speed/etc. from GPS position - else Position->Encode(PosPacket.Packet, BestResid); + else // extrapolate the position when if not at an exact UTC second + { while(BestResid>=50) BestResid-=100; // remove full seconds + Position->Encode(PosPacket.Packet, BestResid); } PosPacket.Packet.Position.AcftType = Parameters.AcftType; // aircraft-type -// { uint8_t Len=PosPacket.Packet.WriteAPRS(Line, PosTime); // print on the console as APRS message -// xSemaphoreTake(CONS_Mutex, portMAX_DELAY); -// Format_String(CONS_UART_Write, Line, 0, Len); -// xSemaphoreGive(CONS_Mutex); } + PosPacket.Packet.Position.Stealth = 0; // Parameters.Stealth; +#ifdef DEBUG_PRINT + { uint8_t Len=PosPacket.Packet.WriteAPRS(Line, PosTime); // print on the console as APRS message + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, Line, 0, Len); + xSemaphoreGive(CONS_Mutex); } +#endif OGN_TxPacket *TxPacket = RF_TxFIFO.getWrite(); TxPacket->Packet = PosPacket.Packet; // copy the position packet to the TxFIFO #ifdef WITH_ENCRYPT @@ -437,7 +530,7 @@ void vTaskPROC(void* pvParameters) TxPacket->calcFEC(); // whiten and calculate FEC code #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60); + Format_UnsDec(CONS_UART_Write, TimeSync_Time()%60, 2); CONS_UART_Write('.'); Format_UnsDec(CONS_UART_Write, TimeSync_msTime(), 3); Format_String(CONS_UART_Write, " TxFIFO <- "); @@ -446,40 +539,28 @@ void vTaskPROC(void* pvParameters) xSemaphoreGive(CONS_Mutex); #endif XorShift32(RX_Random); - if( (AverSpeed>10) || ((RX_Random&0x3)==0) ) // send only some positions if the speed is less than 1m/s - RF_TxFIFO.Write(); // complete the write into the TxFIFO + if( (AverSpeed>10) || ((RX_Random&0x3)==0) ) // send only some positions if the speed is less than 1m/s + RF_TxFIFO.Write(); // complete the write into the TxFIFO Position->Sent=1; -// #ifdef WITH_MAVLINK -// { MAV_HEARTBEAT MAV_HeartBeat; -// // = { custom_mode:0, -// // type:0, -// // autopilot:0, -// // base_mode:0, -// // system_status:4, -// // mavlink_version:1 -// // }; -// MAV_HeartBeat.custom_mode=0; -// MAV_HeartBeat.type=0; -// MAV_HeartBeat.autopilot=0; -// MAV_HeartBeat.base_mode=0; -// MAV_HeartBeat.system_status=4; -// MAV_HeartBeat.mavlink_version=1; -// MAV_RxMsg::Send(sizeof(MAV_HeartBeat), MAV_Seq++, MAV_SysID, MAV_COMP_ID_ADSB, MAV_ID_HEARTBEAT, (const uint8_t *)&MAV_HeartBeat, GPS_UART_Write); -// } -// #endif #ifdef WITH_LOOKOUT - const LookOut_Target *Tgt=Look.ProcessOwn(PosPacket.Packet); // process own position, get the most dangerous target + const LookOut_Target *Tgt=Look.ProcessOwn(PosPacket.Packet); // process own position, get the most dangerous target #ifdef WITH_PFLAA - uint8_t Len=Look.WritePFLAU(Line); - xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, Line, 0, Len); - xSemaphoreGive(CONS_Mutex); + if(Parameters.Verbose) + { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Look.WritePFLA(CONS_UART_Write); // produce PFLAU and PFLAA for all tracked targets + xSemaphoreGive(CONS_Mutex); } +#else + if(Parameters.Verbose) + { uint8_t Len=Look.WritePFLAU(Line); // $PFLAU, overall status + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, Line, 0, Len); + xSemaphoreGive(CONS_Mutex); } #endif // WITH_PFLAA uint8_t Warn = 0; if(Tgt) Warn = Tgt->WarnLevel; // what is the warning level ? - if( (Warn>0) && (AverSpeed>=10) ) // if non-zero warning level and we seem to be moving - { int16_t RelBearing = Look.getRelBearing(Tgt); // relative bearing to the Target - int8_t Bearing = (12*(int32_t)RelBearing+0x8000)>>16; // [-12..+12] + if( (Warn>0) /* && (AverSpeed>=10) */ ) // if non-zero warning level and we seem to be moving + { // int16_t RelBearing = Look.getRelBearing(Tgt); // relative bearing to the Target + // int8_t Bearing = (12*(int32_t)RelBearing+0x8000)>>16; // [-12..+12] #ifdef WITH_BEEPER // make the sound according to the level if(Warn<=1) { if(KNOB_Tick>8) @@ -498,6 +579,9 @@ void vTaskPROC(void* pvParameters) } #endif // WITH_BEEPER +#ifdef WITH_SOUND + Sound_TrafficWarn(Tgt); +#endif } #endif // WITH_LOOKOUT #ifdef WITH_FLASHLOG diff --git a/main/proc.h b/main/proc.h index fb19329..aa7851a 100644 --- a/main/proc.h +++ b/main/proc.h @@ -1,3 +1,20 @@ +extern uint32_t BatteryVoltage; // [1/256 mV] averaged +extern int32_t BatteryVoltageRate; // [1/256 mV] averaged + +#ifdef WITH_LOOKOUT // traffic awareness and warnings +#include "lookout.h" +extern LookOut Look; +#endif + + +#ifdef WITH_ESP32 +const uint8_t RelayQueueSize = 32; +#else +const uint8_t RelayQueueSize = 16; +#endif + +extern OGN_PrioQueue RelayQueue; // received packets and candidates to be relayed + #ifdef __cplusplus extern "C" #endif diff --git a/main/relpos.h b/main/relpos.h new file mode 100644 index 0000000..122c532 --- /dev/null +++ b/main/relpos.h @@ -0,0 +1,279 @@ +#include + +#include +#include +#include + +#include "intmath.h" + +#include "ogn.h" + +// ======================================================================================================= + +class Acft_RelPos // 3-D relative position with speed and turn rate +{ public: + int16_t T; // [0.5sec] + int16_t X,Y,Z; // [0.5m] + uint16_t Speed; // [0.5m/s] + uint16_t Heading; // [360/0x10000 deg] + int16_t Climb; // [0.5m/s] + int16_t Turn; // [360/0x10000 deg/s] [2*PI/0x10000 rad/sec] + int16_t Dx,Dy; // [2^-12] directon vector - calc. on Heading + uint8_t Error; // [0.5m] + uint8_t Spare; + // int16_t Ax,Ay; // [1/16m/s^2] acceleration vactor + // int16_t R; // [0.5m] (signed) turning radius - calc. from Turn and Speed + // int16_t Ox,Oy; // [0.5m] turning circle center - only valid when R!=0 + + public: + void Print(void) const + { printf("%+7.1f: [%+7.1f,%+7.1f,%+7.1f]m %5.1fm/s %05.1fdeg %+5.1fm/s %+5.1fdeg/sec [%3.1fm]", + 0.5*T, 0.5*X, 0.5*Y, 0.5*Z, 0.5*Speed, (360.0/0x10000)*Heading, 0.5*Climb, (360.0/0x10000)*Turn, 0.5*Error); + // printf(" [%+6.2f,%+6.2f]m/s^2", 0.0625*Ax, 0.0625*Ay); + // if(R) printf(" R:%+8.1fm [%+7.1f, %+7.1f]", 0.5*R, 0.5*Ox, 0.5*Oy); + printf("\n"); } + + void Clear(void) + { T =0; + X =0; Y =0; Z =0; + Speed=0; Heading=0; Climb=0; Turn=0; + Error=4; } + + uint32_t SqrDistance(Acft_RelPos &Target) + { int32_t dX = Target.X-X; + int32_t dY = Target.Y-Y; + int32_t dZ = Target.Z-Z; + return dX*dX+dY*dY+dZ*dZ; } // [0.25m^2] + + static uint32_t SqrDistance(int16_t dX, int16_t dY, int16_t dZ) + { return (int32_t)dX*dX + (int32_t)dY*dY + (int32_t)dZ*dZ; } + + static uint32_t SqrDistance(int16_t dX, int16_t dY) + { return (int32_t)dX*dX + (int32_t)dY*dY; } + + uint32_t FastDistance(Acft_RelPos &Target) + { int16_t dX = Target.X-X; + int16_t dY = Target.Y-Y; + int16_t dZ = Target.Z-Z; + return FastDistance(dX, dY, dZ); } // [0.5m] + + static uint16_t FastDistance(int16_t dX, int16_t dY) + { dX = abs(dX); dY = abs(dY); + if(dX>dY) return dX+dY/2; + else return dY+dX/2; } + + static uint16_t FastDistance(int16_t dX, int16_t dY, int16_t dZ) + { return FastDistance((int16_t)FastDistance(dX, dY), dZ); } + + // predict self and target until MinSepar is reached but no longer than MaxTime + int16_t StepTillMinSepar(Acft_RelPos &Target, uint16_t MinSepar, int16_t MaxTime=40) // [0.5m] [0.5s] + { int16_t PredTime=0; // count time by which we predict + uint16_t MaxTurn=abs(Turn); // the max. turn rate + uint16_t Turn2=abs(Target.Turn); if(Turn2>MaxTurn) MaxTurn=Turn2; + int16_t MaxStepTime = 32; // [0.5s] max. allowed stpping time period + if(MaxTurn>=0x100) MaxStepTime=0x2000/MaxTurn; // [0.5s] maximup step time (for sharp turns) + if(MaxStepTime<2) MaxStepTime=2; // [0.5s] but don't do smaller steps than 1sec + uint16_t TotSpeed = FastDistance(Speed, Climb) + FastDistance(Target.Speed, Target.Climb); // [0.5m/s] "total" speed, thus the sum of the two speeds magnitudes +#ifdef DEBUG_PRINT + printf("StepTillMinSepar( , MinSepar=%3.1fm, MaxTime=%3.1fs)\n", 0.5*MinSepar, 0.5*MaxTime); + Print(); Target.Print(); +#endif + for( ; ; ) + { if(MaxTime==0) return PredTime; // if max. prediction time reached: stop + int32_t DistMargin = FastDistance(Target); // [0.5m] Distance margin to the target + if(DistMargin<=MinSepar) return PredTime; // If distance margin already below minimum + int16_t dT = Target.T-T; // [0.5sec] Target may not be exact same time + int32_t dS = (dT*TotSpeed)>>1; // [0.5m] thus some extra distance margin + DistMargin -= abs(dS); // [0.5m] subtract margin due to time difference + if(DistMargin<=MinSepar) return PredTime; // [0.5m] if distance margin below minimum + DistMargin -= MinSepar; // [0.5m] subtract minimum separation from the distance margin + if((TotSpeed*MaxTime) < (2*DistMargin) ) // If plenty enough margin given the speed + { uint16_t TimeMargin=MaxTime+2; + if((2*DistMargin)<(TotSpeed*TimeMargin)) TimeMargin=2*DistMargin/TotSpeed+1; + TimeMargin+=PredTime; // if(TimeMargin>240) TimeMargin=240; + return TimeMargin; } + int16_t StepTime = (2*DistMargin)/TotSpeed+1; // [0.5s] Time margin given distance margin and speed +#ifdef DEBUG_PRINT + printf("DistMargin=%3.1fm => StepTime=%+4.1fs\n", 0.5*DistMargin, 0.5*StepTime); +#endif + // if(StepTime==0) return PredTime; + // if(StepTime<1) StepTime=1; // minimum step time for prediction + if(StepTime>MaxStepTime) StepTime=MaxStepTime; // [0.5s] maximum step time for prediction + if(StepTime>MaxTime) StepTime=MaxTime; + int16_t NextTime = T+StepTime; // [0.5s] time + StepFwd(StepTime); + int16_t TgtStepTime = NextTime-Target.T; + if(TgtStepTime>0) Target.StepFwd(TgtStepTime); +#ifdef DEBUG_PRINT + Print(); Target.Print(); +#endif + PredTime+=StepTime; // [0.5s] count time by which we predict + MaxTime-=StepTime; } // [0.5s] decrement the max. time we should predict + return MaxTime+1; } // return estimated time margin before the separation could fall below minimum + + // predict the minimum distance: does not take turn into account thus must not be used on significant distances + int16_t MissTime(Acft_RelPos &Target, int16_t MaxTime=40) // [0.5s] + { int32_t dX = Target.X; // [0.5m] Target distance vector + int32_t dY = Target.Y; + int32_t dZ = Target.Z; + int32_t tVx, tVy; Target.getSpeedVector(tVx, tVy); // [0.5m/s] Target speed vector + int32_t tVz = Target.Climb; + int16_t dT = Target.T - T; // [0.5sec] time difference betwen target and me + if(dT) + { dX -= (dT*tVx)>>1; // adjust target position for the time diff. + dY -= (dT*tVy)>>1; + dZ -= (dT*tVz)>>1; } + dX -= X; // [0.5m] Target relative position + dY -= Y; + dZ -= Z; + int32_t dVx, dVy; getSpeedVector(dVx, dVy); // [0.5m/s] Target relative speed + dVx = tVx-dVx; + dVy = tVy-dVy; + int32_t dVz = tVz - Climb; + + int32_t DistSqrRate = dVx*dX + dVy*dY + dVz*dZ; // [0.25m2/s] 3-D scalar product: rel. distance x rel. speed + // if(DistSqrRate==0) return MaxTime; // if target neither moving away nor closing + // if(DistSqrRate>0) return MaxTime; // if target moving away from me + + int32_t RelVelSqr = dVx*dVx + dVy*dVy + dVz*dVz; // [0.25m2/s2] 3-D relative velocity square + // printf("MissTime() DistSqrRate = %+4.1fm^2/s RelVelSqr = %3.1f(m/s)^2\n", 0.25*DistSqrRate, 0.5*RelVelSqr); + if( ( RelVelSqr*MaxTime) <= (-2*DistSqrRate) ) return MaxTime; // if min. approach time is longer than maximum + if( (-RelVelSqr*MaxTime) >= (-2*DistSqrRate) ) return -MaxTime; // if min. approach time is longer than maximum + return (-2*DistSqrRate+(RelVelSqr/2))/RelVelSqr; } // [0.5sec] time of the closest approach + + void calcDir(void) // calculate the direction unity vector + { Dx = Icos(Heading); // DirX = cos(Heading) + Dy = Isin(Heading); } // DirY = sin(Heading) + + // void calcAccel(void) + // { int32_t A = ((int32_t)Turn*Speed*201+0x8000)>>16; // [1/64m/s^2] + // Ax = (-A*Dy+0x2000)>>14; // [1/16m/s^2] + // Ay = (+A*Dx+0x2000)>>14; } + + // void calcTurn(int16_t MaxRadius=10000) // calculate the turning radius and turning circle center + // { R=getTurnRadius(MaxRadius); if(R==0) return; + // Ox = X - ((Dy*R)>>12); + // Oy = Y + ((Dx*R)>>12); } + + int16_t getTurnDev(int16_t dT) // approx. horizontal deviation from straight line due to the turn + { int32_t A = ((int32_t)Turn*Speed*201+0x8000)>>16; // [1/64m/s^2] acceleration + // printf("getTurnDev() A=%6.3fm/s^2\n", A/64.0); + return (A*dT*dT+0x80)>>8; } // [0.5m] + + int16_t getTurnRadius(int32_t MaxRadius=0x7FFF) const + { int32_t Radius = (int32_t)Speed*10436; + if(Turn==0) return 0; // return zero for radius larger than maximum defined + Radius/=Turn; + if(abs(Radius)>MaxRadius) return 0; + return Radius; } // return turning radius [0.5m] = turning circle diameter [m] + + void getSpeedVector(int32_t &Vx, int32_t &Vy) // [0.5m/s] [0.5m/s] + { Vx = ((int32_t)Dx*Speed+0x800)>>12; // Vx = DirX * Speed + Vy = ((int32_t)Dy*Speed+0x800)>>12; } // Vy = DirY * Speed + + void getSpeedVector(int16_t &Vx, int16_t &Vy) // [0.5m/s] [0.5m/s] + { Vx = ((int32_t)Dx*Speed+0x800)>>12; // Vx = DirX * Speed + Vy = ((int32_t)Dy*Speed+0x800)>>12; } // Vy = DirY * Speed + + void StepFwd(int16_t Time) // [0.5s] predict the position into the future + { int16_t Vx, Vy; + int16_t Time1 = Time>>1; + int16_t Time2 = Time-Time1; + getSpeedVector(Vx, Vy); + int16_t dX = Time1*Vx; int16_t dY = Time1*Vy; + Heading += (Time*Turn)>>1; + calcDir(); // calcAccel(); + getSpeedVector(Vx, Vy); + dX += Time2*Vx; dY += Time2*Vy; + X += dX/2; + Y += dY/2; + Z += (Time*Climb)>>1; + T += Time; } + + void StepFwdSecs(int16_t Secs) // [sec] predict the position Secs into the future + { int16_t Vx, Vy; + int16_t Secs1=Secs>>1; + int16_t Secs2=Secs-Secs1; + getSpeedVector(Vx, Vy); + X += Secs1*Vx; Y += Secs1*Vy; + Heading += Secs*Turn; + calcDir(); // calcAccel(); + getSpeedVector(Vx, Vy); + X += Secs2*Vx; Y += Secs2*Vy; + Z += Secs*Climb; + T += 2*Secs; } + + void StepFwd4secs(void) // predict the position four second into the future + { int16_t Vx, Vy; + getSpeedVector(Vx, Vy); + X += 2*Vx; Y += 2*Vy; + Heading += 4*Turn; + calcDir(); // calcAccel(); + getSpeedVector(Vx, Vy); + X += 2*Vx; Y += 2*Vy; + Z += 4*Climb; + T += 8; } + + void StepFwd2secs(void) // predict the position two seconds into the future + { int16_t Vx, Vy; + getSpeedVector(Vx, Vy); // get hor. speed vector from speed and dir. vector + X += Vx; Y += Vy; // incr. the hor. coordinates by half the speed + Heading += 2*Turn; // increment the heading by the turning rate + calcDir(); // calcAccel(); // recalc. direction and acceleration + getSpeedVector(Vx, Vy); // recalc. the speed vector + X += Vx; Y += Vy; // incr. horizotal coord. by half the speed vector + Z += 2*Climb; // increment the rel. altitude by the climb rate + T += 4; } // increment time by 2sec + + void StepFwd1sec(void) // predict the position one second into the future + { int16_t Vx, Vy; + getSpeedVector(Vx, Vy); + X += Vx/2; Y += Vy/2; + Heading += Turn; + calcDir(); // calcAccel(); + getSpeedVector(Vx, Vy); + X += Vx/2; Y += Vy/2; + Z += Climb; + T += 2; } + + template // read position from an OGN packet, use provided reference + int32_t Read(OGNx_Packet &Packet, uint8_t RefTime, int32_t RefLat, int32_t RefLon, int32_t RefAlt, uint16_t LatCos=3000, int32_t MaxDist=10000) + { T = (int16_t)Packet.Position.Time-(int16_t)RefTime; + if(T<=(-30)) T+=60; else if(T>30) T-=60; + T<<=1; + int32_t LatDist, LonDist; + if(Packet.calcDistanceVector(LatDist, LonDist, RefLat, RefLon, LatCos, MaxDist)<0) return -1; + X = LatDist<<1; // [m] => [0.5m] + Y = LonDist<<1; // [m] => [0.5m] + Z = (Packet.DecodeAltitude()-RefAlt)<<1; // [m] => [0.5m] + Speed = (Packet.DecodeSpeed()+2)/5; // [0.1m/s] => [0.5m/s] + Heading = Packet.getHeadingAngle(); // [360/0x10000deg] + Climb = Packet.DecodeClimbRate()/5; // [0.1m/s] => [0.5m/s] + Turn = ((int32_t)Packet.DecodeTurnRate()*1165+32)>>6; // [0.1deg/s] => [360/0x10000deg/s] + calcDir(); + Error = (2*Packet.DecodeDOP()+22)/5; + // calcAccel(); + // calcTurn(); + // printf("Read: "); Packet.Print(); + // Print(); + return 1; } + + template // write position into the OGN packet, using given reference + void Write(OGNx_Packet &Packet, uint8_t RefTime, int32_t RefLat, int32_t RefLon, int32_t RefAlt, uint16_t LatCos=3000) + { int16_t Time=RefTime+(T>>1); + // if(Time<0) Time+=60; else if(Time>=60) Time-=60; + Packet.Position.Time = Time%60; + Packet.setDistanceVector(X>>1, Y>>1, RefLat, RefLon, LatCos); + Packet.EncodeAltitude(RefAlt+(Z>>1)); // + Packet.clrBaro(); // don't know the standard pressure altitude + Packet.EncodeSpeed(Speed*5); // [0.5m/s] => [0.1m/s] + Packet.setHeadingAngle(Heading); // + Packet.EncodeClimbRate(Climb*5); // [0.5m/s] => [0.1m/s] + Packet.EncodeTurnRate((Turn*7+64)>>7); // [360/0x10000deg/s] => [0.1deg/s] + Packet.EncodeDOP((5*Error)/2-10); + } + +} ; + +// ======================================================================================================= + diff --git a/main/rf.cpp b/main/rf.cpp index 23389d0..1fbad7f 100644 --- a/main/rf.cpp +++ b/main/rf.cpp @@ -157,16 +157,27 @@ static void SetFreqPlan(void) // set the RF TRX a } static uint8_t StartRFchip(void) -{ TRX.RESET(1); // RESET active - vTaskDelay(10); // wait 10ms +{ TRX.WriteMode(RF_OPMODE_STANDBY); + vTaskDelay(1); + TRX.RESET(1); // RESET active + vTaskDelay(1); // wait 10ms TRX.RESET(0); // RESET released - vTaskDelay(10); // wait 10ms + vTaskDelay(5); // wait 10ms SetFreqPlan(); // set TRX base frequency and channel separation after the frequency hopping plan +#ifdef DEBUG_PRINT + xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + TRX.PrintReg(CONS_UART_Write); + xSemaphoreGive(CONS_Mutex); +#endif +#ifdef WITH_RFM95 + TRX.WriteDefaultReg(); +#endif TRX.Configure(0, OGN_SYNC); // setup RF chip parameters and set to channel #0 TRX.WriteMode(RF_OPMODE_STANDBY); // set RF chip mode to STANDBY uint8_t Version = TRX.ReadVersion(); #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + TRX.PrintReg(CONS_UART_Write); Format_String(CONS_UART_Write, "StartRFchip() v"); Format_Hex(CONS_UART_Write, Version); CONS_UART_Write(' '); @@ -196,7 +207,7 @@ extern "C" TRX.DIO0_isOn = RFM_IRQ_isOn; TRX.RESET = RFM_RESET; - RF_FreqPlan.setPlan(Parameters.FreqPlan); // 1 = Europe/Africa, 2 = USA/CA, 3 = Australia and South America + RF_FreqPlan.setPlan(Parameters.FreqPlan); // 1 = Europe/Africa, 2 = USA/CA, 3 = Australia and South America vTaskDelay(5); @@ -204,8 +215,8 @@ extern "C" { uint8_t ChipVersion = StartRFchip(); xSemaphoreTake(CONS_Mutex, portMAX_DELAY); - Format_String(CONS_UART_Write, "TaskRF: "); - CONS_UART_Write('v'); Format_Hex(CONS_UART_Write, ChipVersion); + Format_String(CONS_UART_Write, "TaskRF: v"); + Format_Hex(CONS_UART_Write, ChipVersion); CONS_UART_Write('\r'); CONS_UART_Write('\n'); xSemaphoreGive(CONS_Mutex); @@ -227,9 +238,10 @@ extern "C" for( ; ; ) { - while(Button_SleepRequest) - { TRX.WriteMode(RF_OPMODE_SLEEP); - vTaskDelay(100); } + +// #ifdef DEBUG_PRINT + // RF_Print(); +// #endif uint32_t RxRssiSum=0; uint16_t RxRssiCount=0; // measure the average RSSI for lower frequency do @@ -246,6 +258,14 @@ extern "C" TRX.WriteMode(RF_OPMODE_STANDBY); // switch to standy vTaskDelay(1); + + if(PowerMode==0) + { TRX.WriteMode(RF_OPMODE_SLEEP); + while(PowerMode==0) + vTaskDelay(1); + TRX.WriteMode(RF_OPMODE_STANDBY); + vTaskDelay(1); } + SetFreqPlan(); TRX.averRSSI=RX_RSSI.getOutput(); diff --git a/main/rfm.h b/main/rfm.h index 9f28612..7f7ea62 100644 --- a/main/rfm.h +++ b/main/rfm.h @@ -23,12 +23,12 @@ class RFM_RxPktData // packet received by the RF chip void Print(void (*CONS_UART_Write)(char), uint8_t WithData=0) const { // uint8_t ManchErr = Count1s(RxPktErr, 26); Format_String(CONS_UART_Write, "RxPktData: "); - Format_UnsDec(CONS_UART_Write, Time); + Format_HHMMSS(CONS_UART_Write, Time); CONS_UART_Write('+'); Format_UnsDec(CONS_UART_Write, msTime, 4, 3); CONS_UART_Write(' '); Format_Hex(CONS_UART_Write, Channel); CONS_UART_Write('/'); - Format_SignDec(CONS_UART_Write, (int16_t)(-5*(int16_t)RSSI), 4, 1); + Format_SignDec(CONS_UART_Write, (int16_t)(-5*(int16_t)RSSI), 3, 1); Format_String(CONS_UART_Write, "dBm\n"); if(WithData==0) return; for(uint8_t Idx=0; Idx7) SyncTol=7; if(WriteSize>8) WriteSize=8; WriteBytes(SyncData+(8-WriteSize), WriteSize, REG_SYNCVALUE1); // write the SYNC, skip some initial bytes - WriteByte( 0x90 | (WriteSize-1), REG_SYNCCONFIG); // write SYNC length [bytes] + WriteByte( 0x90 | (WriteSize-1), REG_SYNCCONFIG); // write SYNC length [bytes] (or 0xB0 for reversed preamble) => p.92 WriteWord( 9-WriteSize, REG_PREAMBLEMSB); } // write preamble length [bytes] (page 71) // ^ 8 or 9 ? #endif @@ -366,7 +388,7 @@ class RFM_TRX uint16_t ReadIrqFlags(void) { return ReadWord(REG_IRQFLAGS1); } - void ClearIrqFlags(void) { WriteWord(RF_IRQ_FifoOverrun | RF_IRQ_Rssi, REG_IRQFLAGS1); } + void ClearIrqFlags(void) { WriteWord(RF_IRQ_FifoOverrun | RF_IRQ_Rssi | RF_IRQ_PreambleDetect | RF_IRQ_SyncAddrMatch, REG_IRQFLAGS1); } #ifdef WITH_RFM69 void WriteTxPower_W(int8_t TxPower=10) // [dBm] for RFM69W: -18..+13dBm @@ -401,12 +423,12 @@ class RFM_TRX void WriteTxPowerMin(void) { WriteTxPower_W(-18); } // set minimal Tx power and setup for reception - int Configure(int16_t Channel, const uint8_t *Sync) + int Configure(int16_t Channel, const uint8_t *Sync, bool PW=0) { WriteMode(RF_OPMODE_STANDBY); // mode = STDBY ClearIrqFlags(); WriteByte( 0x02, REG_DATAMODUL); // [0x00] Packet mode, FSK, 0x02: BT=0.5, 0x01: BT=1.0, 0x03: BT=0.3 - WriteWord(0x0140, REG_BITRATEMSB); // bit rate = 100kbps - WriteWord(0x0333, REG_FDEVMSB); // FSK deviation = +/-50kHz + WriteWord(PW?0x0341:0x0140, REG_BITRATEMSB); // bit rate = 100kbps + WriteWord(PW?0x013B:0x0333, REG_FDEVMSB); // FSK deviation = +/-50kHz setChannel(Channel); // operating channel WriteSYNC(8, 7, Sync); // SYNC pattern (setup for reception) WriteByte( 0x00, REG_PACKETCONFIG1); // [0x10] Fixed size packet, no DC-free encoding, no CRC, no address filtering @@ -434,42 +456,95 @@ class RFM_TRX #if defined(WITH_RFM95) || defined(WITH_SX1272) void WriteTxPower(int8_t TxPower=0) - { if(TxPower<2) TxPower=2; - else if(TxPower>17) TxPower=17; + { if(TxPower>17) + { if(TxPower>20) TxPower=20; + WriteByte(0x87, REG_PADAC); + WriteByte(0xF0 | (TxPower-5), REG_PACONFIG); } + else // if(TxPower>14) + { if(TxPower<2) TxPower=2; + WriteByte(0x84, REG_PADAC); + WriteByte(0xF0 | (TxPower-2), REG_PACONFIG); } + // else + // { if(TxPower<0) TxPower=0; + // WriteByte(0x84, REG_PADAC); + // WriteByte(0x70 | (TxPower+1), REG_PACONFIG); } + + // if(TxPower<2) TxPower=2; + // else if(TxPower>17) TxPower=17; // if(TxPower<=14) // { WriteByte(0x70 | TxPower , REG_PACONFIG); // } // else - { WriteByte(0xF0 | (TxPower-2), REG_PACONFIG); - } + // { WriteByte(0xF0 | (TxPower-2), REG_PACONFIG); } } void WriteTxPowerMin(void) { WriteTxPower(0); } - int Configure(int16_t Channel, const uint8_t *Sync) + int Configure(int16_t Channel, const uint8_t *Sync, bool PW=0) { WriteMode(RF_OPMODE_STANDBY); // mode: STDBY, modulation: FSK, no LoRa // usleep(1000); WriteTxPower(0); - // ClearIrqFlags(); - WriteWord(0x0140, REG_BITRATEMSB); // bit rate = 100kbps (32MHz/100000) + ClearIrqFlags(); + WriteWord(PW?0x0341:0x0140, REG_BITRATEMSB); // bit rate = 100kbps (32MHz/100000) (0x0341 = 38.415kbps) + WriteByte(0x00, REG_BITRATEFRAC); // // ReadWord(REG_BITRATEMSB); - WriteWord(0x0333, REG_FDEVMSB); // FSK deviation = +/-50kHz [32MHz/(1<<19)] + WriteWord(PW?0x013B:0x0333, REG_FDEVMSB); // FSK deviation = +/-50kHz [32MHz/(1<<19)] (0x013B = 19.226kHz) // ReadWord(REG_FDEVMSB); setChannel(Channel); // operating channel WriteSYNC(8, 7, Sync); // SYNC pattern (setup for reception) - WriteByte( 0x8A, REG_PREAMBLEDETECT); // preamble detect: 1 byte, page 92 + WriteByte( 0x85, REG_PREAMBLEDETECT); // preamble detect: 1 byte, page 92 (or 0x85 ?) WriteByte( 0x00, REG_PACKETCONFIG1); // Fixed size packet, no DC-free encoding, no CRC, no address filtering WriteByte( 0x40, REG_PACKETCONFIG2); // Packet mode - WriteByte( 51, REG_FIFOTHRESH); // TxStartCondition=FifoNotEmpty, FIFO threshold = 51 bytes WriteByte( 2*26, REG_PAYLOADLENGTH); // Packet size = 26 bytes Manchester encoded into 52 bytes + WriteByte( 51, REG_FIFOTHRESH); // TxStartCondition=FifoNotEmpty, FIFO threshold = 51 bytes WriteWord(0x3030, REG_DIOMAPPING1); // DIO signals: DIO0=00, DIO1=11, DIO2=00, DIO3=00, DIO4=00, DIO5=11, => p.64, 99 - WriteByte( 0x4A, REG_RXBW); // +/-100kHz Rx bandwidth => p.27+67 + WriteByte( 0x02, REG_RXBW); // +/-125kHz Rx (single-side) bandwidth => p.27,67,83,90 + WriteByte( 0x02, REG_AFCBW); // +/-125kHz AFC bandwidth WriteByte( 0x49, REG_PARAMP); // BT=0.5 shaping, 40us ramp up/down - WriteByte( 0x07, REG_RSSICONFIG); // 256 samples for RSSI, p.90 + WriteByte( 0x0E, REG_RXCONFIG); // => p.90 (or 0x8E ?) + WriteByte( 0x07, REG_RSSICONFIG); // 256 samples for RSSI, no offset, => p.90,82 + WriteByte( 0x20, REG_LNA); // max. LNA gain, => p.89 return 0; } - uint8_t ReadLowBat(void) { return ReadByte(REG_LOWBAT ); } + uint8_t ReadLowBat(void) { return ReadByte(REG_LOWBAT ); } + + void PrintReg(void (*CONS_UART_Write)(char)) + { Format_String(CONS_UART_Write, "RFM95 Mode:"); + uint8_t RxMode=ReadMode(); + Format_Hex(CONS_UART_Write, RxMode); + CONS_UART_Write(' '); CONS_UART_Write('0'+DIO0_isOn()); + Format_String(CONS_UART_Write, " IRQ:"); + Format_Hex(CONS_UART_Write, ReadWord(REG_IRQFLAGS1)); + Format_String(CONS_UART_Write, " Pre:"); + Format_Hex(CONS_UART_Write, ReadWord(REG_PREAMBLEMSB)); + Format_String(CONS_UART_Write, " SYNC:"); + Format_Hex(CONS_UART_Write, ReadByte(REG_SYNCCONFIG)); + CONS_UART_Write('/'); + for(uint8_t Idx=0; Idx<8; Idx++) + Format_Hex(CONS_UART_Write, ReadByte(REG_SYNCVALUE1+Idx)); + Format_String(CONS_UART_Write, " FREQ:"); + Format_Hex(CONS_UART_Write, ReadByte(REG_FRFMSB)); + Format_Hex(CONS_UART_Write, ReadByte(REG_FRFMID)); + Format_Hex(CONS_UART_Write, ReadByte(REG_FRFLSB)); + Format_String(CONS_UART_Write, " RATE:"); + Format_Hex(CONS_UART_Write, ReadWord(REG_BITRATEMSB)); + Format_String(CONS_UART_Write, " FDEV:"); + Format_Hex(CONS_UART_Write, ReadWord(REG_FDEVMSB)); + Format_String(CONS_UART_Write, " DIO:"); + Format_Hex(CONS_UART_Write, ReadWord(REG_DIOMAPPING1)); + Format_String(CONS_UART_Write, " CFG:"); + Format_Hex(CONS_UART_Write, ReadByte(REG_PREAMBLEDETECT)); + Format_Hex(CONS_UART_Write, ReadByte(REG_PACKETCONFIG1)); + Format_Hex(CONS_UART_Write, ReadByte(REG_PACKETCONFIG2)); + Format_Hex(CONS_UART_Write, ReadByte(REG_FIFOTHRESH)); + Format_Hex(CONS_UART_Write, ReadByte(REG_PAYLOADLENGTH)); + Format_Hex(CONS_UART_Write, ReadByte(REG_RXBW)); + Format_Hex(CONS_UART_Write, ReadByte(REG_RSSICONFIG)); + Format_String(CONS_UART_Write, " PA:"); + Format_Hex(CONS_UART_Write, ReadByte(REG_PARAMP)); + Format_Hex(CONS_UART_Write, ReadByte(REG_PACONFIG)); + Format_String(CONS_UART_Write, "\n"); } #endif diff --git a/main/sens.cpp b/main/sens.cpp index c139820..f2fe334 100644 --- a/main/sens.cpp +++ b/main/sens.cpp @@ -6,6 +6,7 @@ #include "parameters.h" +#include "proc.h" #include "ctrl.h" #include "gps.h" @@ -13,6 +14,22 @@ #if defined(WITH_BMP180) || defined(WITH_BMP280) || defined(WITH_MS5607) || defined(WITH_BME280) +#ifdef WITH_BMP180 +#include "bmp180.h" +#endif + +#ifdef WITH_BMP280 +#include "bmp280.h" +#endif + +#ifdef WITH_BME280 +#include "bme280.h" +#endif + +#ifdef WITH_MS5607 +#include "ms5607.h" +#endif + #include "atmosphere.h" #include "slope.h" #include "lowpass2.h" @@ -191,7 +208,9 @@ static void ProcBaro(void) PosPtr->Temperature = Baro.Temperature; // and temperature in the GPS record #ifdef WITH_BME280 if(Baro.hasHumidity()) - PosPtr->Humidity = Baro.Humidity; + { PosPtr->Humidity = Baro.Humidity; + // PosPtr->hasHum=1; + } #endif PosPtr->hasBaro=1; } // tick "hasBaro" flag } @@ -218,7 +237,7 @@ static void ProcBaro(void) Line[Len++]=','; } #endif Len+=NMEA_AppendCheckCRNL(Line, Len); - // if(CONS_UART_Free()>=128) + if(Parameters.Verbose) { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, Line, 0, Len); // send NMEA sentence to the console (UART1) xSemaphoreGive(CONS_Mutex); } @@ -236,7 +255,25 @@ static void ProcBaro(void) Len+=Format_String(Line+Len, "m,"); // normally f for feet, but metres and m works with XcSoar Len+=Format_String(Line+Len, "3"); // 1 no fix, 2 - 2D, 3 - 3D; assume 3D for now Len+=NMEA_AppendCheckCRNL(Line, Len); - // if(CONS_UART_Free()>=128) + if(Parameters.Verbose) + { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); + Format_String(CONS_UART_Write, Line, 0, Len); // send NMEA sentence to the console (UART1) + xSemaphoreGive(CONS_Mutex); } + + Len=0; + Len+=Format_String(Line+Len, "$LK8EX1,"); + Len+=Format_UnsDec(Line+Len, (uint32_t)(Pressure+2)>>2); // [Pa] pressure + Line[Len++]=','; + Len+=Format_SignDec(Line+Len, (StdAltitude+5)/10); // [m] standard altitude (calc. from pressure) + Line[Len++]=','; + Len+=Format_SignDec(Line+Len, ClimbRate); // [cm/s] climb rate + Line[Len++]=','; + Len+=Format_SignDec(Line+Len, (Baro.Temperature+5)/10); // [degC] temperature + Line[Len++]=','; + Len+=Format_UnsDec(Line+Len, (BatteryVoltage+128)>>8, 4, 3); // [mV] Battery voltage + // Len+=Format_String(Line+Len, "999"); // [%] battery level + Len+=NMEA_AppendCheckCRNL(Line, Len); + if(Parameters.Verbose) { xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, Line, 0, Len); // send NMEA sentence to the console (UART1) xSemaphoreGive(CONS_Mutex); } @@ -299,10 +336,9 @@ void vTaskSENS(void* pvParameters) while(1) { - if(Button_SleepRequest) - { vTaskDelay(1000); } #if defined(WITH_BMP180) || defined(WITH_BMP280) || defined(WITH_MS5607) || defined(WITH_BME280) - ProcBaro(); + if(PowerMode) ProcBaro(); + else vTaskDelay(100); #else vTaskDelay(1000); #endif diff --git a/main/sound.h b/main/sound.h new file mode 100644 index 0000000..b47b366 --- /dev/null +++ b/main/sound.h @@ -0,0 +1,11 @@ +#include "hal.h" + +void SoundMsg(char ch); +int SoundMsg(const char *Msg); + +#ifdef __cplusplus + extern "C" +#endif + void vTaskSOUND(void* pvParameters); + + diff --git a/main/st7789.cpp b/main/st7789.cpp new file mode 100644 index 0000000..b082c14 --- /dev/null +++ b/main/st7789.cpp @@ -0,0 +1,731 @@ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" // for SPI +#include "esp32/rom/tjpgd.h" // for JPEG +#include "driver/ledc.h" // for PWM backlight control + +#include "st7789.h" + +#include "hal.h" +#include "format.h" + +// #define LCD_FLIP // flip the LCD: rotate by 180deg, works only for 240x240 displays + +// ============================================================================= + +// pins specific for this LCD +gpio_num_t LCD_PIN_CS = GPIO_NUM_NC; // connected to ground: constantly active +gpio_num_t LCD_PIN_DC = GPIO_NUM_0; // D/C: Command = 0, Data = 1 +gpio_num_t LCD_PIN_RST = GPIO_NUM_NC; // actually connected to system RESET +gpio_num_t LCD_PIN_BCKL = GPIO_NUM_4; // back-light: HIGH active + +#ifdef LCD_FLIP +const int LCD_XOFS = 80; // [pixels] +#endif + +int LCD_TYPE = 0; // 0 = ST7789, 1 = ILI9341 +int LCD_WIDTH = 240; // [pixels] +int LCD_HEIGHT = 240; // [pixels] + +static spi_device_handle_t LCD_SPI; + +// ============================================================================= + +/* +typedef struct +{ uint8_t cmd; // command + uint8_t databytes; // Number of parameter bytes; bit 7 = delay after set; 0xFF = end of cmds. + uint8_t data[14]; // up to 16 parameter bytes per command +} lcd_init_cmd_t; + +static const lcd_init_cmd_t ST7789_init_cmds[] = { + { 0x01, 0xC0, {0} }, // LCD software reset, wait 120ms + { 0x36, 1, {0b01100000} }, // Memory Data Access Control, MX=MV=1, MY=ML=MH=0, RGB=0 + { 0x3A, 1, {0x55} }, // Interface Pixel Format, 16bits/pixel for RGB/MCU interface + { 0xB2, 5, {0x0c, 0x0c, 0x00, 0x33, 0x33} }, // Porch Setting + { 0xB7, 1, {0x45} }, // Gate Control, Vgh=13.65V, Vgl=-10.43V + { 0xBB, 1, {0x2B} }, // VCOM Setting, VCOM=1.175V + { 0xC0, 1, {0x2C} }, // LCM Control, XOR: BGR, MX, MH + { 0xC2, 2, {0x01, 0xff} }, // VDV and VRH Command Enable, enable=1 + { 0xC3, 1, {0x11} }, // VRH Set, Vap=4.4+... + { 0xC4, 1, {0x20} }, // VDV Set, VDV=0 + { 0xC6, 1, {0x0f} }, // Frame Rate Control, 60Hz, inversion=0 + { 0xD0, 2, {0xA4, 0xA1} }, // Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V + // Positive Voltage Gamma Control + { 0xE0, 14, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19} }, + // Negative Voltage Gamma Control + { 0xE1, 14, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19} }, + { 0x11, 0xC0, {0} }, // Sleep Out, wait 120ms + { 0x21, 0, {0} }, // Invert ON + { 0x29, 0x10, {0} }, // Display ON, wait 10ms ? + { 0x00, 0xFF, {0} } +}; +*/ + +static const uint8_t ST7789_init[] = { +// 0x00, 0x10, + 0x01, 0xE0, // LCD software reset, wait 140ms + 0x01, 0xE0, // 2nd LCD software reset, wait 140ms +#ifdef LCD_FLIP + 0x36, 0x01, 0b10100000, // flipped by 180deg but needs horizontal offset +#else + 0x36, 0x01, 0b01100000, // Memory Data Access Control, MX MY MV ML RGB MH 1 0 +#endif + 0x3A, 0x01, 0x55, // Interface Pixel Format, 16bits/pixel for RGB/MCU interface + 0xB2, 0x05, 0x0c, 0x0c, 0x00, 0x33, 0x33, // Porch Setting + 0xB7, 0x01, 0x45, // Gate Control, Vgh=13.65V, Vgl=-10.43V + 0xBB, 0x01, 0x2B, // VCOM Setting, VCOM=1.175V + 0xC0, 0x01, 0x2C, // LCM Control, XOR: BGR, MX, MH + 0xC2, 0x02, 0x01,0xff, // VDV and VRH Command Enable, enable=1 + 0xC3, 0x01, 0x11, // VRH Set, Vap=4.4+... + 0xC4, 0x01, 0x20, // VDV Set, VDV=0 + 0xC6, 0x01, 0x0f, // Frame Rate Control, 60Hz, inversion=0 + 0xD0, 0x02, 0xA4, 0xA1, // Power Control 1, AVDD=6.8V, AVCL=-4.8V, VDDS=2.3V + 0xE0, 0x0E, 0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19, // Positive Voltage Gamma Control + 0xE1, 0x0E, 0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19, // Negative Voltage Gamma Control + 0x11, 0xE0, // Sleep Out, wait 140ms + 0x21, 0x00, // Invert ON + 0x29, 0x20, // Display ON, wait 20ms ? + 0x00, 0xFF // terminator +}; + +static const uint8_t ILI9341_init[] = { + 0x01, 0xD0, // Software RESET + 0xCF, 3, 0x00, 0x83, 0x30 , // Power contorl B, power control = 0, DC_ENA = 1 + 0xED, 4, 0x64, 0x03, 0x12, 0x81 , // Power on sequence control, cp1 keeps 1 frame, 1st frame enable, vcl = 0, ddvdh=3, vgh=1, vgl=2, DDVDH_ENH=1 + 0xE8, 3, 0x85, 0x01, 0x79 , // Driver timing control A, non-overlap=default +1, EQ=default - 1, CR=default, pre-charge=default - 1 + 0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02 , // Power control A, Vcore=1.6V, DDVDH=5.6V + 0xF7, 1, 0x20 , // Pump ratio control, DDVDH=2xVCl + 0xEA, 2, 0x00, 0x00 , // Driver timing control, all=0 unit + // 0xC0, 1, 0x23, + // 0xC1, 1, 0x10, + // 0xC5, 2, 0x3E, 0x28, + // 0xC7, 1, 0x86, + 0xC0, 1, 0x26, // Power control 1, GVDD=4.75V + 0xC1, 1, 0x11, // Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 + 0xC5, 2, 0x35, 0x3E , // VCOM control 1, VCOMH=4.025V, VCOML=-0.950V + 0xC7, 1, 0xBE, // VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 + 0x36, 1, 0b00001000 , // Memory Data Access Control, MX=MV=0, MY=ML=MH=0, RGB=1 + 0x3A, 1, 0x55 , // Pixel format, 16bits/pixel for RGB/MCU interface + 0xB1, 2, 0x00, 0x1B , // Frame rate control, f=fosc, 70Hz fps + // 0xF2, 1, 0x08 , // Enable 3G, disabled + 0xF2, 1, 0x00 , // 3Gamma function disable + 0x26, 1, 0x01 , // Gamma set, curve 1 + // 0x26, 1, 0x02 , // Gamma set, curve 1 + // 0xE0, 15, 0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00 , // Positive gamma correction + // 0xE1, 15, 0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F , // Negative gamma correction + 0xE0, 15, 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00, + 0xE1, 15, 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f, + // 0xE0, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, + // 0xE1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, + 0x2A, 4, 0x00, 0x00, 0x00, 0xEF , // Column address set, SC=0, EC=0xEF + 0x2B, 4, 0x00, 0x00, 0x01, 0x3f , // Page address set, SP=0, EP=0x013F + 0x2C, 0, // Memory write + 0xB7, 1, 0x07 , // Entry mode set, Low vol detect disabled, normal display + 0xB6, 4, 0x0A, 0x82, 0x27, 0x00 , // Display function control + 0x11, 0xC0, // Sleep out + 0x29, 0x20, // Display on + 0x00, 0xFF +}; + +/* Send a command to the LCD. Uses spi_device_polling_transmit, which waits + * until the transfer is complete. + * + * Since command transactions are usually small, they are handled in polling + * mode for higher speed. The overhead of interrupt transactions is more than + * just waiting for the transaction to complete. + */ +static esp_err_t lcd_cmd(const uint8_t cmd) +{ spi_transaction_t t; + memset(&t, 0, sizeof(t)); // Zero out the transaction + t.length=8; // Command is 8 bits + t.tx_buffer=&cmd; // The data is the cmd itself + t.user=(void*)0; // D/C needs to be set to 0 + return spi_device_polling_transmit(LCD_SPI, &t); } // Transmit! + +/* Send data to the LCD. Uses spi_device_polling_transmit, which waits until the + * transfer is complete. + * + * Since data transactions are usually small, they are handled in polling + * mode for higher speed. The overhead of interrupt transactions is more than + * just waiting for the transaction to complete. + */ + +static DRAM_ATTR uint8_t ram_buff[16]; + +static esp_err_t lcd_data(const uint8_t *data, int len) +{ // uint8_t ram_buff[len]; + memcpy(ram_buff, data, len); // copy to RAM buffer, otherwise DMA would not work + spi_transaction_t t; + if (len==0) return ESP_OK; // no need to send anything + memset(&t, 0, sizeof(t)); // Zero out the transaction + t.length=len*8; // Len is in bytes, transaction length is in bits. + t.tx_buffer=ram_buff; // Data + t.user=(void*)1; // D/C needs to be set to 1 + return spi_device_polling_transmit(LCD_SPI, &t); } // Transmit! + +// This function is called (in irq context!) just before a transmission starts. It will +// set the D/C line to the value indicated in the user field. +static void lcd_spi_pre_transfer_callback(spi_transaction_t *t) +{ int DC=(int)t->user; + gpio_set_level(LCD_PIN_DC, DC); + // if(LCD_PIN_CS>GPIO_NUM_NC) gpio_set_level(LCD_PIN_CS, 0); +} + +// static void lcd_spi_post_transfer_callback(spi_transaction_t *t) +// { if(LCD_PIN_CS>GPIO_NUM_NC) gpio_set_level(LCD_PIN_CS, 1); } + +static ledc_timer_config_t LEDC_Timer = +{ speed_mode : LEDC_HIGH_SPEED_MODE, // timer mode + duty_resolution : LEDC_TIMER_8_BIT, // resolution of PWM duty: 0..255 + timer_num : LEDC_TIMER_0, // timer index + freq_hz : 1000 // frequency of PWM signal +} ; + +static ledc_channel_config_t LEDC_Channel = + { gpio_num : LCD_PIN_BCKL, + speed_mode : LEDC_HIGH_SPEED_MODE, + channel : LEDC_CHANNEL_1, + intr_type : LEDC_INTR_DISABLE, + timer_sel : LEDC_TIMER_0, + duty : 0, + hpoint : 0 + } ; + +void LCD_SetBacklightLevel(uint8_t Level) +{ if(Level>8) Level=0; + uint8_t Duty=1; Duty<<=Level; Duty--; + if(LCD_PIN_BCKL>GPIO_NUM_NC) + { ledc_set_duty(LEDC_Channel.speed_mode, LEDC_Channel.channel, Duty); + ledc_update_duty(LEDC_Channel.speed_mode, LEDC_Channel.channel); } +} + +// Initialize the display +static void lcd_gpio_init(void) +{ + if(LCD_PIN_BCKL>GPIO_NUM_NC) + { LEDC_Channel.gpio_num = LCD_PIN_BCKL; + ledc_timer_config(&LEDC_Timer); // Set configuration of timer for high speed channels + ledc_channel_config(&LEDC_Channel); + LCD_SetBacklightOFF(); } + + gpio_set_direction(LCD_PIN_DC, GPIO_MODE_OUTPUT); + + if(LCD_PIN_RST>GPIO_NUM_NC) + { gpio_set_direction(LCD_PIN_RST, GPIO_MODE_OUTPUT); } + + // if(LCD_PIN_CS>GPIO_NUM_NC) + // { gpio_set_direction(LCD_PIN_CS, GPIO_MODE_OUTPUT); + // gpio_set_level(LCD_PIN_CS, 1); } +} + +static void lcd_start(const uint8_t *cmd) // reset controller and send initial config commands +{ + if(LCD_PIN_RST>GPIO_NUM_NC) + { gpio_set_level(LCD_PIN_RST, 0); + vTaskDelay(100); + gpio_set_level(LCD_PIN_RST, 1); + vTaskDelay(140); + cmd+=2; } // skip Software Reset + else + { } // include Software Reset + + for( ; ; ) + { uint8_t Cmd = *cmd++; + uint8_t Len = *cmd++; if(Len==0xFF) break; + uint8_t Wait = Len>>4; Len&=0x0F; + lcd_cmd(Cmd); + lcd_data(cmd, Len); + if(Wait) vTaskDelay(10*Wait); + // else vTaskDelay(1); + cmd+=Len; } + +} + +// ============================================================================= + +// const int LCD_BUFF_SIZE = 12*320; +DRAM_ATTR static uint16_t lcd_buffer[LCD_BUFF_SIZE]; +static int lcd_buffer_filled = 0; // buffer is prefilled up to this size with a fixed RGB565 + +static void lcd_buffer_fill(int size, uint16_t RGB565) // fill the buffer with given RGB565 up to the desired size +{ if(lcd_buffer[0]!=RGB565) lcd_buffer_filled=0; // if filled with a different RGB565 then assume not filled + if(lcd_buffer_filled>=size) return; // if filled up to the desired size then we are done + for(int x=lcd_buffer_filled; x>8; // Start Col MSB + lcd_trans[1].tx_data[1] = xpos&0xFF; // Start Col LSB + xpos += xsize-1; + lcd_trans[1].tx_data[2] = xpos>>8; // End Col MSB + lcd_trans[1].tx_data[3] = xpos&0xFF; // End Col LSB + lcd_trans[2].tx_data[0] = 0x2B; // Page address set + lcd_trans[3].tx_data[0] = ypos>>8; // Start page MSB + lcd_trans[3].tx_data[1] = ypos&0xFF; // start page LSB + ypos += ysize-1; + lcd_trans[3].tx_data[2] = ypos>>8; // end page MSB + lcd_trans[3].tx_data[3] = ypos&0xFF; // end page LSB + lcd_trans[4].tx_data[0] = 0x2C; // memory write + lcd_trans[5].tx_buffer = data; // finally send the line data + lcd_trans[5].length = xsize*ysize*2*8; // Data length, in bits + lcd_trans[5].rxlength = 0; // need to set this, otherwise a previous value is taken and an error occurs + lcd_trans[5].flags=0; } // undo SPI_TRANS_USE_TXDATA flag + +// ================================================================================ + +void LCD_DrawBox(int xpos, int ypos, int xsize, int ysize, uint16_t RGB565) +{ if(xsize==0) return; + if(ysize==0) return; + if(xpos>=LCD_WIDTH) return; + if(ypos>=LCD_HEIGHT) return; + if((xpos+xsize)<=0) return; + if((ypos+ysize)<=0) return; + if(xpos<0) { xsize+=xpos; xpos=0; } + if(ypos<0) { ysize+=ypos; ypos=0; } + if((xpos+xsize)>LCD_WIDTH) { xsize=LCD_WIDTH-xpos; } + if((ypos+ysize)>LCD_HEIGHT) { ysize=LCD_HEIGHT-ypos; } + + if(lcd_transaction_active) lcd_trans_wait(); + + int lines_per_batch = LCD_BUFF_SIZE/xsize; // number of lines we can do per batch given the buffer size + if(lines_per_batch>ysize) lines_per_batch=ysize; // if bigger than we need to do then cut it down + int pixels_per_batch = lines_per_batch*xsize; // number of pixels per batch + lcd_buffer_fill(pixels_per_batch, RGB565); // fill the buffer with uniform color, if not filled already + for( ; ysize; ) // send given number of lines: do it in batches for speed + { if(lines_per_batch>ysize) lines_per_batch=ysize; + lcd_trans_setup(xpos, ypos, xsize, lines_per_batch, lcd_buffer); + lcd_trans_start(); + ypos+=lines_per_batch; ysize-=lines_per_batch; } +} + +// void LCD_DrawHorLine(int xpos, int ypos, int xsize, uint16_t RGB565) +// { LCD_DrawBox(xpos, ypos, xsize, 1, RGB565); } + +// void LCD_DrawVerLine(int xpos, int ypos, int ysize, uint16_t RGB565) +// { LCD_DrawBox(xpos, ypos, 1, ysize, RGB565); } + +// void LCD_DrawPixel(int xpos, int ypos, uint16_t RGB565) +// { LCD_DrawBox(xpos, ypos, 1, 1, RGB565); } + +// ================================================================================ + +static void swap(int &a, int &b) { int t = a; a = b; b = t; } + +void LCD_DrawLine(int x0, int y0, int x1, int y1, uint16_t RGB565) +{ + if (x0 == x1) + { if (y0 <= y1) LCD_DrawVerLine(x0, y0, y1-y0+1, RGB565); + else LCD_DrawVerLine(x0, y1, y0-y1+1, RGB565); + return; } + if (y0 == y1) + { if (x0 <= x1) LCD_DrawHorLine(x0, y0, x1-x0+1, RGB565); + else LCD_DrawHorLine(x1, y0, x0-x1+1, RGB565); + return; } + + int steep = 0; + if (abs(y1-y0) > abs(x1-x0)) steep = 1; + if (steep) { swap(x0, y0); swap(x1, y1); } + if (x0>x1) { swap(x0, x1); swap(y0, y1); } + + int dx = x1 - x0; + int dy = abs(y1 - y0); + int err = dx >> 1; + int ystep = -1; + int xs = x0; + int dlen = 0; + + if (y0= 0) + { y1--; + ddF_y += 2; + f += ddF_y; } + x1++; + ddF_x += 2; + f += ddF_x; + LCD_DrawPixel(x + x1, y + y1, RGB565); + LCD_DrawPixel(x - x1, y + y1, RGB565); + LCD_DrawPixel(x + x1, y - y1, RGB565); + LCD_DrawPixel(x - x1, y - y1, RGB565); + LCD_DrawPixel(x + y1, y + x1, RGB565); + LCD_DrawPixel(x - y1, y + x1, RGB565); + LCD_DrawPixel(x + y1, y - x1, RGB565); + LCD_DrawPixel(x - y1, y - x1, RGB565); + } +} + +// ================================================================================ +// Proportional fonts for Arduino + +class propChar +{ public: + uint8_t charCode; // for example 32 for ' ' + uint8_t yOffset; // empty horizontal space on top + uint8_t width; // core width + uint8_t height; // core height + uint8_t xOffset; // empty vertical space on the left + uint8_t xDelta; // shift X-pos for the next character + uint8_t Data[]; // + public: + uint16_t CoreBits(void) const { return (uint16_t)width*height; } + uint8_t DataBytes(void) const { return (CoreBits()+7)/8; } +} ; + +int LCD_FontHeight(const uint8_t *propFont) { return propFont[1]; } // height of the given font is in the 2nd byte of the font header + +static const propChar *FindChar(char Char, const uint8_t *propFont) // find given character +{ propFont+=4; // skip the font header: 4 bytes + for( ; ; ) // loop over the characters + { const propChar *Geom = (const propChar *)propFont; // pointer to the next character geometry + if(Geom->charCode==0xFF) break; // 0xFF is terminator: no more characters + if(Geom->charCode==(uint8_t)Char) return Geom; // if code matches: return the pointer + propFont += 6 + Geom->DataBytes(); } // if not: skip this character and go to the next + return 0; } // if went through all and not found: return NULL + +// slow, because goes pixel-by-pixel, but simple to code, no big deal with out-of-screen positions +int LCD_DrawTranspChar(char Char, int xpos, int ypos, uint16_t RGB565, const uint8_t *propFont) +{ const propChar *Geom = FindChar(Char, propFont); if(Geom==0) return 0; + xpos += Geom->xOffset; + ypos += Geom->yOffset; + const uint8_t *Data = Geom->Data; + uint8_t Byte = 0x00; + uint8_t Mask = 0x00; + for(int dy=0; dy < Geom->height; dy++) + { for(int dx=0; dx < Geom->width; dx++) + { if(Mask==0) { Byte = *Data++; Mask=0x80; } + if(Byte&Mask) LCD_DrawPixel(xpos+dx, ypos+dy, RGB565); + Mask>>=1; } + } + return Geom->xDelta; } // return by how much move the cursor to draw the next character + +int LCD_DrawTranspString(const char *String, int xpos, int ypos, uint16_t RGB565, const uint8_t *propFont) +{ int Len = 0; + for( ; ; ) + { char Char = *String++; if(Char==0) break; + Len += LCD_DrawTranspChar(Char, xpos+Len, ypos, RGB565, propFont); } + return Len; } + +int LCD_DrawChar(char Char, int xpos, int ypos, uint16_t Fore, uint16_t Back, const uint8_t *propFont) +{ const propChar *Geom = FindChar(Char, propFont); if(Geom==0) return 0; + int Width = Geom->xDelta; // the box for the character to be printed + int Height = LCD_FontHeight(propFont); + int CropLeft = 0; if(xpos<0) { CropLeft=(-xpos); xpos=0; Width -=CropLeft; } + int CropTop = 0; if(ypos<0) { CropTop =(-ypos); ypos=0; Height-=CropTop ; } + int CropRight = 0; if((xpos+Width)>LCD_WIDTH) { CropRight=xpos-LCD_WIDTH; Width-=CropRight; } + int CropBottom = 0; if((ypos+Height)>LCD_HEIGHT) { CropBottom=ypos-LCD_HEIGHT; Height-=CropBottom; } + if(Width<=0 || Height<=0) return Geom->xDelta; + int Pixels = Width*Height; + lcd_buffer_filled=0; + for(int Pix=0; PixData; + uint8_t Byte = 0x00; + uint8_t Mask = 0x00; + for(int dy=0; dy < Geom->height; dy++) + { for(int dx=0; dx < Geom->width; dx++) + { if(Mask==0) { Byte = *Data++; Mask=0x80; } + if(Byte&Mask) + { int X = Geom->xOffset+dx-CropLeft; + int Y = Geom->yOffset+dy-CropTop; + if( X>=0 && X=0 && Y>=1; } + } + lcd_trans_setup(xpos, ypos, Width, Height, lcd_buffer); + lcd_trans_start(); + lcd_trans_wait(); + return Width; } + +int LCD_CharWidth(char Char, const uint8_t *propFont) +{ const propChar *Geom = FindChar(Char, propFont); if(Geom==0) return 0; + return Geom->xDelta; } + +int LCD_StringWidth(const char *String, const uint8_t *propFont) +{ int Len = 0; + for( ; ; ) + { char Char = *String++; if(Char==0) break; + Len += LCD_CharWidth(Char, propFont); } + return Len; } + +// draw opaque characters of a string +int LCD_DrawString(const char *String, int xpos, int ypos, uint16_t Fore, uint16_t Back, const uint8_t *propFont) +{ int Len = 0; + for( ; ; ) + { char Char = *String++; if(Char==0) break; + Len += LCD_DrawChar(Char, xpos+Len, ypos, Fore, Back, propFont); } + return Len; } + +// draw only those characters of a string which have changed to minimize the LCD transfer thus maximize the speed +int LCD_UpdateString(const char *String, const char *RefString, int xpos, int ypos, uint16_t Fore, uint16_t Back, const uint8_t *propFont) +{ int Len = 0; + for( ; ; ) + { char Char = *String; if(Char) String++; + char RefChar = *RefString; if(RefChar) RefString++; + if(Char==0 && RefChar==0) break; + if(Char==RefChar) + { Len += LCD_CharWidth(Char, propFont); } + else + { if(Char) { Len += LCD_DrawChar(Char, xpos+Len, ypos, Fore, Back, propFont); } + else { int Width = LCD_CharWidth(RefChar, propFont); + LCD_DrawBox(xpos+Len, ypos, Width, LCD_FontHeight(propFont), Back); + Len+=LCD_CharWidth(RefChar, propFont); } + } + } + return Len; } + +// ============================================================================= + +typedef struct +{ int16_t xpos; // top left point X position on the LCD + int16_t ypos; // top left point Y position on the LCD + const uint8_t * Input; // memory buffer containing the image + uint32_t InpSize; // size of the input image + uint32_t InpPtr; // input image current position +} JPGIODEV; + +static UINT tjd_buf_input ( + JDEC* jd, // Decompression object + BYTE* buff, // Pointer to the read buffer (NULL:skip) + UINT nd ) // Number of bytes to read/skip from input stream +{ JPGIODEV *dev = (JPGIODEV*)jd->device; + // CONS_UART_Write('i'); + // Format_Hex(CONS_UART_Write, (uint32_t)(dev->Input)); + // CONS_UART_Write(':'); + // Format_Hex(CONS_UART_Write, (uint32_t)(buff)); + // CONS_UART_Write(' '); + // return 0; + if(!dev->Input) return 0; // if Input pointer is NULL (can it be ?) + if(dev->InpPtr >= dev->InpSize) return 0; // if past the InpSize: end of stream + if( (dev->InpPtr+nd) > dev->InpSize) nd = dev->InpSize-dev->InpPtr; + if(buff) // if not NULL pointer then copy, otherwise just skip + { memcpy(buff, dev->Input + dev->InpPtr, nd); } // copy the nd bytes of the input stream + dev->InpPtr += nd; // incremeant the input pointer + return nd; } // return the number of bytes copied or skipped + +static UINT tjd_output ( + JDEC* jd, // Decompression object of current session + void* bitmap, // Bitmap data to be output + JRECT* rect // Rectangular region to output +) +{ // Device identifier for the session (5th argument of jd_prepare function) + JPGIODEV *dev = (JPGIODEV*)jd->device; + // CONS_UART_Write('o'); + + int Lx0 = rect->left + dev->xpos; // coordinates on the screen + int Ly0 = rect->top + dev->ypos; + int Lx1 = rect->right+1 + dev->xpos; // + int Ly1 = rect->bottom+1 + dev->ypos; + int Jxs = Lx1-Lx0; // X-size of the JPEG rectagle + int Jys = Ly1-Ly0; // Y-size of the JPEG rectagle + + int CropLeft = 0; if(Lx0<0) { CropLeft = (-Lx0); Lx0=0; } // how much crop the JPEG rectangle on the left + int CropTop = 0; if(Ly0<0) { CropTop = (-Ly0); Ly0=0; } // how much crop the JPEG rectangle on the right + int CropRight = 0; if(Lx1>LCD_WIDTH) { CropRight = LCD_WIDTH-Lx1; Lx1=LCD_WIDTH; } // + int CropBottom = 0; if(Ly1>LCD_HEIGHT) { CropBottom = LCD_HEIGHT-Ly1; Ly1=LCD_HEIGHT; } // + int Llines = Ly1-Ly0; // number of lines going to LCD + int Lrows = Lx1-Lx0; // number of pixels per line going to LCD + if(Llines<=0) return 1; + if(Lrows<=0) return 1; + + uint16_t *Dst = lcd_buffer; // buffer to form RGB565 for transfer + uint8_t *Src = (uint8_t *)bitmap; // RGB from JPEG decoder + if(CropTop) Src += CropTop*3*Jxs; // Advance by the nuber of lines to skip + for(int Line=0; Line3) Scale=3; + JPGIODEV dev; // input/output device + JDEC decoder; // Decompression object (70 bytes) + // CONS_UART_Write('J'); + const UINT WorkSize = 3800; + char *Work = (char *)malloc(WorkSize); // JPEG decode work space + if(Work==0) return; + // CONS_UART_Write('P'); + + dev.Input = JPEG; + dev.InpSize = JPEGsize; + dev.InpPtr = 0; + dev.xpos = xpos; + dev.ypos = ypos; + + if(jd_prepare(&decoder, tjd_buf_input, (void *)Work, WorkSize, &dev)!=JDR_OK) { free(Work); return ; } + // CONS_UART_Write('E'); + JRESULT rc = jd_decomp(&decoder, tjd_output, Scale); + // CONS_UART_Write('G'); + free(Work); } + +// ================================================================================ + +void LCD_Start(void) +{ // if(LCD_TYPE==1) lcd_start(ILI9341_init); // reset, send initial commands + // else lcd_start(ST7789_init); // reset, send initial commands + if(LCD_TYPE==1) lcd_start(ILI9341_init); // reset, send initial commands + else lcd_start(ST7789_init); // reset, send initial commands + lcd_trans_init(); // initialize SPI transactions + LCD_DrawBox(0, 0, LCD_WIDTH, LCD_HEIGHT, RGB565_WHITE); // screen all-white +} + +void LCD_Init(spi_host_device_t LCD_SPI_HOST, uint8_t LCD_SPI_MODE, int LCD_SPI_SPEED) // Initialize SPI and LCD +{ + spi_device_interface_config_t DevConfig = // specific device on the SPI bus + { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = LCD_SPI_MODE, // SPI mode 3, but cna be 0 for other displays + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = LCD_SPI_SPEED, + .input_delay_ns = 0, // seems to help with the reliability + .spics_io_num = LCD_PIN_CS, // CS pin + .flags = 0, + .queue_size = 8, // We want to be able to queue 7 transactions at a time + .pre_cb = lcd_spi_pre_transfer_callback, // Specify pre-transfer callback to handle D/C line + .post_cb = 0 // lcd_spi_post_transfer_callback + }; + + esp_err_t ret = spi_bus_add_device(LCD_SPI_HOST, &DevConfig, &LCD_SPI); // Attach the LCD to the SPI bus + + lcd_gpio_init(); // setup GPIO + + // LCD_Start(); + + LCD_SetBacklightLevel(8); // max. backlight to signal power-on + +// LCD_DrawJPEG(OGN_logo_jpg, OGN_logo_size, 0, 0); // draw logo +// // LCD_DrawJPEG(Club_logo_jpg, Club_logo_size, 0, 0); +/* + LCD_DrawBox(LCD_SPI, 0, 0, LCD_WIDTH, LCD_HEIGHT, RGB565_RED); + LCD_DrawBox(LCD_SPI, 32, 32, LCD_WIDTH-64, LCD_HEIGHT-64, RGB565_BLUE); + LCD_DrawBox(LCD_SPI, 64, 64, LCD_WIDTH-128, LCD_HEIGHT-128, RGB565_GREEN); +*/ +/* + LCD_DrawLine( 0, 240, 240, 0, RGB565_MAGENTA); + LCD_DrawLine( 0, 180, 240, 60, RGB565_MAGENTA); + LCD_DrawLine( 0, 120, 240, 120, RGB565_MAGENTA); + LCD_DrawLine( 0, 60, 240, 180, RGB565_MAGENTA); + LCD_DrawLine( 0, 0, 240, 240, RGB565_MAGENTA); + LCD_DrawLine( 180, 0, 60, 240, RGB565_MAGENTA); + LCD_DrawLine( 120, 0, 120, 240, RGB565_MAGENTA); + LCD_DrawLine( 60, 0, 180, 240, RGB565_MAGENTA); + + LCD_DrawCircle(120, 120, 25, RGB565_MAGENTA); + LCD_DrawCircle(120, 120, 50, RGB565_MAGENTA); + LCD_DrawCircle(120, 120, 75, RGB565_MAGENTA); + LCD_DrawCircle(120, 120, 100, RGB565_MAGENTA); +*/ + // LCD_DrawString("OGN-Tracker", 40, 210, RGB565_BLUE, RGB565_PINK, tft_Dejavu24); + // LCD_DrawTranspString("OGN-Tracker", 40, 210, RGB565_BLUE, tft_Dejavu24); + // LCD_DrawTranspString("with T-Beam", 4, 20, RGB565_BLACK, tft_minya24); + // LCD_DrawString("Test Test Test Test Test Test Test Test Test", -10, -5, RGB565_BLUE, RGB565_PINK, tft_Dejavu24); + + // LCD_clearDisplay(RGB565_GREEN); +} + +void LCD_ClearDisplay(uint16_t RGB565) +{ LCD_DrawBox(0, 0, LCD_WIDTH, LCD_HEIGHT, RGB565); } + +uint16_t RGB565(uint8_t Red, uint8_t Green, uint8_t Blue) // convert 8/8/8-bit RGB to 5/6/5-bit RGB +{ uint16_t RGB = (Red>>3); + RGB = (RGB<<5) | (Green>>2); + RGB = (RGB<<6) | (Blue>>3); + return (RGB>>8) | (RGB<<8); } + +// uint16_t RGB565(uint8_t *RGB) +// { return RGB565(RGB[0], RGB[1], RGB[2]); } diff --git a/main/st7789.h b/main/st7789.h new file mode 100644 index 0000000..533bac5 --- /dev/null +++ b/main/st7789.h @@ -0,0 +1,102 @@ +#ifndef __ST7789_H__ +#define __ST7789_H__ + +#include + +#include "driver/gpio.h" +#include "driver/spi_master.h" + +// some predefine colors Red Green Blue +const uint16_t RGB565_BLACK = 0x0000; // 0, 0, 0 +const uint16_t RGB565_LIGHTGREY = 0x18C6; // 192, 192, 192 +const uint16_t RGB565_DARKGREY = 0xEF7B; // 128, 128, 128 +const uint16_t RGB565_WHITE = 0xFFFF; // 255, 255, 255 + +const uint16_t RGB565_NAVY = 0x0F00; // 0, 0, 128 +const uint16_t RGB565_BLUE = 0x1F00; // 0, 0, 255 +const uint16_t RGB565_LIGHTBLUE = 0xFF7B; // 127, 127, 255 + +// const uint16_t RGB565_DARKGREEN = 0x0004; // 0, 128, 0 +const uint16_t RGB565_DARKGREEN = 0x0006; // 0, 192, 0 +const uint16_t RGB565_GREEN = 0xE007; // 0, 255, 0 + +const uint16_t RGB565_DARKCYAN = 0xEF03; // 0, 128, 128 +const uint16_t RGB565_CYAN = 0xFF07; // 0, 255, 255 + +const uint16_t RGB565_MAROON = 0x0078; // 128, 0, 0 +const uint16_t RGB565_RED = 0x00F8; // 255, 0, 0 +const uint16_t RGB565_LIGHTRED = 0xEFFB; // 255, 127, 127 + +const uint16_t RGB565_PURPLE = 0x0F78; // 128, 0, 128 +const uint16_t RGB565_MAGENTA = 0x1FF8; // 255, 0, 255 + +const uint16_t RGB565_OLIVE = 0xE07B; // 128, 128, 0 ? + +// const uint16_t RGB565_DARKYELLOW = 0x0084; // 128, 128, 0 +const uint16_t RGB565_DARKYELLOW = 0x00C6; // 192, 192, 0 +const uint16_t RGB565_YELLOW = 0xE0FF; // 255, 255, 0 +const uint16_t RGB565_ORANGE = 0xA0FD; // 255, 180, 0 +const uint16_t RGB565_DARKORANGE = 0xC082; // 128, 90, 0 +const uint16_t RGB565_GREENYELLOW = 0xE0B7; // 180, 255, 0 +const uint16_t RGB565_DARKGREENYELLOW = 0x005C; // 90, 128, 0 + +const uint16_t RGB565_PINK = 0x9FFC; + +// python formula: "%04X" % ( ((Red>>3)<<11) | ((Green>>2)<<5) | (Blue>>3) ) +// and then swap the MSB and LSB bytes + +uint16_t RGB565(uint8_t Red, uint8_t Green, uint8_t Blue); // create RGB565 from RGB888 +inline uint16_t RGB565(const uint8_t *RGB) { return RGB565(RGB[0], RGB[1], RGB[2]); } + +// Embedded fonts +extern uint8_t tft_SmallFont[]; +extern uint8_t tft_DefaultFont[]; +extern uint8_t tft_Dejavu18[]; +extern uint8_t tft_Dejavu24[]; +extern uint8_t tft_Ubuntu16[]; +extern uint8_t tft_Comic24[]; +extern uint8_t tft_minya24[]; +extern uint8_t tft_tooney32[]; +extern uint8_t tft_def_small[]; + +// pins specific for this LCD +extern gpio_num_t LCD_PIN_CS; // connected to ground: constantly active +extern gpio_num_t LCD_PIN_DC; // D/C: Command = 0, Data = 1 +extern gpio_num_t LCD_PIN_RST; // actually connected to system RESET +extern gpio_num_t LCD_PIN_BCKL; // back-light: HIGH active + +extern int LCD_TYPE; // 0 = ST7789 +extern int LCD_WIDTH; // [pixels] +extern int LCD_HEIGHT; // [pixels] + +const int LCD_BUFF_SIZE = 12*320; + +void LCD_Init(spi_host_device_t LCD_SPI_HOST, uint8_t LCD_SPI_MODE, int LCD_SPI_SPEED=10000000 /*, int LCD_TYPE=0 */ ); +void LCD_Start(void); +void LCD_ClearDisplay(uint16_t RGB565); + +void LCD_SetBacklightLevel(uint8_t Level); +inline void LCD_SetBacklightON(void) { LCD_SetBacklightLevel(8); } +inline void LCD_SetBacklightOFF(void) { LCD_SetBacklightLevel(0); } + +void LCD_DrawBox(int xpos, int ypos, int xsize, int ysize, uint16_t RGB565); + +void LCD_DrawLine(int x0, int y0, int x1, int y1, uint16_t RGB565); +inline void LCD_DrawPixel(int xpos, int ypos, uint16_t RGB565) { LCD_DrawBox(xpos, ypos, 1, 1, RGB565); } +inline void LCD_DrawHorLine(int xpos, int ypos, int xsize, uint16_t RGB565) { LCD_DrawBox(xpos, ypos, xsize, 1, RGB565); } +inline void LCD_DrawVerLine(int xpos, int ypos, int ysize, uint16_t RGB565) { LCD_DrawBox(xpos, ypos, 1, ysize, RGB565); } + +void LCD_DrawCircle(int x, int y, int radius, uint16_t RGB565); + +void LCD_DrawJPEG(const uint8_t *JPEG, int JPEGsize, int xpos=0, int ypos=0, int Scale=0); + +int LCD_DrawTranspChar(char Char, int xpos, int ypos, uint16_t RGB565, const uint8_t *propFont = tft_Dejavu24); +int LCD_DrawTranspString(const char *String, int xpos, int ypos, uint16_t RGB565, const uint8_t *propFont = tft_Dejavu24); +int LCD_DrawChar(char Char, int xpos, int ypos, uint16_t Fore, uint16_t Back, const uint8_t *propFont = tft_Dejavu24); +int LCD_DrawString(const char *String, int xpos, int ypos, uint16_t Fore, uint16_t Back, const uint8_t *propFont = tft_Dejavu24); +int LCD_UpdateString(const char *String, const char *RefString, int xpos, int ypos, uint16_t Fore, uint16_t Back, const uint8_t *propFont = tft_Dejavu24); +int LCD_CharWidth(char Char, const uint8_t *propFont = tft_Dejavu24); +int LCD_StringWidth(const char *String, const uint8_t *propFont = tft_Dejavu24); +int LCD_FontHeight(const uint8_t *propFont = tft_Dejavu24); + +#endif // __ST7789_H__ diff --git a/main/ubx.h b/main/ubx.h index a89e82f..0f8f8ff 100644 --- a/main/ubx.h +++ b/main/ubx.h @@ -101,6 +101,12 @@ class UBX_NAV_TIMEUTC // 0x01 0x21 uint8_t valid; // bits: 0:ToW, 1:WN, 2:UTC } ; +class UBX_RXM_PMREQ // 0x02 0x41 +{ public: + uint32_t duration; // [ms] + uint32_t flags; // bit #1 = enter backup mode +} ; + class UBX_CFG_CFG { public: uint32_t clearMask;