From e51e158b012fa3ac68b1c7c4843f01c8466658a4 Mon Sep 17 00:00:00 2001 From: Error414 Date: Tue, 6 Jan 2026 22:20:38 +0100 Subject: [PATCH 01/42] terrain data from SD card functionality to inav --- .gitignore | 3 + docs/Settings.md | 10 + docs/Terrain.md | 73 +++ src/main/CMakeLists.txt | 9 + src/main/blackbox/blackbox.c | 75 +++- src/main/blackbox/blackbox.h | 3 + src/main/blackbox/blackbox_io.c | 25 +- src/main/config/parameter_group_ids.h | 1 + src/main/fc/fc_init.c | 26 +- src/main/fc/fc_tasks.c | 22 + src/main/fc/settings.yaml | 10 + src/main/io/asyncfatfs/asyncfatfs.c | 49 ++ src/main/io/asyncfatfs/asyncfatfs.h | 3 + src/main/io/osd.c | 30 +- src/main/scheduler/scheduler.h | 5 + src/main/target/ALIENFLIGHTF4/target.h | 5 + src/main/target/AOCODARCF4V3/target.h | 4 + src/main/target/ATOMRCF405NAVI/target.h | 5 + src/main/target/ATOMRCF405NAVI_DELUX/target.h | 5 + src/main/target/BEEROTORF4/target.h | 5 + src/main/target/BLUEJAYF4/target.h | 5 + src/main/target/BROTHERHOBBYH743/target.h | 5 + src/main/target/COREWINGF405WINGV2/target.h | 5 + src/main/target/CORVON405V2/target.h | 5 + src/main/target/CORVON743V1/target.h | 5 + src/main/target/F4BY/target.h | 5 + src/main/target/FISHDRONEF4/target.h | 5 + src/main/target/FLYSPARKF4V4/target.h | 5 + src/main/target/FLYWOOH743PRO/target.h | 5 + src/main/target/FRSKYF4/target.h | 5 + src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h | 5 + src/main/target/JHEMCUF405WING/target.h | 5 + src/main/target/KAKUTEH7WING/target.h | 5 + src/main/target/KROOZX/target.h | 5 + src/main/target/MATEKF405/target.h | 6 + src/main/target/MATEKF405CAN/target.h | 5 + src/main/target/MATEKF405SE/target.h | 5 + src/main/target/MATEKF405TE/target.h | 18 +- src/main/target/MATEKH743/target.h | 9 +- src/main/target/MICOAIR405MINI/target.h | 5 + src/main/target/MICOAIR405V2/target.h | 5 + src/main/target/MICOAIR743/target.h | 5 + src/main/target/MICOAIR743AIO/target.h | 5 + src/main/target/MICOAIR743V2/target.h | 5 + src/main/target/NEUTRONRCH7BT/target.h | 5 + src/main/target/OMNIBUSF4/target.h | 5 + src/main/target/SPEEDYBEEF405V3/target.h | 5 + src/main/target/SPEEDYBEEF405V4/target.h | 5 + src/main/target/SPEEDYBEEF405WING/target.h | 5 + src/main/target/SPEEDYBEEF405WINGV2/target.h | 5 + src/main/target/SPRACINGF4EVO/target.h | 5 + src/main/target/TBS_LUCID_H7/target.h | 5 + src/main/target/TBS_LUCID_H7_WING/target.h | 6 + .../target/TBS_LUCID_H7_WING_MINI/target.h | 6 + src/main/target/YUPIF4/target.h | 5 + src/main/terrain/terrain.c | 202 +++++++++ src/main/terrain/terrain.h | 151 +++++++ src/main/terrain/terrain_io.c | 425 ++++++++++++++++++ src/main/terrain/terrain_io.h | 79 ++++ src/main/terrain/terrain_location.c | 91 ++++ src/main/terrain/terrain_location.h | 44 ++ src/main/terrain/terrain_utils.c | 245 ++++++++++ src/main/terrain/terrain_utils.h | 42 ++ src/test/unit/CMakeLists.txt | 3 + src/test/unit/terrain_unittest.cc | 86 ++++ 65 files changed, 1899 insertions(+), 32 deletions(-) create mode 100644 docs/Terrain.md create mode 100644 src/main/terrain/terrain.c create mode 100644 src/main/terrain/terrain.h create mode 100644 src/main/terrain/terrain_io.c create mode 100644 src/main/terrain/terrain_io.h create mode 100644 src/main/terrain/terrain_location.c create mode 100644 src/main/terrain/terrain_location.h create mode 100644 src/main/terrain/terrain_utils.c create mode 100644 src/main/terrain/terrain_utils.h create mode 100644 src/test/unit/terrain_unittest.cc diff --git a/.gitignore b/.gitignore index 6d7138e6bd5..2c99c0b4ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ launch.json # Assitnow token and files for test script tokens.yaml *.ubx +/.idea + +/testing/ # Local development files .semgrepignore diff --git a/docs/Settings.md b/docs/Settings.md index b09f07708ae..1267e08c6e8 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -6711,6 +6711,16 @@ Which aux channel to use to change serial output & baud rate (MSP / Telemetry). --- +### terrain_enabled + +Enable load terrain data from SD card + +| Default | Min | Max | +| --- | --- | --- | +| OFF | OFF | ON | + +--- + ### thr_comp_weight Weight used for the throttle compensation based on battery voltage. See the [battery documentation](Battery.md#automatic-throttle-compensation-based-on-battery-voltage) diff --git a/docs/Terrain.md b/docs/Terrain.md new file mode 100644 index 00000000000..91ed96cb7cf --- /dev/null +++ b/docs/Terrain.md @@ -0,0 +1,73 @@ +# Terrain +``` +┌─────────────────────────────────────────────────┐ +│ ===Experimental=== │ +│ This feature is experimental. Use it with │ +│ caution. │ +└─────────────────────────────────────────────────┘ +``` + +This feature is available **only on H7-based and F4-based flight controllers with an SD card** (preferably SDIO). Due to the high CPU load +and SD card read speed requirements of terrain data processing, it is not supported on weaker hardware. + +This feature in iNav determines the model’s altitude above ground level using preloaded elevation maps +(terrain / SRTM data) stored on an SD card, without requiring a physical rangefinder. Based on the current GPS position, the +flight controller identifies the corresponding point in the terrain map, calculates the ground elevation, and derives the +altitude above terrain. + +In this first implementation, the calculated value is used **only for informational display in the OSD**. It is indicative +only, does not yet behave as a true virtual rangefinder, and is not used for navigation or automatic altitude control. If the +terrain data are unavailable or a read error occurs, the feature is automatically disabled. + +# SD Card Preparation + +For proper operation, the SD card must be prepared in advance. It is recommended to create a **partition with a maximum size +of 4 GB** and format it. Formatting should be done using the official +[**SD Memory Card Formatter**](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-windows-download/) tool from the SD Association, +which ensures correct alignment and a compatible file system structure. Using this tool minimizes the risk of terrain data +read issues during flight and is considered the recommended method for preparing an SD card for iNav. + +# Data Generation and Copying + +To generate elevation maps, use the terrain generator web tool available at https://terrain.ardupilot.org/ + +In iNav, **only 30 m resolution (SRTM1)** is currently supported, so this option must be selected during data generation. +The generated files are then copied to the SD card into the appropriate directory structure. + +Copying can be done via **iNav MSC (Mass Storage Class)**, but this method is very slow, so using an **external SD card reader** +is strongly recommended. Before copying the data, the file **`FREESPAC.E`** must be deleted from the root directory of the SD +card. iNav uses this file to track available disk space, and without deleting it, the card may appear to be full. After the +copying process is complete, iNav will automatically recreate this file on the next startup. + +# Enabling and Displaying Terrain Data + +To display altitude above terrain in iNav, the OSD element **“Rangefinder distance”** must be enabled. If terrain data are +available on the SD card and no valid data are available from a dedicated rangefinder, the value calculated by the terrain +system will be displayed. If a rangefinder is present and providing valid data, its measurements always take priority and the +actual distance to the ground will be shown. + +Loading terrain data from the SD card is enabled via the CLI using the following command: + +```text +set terrain_enabled = ON +save +``` + +After restarting the flight controller, iNav will automatically start loading terrain data and, when conditions are met, use +them to display altitude above terrain. + +Finally, it is **strongly recommended to use only high-quality, branded SD cards** from reputable manufacturers. The terrain +system is sensitive to SD card read speed and reliability, and low-quality or counterfeit cards may cause read errors, +display dropouts, or automatic disabling of the feature during flight. Using a quality SD card significantly improves the +stability and reliability of the terrain feature. + +# TL;DR + +- **H7/F4 flight controller** with SD card required +- Format SD card using [SD Memory Card Formatter](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-windows-download/) (max 4 GB partition) +- Generate terrain data at https://terrain.ardupilot.org/ — select **SRTM1 (30 m)** +- Delete `FREESPAC.E` from SD card root before copying files (use external card reader, not MSC) +- Enable via CLI: `set terrain_enabled = ON` + `save` +- Enable **Rangefinder distance** OSD element to see altitude above terrain +- ⚠️ Displayed value is **informational only** — not used for navigation or altitude control +- Use only **high-quality branded SD cards** \ No newline at end of file diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 4df7bf3e21c..1aff9a7ab00 100755 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -638,6 +638,15 @@ main_sources(COMMON_SRC telemetry/sim.h telemetry/telemetry.c telemetry/telemetry.h + + terrain/terrain.h + terrain/terrain.c + terrain/terrain_utils.h + terrain/terrain_utils.c + terrain/terrain_io.h + terrain/terrain_io.c + terrain/terrain_location.h + terrain/terrain_location.c ) list(APPEND COMMON_SRC ${DSDLC_GENERATED_SRC}) diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index f5225ca3e1e..5a9d72100aa 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -65,6 +65,8 @@ #include "io/beeper.h" #include "io/gps.h" +#include "io/asyncfatfs/asyncfatfs.h" + #include "navigation/navigation.h" @@ -84,6 +86,8 @@ #include "flight/wind_estimator.h" #include "sensors/temperature.h" +#include "terrain/terrain.h" + #if defined(ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT) #define DEFAULT_BLACKBOX_DEVICE BLACKBOX_DEVICE_FLASH @@ -196,6 +200,54 @@ typedef struct blackboxDeltaFieldDefinition_s { uint8_t condition; // Decide whether this field should appear in the log } blackboxDeltaFieldDefinition_t; +static bool canUseBlackboxWithCurrentConfiguration(void) +{ + return feature(FEATURE_BLACKBOX); +} + +#ifdef USE_TERRAIN +//state machine to get access to other device, it's designed only for one other device, in this case for terrain +//if you would like to have more devices, some queue must be implemented +static struct blackboxSDCardAccessStatus_s { + bool blackboxAccessToSDGrantedToOtherDevice; + bool requestToSdCardAccessState; + +} blackboxSDCardAccessStatus = { + .blackboxAccessToSDGrantedToOtherDevice = false, + .requestToSdCardAccessState = false +}; + + +/** + * request access from terrain subsystem + * @return + */ +bool requestToSdCardAccess(void) +{ + if(blackboxConfig()->device != BLACKBOX_DEVICE_SDCARD || !canUseBlackboxWithCurrentConfiguration()){ + return true; + } + + if(blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice){ + return true; + } + + blackboxSDCardAccessStatus.requestToSdCardAccessState = true; + return false; +} + +/** + * release access from terrain subsystem + * @return + */ +void releaseSdCardAccess(void) +{ + blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice = false; + blackboxSDCardAccessStatus.requestToSdCardAccessState = false; +} +#endif + + /** * Description of the blackbox fields we are writing in our main intra (I) and inter (P) frames. This description is * written into the flight log header so the log can be properly interpreted (but these definitions don't actually cause @@ -2177,6 +2229,24 @@ static void blackboxLogIteration(timeUs_t currentTimeUs) */ void blackboxUpdate(timeUs_t currentTimeUs) { +#ifdef USE_TERRAIN + if(blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD){ + //access to SD card is given to other device + if(blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice){ + return; + } + + //incooming request to get access to SD card + if(blackboxSDCardAccessStatus.requestToSdCardAccessState && (blackboxState == BLACKBOX_STATE_RUNNING || blackboxState == BLACKBOX_STATE_STOPPED)){ + //we have to be sure that all writes are already processed and SD card is in idle + if(afatfs_isIdle()){ + blackboxSDCardAccessStatus.requestToSdCardAccessState = false; + blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice = true; + } + } + } +#endif + if (blackboxState >= BLACKBOX_FIRST_HEADER_SENDING_STATE && blackboxState <= BLACKBOX_LAST_HEADER_SENDING_STATE) { blackboxReplenishHeaderBudget(); } @@ -2307,11 +2377,6 @@ void blackboxUpdate(timeUs_t currentTimeUs) } } -static bool canUseBlackboxWithCurrentConfiguration(void) -{ - return feature(FEATURE_BLACKBOX); -} - BlackboxState getBlackboxState(void) { return blackboxState; diff --git a/src/main/blackbox/blackbox.h b/src/main/blackbox/blackbox.h index 1901201fa22..97829df9cca 100644 --- a/src/main/blackbox/blackbox.h +++ b/src/main/blackbox/blackbox.h @@ -75,3 +75,6 @@ void blackboxIncludeFlagSet(uint32_t mask); void blackboxIncludeFlagClear(uint32_t mask); bool blackboxIncludeFlag(uint32_t mask); BlackboxState getBlackboxState(void); + +bool requestToSdCardAccess(void); +void releaseSdCardAccess(void); diff --git a/src/main/blackbox/blackbox_io.c b/src/main/blackbox/blackbox_io.c index b344b0dfff6..e3c6444668b 100644 --- a/src/main/blackbox/blackbox_io.c +++ b/src/main/blackbox/blackbox_io.c @@ -435,7 +435,16 @@ static bool blackboxSDCardBeginLog(void) if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY) { blackboxSDCard.state = BLACKBOX_SDCARD_WAITING; - afatfs_mkdir("logs", blackboxLogDirCreated); + if(afatfs_isCurrentDirRoot()){ + //we are in root of SD card, we have to create or move to log directory + afatfs_mkdir("logs", blackboxLogDirCreated); + } + else + { + //we are already in log directory + blackboxSDCard.logDirectory = NULL; + blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; + } } break; @@ -444,6 +453,12 @@ static bool blackboxSDCardBeginLog(void) break; case BLACKBOX_SDCARD_ENUMERATE_FILES: + + if (blackboxSDCard.logDirectory == NULL) { + blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; + break; + } + while (afatfs_findNext(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder, &directoryEntry) == AFATFS_OPERATION_SUCCESS) { if (directoryEntry && !fat_isDirectoryEntryTerminator(directoryEntry)) { // If this is a log file, parse the log number from the filename @@ -469,6 +484,12 @@ static bool blackboxSDCardBeginLog(void) break; case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY: + //if logDirectory is NULL, it would mean change directory to ROOT, we don't want to do that + if (blackboxSDCard.logDirectory == NULL) { + blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; + break; + } + // Change into the log directory: if (afatfs_chdir(blackboxSDCard.logDirectory)) { // We no longer need our open handle on the log directory @@ -535,7 +556,7 @@ bool blackboxDeviceEndLog(bool retainLog) ) { // Don't bother waiting the for the close to complete, it's queued now and will complete eventually blackboxSDCard.logFile = NULL; - blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; + blackboxSDCard.state = BLACKBOX_SDCARD_INITIAL; return true; } return false; diff --git a/src/main/config/parameter_group_ids.h b/src/main/config/parameter_group_ids.h index da1be28507d..c3d009ecbe4 100644 --- a/src/main/config/parameter_group_ids.h +++ b/src/main/config/parameter_group_ids.h @@ -77,6 +77,7 @@ // #define PG_ELERES_CONFIG 55 #define PG_TEMP_SENSOR_CONFIG 56 #define PG_CF_END 56 +#define PG_TERRAIN_CONFIG 57 // Driver configuration //#define PG_DRIVER_PWM_RX_CONFIG 100 diff --git a/src/main/fc/fc_init.c b/src/main/fc/fc_init.c index 9355cab6153..aa1f724ae45 100644 --- a/src/main/fc/fc_init.c +++ b/src/main/fc/fc_init.c @@ -153,6 +153,8 @@ #include "telemetry/telemetry.h" +#include "terrain/terrain.h" + #if defined(SITL_BUILD) #include "target/SITL/serial_proxy.h" #endif @@ -619,6 +621,19 @@ void init(void) } #endif +#ifdef USE_SDCARD + +#ifdef USE_TERRAIN + if (blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD) +#endif + { + sdcardInsertionDetectInit(); + sdcard_init(); + afatfs_init(); + } +#endif + + #ifdef USE_BLACKBOX //Do not allow blackbox to be run faster that 1kHz. It can cause UAV to drop dead when digital ESC protocol is used @@ -643,20 +658,15 @@ void init(void) } break; #endif - -#ifdef USE_SDCARD - case BLACKBOX_DEVICE_SDCARD: - sdcardInsertionDetectInit(); - sdcard_init(); - afatfs_init(); - break; -#endif default: break; } blackboxInit(); #endif +#ifdef USE_TERRAIN + terrainInit(); +#endif gyroStartCalibration(); diff --git a/src/main/fc/fc_tasks.c b/src/main/fc/fc_tasks.c index 4d72c59f1dc..3f85f4ed6fa 100755 --- a/src/main/fc/fc_tasks.c +++ b/src/main/fc/fc_tasks.c @@ -96,6 +96,9 @@ #include "telemetry/telemetry.h" #include "telemetry/sbus2.h" +#include "terrain/terrain.h" +#include "terrain/terrain_io.h" + #include "config/feature.h" #if defined(SITL_BUILD) @@ -378,6 +381,10 @@ void fcTasksInit(void) #ifdef USE_GPS setTaskEnabled(TASK_GPS, feature(FEATURE_GPS)); #endif +#ifdef USE_TERRAIN + setTaskEnabled(TASK_TERRAIN, terrainConfig()->terrainEnabled); + setTaskEnabled(TASK_TERRAIN_IO, terrainConfig()->terrainEnabled); +#endif #ifdef USE_MAG setTaskEnabled(TASK_COMPASS, sensors(SENSOR_MAG)); #if defined(USE_MAG_MPU9250) @@ -772,6 +779,21 @@ cfTask_t cfTasks[TASK_COUNT] = { }, #endif +#ifdef USE_TERRAIN + [TASK_TERRAIN] = { + .taskName = "TERRAIN", + .taskFunc = terrainUpdateTask, + .desiredPeriod = TASK_PERIOD_HZ(TERRAIN_TASK_RATE_HZ), + .staticPriority = TASK_PRIORITY_LOW, + }, + [TASK_TERRAIN_IO] = { + .taskName = "TERRAIN_IO", + .taskFunc = loadGridToCacheTask, + .desiredPeriod = TASK_PERIOD_HZ(TERRAIN_IO_TASK_RATE_HZ), + .staticPriority = TASK_PRIORITY_LOW, + }, +#endif + #ifdef USE_DRONECAN [TASK_DRONECAN] = { .taskName = "DRONECAN", diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 4ed4507ccd8..df2c20041dd 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -4534,3 +4534,13 @@ groups: field: bitRateKbps table: dronecan_bitrate_table type: uint8_t + - name: PG_TERRAIN_CONFIG + type: terrainConfig_t + headers: ["terrain/terrain.h"] + condition: USE_TERRAIN + members: + - name: terrain_enabled + description: "Enable load terrain data from SD card" + default_value: OFF + field: terrainEnabled + type: bool diff --git a/src/main/io/asyncfatfs/asyncfatfs.c b/src/main/io/asyncfatfs/asyncfatfs.c index d06fc2b8a76..56555873f41 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.c +++ b/src/main/io/asyncfatfs/asyncfatfs.c @@ -495,6 +495,50 @@ static void afatfs_fileOperationContinue(afatfsFile_t *file); static uint8_t* afatfs_fileLockCursorSectorForWrite(afatfsFilePtr_t file); static uint8_t* afatfs_fileRetainCursorSectorForRead(afatfsFilePtr_t file); +bool afatfs_isIdle(void) +{ + // 1. Check if any sector is being actively flushed or read at the hardware level + if (afatfs.cacheFlushInProgress) { + return false; + } + + // 2. Check all cache descriptors for active I/O states (waiting for SD card) + for (int i = 0; i < AFATFS_NUM_CACHE_SECTORS; i++) { + if (afatfs.cacheDescriptor[i].state == AFATFS_CACHE_STATE_READING || + afatfs.cacheDescriptor[i].state == AFATFS_CACHE_STATE_WRITING) { + return false; + } + } + + // 3. Check if current directory state machine is performing an async task (like chdir or findNext) + if (afatfs.currentDirectory.operation.operation != AFATFS_FILE_OPERATION_NONE) { + return false; + } + +#ifdef AFATFS_USE_FREEFILE + // 4. Check freeFile state (ignore LOCKED as it's a static permission marker, not an operation) + if (afatfs.freeFile.operation.operation != AFATFS_FILE_OPERATION_NONE && + afatfs.freeFile.operation.operation != AFATFS_FILE_OPERATION_LOCKED) { + return false; + } +#endif + + // 5. Check all potentially open file handles for pending background operations + for (int i = 0; i < AFATFS_MAX_OPEN_FILES; i++) { + if (afatfs.openFiles[i].type != AFATFS_FILE_TYPE_NONE && + afatfs.openFiles[i].operation.operation != AFATFS_FILE_OPERATION_NONE) { + return false; + } + } + + // 6. During initialization, we are never "idle" in the operational sense + if (afatfs.filesystemState == AFATFS_FILESYSTEM_STATE_INITIALIZATION) { + return false; + } + + return true; +} + static uint32_t roundUpTo(uint32_t value, uint32_t rounding) { uint32_t remainder = value % rounding; @@ -3622,6 +3666,11 @@ afatfsError_e afatfs_getLastError(void) return afatfs.lastError; } +bool afatfs_isCurrentDirRoot(void) +{ + return afatfs.currentDirectory.directoryEntryPos.sectorNumberPhysical == 0; +} + void afatfs_init(void) { #ifdef STM32H7 diff --git a/src/main/io/asyncfatfs/asyncfatfs.h b/src/main/io/asyncfatfs/asyncfatfs.h index 521bd48fba7..9c15eee346d 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.h +++ b/src/main/io/asyncfatfs/asyncfatfs.h @@ -95,3 +95,6 @@ bool afatfs_isFull(void); afatfsFilesystemState_e afatfs_getFilesystemState(void); afatfsError_e afatfs_getLastError(void); + +bool afatfs_isIdle(void); +bool afatfs_isCurrentDirRoot(void); \ No newline at end of file diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 9d0dd55b344..d9e7bf756ff 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -121,6 +121,10 @@ #include "blackbox/blackbox_io.h" #endif +#ifdef USE_TERRAIN +#include "terrain/terrain.h" +#endif + #ifdef USE_HARDWARE_REVISION_DETECTION #include "hardware_revision.h" #endif @@ -2440,19 +2444,29 @@ static bool osdDrawSingleElement(uint8_t item) break; } -#ifdef USE_RANGEFINDER - case OSD_RANGEFINDER: +#if defined(USE_RANGEFINDER) || defined(USE_TERRAIN) + case OSD_RANGEFINDER: { - int32_t range = rangefinderGetLatestRawAltitude(); + int32_t range = -1; + +#if defined(USE_RANGEFINDER) + range = rangefinderGetLatestRawAltitude(); +#ifdef USE_TERRAIN + if (!rangefinderIsHealthy() || range == RANGEFINDER_OUT_OF_RANGE) { + range = terrainGetLastDistanceCm(); + } +#endif +#elif defined(USE_TERRAIN) + range = terrainGetLastDistanceCm(); +#endif + if (range < 0) { - buff[0] = '-'; - buff[1] = '-'; - buff[2] = '-'; + buff[0] = buff[1] = buff[2] = '-'; } else { - osdFormatDistanceSymbol(buff, range, 1, 3); + osdFormatDistanceSymbol(buff, range, 0, 3); } } - break; + break; #endif case OSD_ONTIME: diff --git a/src/main/scheduler/scheduler.h b/src/main/scheduler/scheduler.h index fa4ead40b7f..0644eaf570c 100755 --- a/src/main/scheduler/scheduler.h +++ b/src/main/scheduler/scheduler.h @@ -141,6 +141,11 @@ typedef enum { TASK_GEOZONE, #endif +#if defined (USE_TERRAIN) + TASK_TERRAIN, + TASK_TERRAIN_IO, +#endif + #ifdef USE_DRONECAN TASK_DRONECAN, #endif diff --git a/src/main/target/ALIENFLIGHTF4/target.h b/src/main/target/ALIENFLIGHTF4/target.h index 52cd4cf4a9d..87e4652d478 100644 --- a/src/main/target/ALIENFLIGHTF4/target.h +++ b/src/main/target/ALIENFLIGHTF4/target.h @@ -146,6 +146,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX) #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_UART SERIAL_PORT_USART2 diff --git a/src/main/target/AOCODARCF4V3/target.h b/src/main/target/AOCODARCF4V3/target.h index 0c95564162e..5f32f9577d5 100644 --- a/src/main/target/AOCODARCF4V3/target.h +++ b/src/main/target/AOCODARCF4V3/target.h @@ -151,6 +151,10 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif #else #define USE_FLASHFS #define USE_FLASH_M25P16 diff --git a/src/main/target/ATOMRCF405NAVI/target.h b/src/main/target/ATOMRCF405NAVI/target.h index 5e9c9b1bd4e..cc826ab89a8 100644 --- a/src/main/target/ATOMRCF405NAVI/target.h +++ b/src/main/target/ATOMRCF405NAVI/target.h @@ -151,6 +151,11 @@ #define SDCARD_CS_PIN PB6 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + /* * LED Strip */ diff --git a/src/main/target/ATOMRCF405NAVI_DELUX/target.h b/src/main/target/ATOMRCF405NAVI_DELUX/target.h index f04a5d5da6f..9c7b1630197 100644 --- a/src/main/target/ATOMRCF405NAVI_DELUX/target.h +++ b/src/main/target/ATOMRCF405NAVI_DELUX/target.h @@ -149,6 +149,11 @@ #define SDCARD_CS_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + /* * LED Strip */ diff --git a/src/main/target/BEEROTORF4/target.h b/src/main/target/BEEROTORF4/target.h index 1ff901b870a..1f30c30881c 100644 --- a/src/main/target/BEEROTORF4/target.h +++ b/src/main/target/BEEROTORF4/target.h @@ -52,6 +52,11 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define USE_VCP #define VBUS_SENSING_ENABLED #define VBUS_SENSING_PIN PC5 diff --git a/src/main/target/BLUEJAYF4/target.h b/src/main/target/BLUEJAYF4/target.h index e62120bdc86..d47753ad9a8 100644 --- a/src/main/target/BLUEJAYF4/target.h +++ b/src/main/target/BLUEJAYF4/target.h @@ -58,6 +58,11 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PA15 +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define M25P16_CS_PIN PB7 #define M25P16_SPI_BUS BUS_SPI3 diff --git a/src/main/target/BROTHERHOBBYH743/target.h b/src/main/target/BROTHERHOBBYH743/target.h index b069f0ccfd0..bdacc1a8554 100644 --- a/src/main/target/BROTHERHOBBYH743/target.h +++ b/src/main/target/BROTHERHOBBYH743/target.h @@ -147,6 +147,11 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/COREWINGF405WINGV2/target.h b/src/main/target/COREWINGF405WINGV2/target.h index 2176728d84d..dc0884e2602 100644 --- a/src/main/target/COREWINGF405WINGV2/target.h +++ b/src/main/target/COREWINGF405WINGV2/target.h @@ -133,6 +133,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART6 diff --git a/src/main/target/CORVON405V2/target.h b/src/main/target/CORVON405V2/target.h index 685acbe0588..a8e7842851c 100644 --- a/src/main/target/CORVON405V2/target.h +++ b/src/main/target/CORVON405V2/target.h @@ -66,6 +66,11 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/CORVON743V1/target.h b/src/main/target/CORVON743V1/target.h index fa5ba03d735..f777b9209f4 100644 --- a/src/main/target/CORVON743V1/target.h +++ b/src/main/target/CORVON743V1/target.h @@ -123,6 +123,11 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/F4BY/target.h b/src/main/target/F4BY/target.h index 21f91c1d49b..9da7d048312 100644 --- a/src/main/target/F4BY/target.h +++ b/src/main/target/F4BY/target.h @@ -55,6 +55,11 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN PE15 +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define USE_VCP #define VBUS_SENSING_PIN PA9 diff --git a/src/main/target/FISHDRONEF4/target.h b/src/main/target/FISHDRONEF4/target.h index 10e325202e3..1372a7fd182 100644 --- a/src/main/target/FISHDRONEF4/target.h +++ b/src/main/target/FISHDRONEF4/target.h @@ -82,6 +82,11 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PB6 +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** Flash ***************************** #define M25P16_CS_PIN PA15 #define M25P16_SPI_BUS BUS_SPI3 diff --git a/src/main/target/FLYSPARKF4V4/target.h b/src/main/target/FLYSPARKF4V4/target.h index 9a6619f0793..24b6aedf1a7 100644 --- a/src/main/target/FLYSPARKF4V4/target.h +++ b/src/main/target/FLYSPARKF4V4/target.h @@ -126,6 +126,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** OSD ***************************** #define USE_SPI_DEVICE_2 #define SPI2_SCK_PIN PB13 diff --git a/src/main/target/FLYWOOH743PRO/target.h b/src/main/target/FLYWOOH743PRO/target.h index 47419dc78ff..e98c33799ad 100644 --- a/src/main/target/FLYWOOH743PRO/target.h +++ b/src/main/target/FLYWOOH743PRO/target.h @@ -145,6 +145,11 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/FRSKYF4/target.h b/src/main/target/FRSKYF4/target.h index c014283f33e..eefa86233d5 100755 --- a/src/main/target/FRSKYF4/target.h +++ b/src/main/target/FRSKYF4/target.h @@ -45,6 +45,11 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define USE_VCP #define VBUS_SENSING_PIN PC5 diff --git a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h index 7d93ec91ce6..3c3ce5e7dcf 100644 --- a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h +++ b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h @@ -137,6 +137,11 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/JHEMCUF405WING/target.h b/src/main/target/JHEMCUF405WING/target.h index fd64ff153d6..348c8e096bc 100644 --- a/src/main/target/JHEMCUF405WING/target.h +++ b/src/main/target/JHEMCUF405WING/target.h @@ -121,6 +121,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART1 diff --git a/src/main/target/KAKUTEH7WING/target.h b/src/main/target/KAKUTEH7WING/target.h index 7626b997754..5bd7d9d14a6 100644 --- a/src/main/target/KAKUTEH7WING/target.h +++ b/src/main/target/KAKUTEH7WING/target.h @@ -82,6 +82,11 @@ #define SDCARD_SDIO_NORMAL_SPEED #define SDCARD_SDIO2_CMD_ALT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** I2C /Baro/Mag ********************* diff --git a/src/main/target/KROOZX/target.h b/src/main/target/KROOZX/target.h index ea5ee6a8e0f..f5d8be344a5 100755 --- a/src/main/target/KROOZX/target.h +++ b/src/main/target/KROOZX/target.h @@ -51,6 +51,11 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC13 +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #ifdef USE_MSP_DISPLAYPORT #undef USE_MSP_DISPLAYPORT #endif diff --git a/src/main/target/MATEKF405/target.h b/src/main/target/MATEKF405/target.h index a33807c06a5..8b168fe44b9 100644 --- a/src/main/target/MATEKF405/target.h +++ b/src/main/target/MATEKF405/target.h @@ -69,6 +69,12 @@ #define SDCARD_CS_PIN PC1 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT + +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #endif // *************** OSD ***************************** diff --git a/src/main/target/MATEKF405CAN/target.h b/src/main/target/MATEKF405CAN/target.h index d12b5e3e1a3..b7cfbb5b930 100644 --- a/src/main/target/MATEKF405CAN/target.h +++ b/src/main/target/MATEKF405CAN/target.h @@ -84,6 +84,11 @@ #define SPI3_MISO_PIN PB4 #define SPI3_MOSI_PIN PB5 +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** UART ***************************** diff --git a/src/main/target/MATEKF405SE/target.h b/src/main/target/MATEKF405SE/target.h index 38c8f56f5d9..78146f0ed11 100644 --- a/src/main/target/MATEKF405SE/target.h +++ b/src/main/target/MATEKF405SE/target.h @@ -100,6 +100,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** UART ***************************** #define USE_VCP #define VBUS_SENSING_PIN PC13 diff --git a/src/main/target/MATEKF405TE/target.h b/src/main/target/MATEKF405TE/target.h index fc7c1ad0ce9..668db85686c 100644 --- a/src/main/target/MATEKF405TE/target.h +++ b/src/main/target/MATEKF405TE/target.h @@ -63,12 +63,23 @@ #define SPI2_MISO_PIN PC2 #define SPI2_MOSI_PIN PC3 +#define USE_BARO +#define BARO_I2C_BUS BUS_I2C1 +#define USE_BARO_BMP280 +#define USE_BARO_MS5611 +#define USE_BARO_DPS310 +#define USE_BARO_SPL06 + #if defined(MATEKF405TE_SD) # define USE_SDCARD # define USE_SDCARD_SPI # define SDCARD_SPI_BUS BUS_SPI2 # define SDCARD_CS_PIN PC1 # define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +# if defined(USE_SDCARD) && defined(USE_BARO) +# define USE_TERRAIN +# define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +# endif #else # define USE_FLASHFS # define USE_FLASH_M25P16 @@ -83,13 +94,6 @@ #define I2C1_SCL PB8 #define I2C1_SDA PB7 -#define USE_BARO -#define BARO_I2C_BUS BUS_I2C1 -#define USE_BARO_BMP280 -#define USE_BARO_MS5611 -#define USE_BARO_DPS310 -#define USE_BARO_SPL06 - #define USE_MAG #define MAG_I2C_BUS BUS_I2C1 #define USE_MAG_ALL diff --git a/src/main/target/MATEKH743/target.h b/src/main/target/MATEKH743/target.h index 25ca088e690..378e49098ca 100644 --- a/src/main/target/MATEKH743/target.h +++ b/src/main/target/MATEKH743/target.h @@ -167,10 +167,10 @@ // *************** CANBUS **************************** #define USE_DRONECAN #define CAN1_RX PD0 -#define CAN1_TX PD1 +#define CAN1_TX PD1 #define CAN1_STANDBY PD3 -// *************** SDIO SD BLACKBOX******************* +// *************** SDIO SD BLACKBOX AND TERRAIN ******************* #define USE_SDCARD #define USE_SDCARD_SDIO #define SDCARD_SDIO_DEVICE SDIODEV_1 @@ -178,6 +178,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/MICOAIR405MINI/target.h b/src/main/target/MICOAIR405MINI/target.h index 4b7e0b811da..3693904fe6e 100644 --- a/src/main/target/MICOAIR405MINI/target.h +++ b/src/main/target/MICOAIR405MINI/target.h @@ -65,6 +65,11 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR405V2/target.h b/src/main/target/MICOAIR405V2/target.h index c4a54f003b6..d979c9d3350 100644 --- a/src/main/target/MICOAIR405V2/target.h +++ b/src/main/target/MICOAIR405V2/target.h @@ -66,6 +66,11 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR743/target.h b/src/main/target/MICOAIR743/target.h index 526cf438d08..51304a7b4c8 100644 --- a/src/main/target/MICOAIR743/target.h +++ b/src/main/target/MICOAIR743/target.h @@ -123,6 +123,11 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/MICOAIR743AIO/target.h b/src/main/target/MICOAIR743AIO/target.h index 90ea60f0a0b..6b6174bf894 100755 --- a/src/main/target/MICOAIR743AIO/target.h +++ b/src/main/target/MICOAIR743AIO/target.h @@ -117,6 +117,11 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/MICOAIR743V2/target.h b/src/main/target/MICOAIR743V2/target.h index 78de91df4af..2aa22a833a8 100755 --- a/src/main/target/MICOAIR743V2/target.h +++ b/src/main/target/MICOAIR743V2/target.h @@ -138,6 +138,11 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/NEUTRONRCH7BT/target.h b/src/main/target/NEUTRONRCH7BT/target.h index 9f4ffd118d5..d0a66b6c683 100644 --- a/src/main/target/NEUTRONRCH7BT/target.h +++ b/src/main/target/NEUTRONRCH7BT/target.h @@ -139,6 +139,11 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/OMNIBUSF4/target.h b/src/main/target/OMNIBUSF4/target.h index fcd89fb0e46..8210545a809 100644 --- a/src/main/target/OMNIBUSF4/target.h +++ b/src/main/target/OMNIBUSF4/target.h @@ -234,6 +234,11 @@ #define SDCARD_DETECT_PIN PB7 #define SDCARD_DETECT_INVERTED + + #if defined(USE_SDCARD) && defined(USE_BARO) + #define USE_TERRAIN + #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block + #endif #else #define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT #define M25P16_CS_PIN SPI3_NSS_PIN diff --git a/src/main/target/SPEEDYBEEF405V3/target.h b/src/main/target/SPEEDYBEEF405V3/target.h index 6653ea56c0c..c21ff1b67ab 100644 --- a/src/main/target/SPEEDYBEEF405V3/target.h +++ b/src/main/target/SPEEDYBEEF405V3/target.h @@ -115,6 +115,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** OSD ***************************** #define USE_OSD diff --git a/src/main/target/SPEEDYBEEF405V4/target.h b/src/main/target/SPEEDYBEEF405V4/target.h index 1c5c847597f..4c111f80e12 100644 --- a/src/main/target/SPEEDYBEEF405V4/target.h +++ b/src/main/target/SPEEDYBEEF405V4/target.h @@ -126,6 +126,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + // *************** OSD ***************************** #define USE_SPI_DEVICE_2 #define SPI2_SCK_PIN PB13 diff --git a/src/main/target/SPEEDYBEEF405WING/target.h b/src/main/target/SPEEDYBEEF405WING/target.h index 9da5b115824..dd21a9f154b 100644 --- a/src/main/target/SPEEDYBEEF405WING/target.h +++ b/src/main/target/SPEEDYBEEF405WING/target.h @@ -134,6 +134,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART1 diff --git a/src/main/target/SPEEDYBEEF405WINGV2/target.h b/src/main/target/SPEEDYBEEF405WINGV2/target.h index df7cafcc00a..e79d487cf8e 100644 --- a/src/main/target/SPEEDYBEEF405WINGV2/target.h +++ b/src/main/target/SPEEDYBEEF405WINGV2/target.h @@ -133,6 +133,11 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART6 diff --git a/src/main/target/SPRACINGF4EVO/target.h b/src/main/target/SPRACINGF4EVO/target.h index 8af6981e54f..55c4e0e2f8a 100755 --- a/src/main/target/SPRACINGF4EVO/target.h +++ b/src/main/target/SPRACINGF4EVO/target.h @@ -115,6 +115,11 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + #define MPU6500_CS_PIN SPI1_NSS_PIN #define MPU6500_SPI_BUS BUS_SPI1 diff --git a/src/main/target/TBS_LUCID_H7/target.h b/src/main/target/TBS_LUCID_H7/target.h index e9eef8686bf..eb7c4479548 100644 --- a/src/main/target/TBS_LUCID_H7/target.h +++ b/src/main/target/TBS_LUCID_H7/target.h @@ -141,6 +141,11 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/TBS_LUCID_H7_WING/target.h b/src/main/target/TBS_LUCID_H7_WING/target.h index 8871cee7398..bc91ef1aea1 100644 --- a/src/main/target/TBS_LUCID_H7_WING/target.h +++ b/src/main/target/TBS_LUCID_H7_WING/target.h @@ -141,6 +141,12 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + + #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h index f211decb7bd..1fbb7826732 100644 --- a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h +++ b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h @@ -141,6 +141,12 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#endif + + #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/YUPIF4/target.h b/src/main/target/YUPIF4/target.h index 6a7bb7d545f..facc5c95dde 100644 --- a/src/main/target/YUPIF4/target.h +++ b/src/main/target/YUPIF4/target.h @@ -78,6 +78,11 @@ #define SDCARD_DETECT_PIN PD2 #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN SPI3_NSS_PIN + +#if defined(USE_SDCARD) && defined(USE_BARO) +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif #endif #define USB_IO diff --git a/src/main/terrain/terrain.c b/src/main/terrain/terrain.c new file mode 100644 index 00000000000..55b0482a183 --- /dev/null +++ b/src/main/terrain/terrain.c @@ -0,0 +1,202 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +/* + * Terrain subsystem notes: + * This module runs in the main loop without an RTOS. It relies on synchronous SD card + * access for terrain data, which is subject to I/O latency. Because there is no + * background thread to handle blocking operations, increasing the update frequency + * or adding "look-ahead" calculations for multiple GPS positions is not recommended. + */ +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// + +#include "platform.h" + +#ifdef USE_TERRAIN + +#include "config/parameter_group.h" +#include "config/parameter_group_ids.h" + +#include "terrain.h" +#include "terrain_utils.h" +#include "terrain_io.h" + +#include "common/utils.h" + +#include "navigation/navigation.h" +#include "navigation/navigation_private.h" + +#include "drivers/sdcard/sdcard.h" +#include "drivers/time.h" + + +PG_REGISTER_WITH_RESET_TEMPLATE(terrainConfig_t, terrainConfig, PG_TERRAIN_CONFIG, 1); + +PG_RESET_TEMPLATE(terrainConfig_t, terrainConfig, + .terrainEnabled = false, +); + + +static struct { + timeMs_t lastUpdate; + int32_t terrainAGL; +} terrainHeight = { + .terrainAGL = 0, + .lastUpdate = 0, +}; + +static struct { + bool homeAltitudeFound; + float homeAltitudeM; + gpsLocation_t homeLocation; +} terrainHomePos = { + .homeAltitudeFound = false, + .homeAltitudeM = 0.0f, + .homeLocation = {.lat = 0, .lon = 0}, +}; + +/** + * @brief Get the height above mean sea level (AMSL) in meters for a given GPS location. + */ +static float getHeightAmslMeters(const gpsLocation_t *loc) +{ + if(isTerrainIoFailure()){ + return TERRAIN_STATUS_FAILURE; + } + + gridInfo_t info; + calculateGridInfo(loc, &info); + + if (info.idx_x > TERRAIN_GRID_BLOCK_SIZE_X - 2) { + return TERRAIN_STATUS_WRONG_BLOC_SIZE; + } + if (info.idx_y > TERRAIN_GRID_BLOCK_SIZE_Y - 2) { + return TERRAIN_STATUS_WRONG_BLOC_SIZE; + } + + // find the grid + gridCache_t *cache = findGridCache(&info); + if(cache == NULL){ + return TERRAIN_STATUS_CACHE_FULL; + } + gridBlock_t *grid = &cache->gridBlock; + + // check we have all 4 required heights; it's check if grid is loaded from SD card + if (!checkBitmap(grid, info.idx_x, info.idx_y) || !checkBitmap(grid, info.idx_x, info.idx_y + 1) || !checkBitmap(grid, info.idx_x + 1, info.idx_y) || !checkBitmap(grid, info.idx_x + 1, info.idx_y + 1)) { + markGridBlockNeedRead(grid); + return TERRAIN_STATUS_WRONG_BITMAP; + } + + // hXY are the heights of the 4 surrounding grid points + const int16_t h00 = grid->height[info.idx_x+0][info.idx_y+0]; + const int16_t h01 = grid->height[info.idx_x+0][info.idx_y+1]; + const int16_t h10 = grid->height[info.idx_x+1][info.idx_y+0]; + const int16_t h11 = grid->height[info.idx_x+1][info.idx_y+1]; + + // do a simple dual linear interpolation. We could do something + // fancier, but it probably isn't worth it as long as the + // grid_spacing is kept small enough + const float avg1 = (1.0f-info.frac_x) * h00 + info.frac_x * h10; + const float avg2 = (1.0f-info.frac_x) * h01 + info.frac_x * h11; + const float avg = (1.0f-info.frac_y) * avg1 + info.frac_y * avg2; + + return avg; +} + +void terrainInit(void) +{ +} + +/** + * @brief Update the terrain subsystem. + * + * This function checks the status of the SD card and attempts to load terrain data into the cache. + * It also tries to determine the home altitude based on the GPS origin if it hasn't been found yet. + */ +void terrainUpdateTask(timeUs_t currentTimeUs) +{ + UNUSED(currentTimeUs); + + if(terrainConfig()->terrainEnabled == false){ + return; + } + + if(STATE(GPS_FIX) == false){ + return; + } + + if(ARMING_FLAG(ARMED) && terrainHomePos.homeAltitudeFound == false){ + //vehicle is armed and we don't have loaded values for home position, don't try load from SD card durting flight if we are not sure SD it's possible to load data + return; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + //try to find home altitude to home offset + if(terrainHomePos.homeAltitudeFound == false && posControl.gpsOrigin.valid && (posControl.gpsOrigin.lat != terrainHomePos.homeLocation.lat || posControl.gpsOrigin.lon != terrainHomePos.homeLocation.lon)) { + + gpsLocation_t homeLoc = { + .lat = posControl.gpsOrigin.lat, + .lon = posControl.gpsOrigin.lon, + }; + + float heightASLHome = getHeightAmslMeters(&homeLoc); + + if(heightASLHome >= 0){ + terrainHomePos.homeAltitudeFound = true; + terrainHomePos.homeAltitudeM = heightASLHome; + terrainHomePos.homeLocation.lat = posControl.gpsOrigin.lat; + terrainHomePos.homeLocation.lon = posControl.gpsOrigin.lon; + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + if(terrainHomePos.homeAltitudeFound) + { + float heightASL = getHeightAmslMeters(&gpsSol.llh); + if(heightASL >= 0){ + terrainHeight.terrainAGL = MAX(0, ((int32_t)getEstimatedActualPosition(Z) + (int32_t)(terrainHomePos.homeAltitudeM * 100.0f)) - (int32_t)(heightASL * 100.0f)); + terrainHeight.lastUpdate = millis(); + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////// +} + +int32_t terrainGetLastDistanceCm(void) +{ + //start a terrain system after the first query to terrain height + if(!terrainConfig()->terrainEnabled){ + return TERRAIN_STATUS_NO_DATA; + } + + return terrainHeight.lastUpdate + 1000 < millis() ? TERRAIN_STATUS_NO_DATA : terrainHeight.terrainAGL; +} + +#endif \ No newline at end of file diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h new file mode 100644 index 00000000000..2d7b616cb97 --- /dev/null +++ b/src/main/terrain/terrain.h @@ -0,0 +1,151 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +#pragma once + +#include +#include +#include + +#include "navigation/navigation.h" + +#define TERRAIN_TASK_RATE_HZ 5 +#define TERRAIN_TASK_ 200 + +#define TERRAIN_STATUS_FAILURE (-1) +#define TERRAIN_STATUS_WRONG_BLOC_SIZE (-2) +#define TERRAIN_STATUS_WRONG_BITMAP (-3) +#define TERRAIN_STATUS_NO_DATA (-4) +#define TERRAIN_STATUS_NO_CARD (-5) +#define TERRAIN_STATUS_CACHE_FULL (-6) + +// MAVLink sends 4x4 grids +#define TERRAIN_GRID_MAVLINK_SIZE 4 + +// a 2k grid_block on disk contains 8x7 of the mavlink grids. Each +// grid block overlaps by one with its neighbour. This ensures that +// the altitude at any point can be calculated from a single grid +// block +#define TERRAIN_GRID_BLOCK_MUL_X 7 +#define TERRAIN_GRID_BLOCK_MUL_Y 8 + +// this is the spacing between 32x28 grid blocks, in grid_spacing units +#define TERRAIN_GRID_BLOCK_SPACING_X ((TERRAIN_GRID_BLOCK_MUL_X-1)*TERRAIN_GRID_MAVLINK_SIZE) +#define TERRAIN_GRID_BLOCK_SPACING_Y ((TERRAIN_GRID_BLOCK_MUL_Y-1)*TERRAIN_GRID_MAVLINK_SIZE) + +// giving a total grid size of a disk grid_block of 32x28 +#define TERRAIN_GRID_BLOCK_SIZE_X (TERRAIN_GRID_MAVLINK_SIZE*TERRAIN_GRID_BLOCK_MUL_X) +#define TERRAIN_GRID_BLOCK_SIZE_Y (TERRAIN_GRID_MAVLINK_SIZE*TERRAIN_GRID_BLOCK_MUL_Y) + + +#define TASK_TERRAIN_RATE_MS 10 +#define TERRAIN_MAX_DISTANCE_CM INT16_MAX + +enum GridCacheState { + GRID_CACHE_INVALID = 0, // when first initialised + GRID_CACHE_DISKWAIT = 1, // when waiting for disk read + GRID_CACHE_VALID = 2, // when at least partially valid +}; + +typedef struct __attribute__((packed)){ + // bitmap of 4x4 grids filled in from GCS (56 bits are used) + uint64_t bitmap; + + // south west corner of block in degrees*10^7 + int32_t lat; + int32_t lon; + + // crc of whole block, taken with crc=0 + uint16_t crc; + + // format version number + uint16_t version; + + // grid spacing in meters + uint16_t spacing; + + // heights in meters over a 32*28 grid + int16_t height[TERRAIN_GRID_BLOCK_SIZE_X][TERRAIN_GRID_BLOCK_SIZE_Y]; + + // indices info 32x28 grids for this degree reference + uint16_t grid_idx_x; + uint16_t grid_idx_y; + + // rounded latitude/longitude in degrees. + int16_t lonDegrees; + int8_t latDegrees; + +} gridBlock_t; + + +typedef union { + gridBlock_t block; + uint8_t buffer[2048]; +} gridIoBlock_t; + +/* + grid_info is a broken down representation of a Location, giving + the index terms for finding the right grid + */ +typedef struct { + // rounded latitude/longitude in degrees. + int8_t latDegrees; + int16_t lonDegrees; + + // lat and lon of SW corner of this 32*28 grid, *10^7 degrees + int32_t gridLat; + int32_t gridLon; + + // indices info 32x28 grids for this degree reference + uint16_t grid_idx_x; + uint16_t grid_idx_y; + + // indexes into 32x28 grid + uint8_t idx_x; // north (0..27) + uint8_t idx_y; // east (0..31) + + // fraction within the grid square + float frac_x; // north (0..1) + float frac_y; // east (0..1) + + // file offset of this grid + uint32_t file_offset; +} gridInfo_t; + +typedef struct { + gridBlock_t gridBlock; + enum GridCacheState state; + // the last time access was requested to this block, used for LRU + timeMs_t lastAccessMs; +} gridCache_t; + + +typedef struct terrainConfig_s { + bool terrainEnabled; +} terrainConfig_t; + +PG_DECLARE(terrainConfig_t, terrainConfig); + +void terrainInit(void); +void terrainUpdateTask(timeUs_t currentTimeUs); +int32_t terrainGetLastDistanceCm(void); diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c new file mode 100644 index 00000000000..59f7fe79ac5 --- /dev/null +++ b/src/main/terrain/terrain_io.c @@ -0,0 +1,425 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include +#include "platform.h" + +#ifdef USE_TERRAIN + +#include "terrain_io.h" +#include "terrain_utils.h" + +//#include "common/log.h" +#include "common/utils.h" + +#include "drivers/time.h" +#include "drivers/sdcard/sdcard.h" + +#include "blackbox/blackbox.h" + + +#include "io/asyncfatfs/asyncfatfs.h" + +static terrainIoState_t terrainIoState; + +/** + * @brief Marks the terrain IO state machine as failed, disabling further operations. + */ +static void hardFailure(void) +{ + terrainIoState.datFile = NULL; + terrainIoState.status = TERRAIN_IO_FAILURE; + terrainIoState.gridBlock = NULL; + //LOG_DEBUG(SYSTEM, "TERRAIN RELEASE FAILURE"); + releaseSdCardAccess(); +} + +/** + * @brief Resets the terrain IO state machine to its idle state. + */ +static void resetStateMachine(void) +{ + terrainIoState.status = TERRAIN_IO_IDLE; + terrainIoState.datFile = NULL; + terrainIoState.gridBlock = NULL; + terrainIoState.bytesRead = 0; + terrainIoState.readsZeroBytesCount = 0; + terrainIoState.openFileStartTimeMs = 0; + + //LOG_DEBUG(SYSTEM, "TERRAIN RELEASE RESET STATE"); + releaseSdCardAccess(); +} + +/** + * @brief we need to close file and reset state + */ +static void cleanUp(void) +{ + if(terrainIoState.status == TERRAIN_IO_CLOSE || terrainIoState.status == TERRAIN_IO_CLOSE_PENDING){ + return; + } + + //LOG_DEBUG(SYSTEM, "TERRAIN RELEASE RESET STATE"); + if(terrainIoState.datFile != NULL){ + //LOG_DEBUG(SYSTEM, "TERRAIN CLOSE CLEAN UP FILE"); + terrainIoState.status = TERRAIN_IO_CLOSE; + } else { + //LOG_DEBUG(SYSTEM, "TERRAIN CLOSE CLEAN RESET NOW"); + resetStateMachine(); + } +} + +/** + * @brief Retrieves or initializes the file open status for a given latitude and longitude. + */ +static terrainIoFileOpenStatus_t* getFileOpenStatusIndex(int8_t latDegrees, int16_t lonDegrees) +{ + uint16_t oldest_i = 0; + const timeMs_t nowMs = millis(); + + for(uint8_t i = 0; i < TERRAIN_IO_MAX_FILE_OPEN_STATUS; i++){ + if(terrainIoState.fileOpenStatus[i].latDegrees == latDegrees && terrainIoState.fileOpenStatus[i].lonDegrees == lonDegrees){ + terrainIoState.fileOpenStatus[i].lastAccessTimeMs = nowMs; + return &(terrainIoState.fileOpenStatus[i]); + } + + if(terrainIoState.fileOpenStatus[i].lastAccessTimeMs < terrainIoState.fileOpenStatus[oldest_i].lastAccessTimeMs){ + oldest_i = i; + } + } + + //not found, return oldest + terrainIoState.fileOpenStatus[oldest_i].latDegrees = latDegrees; + terrainIoState.fileOpenStatus[oldest_i].lonDegrees = lonDegrees; + terrainIoState.fileOpenStatus[oldest_i].errorOpenCount = 0; + terrainIoState.fileOpenStatus[oldest_i].lastAccessTimeMs = nowMs; + + return &(terrainIoState.fileOpenStatus[oldest_i]); +} + +static void increaseFileStatusErrorCount(int8_t latDegrees, int16_t lonDegrees) +{ + terrainIoFileOpenStatus_t* fileOpenStatus = getFileOpenStatusIndex(latDegrees, lonDegrees); + fileOpenStatus->errorOpenCount++; +} + +/** + * @brief Callback function invoked when a terrain file is opened. + */ +void terrainIoOpenedFileCallback(afatfsFilePtr_t file) +{ + if (terrainIoState.status == TERRAIN_IO_FAILURE) { + return; + } + + if(terrainIoState.status != TERRAIN_IO_OPEN_FILE_PENDING){ + hardFailure(); + return; + } + + if(file == NULL){ + //LOG_DEBUG(SYSTEM, "TERRAIN OPEN FILE NULL, RESET STATE"); + markGridBlockInvalid(terrainIoState.gridBlock); + increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); + resetStateMachine(); + return; + } + + terrainIoState.status = TERRAIN_IO_SEEK; + terrainIoState.datFile = file; +} + +/** + * @brief Callback function invoked when a terrain file is closed. + */ +void terrainIoClosedFileCallback(void) +{ + if (terrainIoState.status == TERRAIN_IO_FAILURE) { + return; + } + + if(terrainIoState.status != TERRAIN_IO_CLOSE_PENDING){ + hardFailure(); + return; + } + + //LOG_DEBUG(SYSTEM, "TERRAIN CLOSE FILE OK"); + resetStateMachine(); +} + +/** + * @brief Main task function for loading terrain grid data into cache. + */ +void loadGridToCacheTask(timeUs_t currentTimeUs) +{ + UNUSED(currentTimeUs); + + if(terrainIoState.status == TERRAIN_IO_FAILURE){ + //LOG_DEBUG(SYSTEM, "TERRAIN IO FAILURE"); + return; + } + + //SD card or file system is not prepared or it's not ready yet + if(!sdcard_isInserted() || !sdcard_isFunctional() || afatfs_getFilesystemState() != AFATFS_FILESYSTEM_STATE_READY){ + //SD card is not ready but IO is in IDLE, probably something heppend with SD card during reading + if(terrainIoState.status != TERRAIN_IO_IDLE){ + hardFailure(); + return; + }else{ + return; + } + } + + /////////////////////////////////////////////////////////////////// + /////// IDLE ///////////////////////////////////////////// + // check if in the cache is some block which is waiting to read from disk + if(terrainIoState.status == TERRAIN_IO_IDLE){ + gridBlock_t* gridBlock = getGridBlockToRead(); + + //nothing to read + if(gridBlock == NULL){ + return; + } + + //check if we had too many errors opening of this file + terrainIoFileOpenStatus_t* fileOpenStatus = getFileOpenStatusIndex(gridBlock->latDegrees, gridBlock->lonDegrees); + if(fileOpenStatus->errorOpenCount >= 3){ + //LOG_DEBUG(SYSTEM, "TERRAIN IO FAILURE TOO MANY OPEN ERRORS"); + markGridBlockInvalid(gridBlock); + return; + } + + /////////////////////////////////////////////////////////////////// + //request access from blackbox + if(requestToSdCardAccess() == false){ + return; + } + //////////////////////////////////////////////////////////////// + + //set grid block for process. open file -> seek -> read -> close + terrainIoState.gridBlock = gridBlock; + terrainIoState.status = TERRAIN_IO_CHANGE_DIR; + + return; + } + + /////////////////////////////////////////////////////////////////// + /////// CHANGE DIR ///////////////////////////////////////////// + if(terrainIoState.status == TERRAIN_IO_CHANGE_DIR){ + //already in the root directory, no need to change the directory + if(afatfs_isCurrentDirRoot()){ + terrainIoState.status = TERRAIN_IO_OPEN_FILE; + return; + } + + //LOG_DEBUG(SYSTEM, "TERRAIN CHANGING DIR"); + //change dir to ROOT (null) + if(!afatfs_chdir(NULL)){ + //LOG_DEBUG(SYSTEM, "TERRAIN CHANGE DIR ERROR"); + markGridBlockInvalid(terrainIoState.gridBlock); + resetStateMachine(); + return; + } + + terrainIoState.status = TERRAIN_IO_OPEN_FILE; + return; + } + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /////// OPEN FILE ///////////////////////////////////////////// + // wait to call callback terrainIoOpenedFileCallback + if(terrainIoState.status == TERRAIN_IO_OPEN_FILE){ + //LOG_DEBUG(SYSTEM, "TERRAIN OPENING FILE"); + if(terrainIoState.gridBlock == NULL){ + //LOG_DEBUG(SYSTEM, "TERRAIN OPENING FILE GRID BLOCK NULL"); + markGridBlockInvalid(terrainIoState.gridBlock); + resetStateMachine(); + return; + } + + terrainIoState.status = TERRAIN_IO_OPEN_FILE_PENDING; + terrainIoState.openFileStartTimeMs = millis(); + + char filename[20]; + snprintf(filename, sizeof(filename), + "%c%02ld%c%03ld.DAT", + terrainIoState.gridBlock->latDegrees < 0 ? 'S' : 'N', + labs(terrainIoState.gridBlock->latDegrees), + terrainIoState.gridBlock->lonDegrees < 0 ? 'W' : 'E', + labs(terrainIoState.gridBlock->lonDegrees)); + + //of most of the time is callback terrainIoOpenedFileCallback called immediately, not in next cycle + if(!afatfs_fopen(filename, "r", terrainIoOpenedFileCallback)){ + //LOG_DEBUG(SYSTEM, "TERRAIN OPEN FILE ERROR"); + markGridBlockInvalid(terrainIoState.gridBlock); + resetStateMachine(); + return; + } + + //don't add code here, put it to terrainIoOpenedFileCallback + return; + } + /////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /////// OPEN FILE PENDING WATCHDOG /////////////////////////////////// + // asynfatfs can be in infinite state "opening" if the card failures during opening. So after a while reset state machine + if(terrainIoState.status == TERRAIN_IO_OPEN_FILE_PENDING){ + if(millis() - terrainIoState.openFileStartTimeMs > 3000){ + //LOG_DEBUG(SYSTEM, "TERRAIN OPEN FILE TIMEOUT"); + markGridBlockInvalid(terrainIoState.gridBlock); + increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); + resetStateMachine(); + return; + } + } + /////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /////// SEEK TO BLOCK /////////////////////////////////////////////// + if(terrainIoState.status == TERRAIN_IO_SEEK){ + if(terrainIoState.datFile == NULL || terrainIoState.gridBlock == NULL){ + markGridBlockInvalid(terrainIoState.gridBlock); + cleanUp(); + return; + } + + //LOG_DEBUG(SYSTEM, "TERRAIN SEEK TO POSITION %d,%d", terrainIoState.gridBlock->grid_idx_x, terrainIoState.gridBlock->grid_idx_y); + + //calculate file offset + uint32_t blocknum = (eastBlocks(terrainIoState.gridBlock) * terrainIoState.gridBlock->grid_idx_x) + terrainIoState.gridBlock->grid_idx_y; + uint64_t fileOffset64 = (uint64_t)blocknum * sizeof(terrainIoState.ioBlock); + + if(fileOffset64 > UINT32_MAX) { + markGridBlockInvalid(terrainIoState.gridBlock); + cleanUp(); + return; + } + + afatfsOperationStatus_e seekState = afatfs_fseek(terrainIoState.datFile, (int32_t)fileOffset64, AFATFS_SEEK_SET); + if(seekState != AFATFS_OPERATION_FAILURE){ + //LOG_DEBUG(SYSTEM, "TERRAIN SEEK OK"); + terrainIoState.status = TERRAIN_IO_READ; // we don't wait to end of seek, after seek done, reading will be available in next task call + return; + } + + //LOG_DEBUG(SYSTEM, "TERRAIN SEEK ERROR"); + markGridBlockInvalid(terrainIoState.gridBlock); + cleanUp(); + return; + } + ////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + /////// READING BLOCK DATA ///////////////////////////////////////////// + if(terrainIoState.status == TERRAIN_IO_READ){ + if(terrainIoState.datFile == NULL || terrainIoState.gridBlock == NULL){ + markGridBlockInvalid(terrainIoState.gridBlock); + cleanUp(); + return; + } + + uint32_t readNow = afatfs_fread(terrainIoState.datFile, (uint8_t*)&terrainIoState.ioBlock + terrainIoState.bytesRead, sizeof(terrainIoState.ioBlock) - terrainIoState.bytesRead); + terrainIoState.bytesRead += readNow; + + //LOG_DEBUG(SYSTEM, "TERRAIN READING DATA %d/%d", (int)terrainIoState.bytesRead, sizeof(terrainIoState.ioBlock)); + if(terrainIoState.bytesRead == 0 && !(sdcard_isInserted() && sdcard_isFunctional() && afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY)){ + //LOG_DEBUG(SYSTEM, "TERRAIN SD CARD FAILURE"); + hardFailure(); + return; + } + + if(terrainIoState.bytesRead == 0){ + terrainIoState.readsZeroBytesCount++; + } + + // if readNow is zero, it could mean something bad happen, broken file, or any other problem with SD card + // block is 2048, asyncfatfs reads 512. so we accept (2048 / 512) * 2 errors for a single reading. + if(terrainIoState.readsZeroBytesCount > (sizeof(terrainIoState.ioBlock) / 512) * 2){ + markGridBlockInvalid(terrainIoState.gridBlock); + + //we have to increase error for file, for case if error for file reach threshold, and mark file as invalid + increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); + cleanUp(); + return; + } + + if(terrainIoState.bytesRead < sizeof(terrainIoState.ioBlock)){ + //file should be divided by 2048, reading up to end of file and not have all data should never happen + if (afatfs_feof(terrainIoState.datFile)) { + //if it happens we have to close file and increase error count for file + increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); + markGridBlockInvalid(terrainIoState.gridBlock); + cleanUp(); + return; + } + + } else { + //check if idx and idy is same for terrainIoState.ioBlock and terrainIoState.gridBlock, to be sure we loaded from file correct block + if(terrainIoState.ioBlock.block.grid_idx_x != terrainIoState.gridBlock->grid_idx_x || terrainIoState.ioBlock.block.grid_idx_y != terrainIoState.gridBlock->grid_idx_y) { + markGridBlockInvalid(terrainIoState.gridBlock); + cleanUp(); + return; + } + + memcpy(terrainIoState.gridBlock, &terrainIoState.ioBlock.block, sizeof(gridBlock_t)); + markGridBlockAsRead(terrainIoState.gridBlock); + cleanUp(); + return; + } + } + ////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + /////// CLOSING BLOCK DATA ///////////////////////////////////////////// + // wait to call callback terrainIoClosedFileCallback + if(terrainIoState.status == TERRAIN_IO_CLOSE){ + terrainIoState.status = TERRAIN_IO_CLOSE_PENDING; + + if(terrainIoState.datFile == NULL) { + resetStateMachine(); + } + + if(!afatfs_fclose(terrainIoState.datFile, terrainIoClosedFileCallback)){ + resetStateMachine(); + } + return; + } + ////////////////////////////////////////////////////////////////////// +} + +/** + * @brief Checks if the terrain IO system is in a failure state. + * + * @return true if the terrain IO system has failed, false otherwise. + */ +bool isTerrainIoFailure(void) +{ + return terrainIoState.status == TERRAIN_IO_FAILURE; +} + +#endif + diff --git a/src/main/terrain/terrain_io.h b/src/main/terrain/terrain_io.h new file mode 100644 index 00000000000..d627f34377c --- /dev/null +++ b/src/main/terrain/terrain_io.h @@ -0,0 +1,79 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#pragma once + +#include "terrain.h" +#include "io/asyncfatfs/asyncfatfs.h" + +#define TERRAIN_IO_TASK_RATE_HZ 50 +#define TERRAIN_IO_MAX_FILE_OPEN_STATUS 4 //supposed max 4 files opened for a single flight + +/** + * @brief Enumeration of terrain IO status states. + */ +typedef enum { + TERRAIN_IO_IDLE, + TERRAIN_IO_CHANGE_DIR, + TERRAIN_IO_OPEN_FILE, + TERRAIN_IO_OPEN_FILE_PENDING, //wait state in async, waiting to call callback + TERRAIN_IO_SEEK, + TERRAIN_IO_READ, + TERRAIN_IO_CLOSE, + TERRAIN_IO_CLOSE_PENDING, //wait state in async, waiting to call callback + TERRAIN_IO_FAILURE, //something bad happened, disable SD card +} terrainIoStatus_e; + +/** + * @brief Structure to track the open status of terrain data files. + */ +typedef struct { + int8_t latDegrees; + int16_t lonDegrees; + uint8_t errorOpenCount; + timeMs_t lastAccessTimeMs; +} terrainIoFileOpenStatus_t; + +/** + * @brief Structure representing the state of the terrain IO system. + */ +typedef struct { + terrainIoStatus_e status; + gridBlock_t *gridBlock; + gridIoBlock_t ioBlock; // 2048 bytes because block in disk is exactly 2048 bytes + afatfsFilePtr_t datFile; + terrainIoFileOpenStatus_t fileOpenStatus[TERRAIN_IO_MAX_FILE_OPEN_STATUS]; + uint32_t bytesRead; + uint32_t readsZeroBytesCount; + timeMs_t openFileStartTimeMs; +} terrainIoState_t; + + + +void loadGridToCacheTask(timeUs_t currentTimeUs); +void terrainIoOpenedDirCallback(afatfsFilePtr_t directory); +void terrainIoOpenedFileCallback(afatfsFilePtr_t file); +void terrainIoClosedFileCallback(void); +void terrainIoClosedDirCallback(void); +bool isTerrainIoFailure(void); diff --git a/src/main/terrain/terrain_location.c b/src/main/terrain/terrain_location.c new file mode 100644 index 00000000000..e2d988d3a4a --- /dev/null +++ b/src/main/terrain/terrain_location.c @@ -0,0 +1,91 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include "platform.h" + +#ifdef USE_TERRAIN + +#include "terrain_location.h" + +static float longitudeScale(int32_t lat) +{ + float scale = cosf(lat * (1.0e-7f * DEG2RAD)); + return MAX(scale, 0.01f); +} + +static int32_t limitLat(int32_t lat) +{ + if (lat > 900000000L) { + lat = 1800000000LL - lat; + } else if (lat < -900000000L) { + lat = -(1800000000LL + lat); + } + return lat; +} + +static int32_t wrapLongitude(int64_t lon) +{ + if (lon > 1800000000L) { + lon = (int32_t)lon-3600000000LL; + } else if (lon < -1800000000L) { + lon = (int32_t)lon+3600000000LL; + } + return (int32_t) lon; +} + +static int32_t diffLongitude(int32_t lon1, int32_t lon2) +{ + if ((lon1 & 0x80000000) == (lon2 & 0x80000000)) { + // common case of same sign + return lon1 - lon2; + } + int64_t dlon = (int64_t) lon1 - (int64_t) lon2; + if (dlon > 1800000000LL) { + dlon -= 3600000000LL; + } else if (dlon < -1800000000LL) { + dlon += 3600000000LL; + } + return (int32_t) dlon; +} + +void offsetLatlng(gpsLocation_t *loc, float north_m, float east_m) +{ + const int32_t dlat = north_m * LOCATION_SCALING_FACTOR_INV; + const int64_t dlon = (east_m * LOCATION_SCALING_FACTOR_INV) / longitudeScale(loc->lat + (dlat / 2)); + + loc->lat += dlat; + loc->lat = limitLat(loc->lat); + loc->lon = wrapLongitude(dlon + loc->lon); +} + +neVector_t gpsGetDistanceNE(const gpsLocation_t *a, const gpsLocation_t *b) +{ + neVector_t v; + v.north = (float)(b->lat - a->lat) * LOCATION_SCALING_FACTOR; + v.east = (float)diffLongitude(b->lon, a->lon) * LOCATION_SCALING_FACTOR * longitudeScale((b->lat + b->lat)/2); + return v; +} + +#endif \ No newline at end of file diff --git a/src/main/terrain/terrain_location.h b/src/main/terrain/terrain_location.h new file mode 100644 index 00000000000..6d1f70ddc3c --- /dev/null +++ b/src/main/terrain/terrain_location.h @@ -0,0 +1,44 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#pragma once + +#include + +#include "navigation/navigation.h" + +#define LOCATION_SCALING_FACTOR 0.011131884502145034f // m per 1e-7 degree +#define LOCATION_SCALING_FACTOR_INV 89.83204953368922f // 1 / LOCATION_SCALING_FACTOR; + +#define DEG2RAD 0.01745329252f + +#define TERRAIN_LATLON_EQUAL(v1, v2) ((unsigned long)labs((v1) - (v2)) <= 500UL) + +typedef struct { + float north; + float east; +} neVector_t; + +void offsetLatlng(gpsLocation_t *loc, float north_m, float east_m); +neVector_t gpsGetDistanceNE(const gpsLocation_t *a, const gpsLocation_t *b); \ No newline at end of file diff --git a/src/main/terrain/terrain_utils.c b/src/main/terrain/terrain_utils.c new file mode 100644 index 00000000000..04d4ffe2473 --- /dev/null +++ b/src/main/terrain/terrain_utils.c @@ -0,0 +1,245 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include "platform.h" + +#ifdef USE_TERRAIN + +#include "terrain.h" +#include "terrain_utils.h" +#include "terrain_location.h" + +#include "navigation/navigation.h" + +#include "drivers/time.h" + + +static uint8_t grid_spacing = 30; +static gridCache_t cache[TERRAIN_GRID_BLOCK_CACHE_SIZE]; + +/** + given an idx_x and idx_y within a 32x28 grid block, return + the bit number (0..55) corresponding to the 4x4 subgrid + */ +static bool gridBitnum(uint8_t idx_x, uint8_t idx_y, uint8_t *bitnum) +{ + if (idx_x > 27 || idx_y > 31) { + return false; + } + + uint8_t subgrid_x = idx_x / TERRAIN_GRID_MAVLINK_SIZE; + uint8_t subgrid_y = idx_y / TERRAIN_GRID_MAVLINK_SIZE; + + if (subgrid_x >= TERRAIN_GRID_BLOCK_MUL_X || + subgrid_y >= TERRAIN_GRID_BLOCK_MUL_Y) { + return false; + } + + *bitnum = subgrid_y + TERRAIN_GRID_BLOCK_MUL_Y * subgrid_x; + return true; +} + +/** + find a grid block that needs to be read from disk + */ +gridBlock_t* getGridBlockToRead(void) +{ + for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { + if (cache[i].state == GRID_CACHE_DISKWAIT) { + return &(cache[i].gridBlock); + } + } + + return NULL; +} + +/** + mark a grid block as needing to be read from disk + */ +void markGridBlockNeedRead(gridBlock_t *gridBlock) +{ + for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { + if (&(cache[i].gridBlock) == gridBlock) { + cache[i].state = GRID_CACHE_DISKWAIT; + return; + } + } +} + +/** + mark a grid block as having been read from disk + */ +void markGridBlockAsRead(gridBlock_t *gridBlock) +{ + for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { + if (&(cache[i].gridBlock) == gridBlock) { + cache[i].state = GRID_CACHE_VALID; + return; + } + } +} + +/** + mark a grid block as having been read from disk + */ +void markGridBlockInvalid(gridBlock_t *gridBlock) +{ + if (gridBlock == NULL) { + return; + } + + for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { + if (&(cache[i].gridBlock) == gridBlock) { + cache[i].state = GRID_CACHE_INVALID; + return; + } + } +} + +/** + given a gps location, calculate the grid info + */ +bool calculateGridInfo(const gpsLocation_t *loc, gridInfo_t *info) +{ + info->latDegrees = (loc->lat < 0 ? (loc->lat - 9999999L) : loc->lat) / 10000000L; // 10 * 1000 * 1000L + info->lonDegrees = (loc->lon < 0 ? (loc->lon - 9999999L) : loc->lon) / 10000000L; // 10 * 1000 * 1000L + + gpsLocation_t ref; + ref.lat = info->latDegrees * 10000000L; + ref.lon = info->lonDegrees * 10000000L; + + neVector_t offset = gpsGetDistanceNE(&ref, loc); + + uint32_t idx_x = offset.north / grid_spacing; + uint32_t idx_y = offset.east / grid_spacing; + + info->grid_idx_x = idx_x / TERRAIN_GRID_BLOCK_SPACING_X; + info->grid_idx_y = idx_y / TERRAIN_GRID_BLOCK_SPACING_Y; + + info->idx_x = idx_x % TERRAIN_GRID_BLOCK_SPACING_X; + info->idx_y = idx_y % TERRAIN_GRID_BLOCK_SPACING_Y; + + info->frac_x = (offset.north - idx_x * grid_spacing) / grid_spacing; + info->frac_y = (offset.east - idx_y * grid_spacing) / grid_spacing; + + gpsLocation_t gridRef = ref; + offsetLatlng(&gridRef,info->grid_idx_x * TERRAIN_GRID_BLOCK_SPACING_X * grid_spacing,info->grid_idx_y * TERRAIN_GRID_BLOCK_SPACING_Y * grid_spacing); + + info->gridLat = gridRef.lat; + info->gridLon = gridRef.lon; + + return true; +} + +/** + calculate how many blocks east are in a grid block + */ +uint32_t eastBlocks(gridBlock_t *gridBlock) +{ + gpsLocation_t loc1, loc2; + + loc1.lat = gridBlock->latDegrees * 10 * 1000 * 1000L; + loc1.lon = gridBlock->lonDegrees * 10 * 1000 * 1000L; + + loc2.lat = loc1.lat; + loc2.lon = (gridBlock->lonDegrees + 1) * 10 * 1000 * 1000L; + + float east_m = 2.0f * gridBlock->spacing * TERRAIN_GRID_BLOCK_SIZE_Y; + float lat_rad = (loc2.lat * 1e-7f) * DEG2RAD; + float dLon_deg = east_m / (111319.5f * cosf(lat_rad)); + loc2.lon += (int32_t)(dLon_deg * 1e7f); + + neVector_t offset = gpsGetDistanceNE(&loc1, &loc2); + + return offset.east / (gridBlock->spacing * TERRAIN_GRID_BLOCK_SPACING_Y); +} + +/** + find or allocate a grid cache entry for a given grid info + */ +gridCache_t* findGridCache(gridInfo_t *info) +{ + int16_t oldest_i = -1; + + // see if we have that grid + const timeMs_t nowMs = millis(); + uint32_t oldestAccessMs = UINT32_MAX; + + for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { + gridBlock_t *grid = & cache[i].gridBlock; + if (TERRAIN_LATLON_EQUAL(grid->lat, info->gridLat) && + TERRAIN_LATLON_EQUAL(grid->lon , info->gridLon) && + cache[i].gridBlock.spacing == grid_spacing) { + cache[i].lastAccessMs = nowMs; + return &cache[i]; + } + if (cache[i].state != GRID_CACHE_DISKWAIT) { + if (oldest_i == -1 || cache[i].lastAccessMs < oldestAccessMs) { + oldestAccessMs = cache[i].lastAccessMs; + oldest_i = (int16_t)i; + } + } + } + + // Not found. Use the oldest grid and make it this grid, + // initially unpopulated + if(oldest_i < 0){ + return NULL; + } + + gridCache_t *gridCache = &cache[oldest_i]; + memset(gridCache, 0, sizeof(gridCache_t)); + + gridCache->gridBlock.lat = info->gridLat; + gridCache->gridBlock.lon = info->gridLon; + gridCache->gridBlock.spacing = grid_spacing; + gridCache->gridBlock.grid_idx_x = info->grid_idx_x; + gridCache->gridBlock.grid_idx_y = info->grid_idx_y; + gridCache->gridBlock.latDegrees = info->latDegrees; + gridCache->gridBlock.lonDegrees = info->lonDegrees; + gridCache->gridBlock.version = 1; + gridCache->lastAccessMs = nowMs; + + // mark as waiting for disk read + gridCache->state = GRID_CACHE_DISKWAIT; + + return gridCache; +} + +/* + given a grid_info check that a given idx_x/idx_y is available (set + in the bitmap) + */ +bool checkBitmap(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y) +{ + uint8_t bitnum; + if(!gridBitnum(idx_x, idx_y, &bitnum)){ + return false; + } + + return (grid->bitmap & (((uint64_t)1U) << bitnum)) != 0; +} + +#endif \ No newline at end of file diff --git a/src/main/terrain/terrain_utils.h b/src/main/terrain/terrain_utils.h new file mode 100644 index 00000000000..611c1abef6b --- /dev/null +++ b/src/main/terrain/terrain_utils.h @@ -0,0 +1,42 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#pragma once + +#include + +#include "terrain.h" +#include "io/gps.h" + + +bool calculateGridInfo(const gpsLocation_t *loc, gridInfo_t *info); +bool checkBitmap(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y); + +gridCache_t* findGridCache(gridInfo_t *info); +gridBlock_t* getGridBlockToRead(void); + +uint32_t eastBlocks(gridBlock_t *gridBlock); +void markGridBlockAsRead(gridBlock_t *gridBlock); +void markGridBlockNeedRead(gridBlock_t *gridBlock); +void markGridBlockInvalid(gridBlock_t *gridBlock); \ No newline at end of file diff --git a/src/test/unit/CMakeLists.txt b/src/test/unit/CMakeLists.txt index 6347d9052f0..33c5e4abca7 100644 --- a/src/test/unit/CMakeLists.txt +++ b/src/test/unit/CMakeLists.txt @@ -66,6 +66,9 @@ set_property(SOURCE gps_ublox_unittest.cc PROPERTY definitions GPS_UBLOX_UNIT_TE set_property(SOURCE gimbal_serial_unittest.cc PROPERTY depends "io/gimbal_serial.c" "drivers/gimbal_common.c" "common/maths.c" "drivers/headtracker_common.c") set_property(SOURCE gimbal_serial_unittest.cc PROPERTY definitions USE_SERIAL_GIMBAL GIMBAL_UNIT_TEST USE_HEADTRACKER) +set_property(SOURCE terrain_unittest.cc PROPERTY depends "terrain/terrain_utils.c" "terrain/terrain_location.c" "drivers/time.c") +set_property(SOURCE terrain_unittest.cc PROPERTY definitions TERRAIN_UNIT_TEST USE_TERRAIN USE_SDCARD USE_SDCARD_SDIO TERRAIN_GRID_BLOCK_CACHE_SIZE=8) + function(unit_test src) get_filename_component(basename ${src} NAME) string(REPLACE ".cc" "" name ${basename} ) diff --git a/src/test/unit/terrain_unittest.cc b/src/test/unit/terrain_unittest.cc new file mode 100644 index 00000000000..4f2438f7478 --- /dev/null +++ b/src/test/unit/terrain_unittest.cc @@ -0,0 +1,86 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +//Run only this test: +// cmake -DTOOLCHAIN= .. && make terrain_unittest && ./src/test/unit/terrain_unittest --gtest_filter=Terrain* + +#include +#include + +extern "C" { + #include "terrain/terrain_utils.h" + #include "common/time.h" + #include "drivers/time.h" + extern timeUs_t usTicks; + extern volatile timeMs_t sysTickUptime; + extern volatile timeMs_t sysTickValStamp; +} + +#include "unittest_macros.h" +#include "gtest/gtest.h" + +SysTick_Type SysTickValue; +SysTick_Type *SysTick = &SysTickValue; + + +TEST(TerrainTest, calculateGridInfo) +{ + ///////////////////////////////////////////////////////////////////// + gpsLocation_t loc1 = { .lat = 374221234, .lon = -1220845678, .alt = 0 }; + gridInfo_t info1; + + calculateGridInfo(&loc1, &info1); + + EXPECT_EQ(info1.latDegrees, 37); + EXPECT_EQ(info1.lonDegrees, -123); + + EXPECT_EQ(info1.latDegrees, 37); + EXPECT_EQ(info1.lonDegrees, -123); + + EXPECT_EQ(info1.grid_idx_x, 65); + EXPECT_EQ(info1.grid_idx_y, 96); + + EXPECT_EQ(info1.gridLat, 374204140); + EXPECT_EQ(info1.gridLon, -1220904251); + + //////////////////////////////////////////////////////////////////////// + gpsLocation_t loc2 = { .lat = 491304520, .lon = 165976430, .alt = 0 }; + gridInfo_t info2; + + calculateGridInfo(&loc2, &info2); + + EXPECT_EQ(info2.latDegrees, 49); + EXPECT_EQ(info2.lonDegrees, 16); + + EXPECT_EQ(info2.grid_idx_x, 20); + EXPECT_EQ(info2.grid_idx_y, 51); + + EXPECT_EQ(info2.idx_x, 4); + EXPECT_EQ(info2.idx_y, 23); + + EXPECT_EQ(info2.gridLat, 491293581); + EXPECT_EQ(info2.gridLon, 165873573); + + ///////////////////////////////////////////////////////////////////// +} \ No newline at end of file From 3ca41fabbe77c876380015a7f5926478a73c5d7f Mon Sep 17 00:00:00 2001 From: Error414 Date: Sun, 22 Mar 2026 22:26:14 +0100 Subject: [PATCH 02/42] update documentation --- docs/Terrain.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/Terrain.md b/docs/Terrain.md index 91ed96cb7cf..7052e186f4c 100644 --- a/docs/Terrain.md +++ b/docs/Terrain.md @@ -32,8 +32,19 @@ read issues during flight and is considered the recommended method for preparing To generate elevation maps, use the terrain generator web tool available at https://terrain.ardupilot.org/ In iNav, **only 30 m resolution (SRTM1)** is currently supported, so this option must be selected during data generation. -The generated files are then copied to the SD card into the appropriate directory structure. +The generated files are then copied to the SD card into the root directory structure. +For example +``` +SDCARD:\ +├── N47E014.DAT +├── N47E015.DAT +├── N47E016.DAT +├── N49E015.DAT +├── N49E016.DAT +├── N49E017.DAT +└── N50E016.DAT +``` Copying can be done via **iNav MSC (Mass Storage Class)**, but this method is very slow, so using an **external SD card reader** is strongly recommended. Before copying the data, the file **`FREESPAC.E`** must be deleted from the root directory of the SD card. iNav uses this file to track available disk space, and without deleting it, the card may appear to be full. After the @@ -66,7 +77,7 @@ stability and reliability of the terrain feature. - **H7/F4 flight controller** with SD card required - Format SD card using [SD Memory Card Formatter](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-windows-download/) (max 4 GB partition) - Generate terrain data at https://terrain.ardupilot.org/ — select **SRTM1 (30 m)** -- Delete `FREESPAC.E` from SD card root before copying files (use external card reader, not MSC) +- Delete `FREESPAC.E` from SD card root before copying files to ROOT of SDCARD (use external card reader, not MSC) - Enable via CLI: `set terrain_enabled = ON` + `save` - Enable **Rangefinder distance** OSD element to see altitude above terrain - ⚠️ Displayed value is **informational only** — not used for navigation or altitude control From c8482480f0713a15e14a0475e063e97419bf34df Mon Sep 17 00:00:00 2001 From: Error414 Date: Sun, 29 Mar 2026 17:11:45 +0200 Subject: [PATCH 03/42] add terrain data to blackbox --- src/main/blackbox/blackbox.c | 16 ++++++++++++++++ src/main/terrain/terrain.c | 17 ++++++++++++++--- src/main/terrain/terrain.h | 4 +++- src/main/terrain/terrain_location.c | 2 +- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index 5a9d72100aa..ac525609c85 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -520,6 +520,10 @@ static const blackboxSimpleFieldDefinition_t blackboxSlowFields[] = { {"escRPM", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)}, {"escTemperature", -1, SIGNED, PREDICT(PREVIOUS), ENCODING(SIGNED_VB)}, #endif +#ifdef USE_TERRAIN + {"terrainAGL", -1, UNSIGNED, PREDICT(0), ENCODING(SIGNED_VB)}, + {"terrainAMSL", -1, UNSIGNED, PREDICT(0), ENCODING(SIGNED_VB)}, +#endif }; #define BLACKBOX_FIRST_HEADER_SENDING_STATE BLACKBOX_STATE_SEND_HEADER @@ -624,6 +628,10 @@ typedef struct blackboxSlowState_s { #ifdef USE_ESC_SENSOR uint32_t escRPM; int8_t escTemperature; +#endif +#ifdef USE_TERRAIN + int32_t terrainAGL; + int32_t terrainAMSL; #endif uint16_t rxUpdateRate; uint8_t activeWpNumber; @@ -1409,6 +1417,10 @@ static void writeSlowFrame(void) blackboxWriteUnsignedVB(slowHistory.escRPM); blackboxWriteSignedVB(slowHistory.escTemperature); #endif +#ifdef USE_TERRAIN + blackboxWriteSignedVB(slowHistory.terrainAGL); + blackboxWriteSignedVB(slowHistory.terrainAMSL); +#endif blackboxSlowFrameIterationTimer = 0; } @@ -1484,6 +1496,10 @@ static void loadSlowState(blackboxSlowState_t *slow) slow->escRPM = escSensor->rpm; slow->escTemperature = escSensor->temperature; #endif +#ifdef USE_TERRAIN + slow->terrainAGL = terrainGetLastDistanceCm(); + slow->terrainAMSL = terrainGetLastAMSL(); +#endif } /** diff --git a/src/main/terrain/terrain.c b/src/main/terrain/terrain.c index 55b0482a183..97d62d4b26e 100644 --- a/src/main/terrain/terrain.c +++ b/src/main/terrain/terrain.c @@ -65,10 +65,12 @@ PG_RESET_TEMPLATE(terrainConfig_t, terrainConfig, static struct { timeMs_t lastUpdate; - int32_t terrainAGL; + int32_t terrainAGL; // cm + int32_t terrainAMSL; // cm } terrainHeight = { .terrainAGL = 0, .lastUpdate = 0, + .terrainAMSL = 0, }; static struct { @@ -182,13 +184,22 @@ void terrainUpdateTask(timeUs_t currentTimeUs) { float heightASL = getHeightAmslMeters(&gpsSol.llh); if(heightASL >= 0){ - terrainHeight.terrainAGL = MAX(0, ((int32_t)getEstimatedActualPosition(Z) + (int32_t)(terrainHomePos.homeAltitudeM * 100.0f)) - (int32_t)(heightASL * 100.0f)); + terrainHeight.terrainAMSL = (int32_t)(heightASL * 100.0f); + terrainHeight.terrainAGL = MAX(0, ((int32_t)getEstimatedActualPosition(Z) + (int32_t)(terrainHomePos.homeAltitudeM * 100.0f)) - terrainHeight.terrainAMSL); terrainHeight.lastUpdate = millis(); } } ///////////////////////////////////////////////////////////////////////////////////////////////////// } +int32_t terrainGetLastAMSL(void){ + if(!terrainConfig()->terrainEnabled){ + return TERRAIN_STATUS_NO_DATA; + } + + return terrainHeight.lastUpdate + TERRAIN_NO_DATA_DELAY_MS < millis() ? TERRAIN_STATUS_NO_DATA : terrainHeight.terrainAMSL; +} + int32_t terrainGetLastDistanceCm(void) { //start a terrain system after the first query to terrain height @@ -196,7 +207,7 @@ int32_t terrainGetLastDistanceCm(void) return TERRAIN_STATUS_NO_DATA; } - return terrainHeight.lastUpdate + 1000 < millis() ? TERRAIN_STATUS_NO_DATA : terrainHeight.terrainAGL; + return terrainHeight.lastUpdate + TERRAIN_NO_DATA_DELAY_MS < millis() ? TERRAIN_STATUS_NO_DATA : terrainHeight.terrainAGL; } #endif \ No newline at end of file diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h index 2d7b616cb97..f15764b27e6 100644 --- a/src/main/terrain/terrain.h +++ b/src/main/terrain/terrain.h @@ -30,7 +30,7 @@ #include "navigation/navigation.h" #define TERRAIN_TASK_RATE_HZ 5 -#define TERRAIN_TASK_ 200 +#define TERRAIN_NO_DATA_DELAY_MS 1000 #define TERRAIN_STATUS_FAILURE (-1) #define TERRAIN_STATUS_WRONG_BLOC_SIZE (-2) @@ -148,4 +148,6 @@ PG_DECLARE(terrainConfig_t, terrainConfig); void terrainInit(void); void terrainUpdateTask(timeUs_t currentTimeUs); +int32_t terrainGetLastAMSL(void); int32_t terrainGetLastDistanceCm(void); + diff --git a/src/main/terrain/terrain_location.c b/src/main/terrain/terrain_location.c index e2d988d3a4a..450ffbd8da1 100644 --- a/src/main/terrain/terrain_location.c +++ b/src/main/terrain/terrain_location.c @@ -84,7 +84,7 @@ neVector_t gpsGetDistanceNE(const gpsLocation_t *a, const gpsLocation_t *b) { neVector_t v; v.north = (float)(b->lat - a->lat) * LOCATION_SCALING_FACTOR; - v.east = (float)diffLongitude(b->lon, a->lon) * LOCATION_SCALING_FACTOR * longitudeScale((b->lat + b->lat)/2); + v.east = (float)diffLongitude(b->lon, a->lon) * LOCATION_SCALING_FACTOR * longitudeScale((b->lat + a->lat)/2); return v; } From 6a8ed40f409895bea47c898bbf64e9ab749b24b5 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sun, 29 Mar 2026 18:05:50 +0200 Subject: [PATCH 04/42] fix tests: update terrain --- src/test/unit/terrain_unittest.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/unit/terrain_unittest.cc b/src/test/unit/terrain_unittest.cc index 4f2438f7478..60dfc89a681 100644 --- a/src/test/unit/terrain_unittest.cc +++ b/src/test/unit/terrain_unittest.cc @@ -77,7 +77,7 @@ TEST(TerrainTest, calculateGridInfo) EXPECT_EQ(info2.grid_idx_y, 51); EXPECT_EQ(info2.idx_x, 4); - EXPECT_EQ(info2.idx_y, 23); + EXPECT_EQ(info2.idx_y, 24); EXPECT_EQ(info2.gridLat, 491293581); EXPECT_EQ(info2.gridLon, 165873573); From 26db3858ae8ee8840e839abb570dc7134365d5b7 Mon Sep 17 00:00:00 2001 From: Error414 Date: Wed, 1 Apr 2026 20:55:47 +0200 Subject: [PATCH 05/42] update manual --- docs/Terrain.md | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/docs/Terrain.md b/docs/Terrain.md index 7052e186f4c..2fc436354ac 100644 --- a/docs/Terrain.md +++ b/docs/Terrain.md @@ -21,16 +21,20 @@ terrain data are unavailable or a read error occurs, the feature is automaticall # SD Card Preparation +Use only quality SD cards from reputable brands. Note that some combinations of flight controllers and SD cards can cause issues, +so if you experience any problems, try a different card before troubleshooting further. Compatibility problems have been +observed especially with F4-based flight controllers. + For proper operation, the SD card must be prepared in advance. It is recommended to create a **partition with a maximum size -of 4 GB** and format it. Formatting should be done using the official -[**SD Memory Card Formatter**](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-windows-download/) tool from the SD Association, -which ensures correct alignment and a compatible file system structure. Using this tool minimizes the risk of terrain data -read issues during flight and is considered the recommended method for preparing an SD card for iNav. +of 4 GB** and format it to FAT32. # Data Generation and Copying To generate elevation maps, use the terrain generator web tool available at https://terrain.ardupilot.org/ +**Before copying any terrain data files, the SD card must be formatted. Always format the card before each new terrain data +installation to avoid file system errors.** + In iNav, **only 30 m resolution (SRTM1)** is currently supported, so this option must be selected during data generation. The generated files are then copied to the SD card into the root directory structure. @@ -45,10 +49,8 @@ SDCARD:\ ├── N49E017.DAT └── N50E016.DAT ``` -Copying can be done via **iNav MSC (Mass Storage Class)**, but this method is very slow, so using an **external SD card reader** -is strongly recommended. Before copying the data, the file **`FREESPAC.E`** must be deleted from the root directory of the SD -card. iNav uses this file to track available disk space, and without deleting it, the card may appear to be full. After the -copying process is complete, iNav will automatically recreate this file on the next startup. + +Copying can be done via **iNav MSC (Mass Storage Class)** is not recommended. # Enabling and Displaying Terrain Data @@ -70,15 +72,4 @@ them to display altitude above terrain. Finally, it is **strongly recommended to use only high-quality, branded SD cards** from reputable manufacturers. The terrain system is sensitive to SD card read speed and reliability, and low-quality or counterfeit cards may cause read errors, display dropouts, or automatic disabling of the feature during flight. Using a quality SD card significantly improves the -stability and reliability of the terrain feature. - -# TL;DR - -- **H7/F4 flight controller** with SD card required -- Format SD card using [SD Memory Card Formatter](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-windows-download/) (max 4 GB partition) -- Generate terrain data at https://terrain.ardupilot.org/ — select **SRTM1 (30 m)** -- Delete `FREESPAC.E` from SD card root before copying files to ROOT of SDCARD (use external card reader, not MSC) -- Enable via CLI: `set terrain_enabled = ON` + `save` -- Enable **Rangefinder distance** OSD element to see altitude above terrain -- ⚠️ Displayed value is **informational only** — not used for navigation or altitude control -- Use only **high-quality branded SD cards** \ No newline at end of file +stability and reliability of the terrain feature. \ No newline at end of file From b4f054c3f4098a873a718ae71c2d5c995355fedd Mon Sep 17 00:00:00 2001 From: Error414 Date: Fri, 3 Apr 2026 08:50:22 +0200 Subject: [PATCH 06/42] update manual, Troubleshooting --- docs/Terrain.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/Terrain.md b/docs/Terrain.md index 2fc436354ac..cc382a1a92c 100644 --- a/docs/Terrain.md +++ b/docs/Terrain.md @@ -7,8 +7,7 @@ └─────────────────────────────────────────────────┘ ``` -This feature is available **only on H7-based and F4-based flight controllers with an SD card** (preferably SDIO). Due to the high CPU load -and SD card read speed requirements of terrain data processing, it is not supported on weaker hardware. +This feature is available **only on H7-based and F4-based flight controllers with an SD card**. This feature in iNav determines the model’s altitude above ground level using preloaded elevation maps (terrain / SRTM data) stored on an SD card, without requiring a physical rangefinder. Based on the current GPS position, the @@ -52,6 +51,11 @@ SDCARD:\ Copying can be done via **iNav MSC (Mass Storage Class)** is not recommended. +> **Important:** The `.DAT` file covering your **home position** (the location where you arm) must always be present on the SD +> card. The terrain system first reads the ground elevation at the home position to establish an altitude reference. Until this +> succeeds, terrain data will not be displayed for any position — even if tiles for your entire flight area are present. Make +> sure the tile for your take-off site is included when generating and copying terrain files. + # Enabling and Displaying Terrain Data To display altitude above terrain in iNav, the OSD element **“Rangefinder distance”** must be enabled. If terrain data are @@ -72,4 +76,22 @@ them to display altitude above terrain. Finally, it is **strongly recommended to use only high-quality, branded SD cards** from reputable manufacturers. The terrain system is sensitive to SD card read speed and reliability, and low-quality or counterfeit cards may cause read errors, display dropouts, or automatic disabling of the feature during flight. Using a quality SD card significantly improves the -stability and reliability of the terrain feature. \ No newline at end of file +stability and reliability of the terrain feature. + +# Troubleshooting +- OSD shows no terrain value / "Rangefinder distance" is dash: + - Verify `terrain_enabled = ON` is set and saved (`set terrain_enabled = ON` → `save` → reboot) + - Confirm the SD card is mounted: check `status` in the CLI for SD card state + - Confirm a valid GPS fix is present before expecting any terrain value to appear + - Terrain data is only loaded **after the first valid GPS fix**. If the fix is acquired after a long wait, allow a few seconds for the first read to complete + - Check that the `.DAT` file covering your **home position** exists on the SD card — see note in *Data Generation and Copying* above +- Feature disables itself mid-flight: + - The terrain subsystem disables itself on any SD card read failure. This is most commonly caused by a slow or low-quality SD card. Replace the card with a branded, high-speed card + - On F4-based flight controllers, SD card compatibility issues are more common. If problems persist, test with a different card model or switch to an H7-based flight controller + - Ensure the SD card partition is ≤ 4 GB and formatted FAT32. Larger partitions or exFAT formatting can cause intermittent read failures +- Value appears but is clearly wrong: + - Confirm you selected **30 m resolution (SRTM1)** when generating data at https://terrain.ardupilot.org/. Other resolutions are not supported and will produce incorrect values + - Reformat the SD card and recopy the terrain files. A corrupted or partially overwritten FAT32 filesystem can produce plausible but incorrect altitude values + - The displayed value is altitude **above terrain at the current GPS position**, not distance to the nearest object below the aircraft. Trees, buildings, and local obstacles are not accounted for +- Terrain data was not loaded before arming: + - If the flight controller is armed before terrain data for the home position is successfully read from the SD card, the system will not attempt to load data during the flight (to avoid SD card access latency while airborne). Disarm, wait for the OSD value to appear, then arm From f8727111f8dbe58fc6ed6749649c174b0cff5283 Mon Sep 17 00:00:00 2001 From: Error414 Date: Mon, 13 Apr 2026 17:14:47 +0200 Subject: [PATCH 07/42] update settings.md --- docs/Settings.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index 1267e08c6e8..44e3d56d5fd 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -695,6 +695,7 @@ ADC, VIRTUAL, FAKE, ESC, SMARTPORT, CAN, NONE. The virtual current sensor, once | FAKE | | | ESC | | | SMARTPORT | | +| CAN | | --- @@ -780,6 +781,7 @@ Defines debug values exposed in debug variables (developer / debugging setting) | GPS | | | LULU | | | SBUS2 | | +| OSD_REFRESH | | --- @@ -872,9 +874,12 @@ Re-purpose the craft name field for messages. The speed of the CANbus network in kbps. Set all devices to the same speed. -| Default | Min | Max | -| --- | --- | --- | -| 1000 | | | +| Allowed Values | | +| --- | --- | +| 125 | | +| 250 | | +| 500 | | +| 1000 | Default | --- @@ -1859,6 +1864,7 @@ Which GPS protocol to be used. | UBLOX | Default | | MSP | | | FAKE | | +| DRONECAN | | --- @@ -6944,6 +6950,7 @@ Vbat voltage source. Possible values: `NONE`, `ADC`, `SMARTPORT`, `ESC`, 'CAN'. | ESC | | | FAKE | | | SMARTPORT | | +| CAN | | --- From 913a9b0de3602c6fd743a07d10190804f95903c2 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sat, 18 Apr 2026 17:30:35 +0200 Subject: [PATCH 08/42] remove condition in target files, each board has its own --- src/main/target/ALIENFLIGHTF4/target.h | 2 -- src/main/target/AOCODARCF4V3/target.h | 2 -- src/main/target/ATOMRCF405NAVI/target.h | 2 -- src/main/target/ATOMRCF405NAVI_DELUX/target.h | 2 -- src/main/target/BEEROTORF4/target.h | 2 -- src/main/target/BLUEJAYF4/target.h | 2 -- src/main/target/BROTHERHOBBYH743/target.h | 2 -- src/main/target/COREWINGF405WINGV2/target.h | 2 -- src/main/target/CORVON405V2/target.h | 2 -- src/main/target/CORVON743V1/target.h | 2 -- src/main/target/F4BY/target.h | 2 -- src/main/target/FISHDRONEF4/target.h | 2 -- src/main/target/FLYSPARKF4V4/target.h | 2 -- src/main/target/FLYWOOH743PRO/target.h | 2 -- src/main/target/FRSKYF4/target.h | 2 -- src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h | 2 -- src/main/target/JHEMCUF405WING/target.h | 2 -- src/main/target/KAKUTEH7WING/target.h | 2 -- src/main/target/KROOZX/target.h | 2 -- src/main/target/MATEKF405/target.h | 2 -- src/main/target/MATEKF405CAN/target.h | 2 -- src/main/target/MATEKF405SE/target.h | 2 -- src/main/target/MATEKH743/target.h | 2 -- src/main/target/MICOAIR405MINI/target.h | 2 -- src/main/target/MICOAIR405V2/target.h | 2 -- src/main/target/MICOAIR743/target.h | 2 -- src/main/target/MICOAIR743AIO/target.h | 2 -- src/main/target/MICOAIR743V2/target.h | 2 -- src/main/target/NEUTRONRCH7BT/target.h | 2 -- src/main/target/OMNIBUSF4/target.h | 2 -- src/main/target/SPEEDYBEEF405V3/target.h | 2 -- src/main/target/SPEEDYBEEF405V4/target.h | 2 -- src/main/target/SPEEDYBEEF405WING/target.h | 2 -- src/main/target/SPEEDYBEEF405WINGV2/target.h | 2 -- src/main/target/SPRACINGF4EVO/target.h | 2 -- src/main/target/TBS_LUCID_H7/target.h | 2 -- src/main/target/TBS_LUCID_H7_WING/target.h | 2 -- src/main/target/TBS_LUCID_H7_WING_MINI/target.h | 2 -- src/main/target/YUPIF4/target.h | 2 -- 39 files changed, 78 deletions(-) diff --git a/src/main/target/ALIENFLIGHTF4/target.h b/src/main/target/ALIENFLIGHTF4/target.h index 87e4652d478..1d0f60dd1ea 100644 --- a/src/main/target/ALIENFLIGHTF4/target.h +++ b/src/main/target/ALIENFLIGHTF4/target.h @@ -146,10 +146,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX) #define DEFAULT_RX_TYPE RX_TYPE_SERIAL diff --git a/src/main/target/AOCODARCF4V3/target.h b/src/main/target/AOCODARCF4V3/target.h index 5f32f9577d5..1503d220174 100644 --- a/src/main/target/AOCODARCF4V3/target.h +++ b/src/main/target/AOCODARCF4V3/target.h @@ -151,10 +151,8 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #else #define USE_FLASHFS #define USE_FLASH_M25P16 diff --git a/src/main/target/ATOMRCF405NAVI/target.h b/src/main/target/ATOMRCF405NAVI/target.h index cc826ab89a8..4d4cf5a2c5f 100644 --- a/src/main/target/ATOMRCF405NAVI/target.h +++ b/src/main/target/ATOMRCF405NAVI/target.h @@ -151,10 +151,8 @@ #define SDCARD_CS_PIN PB6 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif /* * LED Strip diff --git a/src/main/target/ATOMRCF405NAVI_DELUX/target.h b/src/main/target/ATOMRCF405NAVI_DELUX/target.h index 9c7b1630197..0bf87fb6d67 100644 --- a/src/main/target/ATOMRCF405NAVI_DELUX/target.h +++ b/src/main/target/ATOMRCF405NAVI_DELUX/target.h @@ -149,10 +149,8 @@ #define SDCARD_CS_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif /* * LED Strip diff --git a/src/main/target/BEEROTORF4/target.h b/src/main/target/BEEROTORF4/target.h index 1f30c30881c..006a6ca5712 100644 --- a/src/main/target/BEEROTORF4/target.h +++ b/src/main/target/BEEROTORF4/target.h @@ -52,10 +52,8 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define USE_VCP #define VBUS_SENSING_ENABLED diff --git a/src/main/target/BLUEJAYF4/target.h b/src/main/target/BLUEJAYF4/target.h index d47753ad9a8..1156fa1c55d 100644 --- a/src/main/target/BLUEJAYF4/target.h +++ b/src/main/target/BLUEJAYF4/target.h @@ -58,10 +58,8 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PA15 -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define M25P16_CS_PIN PB7 #define M25P16_SPI_BUS BUS_SPI3 diff --git a/src/main/target/BROTHERHOBBYH743/target.h b/src/main/target/BROTHERHOBBYH743/target.h index bdacc1a8554..568fb2f67ac 100644 --- a/src/main/target/BROTHERHOBBYH743/target.h +++ b/src/main/target/BROTHERHOBBYH743/target.h @@ -147,10 +147,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/COREWINGF405WINGV2/target.h b/src/main/target/COREWINGF405WINGV2/target.h index dc0884e2602..615ecc58c91 100644 --- a/src/main/target/COREWINGF405WINGV2/target.h +++ b/src/main/target/COREWINGF405WINGV2/target.h @@ -133,10 +133,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/CORVON405V2/target.h b/src/main/target/CORVON405V2/target.h index a8e7842851c..c4797871eb6 100644 --- a/src/main/target/CORVON405V2/target.h +++ b/src/main/target/CORVON405V2/target.h @@ -66,10 +66,8 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/CORVON743V1/target.h b/src/main/target/CORVON743V1/target.h index f777b9209f4..a86a8b203ac 100644 --- a/src/main/target/CORVON743V1/target.h +++ b/src/main/target/CORVON743V1/target.h @@ -123,10 +123,8 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/F4BY/target.h b/src/main/target/F4BY/target.h index 9da7d048312..18b2395ea2f 100644 --- a/src/main/target/F4BY/target.h +++ b/src/main/target/F4BY/target.h @@ -55,10 +55,8 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN PE15 -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define USE_VCP #define VBUS_SENSING_PIN PA9 diff --git a/src/main/target/FISHDRONEF4/target.h b/src/main/target/FISHDRONEF4/target.h index 1372a7fd182..eeec8a8b767 100644 --- a/src/main/target/FISHDRONEF4/target.h +++ b/src/main/target/FISHDRONEF4/target.h @@ -82,10 +82,8 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PB6 -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** Flash ***************************** #define M25P16_CS_PIN PA15 diff --git a/src/main/target/FLYSPARKF4V4/target.h b/src/main/target/FLYSPARKF4V4/target.h index 24b6aedf1a7..dab2dc5ef57 100644 --- a/src/main/target/FLYSPARKF4V4/target.h +++ b/src/main/target/FLYSPARKF4V4/target.h @@ -126,10 +126,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** OSD ***************************** #define USE_SPI_DEVICE_2 diff --git a/src/main/target/FLYWOOH743PRO/target.h b/src/main/target/FLYWOOH743PRO/target.h index e98c33799ad..52ffc58a8d6 100644 --- a/src/main/target/FLYWOOH743PRO/target.h +++ b/src/main/target/FLYWOOH743PRO/target.h @@ -145,10 +145,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/FRSKYF4/target.h b/src/main/target/FRSKYF4/target.h index eefa86233d5..23023482b9a 100755 --- a/src/main/target/FRSKYF4/target.h +++ b/src/main/target/FRSKYF4/target.h @@ -45,10 +45,8 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define USE_VCP #define VBUS_SENSING_PIN PC5 diff --git a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h index 3c3ce5e7dcf..6d1d8e690fd 100644 --- a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h +++ b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h @@ -137,10 +137,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/JHEMCUF405WING/target.h b/src/main/target/JHEMCUF405WING/target.h index 348c8e096bc..ba610991dbb 100644 --- a/src/main/target/JHEMCUF405WING/target.h +++ b/src/main/target/JHEMCUF405WING/target.h @@ -121,10 +121,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/KAKUTEH7WING/target.h b/src/main/target/KAKUTEH7WING/target.h index 5bd7d9d14a6..f784f826062 100644 --- a/src/main/target/KAKUTEH7WING/target.h +++ b/src/main/target/KAKUTEH7WING/target.h @@ -82,10 +82,8 @@ #define SDCARD_SDIO_NORMAL_SPEED #define SDCARD_SDIO2_CMD_ALT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/KROOZX/target.h b/src/main/target/KROOZX/target.h index f5d8be344a5..157a2cb4205 100755 --- a/src/main/target/KROOZX/target.h +++ b/src/main/target/KROOZX/target.h @@ -51,10 +51,8 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC13 -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #ifdef USE_MSP_DISPLAYPORT #undef USE_MSP_DISPLAYPORT diff --git a/src/main/target/MATEKF405/target.h b/src/main/target/MATEKF405/target.h index 8b168fe44b9..937b47ca885 100644 --- a/src/main/target/MATEKF405/target.h +++ b/src/main/target/MATEKF405/target.h @@ -70,10 +70,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #endif diff --git a/src/main/target/MATEKF405CAN/target.h b/src/main/target/MATEKF405CAN/target.h index b7cfbb5b930..bb37fc23897 100644 --- a/src/main/target/MATEKF405CAN/target.h +++ b/src/main/target/MATEKF405CAN/target.h @@ -84,10 +84,8 @@ #define SPI3_MISO_PIN PB4 #define SPI3_MOSI_PIN PB5 -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/MATEKF405SE/target.h b/src/main/target/MATEKF405SE/target.h index 78146f0ed11..0c65f6f5711 100644 --- a/src/main/target/MATEKF405SE/target.h +++ b/src/main/target/MATEKF405SE/target.h @@ -100,10 +100,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MATEKH743/target.h b/src/main/target/MATEKH743/target.h index 378e49098ca..c59c59c8d83 100644 --- a/src/main/target/MATEKH743/target.h +++ b/src/main/target/MATEKH743/target.h @@ -178,10 +178,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/MICOAIR405MINI/target.h b/src/main/target/MICOAIR405MINI/target.h index 3693904fe6e..ebf5201041e 100644 --- a/src/main/target/MICOAIR405MINI/target.h +++ b/src/main/target/MICOAIR405MINI/target.h @@ -65,10 +65,8 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR405V2/target.h b/src/main/target/MICOAIR405V2/target.h index d979c9d3350..3461e0730f4 100644 --- a/src/main/target/MICOAIR405V2/target.h +++ b/src/main/target/MICOAIR405V2/target.h @@ -66,10 +66,8 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR743/target.h b/src/main/target/MICOAIR743/target.h index 51304a7b4c8..ab222081816 100644 --- a/src/main/target/MICOAIR743/target.h +++ b/src/main/target/MICOAIR743/target.h @@ -123,10 +123,8 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/MICOAIR743AIO/target.h b/src/main/target/MICOAIR743AIO/target.h index 6b6174bf894..dfe3b065559 100755 --- a/src/main/target/MICOAIR743AIO/target.h +++ b/src/main/target/MICOAIR743AIO/target.h @@ -117,10 +117,8 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/MICOAIR743V2/target.h b/src/main/target/MICOAIR743V2/target.h index 2aa22a833a8..52e1f7fce84 100755 --- a/src/main/target/MICOAIR743V2/target.h +++ b/src/main/target/MICOAIR743V2/target.h @@ -138,10 +138,8 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/NEUTRONRCH7BT/target.h b/src/main/target/NEUTRONRCH7BT/target.h index d0a66b6c683..762e5d699d5 100644 --- a/src/main/target/NEUTRONRCH7BT/target.h +++ b/src/main/target/NEUTRONRCH7BT/target.h @@ -139,10 +139,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/OMNIBUSF4/target.h b/src/main/target/OMNIBUSF4/target.h index 8210545a809..1ebc8a918e2 100644 --- a/src/main/target/OMNIBUSF4/target.h +++ b/src/main/target/OMNIBUSF4/target.h @@ -235,10 +235,8 @@ #define SDCARD_DETECT_PIN PB7 #define SDCARD_DETECT_INVERTED - #if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block - #endif #else #define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT #define M25P16_CS_PIN SPI3_NSS_PIN diff --git a/src/main/target/SPEEDYBEEF405V3/target.h b/src/main/target/SPEEDYBEEF405V3/target.h index c21ff1b67ab..fe4f0cc9319 100644 --- a/src/main/target/SPEEDYBEEF405V3/target.h +++ b/src/main/target/SPEEDYBEEF405V3/target.h @@ -115,10 +115,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** OSD ***************************** #define USE_OSD diff --git a/src/main/target/SPEEDYBEEF405V4/target.h b/src/main/target/SPEEDYBEEF405V4/target.h index 4c111f80e12..4cd5a52efcc 100644 --- a/src/main/target/SPEEDYBEEF405V4/target.h +++ b/src/main/target/SPEEDYBEEF405V4/target.h @@ -126,10 +126,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif // *************** OSD ***************************** #define USE_SPI_DEVICE_2 diff --git a/src/main/target/SPEEDYBEEF405WING/target.h b/src/main/target/SPEEDYBEEF405WING/target.h index dd21a9f154b..4391f97b902 100644 --- a/src/main/target/SPEEDYBEEF405WING/target.h +++ b/src/main/target/SPEEDYBEEF405WING/target.h @@ -134,10 +134,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/SPEEDYBEEF405WINGV2/target.h b/src/main/target/SPEEDYBEEF405WINGV2/target.h index e79d487cf8e..06d229329b9 100644 --- a/src/main/target/SPEEDYBEEF405WINGV2/target.h +++ b/src/main/target/SPEEDYBEEF405WINGV2/target.h @@ -133,10 +133,8 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/SPRACINGF4EVO/target.h b/src/main/target/SPRACINGF4EVO/target.h index 55c4e0e2f8a..d5d1deae2ea 100755 --- a/src/main/target/SPRACINGF4EVO/target.h +++ b/src/main/target/SPRACINGF4EVO/target.h @@ -115,10 +115,8 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -#endif #define MPU6500_CS_PIN SPI1_NSS_PIN #define MPU6500_SPI_BUS BUS_SPI1 diff --git a/src/main/target/TBS_LUCID_H7/target.h b/src/main/target/TBS_LUCID_H7/target.h index eb7c4479548..9bf582bef68 100644 --- a/src/main/target/TBS_LUCID_H7/target.h +++ b/src/main/target/TBS_LUCID_H7/target.h @@ -141,10 +141,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/TBS_LUCID_H7_WING/target.h b/src/main/target/TBS_LUCID_H7_WING/target.h index bc91ef1aea1..d5ccd083e85 100644 --- a/src/main/target/TBS_LUCID_H7_WING/target.h +++ b/src/main/target/TBS_LUCID_H7_WING/target.h @@ -141,10 +141,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define USE_ADC diff --git a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h index 1fbb7826732..135494a27f9 100644 --- a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h +++ b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h @@ -141,10 +141,8 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block -#endif #define USE_ADC diff --git a/src/main/target/YUPIF4/target.h b/src/main/target/YUPIF4/target.h index facc5c95dde..f3c93dca871 100644 --- a/src/main/target/YUPIF4/target.h +++ b/src/main/target/YUPIF4/target.h @@ -79,11 +79,9 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN SPI3_NSS_PIN -#if defined(USE_SDCARD) && defined(USE_BARO) #define USE_TERRAIN #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #endif -#endif #define USB_IO #define USE_VCP From 37e0a3b2a9a086df5c3bdb2e2e0b8771ab61db8f Mon Sep 17 00:00:00 2001 From: Error414 Date: Wed, 20 May 2026 19:06:15 +0200 Subject: [PATCH 09/42] update Settings.md --- docs/Settings.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Settings.md b/docs/Settings.md index 21866fe5425..5fffef1e54a 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -5926,6 +5926,7 @@ Selection of pitot hardware. VIRTUAL only works if a GPS is enabled. | FAKE | | | MSP | | | DLVR-L10D | | +| MS5525 | | --- From 4c53777d21203689d936a696c2bda659d36d4b09 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sat, 23 May 2026 15:23:52 +0200 Subject: [PATCH 10/42] move "define" for terrain to common_post.h --- src/main/target/ALIENFLIGHTF4/target.h | 2 -- src/main/target/AOCODARCF4V3/target.h | 2 -- src/main/target/ATOMRCF405NAVI/target.h | 2 -- src/main/target/ATOMRCF405NAVI_DELUX/target.h | 2 -- src/main/target/BEEROTORF4/target.h | 2 -- src/main/target/BLUEJAYF4/target.h | 2 -- src/main/target/BROTHERHOBBYH743/target.h | 2 -- src/main/target/COREWINGF405WINGV2/target.h | 2 -- src/main/target/CORVON405V2/target.h | 2 -- src/main/target/CORVON743V1/target.h | 2 -- src/main/target/F4BY/target.h | 2 -- src/main/target/FISHDRONEF4/target.h | 2 -- src/main/target/FLYSPARKF4V4/target.h | 2 -- src/main/target/FLYWOOH743PRO/target.h | 2 -- src/main/target/FRSKYF4/target.h | 2 -- src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h | 2 -- src/main/target/JHEMCUF405WING/target.h | 2 -- src/main/target/KAKUTEH7WING/target.h | 2 -- src/main/target/KROOZX/target.h | 2 -- src/main/target/MATEKF405/target.h | 2 -- src/main/target/MATEKF405CAN/target.h | 2 -- src/main/target/MATEKF405SE/target.h | 2 -- src/main/target/MATEKF405TE/target.h | 4 ---- src/main/target/MATEKH743/target.h | 2 -- src/main/target/MICOAIR405MINI/target.h | 2 -- src/main/target/MICOAIR405V2/target.h | 2 -- src/main/target/MICOAIR743/target.h | 2 -- src/main/target/MICOAIR743AIO/target.h | 2 -- src/main/target/MICOAIR743V2/target.h | 2 -- src/main/target/NEUTRONRCH7BT/target.h | 2 -- src/main/target/OMNIBUSF4/target.h | 2 -- src/main/target/SPEEDYBEEF405V3/target.h | 2 -- src/main/target/SPEEDYBEEF405V4/target.h | 2 -- src/main/target/SPEEDYBEEF405WING/target.h | 2 -- src/main/target/SPEEDYBEEF405WINGV2/target.h | 2 -- src/main/target/SPRACINGF4EVO/target.h | 2 -- src/main/target/TBS_LUCID_H7/target.h | 2 -- src/main/target/TBS_LUCID_H7_WING/target.h | 2 -- src/main/target/TBS_LUCID_H7_WING_MINI/target.h | 2 -- src/main/target/YUPIF4/target.h | 2 -- src/main/target/common_post.h | 11 +++++++++++ 41 files changed, 11 insertions(+), 82 deletions(-) diff --git a/src/main/target/ALIENFLIGHTF4/target.h b/src/main/target/ALIENFLIGHTF4/target.h index 1d0f60dd1ea..8b9d9cf22f7 100644 --- a/src/main/target/ALIENFLIGHTF4/target.h +++ b/src/main/target/ALIENFLIGHTF4/target.h @@ -146,8 +146,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX) #define DEFAULT_RX_TYPE RX_TYPE_SERIAL diff --git a/src/main/target/AOCODARCF4V3/target.h b/src/main/target/AOCODARCF4V3/target.h index 1503d220174..0c95564162e 100644 --- a/src/main/target/AOCODARCF4V3/target.h +++ b/src/main/target/AOCODARCF4V3/target.h @@ -151,8 +151,6 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #else #define USE_FLASHFS #define USE_FLASH_M25P16 diff --git a/src/main/target/ATOMRCF405NAVI/target.h b/src/main/target/ATOMRCF405NAVI/target.h index 4d4cf5a2c5f..88fa134e893 100644 --- a/src/main/target/ATOMRCF405NAVI/target.h +++ b/src/main/target/ATOMRCF405NAVI/target.h @@ -151,8 +151,6 @@ #define SDCARD_CS_PIN PB6 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block /* * LED Strip diff --git a/src/main/target/ATOMRCF405NAVI_DELUX/target.h b/src/main/target/ATOMRCF405NAVI_DELUX/target.h index 0bf87fb6d67..a60f244e74a 100644 --- a/src/main/target/ATOMRCF405NAVI_DELUX/target.h +++ b/src/main/target/ATOMRCF405NAVI_DELUX/target.h @@ -149,8 +149,6 @@ #define SDCARD_CS_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block /* * LED Strip diff --git a/src/main/target/BEEROTORF4/target.h b/src/main/target/BEEROTORF4/target.h index 006a6ca5712..7341a7d1eb0 100644 --- a/src/main/target/BEEROTORF4/target.h +++ b/src/main/target/BEEROTORF4/target.h @@ -52,8 +52,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define USE_VCP #define VBUS_SENSING_ENABLED diff --git a/src/main/target/BLUEJAYF4/target.h b/src/main/target/BLUEJAYF4/target.h index 1156fa1c55d..6cbb4cc9216 100644 --- a/src/main/target/BLUEJAYF4/target.h +++ b/src/main/target/BLUEJAYF4/target.h @@ -58,8 +58,6 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PA15 -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define M25P16_CS_PIN PB7 #define M25P16_SPI_BUS BUS_SPI3 diff --git a/src/main/target/BROTHERHOBBYH743/target.h b/src/main/target/BROTHERHOBBYH743/target.h index 568fb2f67ac..72fda9870ba 100644 --- a/src/main/target/BROTHERHOBBYH743/target.h +++ b/src/main/target/BROTHERHOBBYH743/target.h @@ -147,8 +147,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/COREWINGF405WINGV2/target.h b/src/main/target/COREWINGF405WINGV2/target.h index 615ecc58c91..166d31beced 100644 --- a/src/main/target/COREWINGF405WINGV2/target.h +++ b/src/main/target/COREWINGF405WINGV2/target.h @@ -133,8 +133,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/CORVON405V2/target.h b/src/main/target/CORVON405V2/target.h index c4797871eb6..a75f32dcd3b 100644 --- a/src/main/target/CORVON405V2/target.h +++ b/src/main/target/CORVON405V2/target.h @@ -66,8 +66,6 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/CORVON743V1/target.h b/src/main/target/CORVON743V1/target.h index a86a8b203ac..511270d8a25 100644 --- a/src/main/target/CORVON743V1/target.h +++ b/src/main/target/CORVON743V1/target.h @@ -123,8 +123,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/F4BY/target.h b/src/main/target/F4BY/target.h index 18b2395ea2f..7080c1945d0 100644 --- a/src/main/target/F4BY/target.h +++ b/src/main/target/F4BY/target.h @@ -55,8 +55,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN PE15 -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define USE_VCP #define VBUS_SENSING_PIN PA9 diff --git a/src/main/target/FISHDRONEF4/target.h b/src/main/target/FISHDRONEF4/target.h index eeec8a8b767..c7b909c41e7 100644 --- a/src/main/target/FISHDRONEF4/target.h +++ b/src/main/target/FISHDRONEF4/target.h @@ -82,8 +82,6 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PB6 -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** Flash ***************************** #define M25P16_CS_PIN PA15 diff --git a/src/main/target/FLYSPARKF4V4/target.h b/src/main/target/FLYSPARKF4V4/target.h index dab2dc5ef57..02b777cee7f 100644 --- a/src/main/target/FLYSPARKF4V4/target.h +++ b/src/main/target/FLYSPARKF4V4/target.h @@ -126,8 +126,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** OSD ***************************** #define USE_SPI_DEVICE_2 diff --git a/src/main/target/FLYWOOH743PRO/target.h b/src/main/target/FLYWOOH743PRO/target.h index 52ffc58a8d6..070f2e0e336 100644 --- a/src/main/target/FLYWOOH743PRO/target.h +++ b/src/main/target/FLYWOOH743PRO/target.h @@ -145,8 +145,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/FRSKYF4/target.h b/src/main/target/FRSKYF4/target.h index 23023482b9a..f1efde85c0e 100755 --- a/src/main/target/FRSKYF4/target.h +++ b/src/main/target/FRSKYF4/target.h @@ -45,8 +45,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define USE_VCP #define VBUS_SENSING_PIN PC5 diff --git a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h index 6d1d8e690fd..12295b56fd4 100644 --- a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h +++ b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h @@ -137,8 +137,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/JHEMCUF405WING/target.h b/src/main/target/JHEMCUF405WING/target.h index ba610991dbb..1d09c660176 100644 --- a/src/main/target/JHEMCUF405WING/target.h +++ b/src/main/target/JHEMCUF405WING/target.h @@ -121,8 +121,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/KAKUTEH7WING/target.h b/src/main/target/KAKUTEH7WING/target.h index f784f826062..54f0d37053b 100644 --- a/src/main/target/KAKUTEH7WING/target.h +++ b/src/main/target/KAKUTEH7WING/target.h @@ -82,8 +82,6 @@ #define SDCARD_SDIO_NORMAL_SPEED #define SDCARD_SDIO2_CMD_ALT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/KROOZX/target.h b/src/main/target/KROOZX/target.h index 157a2cb4205..f4cdfca3823 100755 --- a/src/main/target/KROOZX/target.h +++ b/src/main/target/KROOZX/target.h @@ -51,8 +51,6 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC13 -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #ifdef USE_MSP_DISPLAYPORT #undef USE_MSP_DISPLAYPORT diff --git a/src/main/target/MATEKF405/target.h b/src/main/target/MATEKF405/target.h index 937b47ca885..ca0f4379c01 100644 --- a/src/main/target/MATEKF405/target.h +++ b/src/main/target/MATEKF405/target.h @@ -70,8 +70,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #endif diff --git a/src/main/target/MATEKF405CAN/target.h b/src/main/target/MATEKF405CAN/target.h index bb37fc23897..bbea1b6e230 100644 --- a/src/main/target/MATEKF405CAN/target.h +++ b/src/main/target/MATEKF405CAN/target.h @@ -84,8 +84,6 @@ #define SPI3_MISO_PIN PB4 #define SPI3_MOSI_PIN PB5 -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/MATEKF405SE/target.h b/src/main/target/MATEKF405SE/target.h index 0c65f6f5711..2be07ef7a41 100644 --- a/src/main/target/MATEKF405SE/target.h +++ b/src/main/target/MATEKF405SE/target.h @@ -100,8 +100,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MATEKF405TE/target.h b/src/main/target/MATEKF405TE/target.h index 668db85686c..22ad2d6d59b 100644 --- a/src/main/target/MATEKF405TE/target.h +++ b/src/main/target/MATEKF405TE/target.h @@ -76,10 +76,6 @@ # define SDCARD_SPI_BUS BUS_SPI2 # define SDCARD_CS_PIN PC1 # define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -# if defined(USE_SDCARD) && defined(USE_BARO) -# define USE_TERRAIN -# define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block -# endif #else # define USE_FLASHFS # define USE_FLASH_M25P16 diff --git a/src/main/target/MATEKH743/target.h b/src/main/target/MATEKH743/target.h index c59c59c8d83..5e61262779f 100644 --- a/src/main/target/MATEKH743/target.h +++ b/src/main/target/MATEKH743/target.h @@ -178,8 +178,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/MICOAIR405MINI/target.h b/src/main/target/MICOAIR405MINI/target.h index ebf5201041e..5c4cbd500f4 100644 --- a/src/main/target/MICOAIR405MINI/target.h +++ b/src/main/target/MICOAIR405MINI/target.h @@ -65,8 +65,6 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR405V2/target.h b/src/main/target/MICOAIR405V2/target.h index 3461e0730f4..2a85a8c338d 100644 --- a/src/main/target/MICOAIR405V2/target.h +++ b/src/main/target/MICOAIR405V2/target.h @@ -66,8 +66,6 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR743/target.h b/src/main/target/MICOAIR743/target.h index ab222081816..841b87f5fda 100644 --- a/src/main/target/MICOAIR743/target.h +++ b/src/main/target/MICOAIR743/target.h @@ -123,8 +123,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/MICOAIR743AIO/target.h b/src/main/target/MICOAIR743AIO/target.h index dfe3b065559..6136abc4f60 100755 --- a/src/main/target/MICOAIR743AIO/target.h +++ b/src/main/target/MICOAIR743AIO/target.h @@ -117,8 +117,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/MICOAIR743V2/target.h b/src/main/target/MICOAIR743V2/target.h index 52e1f7fce84..12f81687c82 100755 --- a/src/main/target/MICOAIR743V2/target.h +++ b/src/main/target/MICOAIR743V2/target.h @@ -138,8 +138,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block // *************** ADC ***************************** #define USE_ADC diff --git a/src/main/target/NEUTRONRCH7BT/target.h b/src/main/target/NEUTRONRCH7BT/target.h index 762e5d699d5..1332a11e2bf 100644 --- a/src/main/target/NEUTRONRCH7BT/target.h +++ b/src/main/target/NEUTRONRCH7BT/target.h @@ -139,8 +139,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT diff --git a/src/main/target/OMNIBUSF4/target.h b/src/main/target/OMNIBUSF4/target.h index 1ebc8a918e2..d8fd765b11a 100644 --- a/src/main/target/OMNIBUSF4/target.h +++ b/src/main/target/OMNIBUSF4/target.h @@ -235,8 +235,6 @@ #define SDCARD_DETECT_PIN PB7 #define SDCARD_DETECT_INVERTED - #define USE_TERRAIN - #define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #else #define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT #define M25P16_CS_PIN SPI3_NSS_PIN diff --git a/src/main/target/SPEEDYBEEF405V3/target.h b/src/main/target/SPEEDYBEEF405V3/target.h index fe4f0cc9319..803aef2b4c0 100644 --- a/src/main/target/SPEEDYBEEF405V3/target.h +++ b/src/main/target/SPEEDYBEEF405V3/target.h @@ -115,8 +115,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** OSD ***************************** #define USE_OSD diff --git a/src/main/target/SPEEDYBEEF405V4/target.h b/src/main/target/SPEEDYBEEF405V4/target.h index 4cd5a52efcc..e5b6ce70cdd 100644 --- a/src/main/target/SPEEDYBEEF405V4/target.h +++ b/src/main/target/SPEEDYBEEF405V4/target.h @@ -126,8 +126,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block // *************** OSD ***************************** #define USE_SPI_DEVICE_2 diff --git a/src/main/target/SPEEDYBEEF405WING/target.h b/src/main/target/SPEEDYBEEF405WING/target.h index 4391f97b902..abeb9b43412 100644 --- a/src/main/target/SPEEDYBEEF405WING/target.h +++ b/src/main/target/SPEEDYBEEF405WING/target.h @@ -134,8 +134,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/SPEEDYBEEF405WINGV2/target.h b/src/main/target/SPEEDYBEEF405WINGV2/target.h index 06d229329b9..9408260f575 100644 --- a/src/main/target/SPEEDYBEEF405WINGV2/target.h +++ b/src/main/target/SPEEDYBEEF405WINGV2/target.h @@ -133,8 +133,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF diff --git a/src/main/target/SPRACINGF4EVO/target.h b/src/main/target/SPRACINGF4EVO/target.h index d5d1deae2ea..6f6f49357bd 100755 --- a/src/main/target/SPRACINGF4EVO/target.h +++ b/src/main/target/SPRACINGF4EVO/target.h @@ -115,8 +115,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #define MPU6500_CS_PIN SPI1_NSS_PIN #define MPU6500_SPI_BUS BUS_SPI1 diff --git a/src/main/target/TBS_LUCID_H7/target.h b/src/main/target/TBS_LUCID_H7/target.h index 57351513db4..680a612d7e4 100644 --- a/src/main/target/TBS_LUCID_H7/target.h +++ b/src/main/target/TBS_LUCID_H7/target.h @@ -143,8 +143,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/TBS_LUCID_H7_WING/target.h b/src/main/target/TBS_LUCID_H7_WING/target.h index 13ef82da8e8..ba310b48fa6 100644 --- a/src/main/target/TBS_LUCID_H7_WING/target.h +++ b/src/main/target/TBS_LUCID_H7_WING/target.h @@ -143,8 +143,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define USE_ADC diff --git a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h index a830ded88a8..34857d4b4db 100644 --- a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h +++ b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h @@ -143,8 +143,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define USE_ADC diff --git a/src/main/target/YUPIF4/target.h b/src/main/target/YUPIF4/target.h index f3c93dca871..988a953a26c 100644 --- a/src/main/target/YUPIF4/target.h +++ b/src/main/target/YUPIF4/target.h @@ -79,8 +79,6 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN SPI3_NSS_PIN -#define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block #endif #define USB_IO diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h index 670d9880ef4..82b12c0aca5 100644 --- a/src/main/target/common_post.h +++ b/src/main/target/common_post.h @@ -131,6 +131,17 @@ extern uint8_t __config_end; #endif +#if defined(USE_BARO) && defined(USE_SDCARD) && (MCU_FLASH_SIZE > 512) && !defined(USE_TERRAIN) +#define USE_TERRAIN + +#if (MCU_FLASH_SIZE > 1024) +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#else +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#endif + +#endif + // CRSF sensor input on a dedicated UART #if defined(USE_SERIALRX_CRSF) #define USE_CRSF_SENSOR_INPUT From e562665aa7be159652c598c7ddb965a56ef396b6 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sat, 23 May 2026 15:31:59 +0200 Subject: [PATCH 11/42] clean changes in target.h files --- src/main/target/ALIENFLIGHTF4/target.h | 1 - src/main/target/ATOMRCF405NAVI/target.h | 1 - src/main/target/ATOMRCF405NAVI_DELUX/target.h | 1 - src/main/target/BEEROTORF4/target.h | 1 - src/main/target/BLUEJAYF4/target.h | 1 - src/main/target/BROTHERHOBBYH743/target.h | 1 - src/main/target/COREWINGF405WINGV2/target.h | 1 - src/main/target/CORVON405V2/target.h | 1 - src/main/target/CORVON743V1/target.h | 1 - src/main/target/F4BY/target.h | 1 - src/main/target/FISHDRONEF4/target.h | 1 - src/main/target/FLYSPARKF4V4/target.h | 1 - src/main/target/FLYWOOH743PRO/target.h | 1 - src/main/target/FRSKYF4/target.h | 1 - src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h | 1 - src/main/target/JHEMCUF405WING/target.h | 1 - src/main/target/KAKUTEH7WING/target.h | 1 - src/main/target/KROOZX/target.h | 1 - src/main/target/MATEKF405/target.h | 2 -- src/main/target/MATEKF405CAN/target.h | 1 - src/main/target/MATEKF405SE/target.h | 1 - src/main/target/MATEKF405TE/target.h | 14 +++++++------- src/main/target/MATEKH743/target.h | 5 ++--- src/main/target/MICOAIR405MINI/target.h | 1 - src/main/target/MICOAIR405V2/target.h | 1 - src/main/target/MICOAIR743/target.h | 1 - src/main/target/MICOAIR743AIO/target.h | 1 - src/main/target/MICOAIR743V2/target.h | 1 - src/main/target/NEUTRONRCH7BT/target.h | 1 - src/main/target/OMNIBUSF4/target.h | 1 - src/main/target/SPEEDYBEEF405V3/target.h | 1 - src/main/target/SPEEDYBEEF405V4/target.h | 1 - src/main/target/SPEEDYBEEF405WING/target.h | 1 - src/main/target/SPEEDYBEEF405WINGV2/target.h | 1 - src/main/target/SPRACINGF4EVO/target.h | 1 - src/main/target/TBS_LUCID_H7/target.h | 1 - src/main/target/TBS_LUCID_H7_WING/target.h | 2 -- src/main/target/TBS_LUCID_H7_WING_MINI/target.h | 2 -- src/main/target/YUPIF4/target.h | 1 - 39 files changed, 9 insertions(+), 50 deletions(-) diff --git a/src/main/target/ALIENFLIGHTF4/target.h b/src/main/target/ALIENFLIGHTF4/target.h index 8b9d9cf22f7..52cd4cf4a9d 100644 --- a/src/main/target/ALIENFLIGHTF4/target.h +++ b/src/main/target/ALIENFLIGHTF4/target.h @@ -146,7 +146,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - #define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX) #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_UART SERIAL_PORT_USART2 diff --git a/src/main/target/ATOMRCF405NAVI/target.h b/src/main/target/ATOMRCF405NAVI/target.h index 88fa134e893..5e9c9b1bd4e 100644 --- a/src/main/target/ATOMRCF405NAVI/target.h +++ b/src/main/target/ATOMRCF405NAVI/target.h @@ -151,7 +151,6 @@ #define SDCARD_CS_PIN PB6 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - /* * LED Strip */ diff --git a/src/main/target/ATOMRCF405NAVI_DELUX/target.h b/src/main/target/ATOMRCF405NAVI_DELUX/target.h index a60f244e74a..f04a5d5da6f 100644 --- a/src/main/target/ATOMRCF405NAVI_DELUX/target.h +++ b/src/main/target/ATOMRCF405NAVI_DELUX/target.h @@ -149,7 +149,6 @@ #define SDCARD_CS_PIN PC14 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - /* * LED Strip */ diff --git a/src/main/target/BEEROTORF4/target.h b/src/main/target/BEEROTORF4/target.h index 7341a7d1eb0..1ff901b870a 100644 --- a/src/main/target/BEEROTORF4/target.h +++ b/src/main/target/BEEROTORF4/target.h @@ -52,7 +52,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN - #define USE_VCP #define VBUS_SENSING_ENABLED #define VBUS_SENSING_PIN PC5 diff --git a/src/main/target/BLUEJAYF4/target.h b/src/main/target/BLUEJAYF4/target.h index 6cbb4cc9216..e62120bdc86 100644 --- a/src/main/target/BLUEJAYF4/target.h +++ b/src/main/target/BLUEJAYF4/target.h @@ -58,7 +58,6 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PA15 - #define M25P16_CS_PIN PB7 #define M25P16_SPI_BUS BUS_SPI3 diff --git a/src/main/target/BROTHERHOBBYH743/target.h b/src/main/target/BROTHERHOBBYH743/target.h index 72fda9870ba..b069f0ccfd0 100644 --- a/src/main/target/BROTHERHOBBYH743/target.h +++ b/src/main/target/BROTHERHOBBYH743/target.h @@ -147,7 +147,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/COREWINGF405WINGV2/target.h b/src/main/target/COREWINGF405WINGV2/target.h index 166d31beced..2176728d84d 100644 --- a/src/main/target/COREWINGF405WINGV2/target.h +++ b/src/main/target/COREWINGF405WINGV2/target.h @@ -133,7 +133,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART6 diff --git a/src/main/target/CORVON405V2/target.h b/src/main/target/CORVON405V2/target.h index a75f32dcd3b..685acbe0588 100644 --- a/src/main/target/CORVON405V2/target.h +++ b/src/main/target/CORVON405V2/target.h @@ -66,7 +66,6 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/CORVON743V1/target.h b/src/main/target/CORVON743V1/target.h index 511270d8a25..fa5ba03d735 100644 --- a/src/main/target/CORVON743V1/target.h +++ b/src/main/target/CORVON743V1/target.h @@ -123,7 +123,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/F4BY/target.h b/src/main/target/F4BY/target.h index 7080c1945d0..21f91c1d49b 100644 --- a/src/main/target/F4BY/target.h +++ b/src/main/target/F4BY/target.h @@ -55,7 +55,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN PE15 - #define USE_VCP #define VBUS_SENSING_PIN PA9 diff --git a/src/main/target/FISHDRONEF4/target.h b/src/main/target/FISHDRONEF4/target.h index c7b909c41e7..10e325202e3 100644 --- a/src/main/target/FISHDRONEF4/target.h +++ b/src/main/target/FISHDRONEF4/target.h @@ -82,7 +82,6 @@ #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN PB6 - // *************** Flash ***************************** #define M25P16_CS_PIN PA15 #define M25P16_SPI_BUS BUS_SPI3 diff --git a/src/main/target/FLYSPARKF4V4/target.h b/src/main/target/FLYSPARKF4V4/target.h index 02b777cee7f..9a6619f0793 100644 --- a/src/main/target/FLYSPARKF4V4/target.h +++ b/src/main/target/FLYSPARKF4V4/target.h @@ -126,7 +126,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** OSD ***************************** #define USE_SPI_DEVICE_2 #define SPI2_SCK_PIN PB13 diff --git a/src/main/target/FLYWOOH743PRO/target.h b/src/main/target/FLYWOOH743PRO/target.h index 070f2e0e336..47419dc78ff 100644 --- a/src/main/target/FLYWOOH743PRO/target.h +++ b/src/main/target/FLYWOOH743PRO/target.h @@ -145,7 +145,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/FRSKYF4/target.h b/src/main/target/FRSKYF4/target.h index f1efde85c0e..c014283f33e 100755 --- a/src/main/target/FRSKYF4/target.h +++ b/src/main/target/FRSKYF4/target.h @@ -45,7 +45,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN - #define USE_VCP #define VBUS_SENSING_PIN PC5 diff --git a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h index 12295b56fd4..7d93ec91ce6 100644 --- a/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h +++ b/src/main/target/IFLIGHT_BLITZ_H7_PRO/target.h @@ -137,7 +137,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/JHEMCUF405WING/target.h b/src/main/target/JHEMCUF405WING/target.h index 1d09c660176..fd64ff153d6 100644 --- a/src/main/target/JHEMCUF405WING/target.h +++ b/src/main/target/JHEMCUF405WING/target.h @@ -121,7 +121,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART1 diff --git a/src/main/target/KAKUTEH7WING/target.h b/src/main/target/KAKUTEH7WING/target.h index 54f0d37053b..7626b997754 100644 --- a/src/main/target/KAKUTEH7WING/target.h +++ b/src/main/target/KAKUTEH7WING/target.h @@ -82,7 +82,6 @@ #define SDCARD_SDIO_NORMAL_SPEED #define SDCARD_SDIO2_CMD_ALT - #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** I2C /Baro/Mag ********************* diff --git a/src/main/target/KROOZX/target.h b/src/main/target/KROOZX/target.h index f4cdfca3823..ea5ee6a8e0f 100755 --- a/src/main/target/KROOZX/target.h +++ b/src/main/target/KROOZX/target.h @@ -51,7 +51,6 @@ #define SDCARD_DETECT_INVERTED #define SDCARD_DETECT_PIN PC13 - #ifdef USE_MSP_DISPLAYPORT #undef USE_MSP_DISPLAYPORT #endif diff --git a/src/main/target/MATEKF405/target.h b/src/main/target/MATEKF405/target.h index ca0f4379c01..a33807c06a5 100644 --- a/src/main/target/MATEKF405/target.h +++ b/src/main/target/MATEKF405/target.h @@ -69,8 +69,6 @@ #define SDCARD_CS_PIN PC1 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - - #endif // *************** OSD ***************************** diff --git a/src/main/target/MATEKF405CAN/target.h b/src/main/target/MATEKF405CAN/target.h index bbea1b6e230..d12b5e3e1a3 100644 --- a/src/main/target/MATEKF405CAN/target.h +++ b/src/main/target/MATEKF405CAN/target.h @@ -84,7 +84,6 @@ #define SPI3_MISO_PIN PB4 #define SPI3_MOSI_PIN PB5 - #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** UART ***************************** diff --git a/src/main/target/MATEKF405SE/target.h b/src/main/target/MATEKF405SE/target.h index 2be07ef7a41..38c8f56f5d9 100644 --- a/src/main/target/MATEKF405SE/target.h +++ b/src/main/target/MATEKF405SE/target.h @@ -100,7 +100,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** UART ***************************** #define USE_VCP #define VBUS_SENSING_PIN PC13 diff --git a/src/main/target/MATEKF405TE/target.h b/src/main/target/MATEKF405TE/target.h index 22ad2d6d59b..fc7c1ad0ce9 100644 --- a/src/main/target/MATEKF405TE/target.h +++ b/src/main/target/MATEKF405TE/target.h @@ -63,13 +63,6 @@ #define SPI2_MISO_PIN PC2 #define SPI2_MOSI_PIN PC3 -#define USE_BARO -#define BARO_I2C_BUS BUS_I2C1 -#define USE_BARO_BMP280 -#define USE_BARO_MS5611 -#define USE_BARO_DPS310 -#define USE_BARO_SPL06 - #if defined(MATEKF405TE_SD) # define USE_SDCARD # define USE_SDCARD_SPI @@ -90,6 +83,13 @@ #define I2C1_SCL PB8 #define I2C1_SDA PB7 +#define USE_BARO +#define BARO_I2C_BUS BUS_I2C1 +#define USE_BARO_BMP280 +#define USE_BARO_MS5611 +#define USE_BARO_DPS310 +#define USE_BARO_SPL06 + #define USE_MAG #define MAG_I2C_BUS BUS_I2C1 #define USE_MAG_ALL diff --git a/src/main/target/MATEKH743/target.h b/src/main/target/MATEKH743/target.h index 5e61262779f..25ca088e690 100644 --- a/src/main/target/MATEKH743/target.h +++ b/src/main/target/MATEKH743/target.h @@ -167,10 +167,10 @@ // *************** CANBUS **************************** #define USE_DRONECAN #define CAN1_RX PD0 -#define CAN1_TX PD1 +#define CAN1_TX PD1 #define CAN1_STANDBY PD3 -// *************** SDIO SD BLACKBOX AND TERRAIN ******************* +// *************** SDIO SD BLACKBOX******************* #define USE_SDCARD #define USE_SDCARD_SDIO #define SDCARD_SDIO_DEVICE SDIODEV_1 @@ -178,7 +178,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/MICOAIR405MINI/target.h b/src/main/target/MICOAIR405MINI/target.h index 5c4cbd500f4..4b7e0b811da 100644 --- a/src/main/target/MICOAIR405MINI/target.h +++ b/src/main/target/MICOAIR405MINI/target.h @@ -65,7 +65,6 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR405V2/target.h b/src/main/target/MICOAIR405V2/target.h index 2a85a8c338d..c4a54f003b6 100644 --- a/src/main/target/MICOAIR405V2/target.h +++ b/src/main/target/MICOAIR405V2/target.h @@ -66,7 +66,6 @@ #define SDCARD_CS_PIN PC9 #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** UART ***************************** #define USE_VCP diff --git a/src/main/target/MICOAIR743/target.h b/src/main/target/MICOAIR743/target.h index 841b87f5fda..526cf438d08 100644 --- a/src/main/target/MICOAIR743/target.h +++ b/src/main/target/MICOAIR743/target.h @@ -123,7 +123,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/MICOAIR743AIO/target.h b/src/main/target/MICOAIR743AIO/target.h index 6136abc4f60..90ea60f0a0b 100755 --- a/src/main/target/MICOAIR743AIO/target.h +++ b/src/main/target/MICOAIR743AIO/target.h @@ -117,7 +117,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/MICOAIR743V2/target.h b/src/main/target/MICOAIR743V2/target.h index 12f81687c82..78de91df4af 100755 --- a/src/main/target/MICOAIR743V2/target.h +++ b/src/main/target/MICOAIR743V2/target.h @@ -138,7 +138,6 @@ #define SDCARD_SDIO_4BIT #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** ADC ***************************** #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/NEUTRONRCH7BT/target.h b/src/main/target/NEUTRONRCH7BT/target.h index 1332a11e2bf..9f4ffd118d5 100644 --- a/src/main/target/NEUTRONRCH7BT/target.h +++ b/src/main/target/NEUTRONRCH7BT/target.h @@ -139,7 +139,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT // *************** ADC ***************************** diff --git a/src/main/target/OMNIBUSF4/target.h b/src/main/target/OMNIBUSF4/target.h index d8fd765b11a..fcd89fb0e46 100644 --- a/src/main/target/OMNIBUSF4/target.h +++ b/src/main/target/OMNIBUSF4/target.h @@ -234,7 +234,6 @@ #define SDCARD_DETECT_PIN PB7 #define SDCARD_DETECT_INVERTED - #else #define ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT #define M25P16_CS_PIN SPI3_NSS_PIN diff --git a/src/main/target/SPEEDYBEEF405V3/target.h b/src/main/target/SPEEDYBEEF405V3/target.h index 803aef2b4c0..6653ea56c0c 100644 --- a/src/main/target/SPEEDYBEEF405V3/target.h +++ b/src/main/target/SPEEDYBEEF405V3/target.h @@ -115,7 +115,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** OSD ***************************** #define USE_OSD diff --git a/src/main/target/SPEEDYBEEF405V4/target.h b/src/main/target/SPEEDYBEEF405V4/target.h index e5b6ce70cdd..1c5c847597f 100644 --- a/src/main/target/SPEEDYBEEF405V4/target.h +++ b/src/main/target/SPEEDYBEEF405V4/target.h @@ -126,7 +126,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - // *************** OSD ***************************** #define USE_SPI_DEVICE_2 #define SPI2_SCK_PIN PB13 diff --git a/src/main/target/SPEEDYBEEF405WING/target.h b/src/main/target/SPEEDYBEEF405WING/target.h index abeb9b43412..9da5b115824 100644 --- a/src/main/target/SPEEDYBEEF405WING/target.h +++ b/src/main/target/SPEEDYBEEF405WING/target.h @@ -134,7 +134,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART1 diff --git a/src/main/target/SPEEDYBEEF405WINGV2/target.h b/src/main/target/SPEEDYBEEF405WINGV2/target.h index 9408260f575..df7cafcc00a 100644 --- a/src/main/target/SPEEDYBEEF405WINGV2/target.h +++ b/src/main/target/SPEEDYBEEF405WINGV2/target.h @@ -133,7 +133,6 @@ #define ENABLE_BLACKBOX_LOGGING_ON_SDCARD_BY_DEFAULT - #define DEFAULT_RX_TYPE RX_TYPE_SERIAL #define SERIALRX_PROVIDER SERIALRX_CRSF #define SERIALRX_UART SERIAL_PORT_USART6 diff --git a/src/main/target/SPRACINGF4EVO/target.h b/src/main/target/SPRACINGF4EVO/target.h index 6f6f49357bd..8af6981e54f 100755 --- a/src/main/target/SPRACINGF4EVO/target.h +++ b/src/main/target/SPRACINGF4EVO/target.h @@ -115,7 +115,6 @@ #define SDCARD_SPI_BUS BUS_SPI2 #define SDCARD_CS_PIN SPI2_NSS_PIN - #define MPU6500_CS_PIN SPI1_NSS_PIN #define MPU6500_SPI_BUS BUS_SPI1 diff --git a/src/main/target/TBS_LUCID_H7/target.h b/src/main/target/TBS_LUCID_H7/target.h index 680a612d7e4..7cecc767868 100644 --- a/src/main/target/TBS_LUCID_H7/target.h +++ b/src/main/target/TBS_LUCID_H7/target.h @@ -143,7 +143,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/TBS_LUCID_H7_WING/target.h b/src/main/target/TBS_LUCID_H7_WING/target.h index ba310b48fa6..9dd98e1527d 100644 --- a/src/main/target/TBS_LUCID_H7_WING/target.h +++ b/src/main/target/TBS_LUCID_H7_WING/target.h @@ -143,8 +143,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - - #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h index 34857d4b4db..4f475a46a0f 100644 --- a/src/main/target/TBS_LUCID_H7_WING_MINI/target.h +++ b/src/main/target/TBS_LUCID_H7_WING_MINI/target.h @@ -143,8 +143,6 @@ #define SDCARD_SDIO_DEVICE SDIODEV_1 #define SDCARD_SDIO_4BIT - - #define USE_ADC #define ADC_INSTANCE ADC1 diff --git a/src/main/target/YUPIF4/target.h b/src/main/target/YUPIF4/target.h index 988a953a26c..6a7bb7d545f 100644 --- a/src/main/target/YUPIF4/target.h +++ b/src/main/target/YUPIF4/target.h @@ -78,7 +78,6 @@ #define SDCARD_DETECT_PIN PD2 #define SDCARD_SPI_BUS BUS_SPI3 #define SDCARD_CS_PIN SPI3_NSS_PIN - #endif #define USB_IO From ce07edca3094c037e74751c08ed87f9bf3786931 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sat, 30 May 2026 18:46:03 +0200 Subject: [PATCH 12/42] terrain: fix blackbox int type, fix closing file, allow negative AMSL --- src/main/blackbox/blackbox.c | 4 ++-- src/main/terrain/terrain.c | 14 +++++++------- src/main/terrain/terrain.h | 11 +++++------ src/main/terrain/terrain_io.c | 5 ++--- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index ac525609c85..3f33295b0bd 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -521,8 +521,8 @@ static const blackboxSimpleFieldDefinition_t blackboxSlowFields[] = { {"escTemperature", -1, SIGNED, PREDICT(PREVIOUS), ENCODING(SIGNED_VB)}, #endif #ifdef USE_TERRAIN - {"terrainAGL", -1, UNSIGNED, PREDICT(0), ENCODING(SIGNED_VB)}, - {"terrainAMSL", -1, UNSIGNED, PREDICT(0), ENCODING(SIGNED_VB)}, + {"terrainAGL", -1, SIGNED, PREDICT(0), ENCODING(SIGNED_VB)}, + {"terrainAMSL", -1, SIGNED, PREDICT(0), ENCODING(SIGNED_VB)}, #endif }; diff --git a/src/main/terrain/terrain.c b/src/main/terrain/terrain.c index 97d62d4b26e..e69c11f53cf 100644 --- a/src/main/terrain/terrain.c +++ b/src/main/terrain/terrain.c @@ -89,30 +89,30 @@ static struct { static float getHeightAmslMeters(const gpsLocation_t *loc) { if(isTerrainIoFailure()){ - return TERRAIN_STATUS_FAILURE; + return TERRAIN_STATUS_NO_DATA; } gridInfo_t info; calculateGridInfo(loc, &info); if (info.idx_x > TERRAIN_GRID_BLOCK_SIZE_X - 2) { - return TERRAIN_STATUS_WRONG_BLOC_SIZE; + return TERRAIN_STATUS_NO_DATA; } if (info.idx_y > TERRAIN_GRID_BLOCK_SIZE_Y - 2) { - return TERRAIN_STATUS_WRONG_BLOC_SIZE; + return TERRAIN_STATUS_NO_DATA; } // find the grid gridCache_t *cache = findGridCache(&info); if(cache == NULL){ - return TERRAIN_STATUS_CACHE_FULL; + return TERRAIN_STATUS_NO_DATA; } gridBlock_t *grid = &cache->gridBlock; // check we have all 4 required heights; it's check if grid is loaded from SD card if (!checkBitmap(grid, info.idx_x, info.idx_y) || !checkBitmap(grid, info.idx_x, info.idx_y + 1) || !checkBitmap(grid, info.idx_x + 1, info.idx_y) || !checkBitmap(grid, info.idx_x + 1, info.idx_y + 1)) { markGridBlockNeedRead(grid); - return TERRAIN_STATUS_WRONG_BITMAP; + return TERRAIN_STATUS_NO_DATA; } // hXY are the heights of the 4 surrounding grid points @@ -169,7 +169,7 @@ void terrainUpdateTask(timeUs_t currentTimeUs) float heightASLHome = getHeightAmslMeters(&homeLoc); - if(heightASLHome >= 0){ + if(heightASLHome != TERRAIN_STATUS_NO_DATA){ terrainHomePos.homeAltitudeFound = true; terrainHomePos.homeAltitudeM = heightASLHome; terrainHomePos.homeLocation.lat = posControl.gpsOrigin.lat; @@ -183,7 +183,7 @@ void terrainUpdateTask(timeUs_t currentTimeUs) if(terrainHomePos.homeAltitudeFound) { float heightASL = getHeightAmslMeters(&gpsSol.llh); - if(heightASL >= 0){ + if(heightASL != TERRAIN_STATUS_NO_DATA){ terrainHeight.terrainAMSL = (int32_t)(heightASL * 100.0f); terrainHeight.terrainAGL = MAX(0, ((int32_t)getEstimatedActualPosition(Z) + (int32_t)(terrainHomePos.homeAltitudeM * 100.0f)) - terrainHeight.terrainAMSL); terrainHeight.lastUpdate = millis(); diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h index f15764b27e6..7b8ef3f903e 100644 --- a/src/main/terrain/terrain.h +++ b/src/main/terrain/terrain.h @@ -32,12 +32,11 @@ #define TERRAIN_TASK_RATE_HZ 5 #define TERRAIN_NO_DATA_DELAY_MS 1000 -#define TERRAIN_STATUS_FAILURE (-1) -#define TERRAIN_STATUS_WRONG_BLOC_SIZE (-2) -#define TERRAIN_STATUS_WRONG_BITMAP (-3) -#define TERRAIN_STATUS_NO_DATA (-4) -#define TERRAIN_STATUS_NO_CARD (-5) -#define TERRAIN_STATUS_CACHE_FULL (-6) +// Sentinel returned by the terrain getters when no valid height/distance is +// available. It must lie outside the range of any real altitude or distance: +// terrain can legitimately be negative (below sea level), so INT32_MIN is used +// to guarantee it never collides with valid data. +#define TERRAIN_STATUS_NO_DATA (INT32_MIN) // MAVLink sends 4x4 grids #define TERRAIN_GRID_MAVLINK_SIZE 4 diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c index 59f7fe79ac5..60252887f89 100644 --- a/src/main/terrain/terrain_io.c +++ b/src/main/terrain/terrain_io.c @@ -397,12 +397,11 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) /////// CLOSING BLOCK DATA ///////////////////////////////////////////// // wait to call callback terrainIoClosedFileCallback if(terrainIoState.status == TERRAIN_IO_CLOSE){ - terrainIoState.status = TERRAIN_IO_CLOSE_PENDING; - if(terrainIoState.datFile == NULL) { resetStateMachine(); + return; } - + terrainIoState.status = TERRAIN_IO_CLOSE_PENDING; if(!afatfs_fclose(terrainIoState.datFile, terrainIoClosedFileCallback)){ resetStateMachine(); } From 3715444387ab7f450fd1aedc5a2209dd4302f3e4 Mon Sep 17 00:00:00 2001 From: Error414 Date: Thu, 11 Jun 2026 20:22:25 +0200 Subject: [PATCH 13/42] add to link to new tile generator --- docs/Terrain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Terrain.md b/docs/Terrain.md index cc382a1a92c..64805a22c90 100644 --- a/docs/Terrain.md +++ b/docs/Terrain.md @@ -29,7 +29,7 @@ of 4 GB** and format it to FAT32. # Data Generation and Copying -To generate elevation maps, use the terrain generator web tool available at https://terrain.ardupilot.org/ +To generate elevation maps, use the terrain generator web tool available at https://martinovem.github.io/High-Resolution-Map-Generator/ or https://terrain.ardupilot.org/ **Before copying any terrain data files, the SD card must be formatted. Always format the card before each new terrain data installation to avoid file system errors.** From d141bfc04d353a5868a79ada4b748d04ddfa0b99 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sat, 13 Jun 2026 10:58:11 +0200 Subject: [PATCH 14/42] terrain: make better return error status --- src/main/terrain/terrain.c | 22 +++++++++++----------- src/main/terrain/terrain.h | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/terrain/terrain.c b/src/main/terrain/terrain.c index e69c11f53cf..4a08e043e82 100644 --- a/src/main/terrain/terrain.c +++ b/src/main/terrain/terrain.c @@ -89,30 +89,30 @@ static struct { static float getHeightAmslMeters(const gpsLocation_t *loc) { if(isTerrainIoFailure()){ - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AMSL_DATA; } gridInfo_t info; calculateGridInfo(loc, &info); if (info.idx_x > TERRAIN_GRID_BLOCK_SIZE_X - 2) { - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AMSL_DATA; } if (info.idx_y > TERRAIN_GRID_BLOCK_SIZE_Y - 2) { - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AMSL_DATA; } // find the grid gridCache_t *cache = findGridCache(&info); if(cache == NULL){ - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AMSL_DATA; } gridBlock_t *grid = &cache->gridBlock; // check we have all 4 required heights; it's check if grid is loaded from SD card if (!checkBitmap(grid, info.idx_x, info.idx_y) || !checkBitmap(grid, info.idx_x, info.idx_y + 1) || !checkBitmap(grid, info.idx_x + 1, info.idx_y) || !checkBitmap(grid, info.idx_x + 1, info.idx_y + 1)) { markGridBlockNeedRead(grid); - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AMSL_DATA; } // hXY are the heights of the 4 surrounding grid points @@ -169,7 +169,7 @@ void terrainUpdateTask(timeUs_t currentTimeUs) float heightASLHome = getHeightAmslMeters(&homeLoc); - if(heightASLHome != TERRAIN_STATUS_NO_DATA){ + if(heightASLHome != TERRAIN_STATUS_NO_AMSL_DATA){ terrainHomePos.homeAltitudeFound = true; terrainHomePos.homeAltitudeM = heightASLHome; terrainHomePos.homeLocation.lat = posControl.gpsOrigin.lat; @@ -183,7 +183,7 @@ void terrainUpdateTask(timeUs_t currentTimeUs) if(terrainHomePos.homeAltitudeFound) { float heightASL = getHeightAmslMeters(&gpsSol.llh); - if(heightASL != TERRAIN_STATUS_NO_DATA){ + if(heightASL != TERRAIN_STATUS_NO_AMSL_DATA){ terrainHeight.terrainAMSL = (int32_t)(heightASL * 100.0f); terrainHeight.terrainAGL = MAX(0, ((int32_t)getEstimatedActualPosition(Z) + (int32_t)(terrainHomePos.homeAltitudeM * 100.0f)) - terrainHeight.terrainAMSL); terrainHeight.lastUpdate = millis(); @@ -194,20 +194,20 @@ void terrainUpdateTask(timeUs_t currentTimeUs) int32_t terrainGetLastAMSL(void){ if(!terrainConfig()->terrainEnabled){ - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AMSL_DATA; } - return terrainHeight.lastUpdate + TERRAIN_NO_DATA_DELAY_MS < millis() ? TERRAIN_STATUS_NO_DATA : terrainHeight.terrainAMSL; + return terrainHeight.lastUpdate + TERRAIN_NO_DATA_DELAY_MS < millis() ? TERRAIN_STATUS_NO_AMSL_DATA : terrainHeight.terrainAMSL; } int32_t terrainGetLastDistanceCm(void) { //start a terrain system after the first query to terrain height if(!terrainConfig()->terrainEnabled){ - return TERRAIN_STATUS_NO_DATA; + return TERRAIN_STATUS_NO_AGL_DATA; } - return terrainHeight.lastUpdate + TERRAIN_NO_DATA_DELAY_MS < millis() ? TERRAIN_STATUS_NO_DATA : terrainHeight.terrainAGL; + return terrainHeight.lastUpdate + TERRAIN_NO_DATA_DELAY_MS < millis() ? TERRAIN_STATUS_NO_AGL_DATA : terrainHeight.terrainAGL; } #endif \ No newline at end of file diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h index 7b8ef3f903e..9e4bf964435 100644 --- a/src/main/terrain/terrain.h +++ b/src/main/terrain/terrain.h @@ -36,7 +36,8 @@ // available. It must lie outside the range of any real altitude or distance: // terrain can legitimately be negative (below sea level), so INT32_MIN is used // to guarantee it never collides with valid data. -#define TERRAIN_STATUS_NO_DATA (INT32_MIN) +#define TERRAIN_STATUS_NO_AMSL_DATA (INT32_MIN) +#define TERRAIN_STATUS_NO_AGL_DATA (-1) // MAVLink sends 4x4 grids #define TERRAIN_GRID_MAVLINK_SIZE 4 From cd87a8b7b01ccc97355644eac904c2ddcd326427 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sun, 21 Jun 2026 14:07:46 +0200 Subject: [PATCH 15/42] terrain - fix missing return and add extra guard not to read if SD card is not in idle --- src/main/blackbox/blackbox.c | 1 + src/main/terrain/terrain_io.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index 3f33295b0bd..0edd2a248ad 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -2258,6 +2258,7 @@ void blackboxUpdate(timeUs_t currentTimeUs) if(afatfs_isIdle()){ blackboxSDCardAccessStatus.requestToSdCardAccessState = false; blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice = true; + return; } } } diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c index 60252887f89..9efdbaf376f 100644 --- a/src/main/terrain/terrain_io.c +++ b/src/main/terrain/terrain_io.c @@ -218,6 +218,15 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) } //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + //for start reading terrain data, SD card must be in idle + if(!afatfs_isIdle()){ + releaseSdCardAccess(); + return; + } + //////////////////////////////////////////////////////////////// + + //set grid block for process. open file -> seek -> read -> close terrainIoState.gridBlock = gridBlock; terrainIoState.status = TERRAIN_IO_CHANGE_DIR; From 989ce2c27a68e84254068822d652a284f7c15802 Mon Sep 17 00:00:00 2001 From: Error414 Date: Fri, 10 Jul 2026 23:40:49 +0200 Subject: [PATCH 16/42] terrain - fix SDcard init if blackbox use diferent device than SD card --- src/main/fc/fc_init.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/fc/fc_init.c b/src/main/fc/fc_init.c index 78ea3079c29..2446edd76aa 100644 --- a/src/main/fc/fc_init.c +++ b/src/main/fc/fc_init.c @@ -628,15 +628,19 @@ void init(void) #ifdef USE_SDCARD + bool sdcardNeeded = false; +#ifdef USE_BLACKBOX + sdcardNeeded = (blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD); +#endif #ifdef USE_TERRAIN - if (blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD) + sdcardNeeded = sdcardNeeded || terrainConfig()->terrainEnabled; #endif - { + if (sdcardNeeded) { sdcardInsertionDetectInit(); sdcard_init(); afatfs_init(); } -#endif +#endif // USE_SDCARD #ifdef USE_BLACKBOX From f4fdd5ea72ecceb954c4c06bf1e4c22795defa63 Mon Sep 17 00:00:00 2001 From: Error414 Date: Sat, 11 Jul 2026 15:17:08 +0200 Subject: [PATCH 17/42] terrain - improve lock releasing, keep DAT file opened to save time, add new DEBUG LOG type "terrain" --- src/main/common/log.h | 1 + src/main/io/asyncfatfs/asyncfatfs.c | 4 +- src/main/terrain/terrain_io.c | 200 +++++++++++++++++++++++----- src/main/terrain/terrain_io.h | 8 +- 4 files changed, 175 insertions(+), 38 deletions(-) diff --git a/src/main/common/log.h b/src/main/common/log.h index f46d8d918f1..34862a12824 100644 --- a/src/main/common/log.h +++ b/src/main/common/log.h @@ -31,6 +31,7 @@ typedef enum { LOG_TOPIC_VTX, // 9, mask = 512 LOG_TOPIC_OSD, // 10, mask = 1024 LOG_TOPIC_CAN, // 11, mask = 2048 + LOG_TOPIC_TERRAIN, // 12, mask = 4096 LOG_TOPIC_COUNT, } logTopic_e; diff --git a/src/main/io/asyncfatfs/asyncfatfs.c b/src/main/io/asyncfatfs/asyncfatfs.c index 56555873f41..cfa7da71ae8 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.c +++ b/src/main/io/asyncfatfs/asyncfatfs.c @@ -44,7 +44,9 @@ #define AFATFS_SECTOR_SIZE 512 #define AFATFS_NUM_FATS 2 -#define AFATFS_MAX_OPEN_FILES 3 +// One handle is held long-term by the terrain module (kept-open .DAT tile file) in addition to the +// blackbox log file and transient directory/open operations +#define AFATFS_MAX_OPEN_FILES 4 #define AFATFS_DEFAULT_FILE_DATE FAT_MAKE_DATE(2015, 12, 01) #define AFATFS_DEFAULT_FILE_TIME FAT_MAKE_TIME(00, 00, 00) diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c index 9efdbaf376f..69a6a16d30b 100644 --- a/src/main/terrain/terrain_io.c +++ b/src/main/terrain/terrain_io.c @@ -31,7 +31,7 @@ #include "terrain_io.h" #include "terrain_utils.h" -//#include "common/log.h" +#include "common/log.h" #include "common/utils.h" #include "drivers/time.h" @@ -44,6 +44,56 @@ static terrainIoState_t terrainIoState; +/** + * @brief Acquires exclusive SD card access from blackbox for a single operation. + * + * Access is held only around individual SD operations (open, seek, sector reads) and released + * in between, so blackbox can keep logging while a grid block load is in progress. + * + * @return true if access is held and the operation may proceed, false to retry on the next task call. + */ +static bool acquireSdAccess(void) +{ + if(terrainIoState.sdAccessHeld){ + return true; + } + + //request access from blackbox, returns false while the request is still pending + if(requestToSdCardAccess() == false){ + if(!terrainIoState.sdAccessRequested){ + LOG_DEBUG(TERRAIN, "TERRAIN SD LOCK REQUESTED, PENDING"); + } + terrainIoState.sdAccessRequested = true; + return false; + } + + //for terrain SD operations the card must be in idle + if(!afatfs_isIdle()){ + LOG_DEBUG(TERRAIN, "TERRAIN SD LOCK RELEASED, FS NOT IDLE"); + releaseSdCardAccess(); + terrainIoState.sdAccessRequested = false; + return false; + } + + LOG_DEBUG(TERRAIN, "TERRAIN SD LOCK ACQUIRED"); + terrainIoState.sdAccessRequested = false; + terrainIoState.sdAccessHeld = true; + return true; +} + +/** + * @brief Releases SD card access back to blackbox, also cancels a pending request. + */ +static void releaseSdAccess(void) +{ + if(terrainIoState.sdAccessHeld || terrainIoState.sdAccessRequested){ + LOG_DEBUG(TERRAIN, "TERRAIN SD LOCK RELEASED (held=%d requested=%d)", terrainIoState.sdAccessHeld, terrainIoState.sdAccessRequested); + releaseSdCardAccess(); + } + terrainIoState.sdAccessHeld = false; + terrainIoState.sdAccessRequested = false; +} + /** * @brief Marks the terrain IO state machine as failed, disabling further operations. */ @@ -52,8 +102,8 @@ static void hardFailure(void) terrainIoState.datFile = NULL; terrainIoState.status = TERRAIN_IO_FAILURE; terrainIoState.gridBlock = NULL; - //LOG_DEBUG(SYSTEM, "TERRAIN RELEASE FAILURE"); - releaseSdCardAccess(); + LOG_DEBUG(TERRAIN, "TERRAIN HARD FAILURE, STATE MACHINE DISABLED"); + releaseSdAccess(); } /** @@ -68,8 +118,26 @@ static void resetStateMachine(void) terrainIoState.readsZeroBytesCount = 0; terrainIoState.openFileStartTimeMs = 0; - //LOG_DEBUG(SYSTEM, "TERRAIN RELEASE RESET STATE"); - releaseSdCardAccess(); + LOG_DEBUG(TERRAIN, "TERRAIN RESET STATE -> IDLE"); + releaseSdAccess(); +} + +/** + * @brief Finishes a successful block read, keeping the .DAT file open for subsequent reads. + * + * Unlike resetStateMachine() the file handle stays open, so the next block from the same + * tile skips the expensive directory scan in afatfs_fopen. + */ +static void finishGridBlockRead(void) +{ + terrainIoState.status = TERRAIN_IO_IDLE; + terrainIoState.gridBlock = NULL; + terrainIoState.bytesRead = 0; + terrainIoState.readsZeroBytesCount = 0; + terrainIoState.lastReadActivityMs = millis(); + + LOG_DEBUG(TERRAIN, "TERRAIN READ FINISHED -> IDLE (file kept open)"); + releaseSdAccess(); } /** @@ -81,12 +149,11 @@ static void cleanUp(void) return; } - //LOG_DEBUG(SYSTEM, "TERRAIN RELEASE RESET STATE"); if(terrainIoState.datFile != NULL){ - //LOG_DEBUG(SYSTEM, "TERRAIN CLOSE CLEAN UP FILE"); + LOG_DEBUG(TERRAIN, "TERRAIN CLEANUP -> CLOSE"); terrainIoState.status = TERRAIN_IO_CLOSE; } else { - //LOG_DEBUG(SYSTEM, "TERRAIN CLOSE CLEAN RESET NOW"); + LOG_DEBUG(TERRAIN, "TERRAIN CLEANUP, NO FILE, RESET NOW"); resetStateMachine(); } } @@ -140,7 +207,7 @@ void terrainIoOpenedFileCallback(afatfsFilePtr_t file) } if(file == NULL){ - //LOG_DEBUG(SYSTEM, "TERRAIN OPEN FILE NULL, RESET STATE"); + LOG_DEBUG(TERRAIN, "TERRAIN OPEN CALLBACK, FILE NULL, RESET STATE"); markGridBlockInvalid(terrainIoState.gridBlock); increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); resetStateMachine(); @@ -149,6 +216,14 @@ void terrainIoOpenedFileCallback(afatfsFilePtr_t file) terrainIoState.status = TERRAIN_IO_SEEK; terrainIoState.datFile = file; + terrainIoState.openFileLatDegrees = terrainIoState.gridBlock->latDegrees; + terrainIoState.openFileLonDegrees = terrainIoState.gridBlock->lonDegrees; + terrainIoState.lastReadActivityMs = millis(); + + LOG_DEBUG(TERRAIN, "TERRAIN OPEN CALLBACK, FILE OK -> SEEK"); + + //the open (directory scan) is the longest SD operation, give the card back to blackbox before the seek starts + releaseSdAccess(); } /** @@ -165,7 +240,7 @@ void terrainIoClosedFileCallback(void) return; } - //LOG_DEBUG(SYSTEM, "TERRAIN CLOSE FILE OK"); + LOG_DEBUG(TERRAIN, "TERRAIN CLOSE CALLBACK, FILE CLOSED"); resetStateMachine(); } @@ -177,14 +252,15 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) UNUSED(currentTimeUs); if(terrainIoState.status == TERRAIN_IO_FAILURE){ - //LOG_DEBUG(SYSTEM, "TERRAIN IO FAILURE"); + //LOG_DEBUG(TERRAIN, "TERRAIN IO FAILURE"); return; } //SD card or file system is not prepared or it's not ready yet if(!sdcard_isInserted() || !sdcard_isFunctional() || afatfs_getFilesystemState() != AFATFS_FILESYSTEM_STATE_READY){ //SD card is not ready but IO is in IDLE, probably something heppend with SD card during reading - if(terrainIoState.status != TERRAIN_IO_IDLE){ + //a kept-open file handle is also invalid once the card went away + if(terrainIoState.status != TERRAIN_IO_IDLE || terrainIoState.datFile != NULL){ hardFailure(); return; }else{ @@ -200,34 +276,59 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) //nothing to read if(gridBlock == NULL){ + //cancel a pending access request so blackbox is not stalled waiting for us + releaseSdAccess(); + + //close the kept-open file when it was not used for a while, frees the file handle + if(terrainIoState.datFile != NULL && millis() - terrainIoState.lastReadActivityMs > TERRAIN_IO_KEEP_OPEN_IDLE_MS){ + LOG_DEBUG(TERRAIN, "TERRAIN IDLE TIMEOUT -> CLOSE"); + terrainIoState.status = TERRAIN_IO_CLOSE; + } return; } //check if we had too many errors opening of this file terrainIoFileOpenStatus_t* fileOpenStatus = getFileOpenStatusIndex(gridBlock->latDegrees, gridBlock->lonDegrees); if(fileOpenStatus->errorOpenCount >= 3){ - //LOG_DEBUG(SYSTEM, "TERRAIN IO FAILURE TOO MANY OPEN ERRORS"); + LOG_DEBUG(TERRAIN, "TERRAIN TOO MANY OPEN ERRORS, BLOCK INVALID"); markGridBlockInvalid(gridBlock); return; } - /////////////////////////////////////////////////////////////////// - //request access from blackbox - if(requestToSdCardAccess() == false){ + if(terrainIoState.datFile != NULL){ + //the file for this tile is still open from the previous block, skip the expensive open and go straight to seek + if(terrainIoState.openFileLatDegrees == gridBlock->latDegrees && terrainIoState.openFileLonDegrees == gridBlock->lonDegrees){ + /////////////////////////////////////////////////////////////////// + //request access from blackbox, SD card must be in idle + if(!acquireSdAccess()){ + return; + } + //////////////////////////////////////////////////////////////// + + LOG_DEBUG(TERRAIN, "TERRAIN IDLE, FILE ALREADY OPEN -> SEEK"); + terrainIoState.gridBlock = gridBlock; + terrainIoState.bytesRead = 0; + terrainIoState.readsZeroBytesCount = 0; + terrainIoState.status = TERRAIN_IO_SEEK; + return; + } + + //block is from a different tile, close the old file first; the block stays pending and is picked up again next call + LOG_DEBUG(TERRAIN, "TERRAIN IDLE, DIFFERENT TILE -> CLOSE"); + terrainIoState.status = TERRAIN_IO_CLOSE; return; } - //////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////// - //for start reading terrain data, SD card must be in idle - if(!afatfs_isIdle()){ - releaseSdCardAccess(); + /////////////////////////////////////////////////////////////////// + //request access from blackbox, SD card must be in idle + //access is held for the whole open (directory scan cannot be split), released again in terrainIoOpenedFileCallback + if(!acquireSdAccess()){ return; } //////////////////////////////////////////////////////////////// - - //set grid block for process. open file -> seek -> read -> close + //set grid block for process. open file -> seek -> read + LOG_DEBUG(TERRAIN, "TERRAIN IDLE -> CHANGE_DIR"); terrainIoState.gridBlock = gridBlock; terrainIoState.status = TERRAIN_IO_CHANGE_DIR; @@ -239,19 +340,21 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) if(terrainIoState.status == TERRAIN_IO_CHANGE_DIR){ //already in the root directory, no need to change the directory if(afatfs_isCurrentDirRoot()){ + LOG_DEBUG(TERRAIN, "TERRAIN CHANGE_DIR, ALREADY ROOT -> OPEN_FILE"); terrainIoState.status = TERRAIN_IO_OPEN_FILE; return; } - //LOG_DEBUG(SYSTEM, "TERRAIN CHANGING DIR"); + LOG_DEBUG(TERRAIN, "TERRAIN CHANGE_DIR, CHANGING TO ROOT"); //change dir to ROOT (null) if(!afatfs_chdir(NULL)){ - //LOG_DEBUG(SYSTEM, "TERRAIN CHANGE DIR ERROR"); + LOG_DEBUG(TERRAIN, "TERRAIN CHANGE_DIR ERROR"); markGridBlockInvalid(terrainIoState.gridBlock); resetStateMachine(); return; } + LOG_DEBUG(TERRAIN, "TERRAIN CHANGE_DIR OK -> OPEN_FILE"); terrainIoState.status = TERRAIN_IO_OPEN_FILE; return; } @@ -261,9 +364,8 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) /////// OPEN FILE ///////////////////////////////////////////// // wait to call callback terrainIoOpenedFileCallback if(terrainIoState.status == TERRAIN_IO_OPEN_FILE){ - //LOG_DEBUG(SYSTEM, "TERRAIN OPENING FILE"); if(terrainIoState.gridBlock == NULL){ - //LOG_DEBUG(SYSTEM, "TERRAIN OPENING FILE GRID BLOCK NULL"); + LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE, GRID BLOCK NULL, RESET STATE"); markGridBlockInvalid(terrainIoState.gridBlock); resetStateMachine(); return; @@ -280,9 +382,11 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) terrainIoState.gridBlock->lonDegrees < 0 ? 'W' : 'E', labs(terrainIoState.gridBlock->lonDegrees)); + LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE %s -> OPEN_FILE_PENDING", filename); + //of most of the time is callback terrainIoOpenedFileCallback called immediately, not in next cycle if(!afatfs_fopen(filename, "r", terrainIoOpenedFileCallback)){ - //LOG_DEBUG(SYSTEM, "TERRAIN OPEN FILE ERROR"); + LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE ERROR"); markGridBlockInvalid(terrainIoState.gridBlock); resetStateMachine(); return; @@ -298,7 +402,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) // asynfatfs can be in infinite state "opening" if the card failures during opening. So after a while reset state machine if(terrainIoState.status == TERRAIN_IO_OPEN_FILE_PENDING){ if(millis() - terrainIoState.openFileStartTimeMs > 3000){ - //LOG_DEBUG(SYSTEM, "TERRAIN OPEN FILE TIMEOUT"); + LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE_PENDING TIMEOUT, RESET STATE"); markGridBlockInvalid(terrainIoState.gridBlock); increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); resetStateMachine(); @@ -316,7 +420,14 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) return; } - //LOG_DEBUG(SYSTEM, "TERRAIN SEEK TO POSITION %d,%d", terrainIoState.gridBlock->grid_idx_x, terrainIoState.gridBlock->grid_idx_y); + /////////////////////////////////////////////////////////////////// + //access was released after the open, request it again for the seek + read + if(!acquireSdAccess()){ + return; + } + //////////////////////////////////////////////////////////////// + + LOG_DEBUG(TERRAIN, "TERRAIN SEEK TO POSITION %d,%d", (int)terrainIoState.gridBlock->grid_idx_x, (int)terrainIoState.gridBlock->grid_idx_y); //calculate file offset uint32_t blocknum = (eastBlocks(terrainIoState.gridBlock) * terrainIoState.gridBlock->grid_idx_x) + terrainIoState.gridBlock->grid_idx_y; @@ -330,12 +441,12 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) afatfsOperationStatus_e seekState = afatfs_fseek(terrainIoState.datFile, (int32_t)fileOffset64, AFATFS_SEEK_SET); if(seekState != AFATFS_OPERATION_FAILURE){ - //LOG_DEBUG(SYSTEM, "TERRAIN SEEK OK"); + LOG_DEBUG(TERRAIN, "TERRAIN SEEK OK -> READ"); terrainIoState.status = TERRAIN_IO_READ; // we don't wait to end of seek, after seek done, reading will be available in next task call return; } - //LOG_DEBUG(SYSTEM, "TERRAIN SEEK ERROR"); + LOG_DEBUG(TERRAIN, "TERRAIN SEEK ERROR"); markGridBlockInvalid(terrainIoState.gridBlock); cleanUp(); return; @@ -351,12 +462,19 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) return; } + /////////////////////////////////////////////////////////////////// + //access is released after every completed sector read, request it again for the next one + if(!acquireSdAccess()){ + return; + } + //////////////////////////////////////////////////////////////// + uint32_t readNow = afatfs_fread(terrainIoState.datFile, (uint8_t*)&terrainIoState.ioBlock + terrainIoState.bytesRead, sizeof(terrainIoState.ioBlock) - terrainIoState.bytesRead); terrainIoState.bytesRead += readNow; - //LOG_DEBUG(SYSTEM, "TERRAIN READING DATA %d/%d", (int)terrainIoState.bytesRead, sizeof(terrainIoState.ioBlock)); + LOG_DEBUG(TERRAIN, "TERRAIN READING DATA %d/%d", (int)terrainIoState.bytesRead, (int)sizeof(terrainIoState.ioBlock)); if(terrainIoState.bytesRead == 0 && !(sdcard_isInserted() && sdcard_isFunctional() && afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY)){ - //LOG_DEBUG(SYSTEM, "TERRAIN SD CARD FAILURE"); + LOG_DEBUG(TERRAIN, "TERRAIN READ, SD CARD FAILURE"); hardFailure(); return; } @@ -368,6 +486,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) // if readNow is zero, it could mean something bad happen, broken file, or any other problem with SD card // block is 2048, asyncfatfs reads 512. so we accept (2048 / 512) * 2 errors for a single reading. if(terrainIoState.readsZeroBytesCount > (sizeof(terrainIoState.ioBlock) / 512) * 2){ + LOG_DEBUG(TERRAIN, "TERRAIN READ, TOO MANY ZERO READS"); markGridBlockInvalid(terrainIoState.gridBlock); //we have to increase error for file, for case if error for file reach threshold, and mark file as invalid @@ -379,6 +498,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) if(terrainIoState.bytesRead < sizeof(terrainIoState.ioBlock)){ //file should be divided by 2048, reading up to end of file and not have all data should never happen if (afatfs_feof(terrainIoState.datFile)) { + LOG_DEBUG(TERRAIN, "TERRAIN READ, UNEXPECTED EOF"); //if it happens we have to close file and increase error count for file increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); markGridBlockInvalid(terrainIoState.gridBlock); @@ -386,9 +506,15 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) return; } + //a sector was read, give the card back to blackbox so it can flush before the next sector read + if(readNow > 0){ + releaseSdAccess(); + } + } else { //check if idx and idy is same for terrainIoState.ioBlock and terrainIoState.gridBlock, to be sure we loaded from file correct block if(terrainIoState.ioBlock.block.grid_idx_x != terrainIoState.gridBlock->grid_idx_x || terrainIoState.ioBlock.block.grid_idx_y != terrainIoState.gridBlock->grid_idx_y) { + LOG_DEBUG(TERRAIN, "TERRAIN READ, BLOCK IDX MISMATCH"); markGridBlockInvalid(terrainIoState.gridBlock); cleanUp(); return; @@ -396,7 +522,9 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) memcpy(terrainIoState.gridBlock, &terrainIoState.ioBlock.block, sizeof(gridBlock_t)); markGridBlockAsRead(terrainIoState.gridBlock); - cleanUp(); + + //keep the file open, next block from the same tile skips the expensive open + finishGridBlockRead(); return; } } @@ -410,8 +538,10 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) resetStateMachine(); return; } + LOG_DEBUG(TERRAIN, "TERRAIN CLOSE -> CLOSE_PENDING"); terrainIoState.status = TERRAIN_IO_CLOSE_PENDING; if(!afatfs_fclose(terrainIoState.datFile, terrainIoClosedFileCallback)){ + LOG_DEBUG(TERRAIN, "TERRAIN CLOSE ERROR, RESET STATE"); resetStateMachine(); } return; diff --git a/src/main/terrain/terrain_io.h b/src/main/terrain/terrain_io.h index d627f34377c..c05fa078708 100644 --- a/src/main/terrain/terrain_io.h +++ b/src/main/terrain/terrain_io.h @@ -29,6 +29,7 @@ #define TERRAIN_IO_TASK_RATE_HZ 50 #define TERRAIN_IO_MAX_FILE_OPEN_STATUS 4 //supposed max 4 files opened for a single flight +#define TERRAIN_IO_KEEP_OPEN_IDLE_MS 60000 //close the kept-open .DAT file after this long without any block read /** * @brief Enumeration of terrain IO status states. @@ -67,13 +68,16 @@ typedef struct { uint32_t bytesRead; uint32_t readsZeroBytesCount; timeMs_t openFileStartTimeMs; + int8_t openFileLatDegrees; //tile of the currently open datFile, valid only while datFile != NULL + int16_t openFileLonDegrees; + timeMs_t lastReadActivityMs; //last time a block was read from datFile, used for the keep-open idle timeout + bool sdAccessHeld; //we currently hold exclusive SD card access granted by blackbox + bool sdAccessRequested; //a request to blackbox is pending and must be cancelled if we stop needing it } terrainIoState_t; void loadGridToCacheTask(timeUs_t currentTimeUs); -void terrainIoOpenedDirCallback(afatfsFilePtr_t directory); void terrainIoOpenedFileCallback(afatfsFilePtr_t file); void terrainIoClosedFileCallback(void); -void terrainIoClosedDirCallback(void); bool isTerrainIoFailure(void); From 99e4889f43693661d60efd5faed0601180b0598a Mon Sep 17 00:00:00 2001 From: Error414 Date: Wed, 12 Aug 2026 08:41:01 +0200 Subject: [PATCH 18/42] Terrain - direct reading data from SD card to cache --- src/main/terrain/terrain.h | 11 +++-- src/main/terrain/terrain_io.c | 60 ++++++++++++---------- src/main/terrain/terrain_io.h | 5 +- src/main/terrain/terrain_utils.c | 85 +++++++++++++++++++++----------- src/main/terrain/terrain_utils.h | 6 +-- 5 files changed, 102 insertions(+), 65 deletions(-) diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h index 9e4bf964435..77fc3dc8c44 100644 --- a/src/main/terrain/terrain.h +++ b/src/main/terrain/terrain.h @@ -65,6 +65,7 @@ enum GridCacheState { GRID_CACHE_INVALID = 0, // when first initialised GRID_CACHE_DISKWAIT = 1, // when waiting for disk read GRID_CACHE_VALID = 2, // when at least partially valid + GRID_CACHE_READING = 3, // disk read in flight, block content is partially overwritten }; typedef struct __attribute__((packed)){ @@ -98,11 +99,6 @@ typedef struct __attribute__((packed)){ } gridBlock_t; -typedef union { - gridBlock_t block; - uint8_t buffer[2048]; -} gridIoBlock_t; - /* grid_info is a broken down representation of a Location, giving the index terms for finding the right grid @@ -137,6 +133,11 @@ typedef struct { enum GridCacheState state; // the last time access was requested to this block, used for LRU timeMs_t lastAccessMs; + // lat/lon this slot is expected to hold, captured when entering + // GRID_CACHE_DISKWAIT. The disk read overwrites gridBlock (including its + // lat/lon), so a READING slot can only be identified by these fields + int32_t expectedLat; + int32_t expectedLon; } gridCache_t; diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c index 69a6a16d30b..a2ded35c78d 100644 --- a/src/main/terrain/terrain_io.c +++ b/src/main/terrain/terrain_io.c @@ -23,7 +23,6 @@ */ #include -#include #include "platform.h" #ifdef USE_TERRAIN @@ -208,7 +207,7 @@ void terrainIoOpenedFileCallback(afatfsFilePtr_t file) if(file == NULL){ LOG_DEBUG(TERRAIN, "TERRAIN OPEN CALLBACK, FILE NULL, RESET STATE"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); resetStateMachine(); return; @@ -291,7 +290,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) terrainIoFileOpenStatus_t* fileOpenStatus = getFileOpenStatusIndex(gridBlock->latDegrees, gridBlock->lonDegrees); if(fileOpenStatus->errorOpenCount >= 3){ LOG_DEBUG(TERRAIN, "TERRAIN TOO MANY OPEN ERRORS, BLOCK INVALID"); - markGridBlockInvalid(gridBlock); + setGridStatus(gridBlock, GRID_CACHE_INVALID); return; } @@ -349,7 +348,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) //change dir to ROOT (null) if(!afatfs_chdir(NULL)){ LOG_DEBUG(TERRAIN, "TERRAIN CHANGE_DIR ERROR"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); resetStateMachine(); return; } @@ -366,7 +365,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) if(terrainIoState.status == TERRAIN_IO_OPEN_FILE){ if(terrainIoState.gridBlock == NULL){ LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE, GRID BLOCK NULL, RESET STATE"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); resetStateMachine(); return; } @@ -387,7 +386,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) //of most of the time is callback terrainIoOpenedFileCallback called immediately, not in next cycle if(!afatfs_fopen(filename, "r", terrainIoOpenedFileCallback)){ LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE ERROR"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); resetStateMachine(); return; } @@ -403,7 +402,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) if(terrainIoState.status == TERRAIN_IO_OPEN_FILE_PENDING){ if(millis() - terrainIoState.openFileStartTimeMs > 3000){ LOG_DEBUG(TERRAIN, "TERRAIN OPEN_FILE_PENDING TIMEOUT, RESET STATE"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); resetStateMachine(); return; @@ -415,7 +414,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) /////// SEEK TO BLOCK /////////////////////////////////////////////// if(terrainIoState.status == TERRAIN_IO_SEEK){ if(terrainIoState.datFile == NULL || terrainIoState.gridBlock == NULL){ - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; } @@ -431,10 +430,10 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) //calculate file offset uint32_t blocknum = (eastBlocks(terrainIoState.gridBlock) * terrainIoState.gridBlock->grid_idx_x) + terrainIoState.gridBlock->grid_idx_y; - uint64_t fileOffset64 = (uint64_t)blocknum * sizeof(terrainIoState.ioBlock); + uint64_t fileOffset64 = (uint64_t)blocknum * TERRAIN_GRID_BLOCK_SIZE_SD_CARD; if(fileOffset64 > UINT32_MAX) { - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; } @@ -442,12 +441,16 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) afatfsOperationStatus_e seekState = afatfs_fseek(terrainIoState.datFile, (int32_t)fileOffset64, AFATFS_SEEK_SET); if(seekState != AFATFS_OPERATION_FAILURE){ LOG_DEBUG(TERRAIN, "TERRAIN SEEK OK -> READ"); + //the read overwrites the destination block, stash the expected idx for the completion check + terrainIoState.expectedIdxX = terrainIoState.gridBlock->grid_idx_x; + terrainIoState.expectedIdxY = terrainIoState.gridBlock->grid_idx_y; + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_READING); terrainIoState.status = TERRAIN_IO_READ; // we don't wait to end of seek, after seek done, reading will be available in next task call return; } LOG_DEBUG(TERRAIN, "TERRAIN SEEK ERROR"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; } @@ -457,7 +460,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) /////// READING BLOCK DATA ///////////////////////////////////////////// if(terrainIoState.status == TERRAIN_IO_READ){ if(terrainIoState.datFile == NULL || terrainIoState.gridBlock == NULL){ - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; } @@ -469,10 +472,10 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) } //////////////////////////////////////////////////////////////// - uint32_t readNow = afatfs_fread(terrainIoState.datFile, (uint8_t*)&terrainIoState.ioBlock + terrainIoState.bytesRead, sizeof(terrainIoState.ioBlock) - terrainIoState.bytesRead); + uint32_t readNow = afatfs_fread(terrainIoState.datFile, (uint8_t*)terrainIoState.gridBlock + terrainIoState.bytesRead, sizeof(gridBlock_t) - terrainIoState.bytesRead); terrainIoState.bytesRead += readNow; - LOG_DEBUG(TERRAIN, "TERRAIN READING DATA %d/%d", (int)terrainIoState.bytesRead, (int)sizeof(terrainIoState.ioBlock)); + LOG_DEBUG(TERRAIN, "TERRAIN READING DATA %d/%d", (int)terrainIoState.bytesRead, (int)sizeof(gridBlock_t)); if(terrainIoState.bytesRead == 0 && !(sdcard_isInserted() && sdcard_isFunctional() && afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY)){ LOG_DEBUG(TERRAIN, "TERRAIN READ, SD CARD FAILURE"); hardFailure(); @@ -484,24 +487,26 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) } // if readNow is zero, it could mean something bad happen, broken file, or any other problem with SD card - // block is 2048, asyncfatfs reads 512. so we accept (2048 / 512) * 2 errors for a single reading. - if(terrainIoState.readsZeroBytesCount > (sizeof(terrainIoState.ioBlock) / 512) * 2){ + // a block needs ceil(sizeof(gridBlock_t) / 512) sector reads, asyncfatfs reads 512. so we accept 2 errors per sector for a single reading. + if(terrainIoState.readsZeroBytesCount > ((sizeof(gridBlock_t) + 511) / 512) * 2){ LOG_DEBUG(TERRAIN, "TERRAIN READ, TOO MANY ZERO READS"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); //we have to increase error for file, for case if error for file reach threshold, and mark file as invalid - increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); + //use the open-file tile, the block's own degrees may already be overwritten by the partial read + increaseFileStatusErrorCount(terrainIoState.openFileLatDegrees, terrainIoState.openFileLonDegrees); cleanUp(); return; } - if(terrainIoState.bytesRead < sizeof(terrainIoState.ioBlock)){ + if(terrainIoState.bytesRead < sizeof(gridBlock_t)){ //file should be divided by 2048, reading up to end of file and not have all data should never happen if (afatfs_feof(terrainIoState.datFile)) { LOG_DEBUG(TERRAIN, "TERRAIN READ, UNEXPECTED EOF"); //if it happens we have to close file and increase error count for file - increaseFileStatusErrorCount(terrainIoState.gridBlock->latDegrees, terrainIoState.gridBlock->lonDegrees); - markGridBlockInvalid(terrainIoState.gridBlock); + //use the open-file tile, the block's own degrees may already be overwritten by the partial read + increaseFileStatusErrorCount(terrainIoState.openFileLatDegrees, terrainIoState.openFileLonDegrees); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; } @@ -512,16 +517,19 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) } } else { - //check if idx and idy is same for terrainIoState.ioBlock and terrainIoState.gridBlock, to be sure we loaded from file correct block - if(terrainIoState.ioBlock.block.grid_idx_x != terrainIoState.gridBlock->grid_idx_x || terrainIoState.ioBlock.block.grid_idx_y != terrainIoState.gridBlock->grid_idx_y) { + if( + terrainIoState.gridBlock->grid_idx_x != terrainIoState.expectedIdxX + || terrainIoState.gridBlock->grid_idx_y != terrainIoState.expectedIdxY + || terrainIoState.gridBlock->crc != getBlockCrc(terrainIoState.gridBlock) + + ) { LOG_DEBUG(TERRAIN, "TERRAIN READ, BLOCK IDX MISMATCH"); - markGridBlockInvalid(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; } - memcpy(terrainIoState.gridBlock, &terrainIoState.ioBlock.block, sizeof(gridBlock_t)); - markGridBlockAsRead(terrainIoState.gridBlock); + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_VALID); //keep the file open, next block from the same tile skips the expensive open finishGridBlockRead(); diff --git a/src/main/terrain/terrain_io.h b/src/main/terrain/terrain_io.h index c05fa078708..b529892f72e 100644 --- a/src/main/terrain/terrain_io.h +++ b/src/main/terrain/terrain_io.h @@ -31,6 +31,8 @@ #define TERRAIN_IO_MAX_FILE_OPEN_STATUS 4 //supposed max 4 files opened for a single flight #define TERRAIN_IO_KEEP_OPEN_IDLE_MS 60000 //close the kept-open .DAT file after this long without any block read +#define TERRAIN_GRID_BLOCK_SIZE_SD_CARD 2048 + /** * @brief Enumeration of terrain IO status states. */ @@ -62,11 +64,12 @@ typedef struct { typedef struct { terrainIoStatus_e status; gridBlock_t *gridBlock; - gridIoBlock_t ioBlock; // 2048 bytes because block in disk is exactly 2048 bytes afatfsFilePtr_t datFile; terrainIoFileOpenStatus_t fileOpenStatus[TERRAIN_IO_MAX_FILE_OPEN_STATUS]; uint32_t bytesRead; uint32_t readsZeroBytesCount; + uint16_t expectedIdxX; //grid_idx of the block being read, stashed before the direct read overwrites the destination + uint16_t expectedIdxY; timeMs_t openFileStartTimeMs; int8_t openFileLatDegrees; //tile of the currently open datFile, valid only while datFile != NULL int16_t openFileLonDegrees; diff --git a/src/main/terrain/terrain_utils.c b/src/main/terrain/terrain_utils.c index 04d4ffe2473..5465d982219 100644 --- a/src/main/terrain/terrain_utils.c +++ b/src/main/terrain/terrain_utils.c @@ -33,6 +33,8 @@ #include "navigation/navigation.h" +#include "common/crc.h" + #include "drivers/time.h" @@ -76,46 +78,34 @@ gridBlock_t* getGridBlockToRead(void) } /** - mark a grid block as needing to be read from disk + set the cache state of a grid block */ -void markGridBlockNeedRead(gridBlock_t *gridBlock) +void setGridStatus(gridBlock_t *gridBlock, enum GridCacheState state) { - for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { - if (&(cache[i].gridBlock) == gridBlock) { - cache[i].state = GRID_CACHE_DISKWAIT; - return; - } + if (gridBlock == NULL) { + return; } -} -/** - mark a grid block as having been read from disk - */ -void markGridBlockAsRead(gridBlock_t *gridBlock) -{ for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { if (&(cache[i].gridBlock) == gridBlock) { - cache[i].state = GRID_CACHE_VALID; + if (state == GRID_CACHE_DISKWAIT) { + //block lat/lon are still valid here, stash them so the slot + //stays identifiable while the disk read overwrites the block + cache[i].expectedLat = gridBlock->lat; + cache[i].expectedLon = gridBlock->lon; + } + cache[i].state = state; return; } } } /** - mark a grid block as having been read from disk + mark a grid block as needing to be read from disk */ -void markGridBlockInvalid(gridBlock_t *gridBlock) +void markGridBlockNeedRead(gridBlock_t *gridBlock) { - if (gridBlock == NULL) { - return; - } - - for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { - if (&(cache[i].gridBlock) == gridBlock) { - cache[i].state = GRID_CACHE_INVALID; - return; - } - } + setGridStatus(gridBlock, GRID_CACHE_DISKWAIT); } /** @@ -177,7 +167,8 @@ uint32_t eastBlocks(gridBlock_t *gridBlock) } /** - find or allocate a grid cache entry for a given grid info + find or allocate a grid cache entry for a given grid info; returns NULL + while the block is not readable yet (DISKWAIT/READING or newly allocated) */ gridCache_t* findGridCache(gridInfo_t *info) { @@ -189,10 +180,31 @@ gridCache_t* findGridCache(gridInfo_t *info) for (uint16_t i = 0; i < TERRAIN_GRID_BLOCK_CACHE_SIZE; i++) { gridBlock_t *grid = & cache[i].gridBlock; - if (TERRAIN_LATLON_EQUAL(grid->lat, info->gridLat) && + + if (cache[i].state == GRID_CACHE_READING) { + //block content is being overwritten by the disk read, match by the + //identity stashed at DISKWAIT time instead, otherwise a repeat query + //would allocate a duplicate slot for the same block + if (TERRAIN_LATLON_EQUAL(cache[i].expectedLat, info->gridLat) && + TERRAIN_LATLON_EQUAL(cache[i].expectedLon, info->gridLon)) { + cache[i].lastAccessMs = nowMs; + //not readable until the disk read finished + return NULL; + } + //never evict while the read is in flight + continue; + } + + //INVALID blocks never match by content, a failed direct read leaves partial file data in them + if (cache[i].state != GRID_CACHE_INVALID && + TERRAIN_LATLON_EQUAL(grid->lat, info->gridLat) && TERRAIN_LATLON_EQUAL(grid->lon , info->gridLon) && cache[i].gridBlock.spacing == grid_spacing) { cache[i].lastAccessMs = nowMs; + //not readable until the disk read finished + if (cache[i].state == GRID_CACHE_DISKWAIT) { + return NULL; + } return &cache[i]; } if (cache[i].state != GRID_CACHE_DISKWAIT) { @@ -222,10 +234,13 @@ gridCache_t* findGridCache(gridInfo_t *info) gridCache->gridBlock.version = 1; gridCache->lastAccessMs = nowMs; - // mark as waiting for disk read + // mark as waiting for disk read; a DISKWAIT block is not readable, + // so the caller gets NULL until the disk read has finished + gridCache->expectedLat = info->gridLat; + gridCache->expectedLon = info->gridLon; gridCache->state = GRID_CACHE_DISKWAIT; - return gridCache; + return NULL; } /* @@ -242,4 +257,14 @@ bool checkBitmap(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y) return (grid->bitmap & (((uint64_t)1U) << bitnum)) != 0; } +uint16_t getBlockCrc(gridBlock_t *grid){ + // crc is taken over the whole block with the crc field zeroed + uint16_t saved_crc = grid->crc; + grid->crc = 0; + uint16_t ret = crc16_ccitt_update(0, grid, sizeof(*grid)); + grid->crc = saved_crc; + return ret; +} + + #endif \ No newline at end of file diff --git a/src/main/terrain/terrain_utils.h b/src/main/terrain/terrain_utils.h index 611c1abef6b..1303b15897f 100644 --- a/src/main/terrain/terrain_utils.h +++ b/src/main/terrain/terrain_utils.h @@ -32,11 +32,11 @@ bool calculateGridInfo(const gpsLocation_t *loc, gridInfo_t *info); bool checkBitmap(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y); +uint16_t getBlockCrc(gridBlock_t *grid); gridCache_t* findGridCache(gridInfo_t *info); gridBlock_t* getGridBlockToRead(void); uint32_t eastBlocks(gridBlock_t *gridBlock); -void markGridBlockAsRead(gridBlock_t *gridBlock); -void markGridBlockNeedRead(gridBlock_t *gridBlock); -void markGridBlockInvalid(gridBlock_t *gridBlock); \ No newline at end of file +void setGridStatus(gridBlock_t *gridBlock, enum GridCacheState state); +void markGridBlockNeedRead(gridBlock_t *gridBlock); \ No newline at end of file From 8301bb9eef2b4c11a67ca70d828cad180dd94120 Mon Sep 17 00:00:00 2001 From: Error414 Date: Wed, 12 Aug 2026 11:42:32 +0200 Subject: [PATCH 19/42] Terrain - use to 2m resolution, 10bits. base + (heightOffset * 2) --- src/main/target/common_post.h | 2 +- src/main/terrain/terrain.c | 11 ++++++----- src/main/terrain/terrain.h | 15 ++++++++++++--- src/main/terrain/terrain_utils.c | 21 +++++++++++++++++++++ src/main/terrain/terrain_utils.h | 1 + 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h index 46ea481c264..797247106a6 100644 --- a/src/main/target/common_post.h +++ b/src/main/target/common_post.h @@ -135,7 +135,7 @@ extern uint8_t __config_end; #if (MCU_FLASH_SIZE > 1024) #define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #else -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 4 // 2048 bytes = 1 grid block #endif #endif diff --git a/src/main/terrain/terrain.c b/src/main/terrain/terrain.c index 4a08e043e82..826faea6fd9 100644 --- a/src/main/terrain/terrain.c +++ b/src/main/terrain/terrain.c @@ -115,11 +115,12 @@ static float getHeightAmslMeters(const gpsLocation_t *loc) return TERRAIN_STATUS_NO_AMSL_DATA; } - // hXY are the heights of the 4 surrounding grid points - const int16_t h00 = grid->height[info.idx_x+0][info.idx_y+0]; - const int16_t h01 = grid->height[info.idx_x+0][info.idx_y+1]; - const int16_t h10 = grid->height[info.idx_x+1][info.idx_y+0]; - const int16_t h11 = grid->height[info.idx_x+1][info.idx_y+1]; + // hXY are the heights of the 4 surrounding grid points, reconstructed from + // the packed 10-bit offsets plus the block's heightBase + const int16_t h00 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x+0, info.idx_y+0); + const int16_t h01 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x+0, info.idx_y+1); + const int16_t h10 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x+1, info.idx_y+0); + const int16_t h11 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x+1, info.idx_y+1); // do a simple dual linear interpolation. We could do something // fancier, but it probably isn't worth it as long as the diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h index 77fc3dc8c44..e0d48314cc4 100644 --- a/src/main/terrain/terrain.h +++ b/src/main/terrain/terrain.h @@ -56,7 +56,13 @@ // giving a total grid size of a disk grid_block of 32x28 #define TERRAIN_GRID_BLOCK_SIZE_X (TERRAIN_GRID_MAVLINK_SIZE*TERRAIN_GRID_BLOCK_MUL_X) #define TERRAIN_GRID_BLOCK_SIZE_Y (TERRAIN_GRID_MAVLINK_SIZE*TERRAIN_GRID_BLOCK_MUL_Y) - +// heights are packed 10 bits each, so the whole 32x28 grid needs +// TERRAIN_GRID_BLOCK_SIZE_X * TERRAIN_GRID_BLOCK_SIZE_Y * 10 / 8 bytes. +// Integer math keeps this a valid array dimension (896 values -> 1120 bytes). +#define TERRAIN_GRID_BLOCK_DATA_SIZE ((TERRAIN_GRID_BLOCK_SIZE_X * TERRAIN_GRID_BLOCK_SIZE_Y * 10) / 8) +// each 10-bit height offset is stored at 2 m resolution, so a raw code of +// 0..1023 maps to 0..2046 m above heightBase +#define TERRAIN_HEIGHT_OFFSET_RESOLUTION_M 2 #define TASK_TERRAIN_RATE_MS 10 #define TERRAIN_MAX_DISTANCE_CM INT16_MAX @@ -85,8 +91,11 @@ typedef struct __attribute__((packed)){ // grid spacing in meters uint16_t spacing; - // heights in meters over a 32*28 grid - int16_t height[TERRAIN_GRID_BLOCK_SIZE_X][TERRAIN_GRID_BLOCK_SIZE_Y]; + // heights in meters over a 32*28 grid, stored as 10-bit offsets from + // heightBase, little-endian bit-packed (4 values / 5 bytes). Use + // getHeightOffsetByIndex() to decode a single value. + uint8_t heightOffset[TERRAIN_GRID_BLOCK_DATA_SIZE]; + int16_t heightBase; // indices info 32x28 grids for this degree reference uint16_t grid_idx_x; diff --git a/src/main/terrain/terrain_utils.c b/src/main/terrain/terrain_utils.c index 5465d982219..3d751bb956e 100644 --- a/src/main/terrain/terrain_utils.c +++ b/src/main/terrain/terrain_utils.c @@ -257,6 +257,27 @@ bool checkBitmap(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y) return (grid->bitmap & (((uint64_t)1U) << bitnum)) != 0; } +/* + decode a single 10-bit height offset from the packed heightOffset[] array. + + Values are stored in row-major order (idx_x * TERRAIN_GRID_BLOCK_SIZE_Y + + idx_y), little-endian bit-packed, 10 bits each (4 values per 5 bytes). The + bit offset within a byte is therefore always 0, 2, 4 or 6, so a single value + never spans more than two bytes: read those two bytes little-endian, shift + down by the bit offset and mask off the low 10 bits. + */ +uint16_t getHeightOffsetByIndex(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y) +{ + uint32_t valueIndex = (uint32_t)idx_x * TERRAIN_GRID_BLOCK_SIZE_Y + idx_y; + uint32_t bitPos = valueIndex * 10; + uint32_t bytePos = bitPos / 8; + uint32_t bitOffset = bitPos % 8; + + uint16_t twoBytes = grid->heightOffset[bytePos] | ((uint16_t)grid->heightOffset[bytePos + 1] << 8); + + return (twoBytes >> bitOffset) & 0x3FF; +} + uint16_t getBlockCrc(gridBlock_t *grid){ // crc is taken over the whole block with the crc field zeroed uint16_t saved_crc = grid->crc; diff --git a/src/main/terrain/terrain_utils.h b/src/main/terrain/terrain_utils.h index 1303b15897f..4ba115ec5ff 100644 --- a/src/main/terrain/terrain_utils.h +++ b/src/main/terrain/terrain_utils.h @@ -32,6 +32,7 @@ bool calculateGridInfo(const gpsLocation_t *loc, gridInfo_t *info); bool checkBitmap(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y); +uint16_t getHeightOffsetByIndex(gridBlock_t *grid, uint8_t idx_x, uint8_t idx_y); uint16_t getBlockCrc(gridBlock_t *grid); gridCache_t* findGridCache(gridInfo_t *info); From 9a65ac0ddb3c436dae9cadb7e50ba29afb5ffe39 Mon Sep 17 00:00:00 2001 From: Error414 Date: Wed, 12 Aug 2026 20:38:43 +0200 Subject: [PATCH 20/42] Terrain - fix bugs --- src/main/target/common_post.h | 6 ++++-- src/main/terrain/terrain_io.c | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h index 797247106a6..44127ef1219 100644 --- a/src/main/target/common_post.h +++ b/src/main/target/common_post.h @@ -132,10 +132,12 @@ extern uint8_t __config_end; #if defined(USE_BARO) && defined(USE_SDCARD) && (MCU_FLASH_SIZE > 512) && !defined(USE_TERRAIN) #define USE_TERRAIN +// number of grid blocks held in the RAM cache; each entry is a gridCache_t +// wrapping one packed gridBlock_t (~1.1 KB), no longer the old 2048-byte block #if (MCU_FLASH_SIZE > 1024) -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 #else -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 4 // 2048 bytes = 1 grid block +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 4 #endif #endif diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c index a2ded35c78d..5e1d23a55d9 100644 --- a/src/main/terrain/terrain_io.c +++ b/src/main/terrain/terrain_io.c @@ -478,6 +478,8 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) LOG_DEBUG(TERRAIN, "TERRAIN READING DATA %d/%d", (int)terrainIoState.bytesRead, (int)sizeof(gridBlock_t)); if(terrainIoState.bytesRead == 0 && !(sdcard_isInserted() && sdcard_isFunctional() && afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY)){ LOG_DEBUG(TERRAIN, "TERRAIN READ, SD CARD FAILURE"); + //the block is left half-overwritten by the partial read, drop it out of READING so the slot is reusable + setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); hardFailure(); return; } From 8f675cfb189ecfa7f0ecec054813a22530477935 Mon Sep 17 00:00:00 2001 From: Error414 Date: Thu, 13 Aug 2026 18:02:51 +0200 Subject: [PATCH 21/42] Terrain - change DAt file to TER file, and adjust documentation --- docs/Terrain.md | 25 +++++++++++++------------ src/main/terrain/terrain.h | 3 +++ src/main/terrain/terrain_io.c | 6 ++++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/Terrain.md b/docs/Terrain.md index 64805a22c90..e9303330eff 100644 --- a/docs/Terrain.md +++ b/docs/Terrain.md @@ -29,29 +29,31 @@ of 4 GB** and format it to FAT32. # Data Generation and Copying -To generate elevation maps, use the terrain generator web tool available at https://martinovem.github.io/High-Resolution-Map-Generator/ or https://terrain.ardupilot.org/ +To generate elevation maps, use the terrain generator web tool available at https://martinovem.github.io/High-Resolution-Map-Generator/ + +The generated `.TER` files use an iNav-specific format and are **not compatible with ArduPilot terrain data**. Do not use files from https://terrain.ardupilot.org/ or copy `.DAT` files from an ArduPilot setup — they will not be read correctly. **Before copying any terrain data files, the SD card must be formatted. Always format the card before each new terrain data installation to avoid file system errors.** -In iNav, **only 30 m resolution (SRTM1)** is currently supported, so this option must be selected during data generation. +The generator always produces **30 m resolution** data, which is the only resolution iNav supports — there is no resolution setting to choose. The generated files are then copied to the SD card into the root directory structure. For example ``` SDCARD:\ -├── N47E014.DAT -├── N47E015.DAT -├── N47E016.DAT -├── N49E015.DAT -├── N49E016.DAT -├── N49E017.DAT -└── N50E016.DAT +├── N47E014.TER +├── N47E015.TER +├── N47E016.TER +├── N49E015.TER +├── N49E016.TER +├── N49E017.TER +└── N50E016.TER ``` Copying can be done via **iNav MSC (Mass Storage Class)** is not recommended. -> **Important:** The `.DAT` file covering your **home position** (the location where you arm) must always be present on the SD +> **Important:** The `.TER` file covering your **home position** (the location where you arm) must always be present on the SD > card. The terrain system first reads the ground elevation at the home position to establish an altitude reference. Until this > succeeds, terrain data will not be displayed for any position — even if tiles for your entire flight area are present. Make > sure the tile for your take-off site is included when generating and copying terrain files. @@ -84,13 +86,12 @@ stability and reliability of the terrain feature. - Confirm the SD card is mounted: check `status` in the CLI for SD card state - Confirm a valid GPS fix is present before expecting any terrain value to appear - Terrain data is only loaded **after the first valid GPS fix**. If the fix is acquired after a long wait, allow a few seconds for the first read to complete - - Check that the `.DAT` file covering your **home position** exists on the SD card — see note in *Data Generation and Copying* above + - Check that the `.TER` file covering your **home position** exists on the SD card — see note in *Data Generation and Copying* above - Feature disables itself mid-flight: - The terrain subsystem disables itself on any SD card read failure. This is most commonly caused by a slow or low-quality SD card. Replace the card with a branded, high-speed card - On F4-based flight controllers, SD card compatibility issues are more common. If problems persist, test with a different card model or switch to an H7-based flight controller - Ensure the SD card partition is ≤ 4 GB and formatted FAT32. Larger partitions or exFAT formatting can cause intermittent read failures - Value appears but is clearly wrong: - - Confirm you selected **30 m resolution (SRTM1)** when generating data at https://terrain.ardupilot.org/. Other resolutions are not supported and will produce incorrect values - Reformat the SD card and recopy the terrain files. A corrupted or partially overwritten FAT32 filesystem can produce plausible but incorrect altitude values - The displayed value is altitude **above terrain at the current GPS position**, not distance to the nearest object below the aircraft. Trees, buildings, and local obstacles are not accounted for - Terrain data was not loaded before arming: diff --git a/src/main/terrain/terrain.h b/src/main/terrain/terrain.h index e0d48314cc4..795bbb3a07e 100644 --- a/src/main/terrain/terrain.h +++ b/src/main/terrain/terrain.h @@ -32,6 +32,9 @@ #define TERRAIN_TASK_RATE_HZ 5 #define TERRAIN_NO_DATA_DELAY_MS 1000 +#define TERRAIN_TER_FILE_FORMAT_VERSION 50 +#define TERRAIN_SPACING 30 + // Sentinel returned by the terrain getters when no valid height/distance is // available. It must lie outside the range of any real altitude or distance: // terrain can legitimately be negative (below sea level), so INT32_MIN is used diff --git a/src/main/terrain/terrain_io.c b/src/main/terrain/terrain_io.c index 5e1d23a55d9..8d9d1d7781d 100644 --- a/src/main/terrain/terrain_io.c +++ b/src/main/terrain/terrain_io.c @@ -375,7 +375,7 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) char filename[20]; snprintf(filename, sizeof(filename), - "%c%02ld%c%03ld.DAT", + "%c%02ld%c%03ld.TER", terrainIoState.gridBlock->latDegrees < 0 ? 'S' : 'N', labs(terrainIoState.gridBlock->latDegrees), terrainIoState.gridBlock->lonDegrees < 0 ? 'W' : 'E', @@ -523,9 +523,11 @@ void loadGridToCacheTask(timeUs_t currentTimeUs) terrainIoState.gridBlock->grid_idx_x != terrainIoState.expectedIdxX || terrainIoState.gridBlock->grid_idx_y != terrainIoState.expectedIdxY || terrainIoState.gridBlock->crc != getBlockCrc(terrainIoState.gridBlock) + || terrainIoState.gridBlock->version != TERRAIN_TER_FILE_FORMAT_VERSION + || terrainIoState.gridBlock->spacing != TERRAIN_SPACING ) { - LOG_DEBUG(TERRAIN, "TERRAIN READ, BLOCK IDX MISMATCH"); + LOG_DEBUG(TERRAIN, "TERRAIN READ, BLOCK VALIDATION FAILED"); setGridStatus(terrainIoState.gridBlock, GRID_CACHE_INVALID); cleanUp(); return; From b203d4feb37a5e83f0e52a2e920c7b0dcf3a723d Mon Sep 17 00:00:00 2001 From: Error414 Date: Tue, 18 Aug 2026 17:46:45 +0200 Subject: [PATCH 22/42] Terrain - allow terrain only for F7 H7 and AT32 --- src/main/target/common_post.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h index 44127ef1219..765eef6a10b 100644 --- a/src/main/target/common_post.h +++ b/src/main/target/common_post.h @@ -129,7 +129,16 @@ extern uint8_t __config_end; #endif -#if defined(USE_BARO) && defined(USE_SDCARD) && (MCU_FLASH_SIZE > 512) && !defined(USE_TERRAIN) +// Terrain keeps a sizeable grid cache in RAM, so restrict it to MCUs with enough of it. +// MCU_FLASH_SIZE is a poor proxy for RAM (e.g. STM32F405 has 1 MB flash but only ~192 KB +// RAM), so gate on the MCU family/subfamily instead. Enabled targets (>= ~320 KB RAM): +// STM32H7 (512 KB+), STM32F745/F746 (320 KB), STM32F765 (512 KB), AT32F43x (384 KB). +#if defined(USE_BARO) && defined(USE_SDCARD) && !defined(USE_TERRAIN) && ( \ + defined(STM32H7) || \ + defined(AT32F43x) || \ + defined(STM32F745xx) || \ + defined(STM32F746xx) || \ + defined(STM32F765xx)) #define USE_TERRAIN // number of grid blocks held in the RAM cache; each entry is a gridCache_t From a07d5270664cfb0605c62496d1adceced4f2abce Mon Sep 17 00:00:00 2001 From: Martin Petrov Date: Thu, 20 Aug 2026 17:33:29 +0300 Subject: [PATCH 23/42] fix(terrain): build terrain unit test --- src/test/unit/CMakeLists.txt | 2 +- src/test/unit/terrain_unittest.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/unit/CMakeLists.txt b/src/test/unit/CMakeLists.txt index aa89815ff33..98ab3799f1d 100644 --- a/src/test/unit/CMakeLists.txt +++ b/src/test/unit/CMakeLists.txt @@ -73,7 +73,7 @@ set_property(SOURCE gps_null_port_unittest.cc PROPERTY definitions GPS_NULL_PORT set_property(SOURCE gimbal_serial_unittest.cc PROPERTY depends "io/gimbal_serial.c" "drivers/gimbal_common.c" "common/maths.c" "drivers/headtracker_common.c") set_property(SOURCE gimbal_serial_unittest.cc PROPERTY definitions USE_SERIAL_GIMBAL GIMBAL_UNIT_TEST USE_HEADTRACKER) -set_property(SOURCE terrain_unittest.cc PROPERTY depends "terrain/terrain_utils.c" "terrain/terrain_location.c" "drivers/time.c") +set_property(SOURCE terrain_unittest.cc PROPERTY depends "common/crc.c" "common/streambuf.c" "terrain/terrain_utils.c" "terrain/terrain_location.c" "drivers/time.c") set_property(SOURCE terrain_unittest.cc PROPERTY definitions TERRAIN_UNIT_TEST USE_TERRAIN USE_SDCARD USE_SDCARD_SDIO TERRAIN_GRID_BLOCK_CACHE_SIZE=8) set_property(SOURCE mavlink_unittest.cc PROPERTY depends diff --git a/src/test/unit/terrain_unittest.cc b/src/test/unit/terrain_unittest.cc index 60dfc89a681..5e3d63ab21b 100644 --- a/src/test/unit/terrain_unittest.cc +++ b/src/test/unit/terrain_unittest.cc @@ -32,7 +32,7 @@ extern "C" { #include "terrain/terrain_utils.h" #include "common/time.h" #include "drivers/time.h" - extern timeUs_t usTicks; + extern uint32_t usTicks; extern volatile timeMs_t sysTickUptime; extern volatile timeMs_t sysTickValStamp; } @@ -83,4 +83,4 @@ TEST(TerrainTest, calculateGridInfo) EXPECT_EQ(info2.gridLon, 165873573); ///////////////////////////////////////////////////////////////////// -} \ No newline at end of file +} From d8c74f0d9a17f1cd02176fb158f4eed4ca00c3bc Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Fri, 10 Jul 2026 01:24:22 +0300 Subject: [PATCH 24/42] feat(SITL): simulated SD card backed by a host image file (--sdcard) Adds a third sdcardVTable_t implementation for the SITL target that reads/writes 512-byte blocks in a disk-image file on the host (--sdcard=), following the SPI/SDIO drivers contract (deferred completion callbacks from poll()). Enables USE_SDCARD and USE_TERRAIN for SITL, so asyncfatfs and the terrain tile reader run unchanged in the simulator. Without --sdcard the virtual FC behaves like one with an empty card slot. --- cmake/sitl.cmake | 5 + src/main/drivers/sdcard/sdcard.c | 2 + src/main/drivers/sdcard/sdcard_impl.h | 4 + src/main/drivers/sdcard/sdcard_sitl.c | 215 ++++++++++++++++++++++++++ src/main/drivers/sdcard/sdcard_sitl.h | 22 +++ src/main/target/SITL/target.c | 8 + src/main/target/SITL/target.h | 7 + 7 files changed, 263 insertions(+) create mode 100644 src/main/drivers/sdcard/sdcard_sitl.c create mode 100644 src/main/drivers/sdcard/sdcard_sitl.h diff --git a/cmake/sitl.cmake b/cmake/sitl.cmake index d82e32b4ac3..2b5c5734479 100644 --- a/cmake/sitl.cmake +++ b/cmake/sitl.cmake @@ -14,8 +14,13 @@ main_sources(SITL_COMMON_SRC_EXCLUDES main_sources(SITL_SRC config/config_streamer_file.c drivers/dronecan/libcanard/canard_sitl_driver.c + drivers/sdcard/sdcard.c + drivers/sdcard/sdcard_sitl.c + drivers/sdcard/sdcard_sitl.h drivers/serial_tcp.c drivers/serial_tcp.h + io/asyncfatfs/asyncfatfs.c + io/asyncfatfs/fat_standard.c target/SITL/sim/realFlight.c target/SITL/sim/realFlight.h target/SITL/sim/simHelper.c diff --git a/src/main/drivers/sdcard/sdcard.c b/src/main/drivers/sdcard/sdcard.c index ff8cc4fa850..a5b088243dd 100644 --- a/src/main/drivers/sdcard/sdcard.c +++ b/src/main/drivers/sdcard/sdcard.c @@ -91,6 +91,8 @@ void sdcard_init(void) sdcardVTable = &sdcardSpiVTable; #elif defined(USE_SDCARD_SDIO) sdcardVTable = &sdcardSdioVTable; +#elif defined(USE_SDCARD_SITL) + sdcardVTable = &sdcardSitlVTable; #endif if (sdcardVTable) { diff --git a/src/main/drivers/sdcard/sdcard_impl.h b/src/main/drivers/sdcard/sdcard_impl.h index 3eba6095904..81839c28a64 100644 --- a/src/main/drivers/sdcard/sdcard_impl.h +++ b/src/main/drivers/sdcard/sdcard_impl.h @@ -112,3 +112,7 @@ extern sdcardVTable_t sdcardSpiVTable; #ifdef USE_SDCARD_SDIO extern sdcardVTable_t sdcardSdioVTable; #endif + +#ifdef USE_SDCARD_SITL +extern sdcardVTable_t sdcardSitlVTable; +#endif diff --git a/src/main/drivers/sdcard/sdcard_sitl.c b/src/main/drivers/sdcard/sdcard_sitl.c new file mode 100644 index 00000000000..98cb2743227 --- /dev/null +++ b/src/main/drivers/sdcard/sdcard_sitl.c @@ -0,0 +1,215 @@ +/* + * This file is part of INAV. + * + * INAV is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * INAV is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with INAV. If not, see . + */ + +#include +#include +#include +#include + +#include "platform.h" + +#if defined(USE_SDCARD) && defined(USE_SDCARD_SITL) + +#include "common/utils.h" + +#include "drivers/sdcard/sdcard.h" +#include "drivers/sdcard/sdcard_impl.h" +#include "drivers/sdcard/sdcard_standard.h" +#include "drivers/sdcard/sdcard_sitl.h" + +#define SDCARD_SITL_PATH_MAX 260 + +static FILE *imageFile = NULL; +static char imagePath[SDCARD_SITL_PATH_MAX]; + +bool sdcardSitlSetPath(const char *path) +{ + if (path == NULL || strlen(path) == 0 || strlen(path) >= SDCARD_SITL_PATH_MAX) { + return false; + } + strcpy(imagePath, path); + return true; +} + +static void sdcardSitl_init(void) +{ + sdcard.state = SDCARD_STATE_NOT_PRESENT; + + if (imagePath[0] == '\0') { + fprintf(stderr, "[SDCARD] No image file specified (--sdcard=), no card simulated\n"); + return; + } + + imageFile = fopen(imagePath, "r+b"); + if (imageFile == NULL) { + fprintf(stderr, "[SDCARD] Cannot open image file %s, no card simulated\n", imagePath); + return; + } + + long imageSize = -1; + if (fseek(imageFile, 0, SEEK_END) == 0) { + imageSize = ftell(imageFile); + } + + if (imageSize < SDCARD_BLOCK_SIZE) { + fprintf(stderr, "[SDCARD] Image file %s is unusable, no card simulated\n", imagePath); + fclose(imageFile); + imageFile = NULL; + return; + } + + memset(&sdcard.metadata, 0, sizeof(sdcard.metadata)); + sdcard.metadata.numBlocks = imageSize / SDCARD_BLOCK_SIZE; + memcpy(sdcard.metadata.productName, "SITL", 4); + + sdcard.state = SDCARD_STATE_READY; + fprintf(stderr, "[SDCARD] Simulating card from %s (%u blocks)\n", imagePath, (unsigned)sdcard.metadata.numBlocks); +} + +static bool sdcardSitl_readBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData) +{ + if (sdcard.state != SDCARD_STATE_READY) { + return false; + } + + sdcard.pendingOperation.buffer = buffer; + sdcard.pendingOperation.blockIndex = blockIndex; + sdcard.pendingOperation.callback = callback; + sdcard.pendingOperation.callbackData = callbackData; + + sdcard.state = SDCARD_STATE_READING; + + return true; +} + +static sdcardOperationStatus_e sdcardSitl_beginWriteBlocks(uint32_t blockIndex, uint32_t blockCount) +{ + UNUSED(blockIndex); + UNUSED(blockCount); + + // Writes are addressed per block, no setup is needed for the backing file + if (sdcard.state != SDCARD_STATE_READY) { + return SDCARD_OPERATION_BUSY; + } + + return SDCARD_OPERATION_SUCCESS; +} + +static sdcardOperationStatus_e sdcardSitl_writeBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData) +{ + if (sdcard.state != SDCARD_STATE_READY) { + return SDCARD_OPERATION_BUSY; + } + + sdcard.pendingOperation.buffer = buffer; + sdcard.pendingOperation.blockIndex = blockIndex; + sdcard.pendingOperation.callback = callback; + sdcard.pendingOperation.callbackData = callbackData; + + sdcard.state = SDCARD_STATE_SENDING_WRITE; + + return SDCARD_OPERATION_IN_PROGRESS; +} + +static bool sdcardSitl_transferBlock(bool isWrite) +{ + if (imageFile == NULL || sdcard.pendingOperation.blockIndex >= sdcard.metadata.numBlocks) { + return false; + } + + if (fseek(imageFile, (long)sdcard.pendingOperation.blockIndex * SDCARD_BLOCK_SIZE, SEEK_SET) != 0) { + return false; + } + + if (isWrite) { + if (fwrite(sdcard.pendingOperation.buffer, 1, SDCARD_BLOCK_SIZE, imageFile) != SDCARD_BLOCK_SIZE) { + return false; + } + fflush(imageFile); + return true; + } + + return fread(sdcard.pendingOperation.buffer, 1, SDCARD_BLOCK_SIZE, imageFile) == SDCARD_BLOCK_SIZE; +} + +static bool sdcardSitl_poll(void) +{ + switch (sdcard.state) { + case SDCARD_STATE_READING: { + bool success = sdcardSitl_transferBlock(false); + + sdcard.state = SDCARD_STATE_READY; + + if (sdcard.pendingOperation.callback) { + sdcard.pendingOperation.callback( + SDCARD_BLOCK_OPERATION_READ, + sdcard.pendingOperation.blockIndex, + success ? sdcard.pendingOperation.buffer : NULL, + sdcard.pendingOperation.callbackData + ); + } + break; + } + case SDCARD_STATE_SENDING_WRITE: { + bool success = sdcardSitl_transferBlock(true); + + sdcard.state = SDCARD_STATE_READY; + + if (sdcard.pendingOperation.callback) { + sdcard.pendingOperation.callback( + SDCARD_BLOCK_OPERATION_WRITE, + sdcard.pendingOperation.blockIndex, + success ? sdcard.pendingOperation.buffer : NULL, + sdcard.pendingOperation.callbackData + ); + } + break; + } + default: + break; + } + + return sdcard.state == SDCARD_STATE_READY; +} + +static bool sdcardSitl_isFunctional(void) +{ + return sdcard.state != SDCARD_STATE_NOT_PRESENT; +} + +static bool sdcardSitl_isInitialized(void) +{ + return sdcard.state >= SDCARD_STATE_READY; +} + +static const sdcardMetadata_t* sdcardSitl_getMetadata(void) +{ + return &sdcard.metadata; +} + +sdcardVTable_t sdcardSitlVTable = { + .init = &sdcardSitl_init, + .readBlock = &sdcardSitl_readBlock, + .beginWriteBlocks = &sdcardSitl_beginWriteBlocks, + .writeBlock = &sdcardSitl_writeBlock, + .poll = &sdcardSitl_poll, + .isFunctional = &sdcardSitl_isFunctional, + .isInitialized = &sdcardSitl_isInitialized, + .getMetadata = &sdcardSitl_getMetadata, +}; + +#endif diff --git a/src/main/drivers/sdcard/sdcard_sitl.h b/src/main/drivers/sdcard/sdcard_sitl.h new file mode 100644 index 00000000000..7fdbef7f4e3 --- /dev/null +++ b/src/main/drivers/sdcard/sdcard_sitl.h @@ -0,0 +1,22 @@ +/* + * This file is part of INAV. + * + * INAV is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * INAV is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with INAV. If not, see . + */ + +#pragma once + +#include + +bool sdcardSitlSetPath(const char *path); diff --git a/src/main/target/SITL/target.c b/src/main/target/SITL/target.c index 2542a352bc1..201977813d0 100644 --- a/src/main/target/SITL/target.c +++ b/src/main/target/SITL/target.c @@ -52,6 +52,7 @@ #include "drivers/timer.h" #include "drivers/serial.h" #include "drivers/serial_tcp.h" +#include "drivers/sdcard/sdcard_sitl.h" #include "config/config_streamer.h" #include "build/version.h" @@ -237,6 +238,7 @@ void printCmdLineOptions(void) fprintf(stderr, "--parity=[Even|None|Odd] Serial receiver parity (default: None).\n"); fprintf(stderr, "--fcproxy Use inav/betaflight FC as a proxy for serial receiver.\n"); fprintf(stderr, "--tcpbaseport=[port] Base TCP port for UART sockets (default: 5760)\n"); + fprintf(stderr, "--sdcard=[path] Path to a FAT32 SD-card image file. If not specified, no SD card is simulated.\n"); fprintf(stderr, "--chanmap=[mapstring] Channel mapping. Maps INAVs motor and servo PWM outputs to the virtual receiver output in the simulator.\n"); fprintf(stderr, " The mapstring has the following format: M(otor)|S(servo)-,... All numbers must have two digits\n"); fprintf(stderr, " For example: Map motor 1 to virtal receiver output 1, servo 1 to output 2 and servo 2 to output 3:\n"); @@ -268,6 +270,7 @@ void parseArguments(int argc, char *argv[]) {"parity", required_argument, 0, '4'}, {"fcproxy", no_argument, 0, '5'}, {"tcpbaseport", required_argument, 0, '6'}, + {"sdcard", required_argument, 0, '7'}, {NULL, 0, NULL, 0} }; @@ -363,6 +366,11 @@ void parseArguments(int argc, char *argv[]) tcpBasePort = (uint16_t)basePort; break; } + case '7': + if (!sdcardSitlSetPath(optarg)) { + fprintf(stderr, "[SDCARD] Invalid image path, no SD card will be simulated\n."); + } + break; default: printCmdLineOptions(); diff --git a/src/main/target/SITL/target.h b/src/main/target/SITL/target.h index aefd6686e10..242ea22e124 100644 --- a/src/main/target/SITL/target.h +++ b/src/main/target/SITL/target.h @@ -70,6 +70,13 @@ #define USE_RANGEFINDER_FAKE #define USE_RX_SIM + +// SD card simulated from a host image file (see drivers/sdcard/sdcard_sitl.c), +// which also enables the terrain module for SITL +#define USE_SDCARD +#define USE_SDCARD_SITL +#define USE_TERRAIN +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block #define USE_DRONECAN #define DRONECAN_SITL_INTERFACE "vcan0" From 3d12ae76f1ef1d6525ab3b20a6edfea7be159618 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Fri, 10 Jul 2026 13:24:13 +0300 Subject: [PATCH 25/42] feat(terrain-nav): navigation-facing terrain API - health-gated queries and climb lookahead Adds terrain_nav.{c,h}: a strict wrapper around the terrain module for future navigation consumers. Success is always an explicit boolean and values are written only on success - no sentinel, stale or substituted value ever reaches a caller. Provides current AGL, terrain height at an arbitrary location (scheduling the block load on cache miss), terrain height at the GPS origin, and a bounded climb-lookahead that walks the grid along a bearing, accumulates achievable climb and reports the worst height deficit together with an explicit count of unavailable samples. Built entirely on the terrain module's public utilities; no existing files changed apart from the build list. Nothing in flight code calls this API yet - flight behavior is unchanged. --- src/main/CMakeLists.txt | 2 + src/main/terrain/terrain_nav.c | 181 +++++++++++++++++++++++++++++++++ src/main/terrain/terrain_nav.h | 66 ++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 src/main/terrain/terrain_nav.c create mode 100644 src/main/terrain/terrain_nav.h diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 849f12cab83..f912f693311 100755 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -678,6 +678,8 @@ main_sources(COMMON_SRC terrain/terrain_io.c terrain/terrain_location.h terrain/terrain_location.c + terrain/terrain_nav.h + terrain/terrain_nav.c ) list(APPEND COMMON_SRC ${DSDLC_GENERATED_SRC}) diff --git a/src/main/terrain/terrain_nav.c b/src/main/terrain/terrain_nav.c new file mode 100644 index 00000000000..aa30a589320 --- /dev/null +++ b/src/main/terrain/terrain_nav.c @@ -0,0 +1,181 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include +#include + +#include "platform.h" + +#ifdef USE_TERRAIN + +#include "terrain.h" +#include "terrain_utils.h" +#include "terrain_io.h" +#include "terrain_location.h" +#include "terrain_nav.h" + +#include "io/gps.h" +#include "navigation/navigation.h" +#include "navigation/navigation_private.h" + +// The module's grid spacing is fixed at 30 m (terrain_utils.c); sampling at +// the same step never skips a grid cell along the path +#define TERRAIN_NAV_STEP_M 30.0f + +// Bound the work (and cache pressure) of a single lookahead call: 64 samples +// at 30 m cover ~1.9 km ahead, well beyond a practical climb-planning horizon +#define TERRAIN_NAV_LOOKAHEAD_MAX_SAMPLES 64 + +bool terrainNavIsHealthy(void) +{ + if (!terrainConfig()->terrainEnabled || isTerrainIoFailure()) { + return false; + } + + // terrainGetLastAMSL returns its no-data sentinel unless the home anchor + // is latched AND the last update is fresher than TERRAIN_NO_DATA_DELAY_MS + return terrainGetLastAMSL() != TERRAIN_STATUS_NO_AMSL_DATA; +} + +bool terrainNavGetAGLCm(int32_t *aglCm) +{ + if (!terrainNavIsHealthy()) { + return false; + } + + const int32_t agl = terrainGetLastDistanceCm(); + if (agl == TERRAIN_STATUS_NO_AGL_DATA) { + return false; + } + + *aglCm = agl; + return true; +} + +bool terrainNavGetHeightAtLocation(const gpsLocation_t *loc, float *heightM) +{ + if (!terrainConfig()->terrainEnabled || isTerrainIoFailure()) { + return false; + } + + gridInfo_t info; + calculateGridInfo(loc, &info); + + // the +1 neighbours used for interpolation must lie inside the same block + if (info.idx_x > TERRAIN_GRID_BLOCK_SIZE_X - 2 || info.idx_y > TERRAIN_GRID_BLOCK_SIZE_Y - 2) { + return false; + } + + gridCache_t *cache = findGridCache(&info); + if (cache == NULL) { + return false; + } + gridBlock_t *grid = &cache->gridBlock; + + // all four surrounding grid points must have been loaded from the SD card + if (!checkBitmap(grid, info.idx_x, info.idx_y) || + !checkBitmap(grid, info.idx_x, info.idx_y + 1) || + !checkBitmap(grid, info.idx_x + 1, info.idx_y) || + !checkBitmap(grid, info.idx_x + 1, info.idx_y + 1)) { + markGridBlockNeedRead(grid); + return false; + } + + const int16_t h00 = grid->height[info.idx_x][info.idx_y]; + const int16_t h01 = grid->height[info.idx_x][info.idx_y + 1]; + const int16_t h10 = grid->height[info.idx_x + 1][info.idx_y]; + const int16_t h11 = grid->height[info.idx_x + 1][info.idx_y + 1]; + + const float avg1 = (1.0f - info.frac_x) * h00 + info.frac_x * h10; + const float avg2 = (1.0f - info.frac_x) * h01 + info.frac_x * h11; + + *heightM = (1.0f - info.frac_y) * avg1 + info.frac_y * avg2; + return true; +} + +bool terrainNavGetHomeTerrainHeight(float *heightM) +{ + if (!posControl.gpsOrigin.valid) { + return false; + } + + const gpsLocation_t homeLoc = { + .lat = posControl.gpsOrigin.lat, + .lon = posControl.gpsOrigin.lon, + .alt = 0, + }; + + return terrainNavGetHeightAtLocation(&homeLoc, heightM); +} + +bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, terrainNavLookaheadResult_t *result) +{ + if (result == NULL || !terrainNavIsHealthy()) { + return false; + } + + result->climbNeededM = 0.0f; + result->samplesTotal = 0; + result->samplesMissed = 0; + result->complete = true; + + gpsLocation_t pos = gpsSol.llh; + float baseHeightM; + if (!terrainNavGetHeightAtLocation(&pos, &baseHeightM)) { + return false; + } + + const float bearingRad = bearingDeg * DEG2RAD; + const float stepNorthM = cosf(bearingRad) * TERRAIN_NAV_STEP_M; + const float stepEastM = sinf(bearingRad) * TERRAIN_NAV_STEP_M; + const float climbPerStepM = climbRatio * TERRAIN_NAV_STEP_M; + + float achievableClimbM = 0.0f; + + for (float travelledM = TERRAIN_NAV_STEP_M; travelledM <= distanceM; travelledM += TERRAIN_NAV_STEP_M) { + if (result->samplesTotal >= TERRAIN_NAV_LOOKAHEAD_MAX_SAMPLES) { + break; + } + + offsetLatlng(&pos, stepNorthM, stepEastM); + achievableClimbM += climbPerStepM; + result->samplesTotal++; + + float heightM; + if (terrainNavGetHeightAtLocation(&pos, &heightM)) { + const float deficitM = (heightM - baseHeightM) - achievableClimbM; + if (deficitM > result->climbNeededM) { + result->climbNeededM = deficitM; + } + } else { + result->samplesMissed++; + } + } + + result->complete = (result->samplesMissed == 0); + return true; +} + +#endif diff --git a/src/main/terrain/terrain_nav.h b/src/main/terrain/terrain_nav.h new file mode 100644 index 00000000000..93fbb937a5c --- /dev/null +++ b/src/main/terrain/terrain_nav.h @@ -0,0 +1,66 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#pragma once + +#include +#include + +#include "io/gps.h" + +/* + * Navigation-facing terrain API. + * + * Contract for every function here: success/failure is an explicit boolean, + * values are returned through out-parameters and are written ONLY on success. + * No caller ever sees a sentinel, a stale value or a substituted value. + */ + +typedef struct { + float climbNeededM; // extra climb needed now to clear terrain along the path (>= 0) + uint8_t samplesTotal; // points sampled along the path + uint8_t samplesMissed; // points that had no terrain data (loads were scheduled) + bool complete; // true when every sampled point had terrain data +} terrainNavLookaheadResult_t; + +// True only while terrain data is usable: module enabled, no IO failure +// latched, home anchor found and data fresher than TERRAIN_NO_DATA_DELAY_MS +bool terrainNavIsHealthy(void); + +// Height above ground at the current position, in cm +bool terrainNavGetAGLCm(int32_t *aglCm); + +// Terrain height (map datum, meters) at an arbitrary location. On a cache +// miss the block is scheduled for loading and the call fails - retry later. +bool terrainNavGetHeightAtLocation(const gpsLocation_t *loc, float *heightM); + +// Terrain height (map datum, meters) at the GPS origin (home) +bool terrainNavGetHomeTerrainHeight(float *heightM); + +// Walk the map from the current position along bearingDeg for distanceM, +// accumulating achievable climb (climbRatio = m of climb per m travelled), +// and report the worst height deficit found. A descending path (negative +// climbRatio) correctly increases the reported deficit. Sampled blocks not +// yet cached are scheduled for loading and counted in samplesMissed. +bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, terrainNavLookaheadResult_t *result); From 87d74d8ffe861b460dc6a8513a6dbe5dde401149 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Sun, 12 Jul 2026 00:39:57 +0300 Subject: [PATCH 26/42] feat(terrain-nav): TERRAIN AGL HOLD - cruise holds height above ground FW 3D Cruise only, opt-in RC box, health-gated, all commands through the single altitude-target funnel. Engage captures current AGL (below minimum: slew-limited climb to it); data loss freezes the target with an OSD warning, resume after 3 s healthy; pitch stick pauses the hold and re-captures on release; directional lookahead climbs early for rising terrain within the global per-cycle block budget (cache - 2); nav_max_altitude always wins with TERRAIN VS MAX ALT / TERRAIN PULL UP warnings. New settings: terrain_nav_min_agl (60 m default), terrain_nav_lookahead (1000 m, 0=off). SITL tile cache set to 5 to mirror the small-cache board tier. --- src/main/CMakeLists.txt | 4 + src/main/config/parameter_group_ids.h | 3 +- src/main/fc/fc_msp_box.c | 14 ++ src/main/fc/rc_modes.h | 1 + src/main/fc/settings.yaml | 17 ++ src/main/io/osd.c | 22 +++ src/main/io/osd.h | 7 + src/main/navigation/navigation.c | 11 ++ src/main/target/SITL/target.h | 2 +- src/main/terrain/terrain_nav_hold.c | 228 +++++++++++++++++++++++ src/main/terrain/terrain_nav_hold.h | 64 +++++++ src/main/terrain/terrain_nav_hold_core.c | 156 ++++++++++++++++ src/main/terrain/terrain_nav_hold_core.h | 92 +++++++++ 13 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 src/main/terrain/terrain_nav_hold.c create mode 100644 src/main/terrain/terrain_nav_hold.h create mode 100644 src/main/terrain/terrain_nav_hold_core.c create mode 100644 src/main/terrain/terrain_nav_hold_core.h diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index f912f693311..bd33dd477f9 100755 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -680,6 +680,10 @@ main_sources(COMMON_SRC terrain/terrain_location.c terrain/terrain_nav.h terrain/terrain_nav.c + terrain/terrain_nav_hold_core.h + terrain/terrain_nav_hold_core.c + terrain/terrain_nav_hold.h + terrain/terrain_nav_hold.c ) list(APPEND COMMON_SRC ${DSDLC_GENERATED_SRC}) diff --git a/src/main/config/parameter_group_ids.h b/src/main/config/parameter_group_ids.h index c3d009ecbe4..203eee4ef36 100644 --- a/src/main/config/parameter_group_ids.h +++ b/src/main/config/parameter_group_ids.h @@ -134,7 +134,8 @@ #define PG_GEOZONES 1043 #define PG_GEOZONE_VERTICES 1044 #define PG_DRONECAN_CONFIG 1045 -#define PG_INAV_END PG_DRONECAN_CONFIG +#define PG_TERRAIN_NAV_CONFIG 1046 +#define PG_INAV_END PG_TERRAIN_NAV_CONFIG // OSD configuration (subject to change) //#define PG_OSD_FONT_CONFIG 2047 diff --git a/src/main/fc/fc_msp_box.c b/src/main/fc/fc_msp_box.c index 842b56563a0..ecac17bf1d7 100644 --- a/src/main/fc/fc_msp_box.c +++ b/src/main/fc/fc_msp_box.c @@ -41,6 +41,11 @@ #include "navigation/navigation.h" +#ifdef USE_TERRAIN +#include "terrain/terrain.h" +#include "terrain/terrain_nav_hold.h" +#endif + #include "telemetry/telemetry.h" #include "drivers/gimbal_common.h" @@ -110,6 +115,7 @@ static const box_t boxes[CHECKBOX_ITEM_COUNT + 1] = { { .boxId = BOXGIMBALCENTER, .boxName = "GIMBAL CENTER", .permanentId = 67 }, { .boxId = BOXGIMBALHTRK, .boxName = "GIMBAL HEADTRACKER", .permanentId = 68 }, { .boxId = BOXAUTOSPEED, .boxName = "AUTO SPEED", .permanentId = 69 }, + { .boxId = BOXTERRAINAGLHOLD, .boxName = "TERRAIN AGL HOLD", .permanentId = 70 }, { .boxId = CHECKBOX_ITEM_COUNT, .boxName = NULL, .permanentId = 0xFF } }; @@ -250,6 +256,11 @@ void initActiveBoxIds(void) if (STATE(AIRPLANE) || platformTypeConfigured(PLATFORM_AIRPLANE)) { ADD_ACTIVE_BOX(BOXSOARING); ADD_ACTIVE_BOX(BOXAUTOSPEED); +#ifdef USE_TERRAIN + if (terrainConfig()->terrainEnabled) { + ADD_ACTIVE_BOX(BOXTERRAINAGLHOLD); + } +#endif } } @@ -440,6 +451,9 @@ void packBoxModeFlags(boxBitmask_t * mspBoxModeFlags) CHECK_ACTIVE_BOX(IS_ENABLED(IS_RC_MODE_ACTIVE(BOXAUTOLEVEL)), BOXAUTOLEVEL); CHECK_ACTIVE_BOX(IS_ENABLED(IS_RC_MODE_ACTIVE(BOXPLANWPMISSION)), BOXPLANWPMISSION); CHECK_ACTIVE_BOX(IS_ENABLED(IS_RC_MODE_ACTIVE(BOXSOARING)), BOXSOARING); +#ifdef USE_TERRAIN + CHECK_ACTIVE_BOX(IS_ENABLED(terrainNavHoldIsEngaged()), BOXTERRAINAGLHOLD); +#endif #ifdef USE_MULTI_MISSION CHECK_ACTIVE_BOX(IS_ENABLED(IS_RC_MODE_ACTIVE(BOXCHANGEMISSION)), BOXCHANGEMISSION); #endif diff --git a/src/main/fc/rc_modes.h b/src/main/fc/rc_modes.h index 48e4357a1a5..5fba051378d 100644 --- a/src/main/fc/rc_modes.h +++ b/src/main/fc/rc_modes.h @@ -86,6 +86,7 @@ typedef enum { BOXGIMBALCENTER = 58, BOXGIMBALHTRK = 59, BOXAUTOSPEED = 60, + BOXTERRAINAGLHOLD = 61, CHECKBOX_ITEM_COUNT } boxId_e; diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 00aa2e54d17..1223442cb6e 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -4653,3 +4653,20 @@ groups: default_value: OFF field: terrainEnabled type: bool + - name: PG_TERRAIN_NAV_CONFIG + type: terrainNavConfig_t + headers: ["terrain/terrain_nav_hold.h"] + condition: USE_TERRAIN + members: + - name: terrain_nav_min_agl + description: "TERRAIN AGL HOLD: minimum held height above ground [cm]. Engaging below it commands a gentle climb to it. Covers worst-case terrain map error on steep slopes plus canopy - lowering it below the default is not recommended" + default_value: 6000 + field: minAglCm + min: 3000 + max: 12000 + - name: terrain_nav_lookahead + description: "TERRAIN AGL HOLD: check terrain up to this distance ahead along the flight path and climb early for rising ground [m]. 0 disables the lookahead. The effective distance is also limited by the terrain block cache of the flight controller" + default_value: 1000 + field: lookaheadDistM + min: 0 + max: 2000 diff --git a/src/main/io/osd.c b/src/main/io/osd.c index a16d9b6adb8..121a7996239 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -99,6 +99,10 @@ #include "navigation/navigation.h" #include "navigation/navigation_private.h" +#ifdef USE_TERRAIN +#include "terrain/terrain_nav_hold.h" +#endif + #include "rx/rx.h" #include "rx/msp_override.h" @@ -6262,6 +6266,24 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter case GEOZONE_MESSAGE_STATE_NONE: break; } +#endif +#ifdef USE_TERRAIN + switch (terrainNavHoldGetWarning()) { /* ADDS MAXIMUM OF 1 MESSAGE TO TOTAL */ + case TERRAIN_NAV_HOLD_WARN_NOT_READY: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_NOT_READY)); + break; + case TERRAIN_NAV_HOLD_WARN_DATA_LOST: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_DATA_LOST)); + break; + case TERRAIN_NAV_HOLD_WARN_MAX_ALT: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_VS_MAX_ALT)); + break; + case TERRAIN_NAV_HOLD_WARN_PULL_UP: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_PULL_UP)); + break; + case TERRAIN_NAV_HOLD_WARN_NONE: + break; + } #endif if (STATE(AIRPLANE)) { /* ADDS MAXIMUM OF 3 MESSAGES TO TOTAL */ #ifdef USE_FW_AUTOLAND diff --git a/src/main/io/osd.h b/src/main/io/osd.h index 21af2d7b290..97086eceb4b 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -147,6 +147,13 @@ #define OSD_MSG_GEOZONE_ACTION "PERFORM ACTION IN %s %s" #endif +#if defined(USE_TERRAIN) +#define OSD_MSG_TERRAIN_NOT_READY "TERRAIN NOT READY" +#define OSD_MSG_TERRAIN_DATA_LOST "TERRAIN LOST - ALT FROZEN" +#define OSD_MSG_TERRAIN_VS_MAX_ALT "TERRAIN VS MAX ALT" +#define OSD_MSG_TERRAIN_PULL_UP "TERRAIN PULL UP" +#endif + #ifdef USE_ADSB static const char* const ADSB_EMITTER_TYPE_STRINGS[] = { "NOINFO", // 0 - No information about the emitter type diff --git a/src/main/navigation/navigation.c b/src/main/navigation/navigation.c index e112ce8a1b9..330057111f3 100644 --- a/src/main/navigation/navigation.c +++ b/src/main/navigation/navigation.c @@ -58,6 +58,10 @@ #include "navigation/navigation_private.h" #include "navigation/rth_trackback.h" +#ifdef USE_TERRAIN +#include "terrain/terrain_nav_hold.h" +#endif + #include "rx/rx.h" #include "sensors/sensors.h" @@ -4416,6 +4420,13 @@ void applyWaypointNavigationAndAltitudeHold(void) // Reset all navigation requests - NAV controllers will set them if necessary DISABLE_STATE(NAV_MOTOR_STOP_OR_IDLE); +#ifdef USE_TERRAIN + // TERRAIN AGL HOLD gate - the single entry point for map terrain into the + // altitude target path. Runs every cycle (also disarmed) so it engages, + // freezes and disengages in the same place it commands + terrainNavCruiseHoldUpdate(); +#endif + // No navigation when disarmed if (!ARMING_FLAG(ARMED)) { // If we are disarmed, abort forced RTH or Emergency Landing diff --git a/src/main/target/SITL/target.h b/src/main/target/SITL/target.h index 242ea22e124..ba2d6f1d818 100644 --- a/src/main/target/SITL/target.h +++ b/src/main/target/SITL/target.h @@ -76,7 +76,7 @@ #define USE_SDCARD #define USE_SDCARD_SITL #define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 8 // 2048 bytes = 1 grid block +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block; 5 mirrors the small-cache (F405 class) tier #define USE_DRONECAN #define DRONECAN_SITL_INTERFACE "vcan0" diff --git a/src/main/terrain/terrain_nav_hold.c b/src/main/terrain/terrain_nav_hold.c new file mode 100644 index 00000000000..f04c838e2e4 --- /dev/null +++ b/src/main/terrain/terrain_nav_hold.c @@ -0,0 +1,228 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include +#include + +#include "platform.h" + +#ifdef USE_TERRAIN + +#include "common/maths.h" + +#include "config/parameter_group.h" +#include "config/parameter_group_ids.h" + +#include "drivers/time.h" + +#include "fc/rc_modes.h" +#include "fc/runtime_config.h" + +#include "io/gps.h" + +#include "navigation/navigation.h" +#include "navigation/navigation_private.h" + +#include "terrain/terrain_nav.h" +#include "terrain/terrain_nav_hold.h" + +PG_REGISTER_WITH_RESET_TEMPLATE(terrainNavConfig_t, terrainNavConfig, PG_TERRAIN_NAV_CONFIG, 0); + +PG_RESET_TEMPLATE(terrainNavConfig_t, terrainNavConfig, + .minAglCm = 6000, // 60 m: worst-case map error in steep terrain + grid blind spots + canopy + .lookaheadDistM = 1000, +); + +// Terrain queries run at this interval; the block budget below is defined +// per query cycle (the cache starves when a cycle exceeds it - C-9) +#define TERRAIN_NAV_HOLD_QUERY_INTERVAL_MS 100 + +// Worst-case meters of straight flight per newly touched cache block: block +// stride is 720 x 840 m ground (24 x 28 points at 30 m), the worst bearing +// crosses a block boundary every 1/sqrt(1/720^2 + 1/840^2) = 546 m +#define TERRAIN_NAV_HOLD_M_PER_BLOCK_WORST 540 + +// Below this ground speed the course over ground is too noisy for a lookahead +#define TERRAIN_NAV_HOLD_MIN_LOOKAHEAD_SPEED_CM_S 300 + +static terrainNavHoldState_t holdState; +static terrainNavHoldOutput_t holdOutput; + +// Latest terrain answers, refreshed once per query interval +static timeMs_t lastQueryTimeMs; +static bool queryAglValid; +static int32_t queryAglCm; +static bool queryLookaheadValid; +static float queryLookaheadClimbCm; + +// The hard-disengage conditions: launch, any landing, emergency landing, +// VTOL transition, non-altitude platforms, degraded GPS, rangefinder SURFACE +// mode. The cruise whitelist is the primary defense - this is the explicit +// override on top of it +static bool terrainNavHoldMustDisengage(void) +{ + if (navGetCurrentStateFlags() & (NAV_CTL_LAUNCH | NAV_CTL_LAND | NAV_CTL_EMERG | NAV_MIXERAT)) { + return true; + } + + if (FLIGHT_MODE(NAV_LAUNCH_MODE) || FLIGHT_MODE(NAV_FW_AUTOLAND)) { + return true; + } + + if (!STATE(ALTITUDE_CONTROL)) { + return true; + } + + if (!STATE(GPS_FIX)) { + return true; + } + +#ifdef USE_GPS_FIX_ESTIMATION + if (STATE(GPS_ESTIMATED_FIX)) { + return true; + } +#endif + + // Never stack with the rangefinder surface mode + if (posControl.flags.isTerrainFollowEnabled) { + return true; + } + + return false; +} + +static bool terrainNavHoldEligible(void) +{ + if (!ARMING_FLAG(ARMED) || !STATE(AIRPLANE)) { + return false; + } + + if (!IS_RC_MODE_ACTIVE(BOXTERRAINAGLHOLD)) { + return false; + } + + // The whitelist: 3D cruise only + if (posControl.navState != NAV_STATE_CRUISE_IN_PROGRESS && posControl.navState != NAV_STATE_CRUISE_ADJUSTING) { + return false; + } + + return !terrainNavHoldMustDisengage(); +} + +static void terrainNavHoldRunQueries(timeMs_t currentTimeMs) +{ + lastQueryTimeMs = currentTimeMs; + + queryAglValid = terrainNavGetAGLCm(&queryAglCm); + queryLookaheadValid = false; + + if (terrainNavConfig()->lookaheadDistM == 0 || gpsSol.groundSpeed < TERRAIN_NAV_HOLD_MIN_LOOKAHEAD_SPEED_CM_S) { + return; + } + + // Global block budget per query cycle: cache size minus 2 (C-9). The AGL + // query above uses the current block, so the lookahead may touch at most + // cacheSize - 3 new blocks - its distance is capped accordingly + const int32_t lookaheadBudgetM = (TERRAIN_GRID_BLOCK_CACHE_SIZE - 3) * TERRAIN_NAV_HOLD_M_PER_BLOCK_WORST; + const float lookaheadDistM = MIN((int32_t)terrainNavConfig()->lookaheadDistM, lookaheadBudgetM); + + // Conservative achievable climb slope: half the configured climb rate at + // the current ground speed - underestimating it makes climbs start early + const float groundSpeedCmS = MAX(gpsSol.groundSpeed, 800); + const float climbRatio = 0.5f * navConfig()->fw.max_auto_climb_rate / groundSpeedCmS; + + const float bearingDeg = CENTIDEGREES_TO_DEGREES((float)posControl.actualState.cog); + + terrainNavLookaheadResult_t lookahead; + if (terrainNavLookahead(bearingDeg, lookaheadDistM, climbRatio, &lookahead)) { + // A partial answer (samples missed at the far end) is still a valid + // lower bound; the missed blocks are already scheduled for loading + queryLookaheadValid = true; + queryLookaheadClimbCm = lookahead.climbNeededM * 100.0f; + } +} + +void terrainNavCruiseHoldUpdate(void) +{ + const timeMs_t currentTimeMs = millis(); + + terrainNavHoldInput_t in = { 0 }; + in.nowMs = currentTimeMs; + in.eligible = terrainNavHoldEligible(); + + if (!in.eligible) { + // Resets the core state; cruise and every other mode fly as stock + terrainNavHoldCoreUpdate(&holdState, &in, &holdOutput); + return; + } + + if (currentTimeMs - lastQueryTimeMs >= TERRAIN_NAV_HOLD_QUERY_INTERVAL_MS) { + terrainNavHoldRunQueries(currentTimeMs); + } + + in.stickAdjusting = posControl.flags.isAdjustingAltitude; + in.healthy = terrainNavIsHealthy(); + in.aglValid = queryAglValid; + in.aglCm = queryAglCm; + in.currentZCm = navGetCurrentActualPositionAndVelocity()->pos.z; + in.lookaheadValid = queryLookaheadValid; + in.lookaheadClimbCm = queryLookaheadClimbCm; + in.minAglCm = terrainNavConfig()->minAglCm; + in.maxAltCm = navConfig()->general.max_altitude; + + terrainNavHoldCoreUpdate(&holdState, &in, &holdOutput); + + if (holdOutput.writeTarget) { + // The one gate into the altitude target path. The funnel enforces the + // slew limit (climb rate) and the nav_max_altitude clamp downstream + updateClimbRateToAltitudeController(navConfig()->fw.max_auto_climb_rate, holdOutput.targetZCm, ROC_TO_ALT_TARGET); + } +} + +terrainNavHoldStatus_e terrainNavHoldGetStatus(void) +{ + return holdState.status; +} + +terrainNavHoldWarning_e terrainNavHoldGetWarning(void) +{ + return holdOutput.warning; +} + +bool terrainNavHoldIsEngaged(void) +{ + return holdState.status == TERRAIN_NAV_HOLD_ACTIVE || holdState.status == TERRAIN_NAV_HOLD_FROZEN; +} + +bool terrainNavHoldGetTargetAglCm(int32_t *targetAglCm) +{ + if (!terrainNavHoldIsEngaged()) { + return false; + } + + *targetAglCm = holdState.targetAglCm; + return true; +} + +#endif diff --git a/src/main/terrain/terrain_nav_hold.h b/src/main/terrain/terrain_nav_hold.h new file mode 100644 index 00000000000..d04012a41b8 --- /dev/null +++ b/src/main/terrain/terrain_nav_hold.h @@ -0,0 +1,64 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#pragma once + +#include +#include + +#include "config/parameter_group.h" + +#include "terrain/terrain_nav_hold_core.h" + +/* + * TERRAIN AGL HOLD - cruise holds height above ground from SD terrain data. + * + * The single gate for map terrain into the altitude target path: one update + * per navigation cycle, active only in the cruise whitelist, opt-in by RC box, + * health-gated, all commands through the one altitude-target funnel + * (updateClimbRateToAltitudeController). Everything else in the firmware + * behaves bit-for-bit as stock when the box is off. + * + * Not to be confused with INAV's existing "terrain follow" naming, which is + * the rangefinder SURFACE mode. + */ + +typedef struct terrainNavConfig_s { + uint16_t minAglCm; // minimum held height above ground [cm] + uint16_t lookaheadDistM; // how far ahead rising terrain is checked [m], 0 = off +} terrainNavConfig_t; + +PG_DECLARE(terrainNavConfig_t, terrainNavConfig); + +// The gate. Called once per navigation cycle from applyWaypointNavigationAndAltitudeHold() +void terrainNavCruiseHoldUpdate(void); + +terrainNavHoldStatus_e terrainNavHoldGetStatus(void); +terrainNavHoldWarning_e terrainNavHoldGetWarning(void); + +// True while the hold owns the altitude target (active or frozen) +bool terrainNavHoldIsEngaged(void); + +// The held height above ground; false unless engaged +bool terrainNavHoldGetTargetAglCm(int32_t *targetAglCm); diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c new file mode 100644 index 00000000000..7a2b28fba5f --- /dev/null +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -0,0 +1,156 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include "terrain_nav_hold_core.h" + +void terrainNavHoldCoreReset(terrainNavHoldState_t *state) +{ + state->status = TERRAIN_NAV_HOLD_INACTIVE; + state->targetAglCm = 0; + state->usableSinceMs = 0; + state->lastUsableMs = 0; + state->reCapturePending = false; +} + +static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out) +{ + // Hold: climb or descend by exactly the difference between the height + // above ground we have and the one we are holding + float targetZCm = in->currentZCm + (state->targetAglCm - in->aglCm); + + // Lookahead: if terrain ahead demands more, climb early so the worst + // point along the path still clears the minimum AGL + if (in->lookaheadValid && in->lookaheadClimbCm > 0.0f) { + const float lookaheadZCm = in->currentZCm + (in->lookaheadClimbCm + in->minAglCm - in->aglCm); + if (lookaheadZCm > targetZCm) { + targetZCm = lookaheadZCm; + } + } + + // Ceiling arbitration: nav_max_altitude always wins - the funnel clamps + // the target for real, here we only detect the conflict and warn. Warns + // ahead of the pinch too, because the lookahead demand is in targetZCm + if (in->maxAltCm > 0 && targetZCm > in->maxAltCm) { + out->warning = (in->aglCm < in->minAglCm) ? TERRAIN_NAV_HOLD_WARN_PULL_UP : TERRAIN_NAV_HOLD_WARN_MAX_ALT; + } + + out->writeTarget = true; + out->targetZCm = targetZCm; +} + +static void engage(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out) +{ + // Capture the current AGL: zero commanded movement at engagement. + // Sole exception: below the minimum - the target becomes the minimum and + // the slew-limited funnel turns that into a gentle climb + state->targetAglCm = (in->aglCm > in->minAglCm) ? in->aglCm : in->minAglCm; + state->reCapturePending = false; + state->status = TERRAIN_NAV_HOLD_ACTIVE; + + if (in->stickAdjusting) { + state->reCapturePending = true; // stock stick behavior runs first + return; + } + + computeTarget(state, in, out); +} + +void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out) +{ + out->writeTarget = false; + out->targetZCm = 0.0f; + out->warning = TERRAIN_NAV_HOLD_WARN_NONE; + + if (!in->eligible) { + // Disengagement always clears the captured target - a fresh + // engagement captures a fresh AGL, a stale setpoint never survives + terrainNavHoldCoreReset(state); + out->status = state->status; + return; + } + + // Track data usability for the freeze grace period and resume hysteresis + const bool usable = in->healthy && in->aglValid; + if (usable) { + if (state->usableSinceMs == 0) { + state->usableSinceMs = (in->nowMs != 0) ? in->nowMs : 1; + } + state->lastUsableMs = in->nowMs; + } else { + state->usableSinceMs = 0; + } + const bool usableStable = usable && + (in->nowMs - state->usableSinceMs >= TERRAIN_NAV_HOLD_RESUME_HYSTERESIS_MS); + + switch (state->status) { + case TERRAIN_NAV_HOLD_INACTIVE: + case TERRAIN_NAV_HOLD_WAITING: + if (!usable) { + // Refuse engagement instead of engaging and degrading: + // cruise keeps flying exactly as stock, the pilot is told why + state->status = TERRAIN_NAV_HOLD_WAITING; + out->warning = TERRAIN_NAV_HOLD_WARN_NOT_READY; + break; + } + engage(state, in, out); + break; + + case TERRAIN_NAV_HOLD_ACTIVE: + if (!usable) { + if (in->nowMs - state->lastUsableMs > TERRAIN_NAV_HOLD_FREEZE_GRACE_MS) { + // Freeze: stop commanding, the last target holds. + // Never descend on dead data + state->status = TERRAIN_NAV_HOLD_FROZEN; + out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; + } + // Inside the grace period: a single missed read just pauses + // target updates for a cycle - no drama, no warning + break; + } + if (in->stickAdjusting) { + // Pilot input wins: stock climb-rate control runs, and the + // AGL present at release becomes the new held target + state->reCapturePending = true; + break; + } + if (state->reCapturePending) { + state->targetAglCm = (in->aglCm > in->minAglCm) ? in->aglCm : in->minAglCm; + state->reCapturePending = false; + } + computeTarget(state, in, out); + break; + + case TERRAIN_NAV_HOLD_FROZEN: + if (usableStable) { + // Resume the same held AGL, slew-limited by the funnel rate + state->status = TERRAIN_NAV_HOLD_ACTIVE; + computeTarget(state, in, out); + } else { + out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; + } + break; + } + + out->status = state->status; +} diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h new file mode 100644 index 00000000000..bd9658b8a6c --- /dev/null +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -0,0 +1,92 @@ +/* + * This file is part of INAV Project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU General Public License Version 3, as described below: + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#pragma once + +#include +#include + +/* + * TERRAIN AGL HOLD decision core. + * + * Pure logic, no firmware dependencies: everything it needs comes in through + * terrainNavHoldInput_t and everything it decides goes out through + * terrainNavHoldOutput_t. It never touches the altitude controllers - the + * caller applies out->targetZCm through the one altitude-target funnel. + */ + +typedef enum { + TERRAIN_NAV_HOLD_INACTIVE = 0, // not eligible (switch off, not in cruise, disengage condition) + TERRAIN_NAV_HOLD_WAITING, // eligible but terrain data unusable - engagement refused + TERRAIN_NAV_HOLD_ACTIVE, // holding height above ground + TERRAIN_NAV_HOLD_FROZEN, // data lost mid-hold - altitude target frozen, never descend on dead data +} terrainNavHoldStatus_e; + +typedef enum { + TERRAIN_NAV_HOLD_WARN_NONE = 0, + TERRAIN_NAV_HOLD_WARN_NOT_READY, // switch is on but terrain data is not usable + TERRAIN_NAV_HOLD_WARN_DATA_LOST, // frozen: holding last altitude on stale map data + TERRAIN_NAV_HOLD_WARN_MAX_ALT, // terrain needs more altitude than nav_max_altitude allows + TERRAIN_NAV_HOLD_WARN_PULL_UP, // capped at nav_max_altitude AND below the minimum AGL +} terrainNavHoldWarning_e; + +typedef struct { + uint32_t nowMs; + bool eligible; // armed + cruise whitelist + switch on + no hard-disengage condition + bool stickAdjusting; // pilot is moving the pitch stick - stock behavior runs, re-capture on release + bool healthy; // terrain module healthy this cycle + bool aglValid; // AGL answer available (may lag one query cycle behind healthy) + int32_t aglCm; // current height above ground, valid only when aglValid + float currentZCm; // current altitude in the local frame (same frame as the target funnel) + bool lookaheadValid; // lookahead answer available + float lookaheadClimbCm; // worst height deficit along the path ahead (>= 0) + int32_t minAglCm; // configured minimum AGL + int32_t maxAltCm; // nav_max_altitude in the same frame, 0 = no ceiling +} terrainNavHoldInput_t; + +typedef struct { + bool writeTarget; // when true the caller commands targetZCm through the funnel + float targetZCm; + terrainNavHoldStatus_e status; + terrainNavHoldWarning_e warning; +} terrainNavHoldOutput_t; + +typedef struct { + terrainNavHoldStatus_e status; + int32_t targetAglCm; // the held height above ground; captured at engagement, never persisted across disengagement + uint32_t usableSinceMs; // 0 = data not usable right now (for the re-engage hysteresis) + uint32_t lastUsableMs; // last time data was usable (for the freeze grace period) + bool reCapturePending; // stick released - capture a new target AGL on the next usable cycle +} terrainNavHoldState_t; + +// A momentary AGL gap (single cache miss) only pauses target updates; the hold +// freezes - with a pilot warning - when data stays unusable longer than this +#define TERRAIN_NAV_HOLD_FREEZE_GRACE_MS 500 + +// After a freeze, data must be continuously usable this long before the hold +// resumes (slew-limited); prevents flapping on marginal data +#define TERRAIN_NAV_HOLD_RESUME_HYSTERESIS_MS 3000 + +void terrainNavHoldCoreReset(terrainNavHoldState_t *state); +void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out); From 377e165fc1659c176bfbe3ba1f66bf389f06bfdc Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Mon, 13 Jul 2026 22:47:42 +0300 Subject: [PATCH 27/42] feat(terrain-nav): lookahead heading gate + TERRAIN LOOKAHEAD OFF warning The climb lookahead scans along the course over ground; when the heading estimate is invalid the cog can be stale, so the lookahead now switches itself off (same skip as the config and low-speed gates) and the pilot gets a new lowest-priority OSD warning - the ceiling and data-loss warnings always override it. The reactive hold keeps tracking unchanged (the same degraded mode as terrain_nav_lookahead = 0). No new settings. --- src/main/io/osd.c | 3 +++ src/main/io/osd.h | 1 + src/main/terrain/terrain_nav_hold.c | 8 +++++++- src/main/terrain/terrain_nav_hold_core.c | 7 +++++++ src/main/terrain/terrain_nav_hold_core.h | 2 ++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 121a7996239..d69c213c3ae 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6281,6 +6281,9 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter case TERRAIN_NAV_HOLD_WARN_PULL_UP: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_PULL_UP)); break; + case TERRAIN_NAV_HOLD_WARN_NO_HEADING: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_NO_HEADING)); + break; case TERRAIN_NAV_HOLD_WARN_NONE: break; } diff --git a/src/main/io/osd.h b/src/main/io/osd.h index 97086eceb4b..848c67efdb0 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -152,6 +152,7 @@ #define OSD_MSG_TERRAIN_DATA_LOST "TERRAIN LOST - ALT FROZEN" #define OSD_MSG_TERRAIN_VS_MAX_ALT "TERRAIN VS MAX ALT" #define OSD_MSG_TERRAIN_PULL_UP "TERRAIN PULL UP" +#define OSD_MSG_TERRAIN_NO_HEADING "TERRAIN LOOKAHEAD OFF" #endif #ifdef USE_ADSB diff --git a/src/main/terrain/terrain_nav_hold.c b/src/main/terrain/terrain_nav_hold.c index f04c838e2e4..cb93c94a80f 100644 --- a/src/main/terrain/terrain_nav_hold.c +++ b/src/main/terrain/terrain_nav_hold.c @@ -39,6 +39,8 @@ #include "fc/rc_modes.h" #include "fc/runtime_config.h" +#include "flight/imu.h" + #include "io/gps.h" #include "navigation/navigation.h" @@ -137,7 +139,10 @@ static void terrainNavHoldRunQueries(timeMs_t currentTimeMs) queryAglValid = terrainNavGetAGLCm(&queryAglCm); queryLookaheadValid = false; - if (terrainNavConfig()->lookaheadDistM == 0 || gpsSol.groundSpeed < TERRAIN_NAV_HOLD_MIN_LOOKAHEAD_SPEED_CM_S) { + // No course to scan along: lookahead disabled by config, too slow for a + // usable course over ground, or the heading estimate itself is invalid + // (the cog would be stale - never scan a direction we cannot trust) + if (terrainNavConfig()->lookaheadDistM == 0 || gpsSol.groundSpeed < TERRAIN_NAV_HOLD_MIN_LOOKAHEAD_SPEED_CM_S || !isImuHeadingValid()) { return; } @@ -188,6 +193,7 @@ void terrainNavCruiseHoldUpdate(void) in.currentZCm = navGetCurrentActualPositionAndVelocity()->pos.z; in.lookaheadValid = queryLookaheadValid; in.lookaheadClimbCm = queryLookaheadClimbCm; + in.lookaheadDegraded = terrainNavConfig()->lookaheadDistM != 0 && !isImuHeadingValid(); in.minAglCm = terrainNavConfig()->minAglCm; in.maxAltCm = navConfig()->general.max_altitude; diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index 7a2b28fba5f..6e3bcab2515 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -39,6 +39,13 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu // above ground we have and the one we are holding float targetZCm = in->currentZCm + (state->targetAglCm - in->aglCm); + // The lookahead cannot scan ahead (heading estimate invalid) - the + // reactive hold keeps tracking, but the pilot loses the early-climb + // layer and must be told. The ceiling warnings below override this one + if (in->lookaheadDegraded) { + out->warning = TERRAIN_NAV_HOLD_WARN_NO_HEADING; + } + // Lookahead: if terrain ahead demands more, climb early so the worst // point along the path still clears the minimum AGL if (in->lookaheadValid && in->lookaheadClimbCm > 0.0f) { diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index bd9658b8a6c..15aeeafc6b4 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -49,6 +49,7 @@ typedef enum { TERRAIN_NAV_HOLD_WARN_DATA_LOST, // frozen: holding last altitude on stale map data TERRAIN_NAV_HOLD_WARN_MAX_ALT, // terrain needs more altitude than nav_max_altitude allows TERRAIN_NAV_HOLD_WARN_PULL_UP, // capped at nav_max_altitude AND below the minimum AGL + TERRAIN_NAV_HOLD_WARN_NO_HEADING, // heading estimate invalid: lookahead off, reactive hold continues } terrainNavHoldWarning_e; typedef struct { @@ -61,6 +62,7 @@ typedef struct { float currentZCm; // current altitude in the local frame (same frame as the target funnel) bool lookaheadValid; // lookahead answer available float lookaheadClimbCm; // worst height deficit along the path ahead (>= 0) + bool lookaheadDegraded; // lookahead wanted but unavailable for an abnormal reason (heading estimate invalid) - warn the pilot int32_t minAglCm; // configured minimum AGL int32_t maxAltCm; // nav_max_altitude in the same frame, 0 = no ceiling } terrainNavHoldInput_t; From 4e235a34982a27b449304330174d1320f92c488a Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 29 Jul 2026 16:23:27 +0300 Subject: [PATCH 28/42] feat(terrain-nav): unconditional floor alarm with hysteresis + AUTO CLIMB info + TERR flymode indicator --- src/main/io/osd.c | 7 ++ src/main/io/osd.h | 1 + src/main/terrain/terrain_nav_hold_core.c | 84 ++++++++++++++++++++---- src/main/terrain/terrain_nav_hold_core.h | 12 +++- 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index d69c213c3ae..22269be5589 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -2645,6 +2645,10 @@ static bool osdDrawSingleElement(uint8_t item) p = "LOTR"; else if (FLIGHT_MODE(NAV_POSHOLD_MODE)) p = "HOLD"; +#ifdef USE_TERRAIN + else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE) && FLIGHT_MODE(NAV_ALTHOLD_MODE) && terrainNavHoldIsEngaged()) + p = "TERR"; +#endif else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE) && FLIGHT_MODE(NAV_ALTHOLD_MODE)) p = "CRUZ"; else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE)) @@ -6284,6 +6288,9 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter case TERRAIN_NAV_HOLD_WARN_NO_HEADING: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_NO_HEADING)); break; + case TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_AUTO_CLIMB)); + break; case TERRAIN_NAV_HOLD_WARN_NONE: break; } diff --git a/src/main/io/osd.h b/src/main/io/osd.h index 848c67efdb0..ebf84a12d33 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -153,6 +153,7 @@ #define OSD_MSG_TERRAIN_VS_MAX_ALT "TERRAIN VS MAX ALT" #define OSD_MSG_TERRAIN_PULL_UP "TERRAIN PULL UP" #define OSD_MSG_TERRAIN_NO_HEADING "TERRAIN LOOKAHEAD OFF" +#define OSD_MSG_TERRAIN_AUTO_CLIMB "TERRAIN AUTO CLIMB TO MIN" #endif #ifdef USE_ADSB diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index 6e3bcab2515..523c200ad05 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -31,6 +31,49 @@ void terrainNavHoldCoreReset(terrainNavHoldState_t *state) state->usableSinceMs = 0; state->lastUsableMs = 0; state->reCapturePending = false; + state->minAglReached = false; + state->climbBestAglCm = 0; + state->pullUpActive = false; +} + +// Floor alarm: PULL UP must fire on real AGL, ceiling or not. Once the +// minimum has been reached, dropping below (min - margin) latches the alarm +// and only climbing back to the minimum clears it. During the initial +// automatic climb the aircraft is legitimately below the minimum, so there +// the alarm fires only when height above terrain is actually being LOST +// (terrain outclimbing the aircraft), not merely still low +static void updateFloorAlarm(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in) +{ + if (in->aglCm >= in->minAglCm) { + state->minAglReached = true; + state->pullUpActive = false; + return; + } + + if (state->minAglReached) { + if (in->aglCm < in->minAglCm - TERRAIN_NAV_HOLD_PULLUP_HYST_CM) { + state->pullUpActive = true; + } + } else { + if (in->aglCm > state->climbBestAglCm) { + state->climbBestAglCm = in->aglCm; + } else if (in->aglCm < state->climbBestAglCm - TERRAIN_NAV_HOLD_PULLUP_HYST_CM) { + state->pullUpActive = true; + } + } +} + +// Every capture (engagement or stick release) starts a fresh floor-alarm +// phase: capturing at/above the minimum arms the alarm immediately, below it +// the automatic climb to the minimum begins +static void startCapture(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in) +{ + state->targetAglCm = (in->aglCm > in->minAglCm) ? in->aglCm : in->minAglCm; + state->reCapturePending = false; + state->minAglReached = false; + state->climbBestAglCm = in->aglCm; + state->pullUpActive = false; + updateFloorAlarm(state, in); } static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out) @@ -39,13 +82,6 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu // above ground we have and the one we are holding float targetZCm = in->currentZCm + (state->targetAglCm - in->aglCm); - // The lookahead cannot scan ahead (heading estimate invalid) - the - // reactive hold keeps tracking, but the pilot loses the early-climb - // layer and must be told. The ceiling warnings below override this one - if (in->lookaheadDegraded) { - out->warning = TERRAIN_NAV_HOLD_WARN_NO_HEADING; - } - // Lookahead: if terrain ahead demands more, climb early so the worst // point along the path still clears the minimum AGL if (in->lookaheadValid && in->lookaheadClimbCm > 0.0f) { @@ -55,11 +91,27 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu } } + // Warning ladder, least severe first - a later assignment overrides. + // The lookahead cannot scan ahead (heading estimate invalid) - the + // reactive hold keeps tracking, but the pilot loses the early-climb + // layer and must be told + if (in->lookaheadDegraded) { + out->warning = TERRAIN_NAV_HOLD_WARN_NO_HEADING; + } + // Engaged below the minimum: the hold itself is climbing to the floor - + // information, not an alarm, the pilot has nothing to do + if (!state->minAglReached && in->aglCm < in->minAglCm) { + out->warning = TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB; + } // Ceiling arbitration: nav_max_altitude always wins - the funnel clamps // the target for real, here we only detect the conflict and warn. Warns // ahead of the pinch too, because the lookahead demand is in targetZCm if (in->maxAltCm > 0 && targetZCm > in->maxAltCm) { - out->warning = (in->aglCm < in->minAglCm) ? TERRAIN_NAV_HOLD_WARN_PULL_UP : TERRAIN_NAV_HOLD_WARN_MAX_ALT; + out->warning = TERRAIN_NAV_HOLD_WARN_MAX_ALT; + } + // The floor alarm outranks everything: too low and not recovering + if (state->pullUpActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; } out->writeTarget = true; @@ -71,8 +123,7 @@ static void engage(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in // Capture the current AGL: zero commanded movement at engagement. // Sole exception: below the minimum - the target becomes the minimum and // the slew-limited funnel turns that into a gentle climb - state->targetAglCm = (in->aglCm > in->minAglCm) ? in->aglCm : in->minAglCm; - state->reCapturePending = false; + startCapture(state, in); state->status = TERRAIN_NAV_HOLD_ACTIVE; if (in->stickAdjusting) { @@ -135,15 +186,21 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold // target updates for a cycle - no drama, no warning break; } + updateFloorAlarm(state, in); if (in->stickAdjusting) { // Pilot input wins: stock climb-rate control runs, and the - // AGL present at release becomes the new held target + // AGL present at release becomes the new held target. The + // floor alarm stays live - it reports the aircraft's real + // state, not the hold's commands state->reCapturePending = true; + if (state->pullUpActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + } break; } if (state->reCapturePending) { - state->targetAglCm = (in->aglCm > in->minAglCm) ? in->aglCm : in->minAglCm; - state->reCapturePending = false; + // A release is a fresh capture - fresh floor-alarm phase too + startCapture(state, in); } computeTarget(state, in, out); break; @@ -152,6 +209,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold if (usableStable) { // Resume the same held AGL, slew-limited by the funnel rate state->status = TERRAIN_NAV_HOLD_ACTIVE; + updateFloorAlarm(state, in); computeTarget(state, in, out); } else { out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index 15aeeafc6b4..807103b2f9f 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -48,8 +48,9 @@ typedef enum { TERRAIN_NAV_HOLD_WARN_NOT_READY, // switch is on but terrain data is not usable TERRAIN_NAV_HOLD_WARN_DATA_LOST, // frozen: holding last altitude on stale map data TERRAIN_NAV_HOLD_WARN_MAX_ALT, // terrain needs more altitude than nav_max_altitude allows - TERRAIN_NAV_HOLD_WARN_PULL_UP, // capped at nav_max_altitude AND below the minimum AGL + TERRAIN_NAV_HOLD_WARN_PULL_UP, // below the minimum AGL and not recovering - fires with or without a ceiling TERRAIN_NAV_HOLD_WARN_NO_HEADING, // heading estimate invalid: lookahead off, reactive hold continues + TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB, // engaged below the minimum AGL - automatic climb to the minimum in progress } terrainNavHoldWarning_e; typedef struct { @@ -80,6 +81,9 @@ typedef struct { uint32_t usableSinceMs; // 0 = data not usable right now (for the re-engage hysteresis) uint32_t lastUsableMs; // last time data was usable (for the freeze grace period) bool reCapturePending; // stick released - capture a new target AGL on the next usable cycle + bool minAglReached; // AGL has reached the minimum since this capture - arms the floor alarm + int32_t climbBestAglCm; // best AGL seen while still below the minimum (the auto-climb phase) + bool pullUpActive; // floor alarm latched: on below (min - margin), off at/above the minimum } terrainNavHoldState_t; // A momentary AGL gap (single cache miss) only pauses target updates; the hold @@ -90,5 +94,11 @@ typedef struct { // resumes (slew-limited); prevents flapping on marginal data #define TERRAIN_NAV_HOLD_RESUME_HYSTERESIS_MS 3000 +// Floor alarm margin: terrain naturally breathes around the minimum when +// riding the floor, so once the minimum has been reached PULL UP fires only +// below (min - margin) and clears at/above the minimum. The same margin +// decides "losing height" during the initial automatic climb +#define TERRAIN_NAV_HOLD_PULLUP_HYST_CM 500 + void terrainNavHoldCoreReset(terrainNavHoldState_t *state); void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out); From ed28de2714f1df765dd574a8e0fac53c443d7dec Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Thu, 30 Jul 2026 16:38:41 +0300 Subject: [PATCH 29/42] feat(terrain-nav): smooth stick handover blend + retake dwell (no command step, no deadband-edge writer flap) --- src/main/terrain/terrain_nav_hold.c | 26 +++++++ src/main/terrain/terrain_nav_hold_core.c | 86 +++++++++++++++++++++++- src/main/terrain/terrain_nav_hold_core.h | 32 +++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/main/terrain/terrain_nav_hold.c b/src/main/terrain/terrain_nav_hold.c index cb93c94a80f..7bbfe74fb2c 100644 --- a/src/main/terrain/terrain_nav_hold.c +++ b/src/main/terrain/terrain_nav_hold.c @@ -40,6 +40,7 @@ #include "fc/runtime_config.h" #include "flight/imu.h" +#include "flight/pid.h" #include "io/gps.h" @@ -197,12 +198,37 @@ void terrainNavCruiseHoldUpdate(void) in.minAglCm = terrainNavConfig()->minAglCm; in.maxAltCm = navConfig()->general.max_altitude; + // Handover blend inputs. The blend seed is the aircraft's real vertical + // speed, bounded by the same authority limit as every hold command. The + // stick's wish is read back from the funnel - the stock stick writer ran + // earlier this same cycle (fc_core.c ordering) - with the same manual + // clamp getDesiredClimbRate applies to it + in.actualClimbRateCmS = constrainf(navGetCurrentActualPositionAndVelocity()->vel.z, + -(float)navConfig()->fw.max_auto_climb_rate, + (float)navConfig()->fw.max_auto_climb_rate); + if (in.stickAdjusting && posControl.flags.rocToAltMode == ROC_TO_ALT_CONSTANT) { + in.stickWishValid = true; + in.stickWishCmS = constrainf(posControl.desiredState.climbRateDemand, + -(float)navConfig()->fw.max_manual_climb_rate, + (float)navConfig()->fw.max_manual_climb_rate); + } + terrainNavHoldCoreUpdate(&holdState, &in, &holdOutput); if (holdOutput.writeTarget) { // The one gate into the altitude target path. The funnel enforces the // slew limit (climb rate) and the nav_max_altitude clamp downstream updateClimbRateToAltitudeController(navConfig()->fw.max_auto_climb_rate, holdOutput.targetZCm, ROC_TO_ALT_TARGET); + } else if (holdOutput.writeRate) { + // Handover blend: a signed climb rate expressed through the funnel's + // own target math (rate = response_factor * altitude_error / 100), + // so the command is smooth in both directions and the ceiling clamp + // still applies downstream. The rate itself is already bounded by + // max_auto_climb_rate through the blend inputs above + const float responseFactor = MAX(pidProfile()->fwAltControlResponseFactor, 1); + const float blendTargetZCm = navGetCurrentActualPositionAndVelocity()->pos.z + + holdOutput.rateCmS * 100.0f / responseFactor; + updateClimbRateToAltitudeController(navConfig()->fw.max_auto_climb_rate, blendTargetZCm, ROC_TO_ALT_TARGET); } } diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index 523c200ad05..0585233ca62 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -34,6 +34,47 @@ void terrainNavHoldCoreReset(terrainNavHoldState_t *state) state->minAglReached = false; state->climbBestAglCm = 0; state->pullUpActive = false; + state->stickWasAdjusting = false; + state->blendActive = false; + state->blendRateCmS = 0.0f; + state->blendStartMs = 0; + state->blendLastMs = 0; + state->stickQuietSinceMs = 0; +} + +// Handover blend: one continuous writer across the whole stick episode. +// Seeded from the aircraft's real climb rate at the grab (zero step by +// construction), it chases the wish - the stick's clamped demand while +// deflected, zero after release (the stock release semantics) - and goes +// silent once close enough or past the lifetime bound. While it runs the +// caller commands out->rateCmS; the stock writer takes over seamlessly after +static void runBlend(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out, float wishCmS) +{ + if (!state->blendActive) { + return; + } + + if (in->nowMs - state->blendStartMs >= TERRAIN_NAV_HOLD_BLEND_MAX_MS) { + state->blendActive = false; + return; + } + + // First-order chase, dt-based so the loop rate does not matter + float alpha = (float)(in->nowMs - state->blendLastMs) / TERRAIN_NAV_HOLD_BLEND_TAU_MS; + if (alpha > 1.0f) { + alpha = 1.0f; + } + state->blendLastMs = in->nowMs; + state->blendRateCmS += (wishCmS - state->blendRateCmS) * alpha; + + const float remaining = wishCmS - state->blendRateCmS; + if (remaining < TERRAIN_NAV_HOLD_BLEND_DONE_CM_S && remaining > -TERRAIN_NAV_HOLD_BLEND_DONE_CM_S) { + state->blendActive = false; + return; + } + + out->writeRate = true; + out->rateCmS = state->blendRateCmS; } // Floor alarm: PULL UP must fire on real AGL, ceiling or not. Once the @@ -138,6 +179,8 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold { out->writeTarget = false; out->targetZCm = 0.0f; + out->writeRate = false; + out->rateCmS = 0.0f; out->warning = TERRAIN_NAV_HOLD_WARN_NONE; if (!in->eligible) { @@ -178,8 +221,10 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold if (!usable) { if (in->nowMs - state->lastUsableMs > TERRAIN_NAV_HOLD_FREEZE_GRACE_MS) { // Freeze: stop commanding, the last target holds. - // Never descend on dead data + // Never descend on dead data. A running handover blend + // dies with the writes - health gates every output state->status = TERRAIN_NAV_HOLD_FROZEN; + state->blendActive = false; out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; } // Inside the grace period: a single missed read just pauses @@ -193,13 +238,42 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold // floor alarm stays live - it reports the aircraft's real // state, not the hold's commands state->reCapturePending = true; + state->stickQuietSinceMs = 0; + if (!state->blendActive && !state->stickWasAdjusting) { + // The grab: seed the handover blend from the aircraft's + // real climb rate - zero command step by construction. + // (Not re-seeded when a blend already runs: a stick + // re-crossing the deadband keeps one continuous command) + state->blendActive = true; + state->blendRateCmS = in->actualClimbRateCmS; + state->blendStartMs = in->nowMs; + state->blendLastMs = in->nowMs; + } + runBlend(state, in, out, in->stickWishValid ? in->stickWishCmS : 0.0f); if (state->pullUpActive) { out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; } break; } if (state->reCapturePending) { + // Retake dwell: the stick must stay inside the deadband this + // long before the hold takes back - a stick hovering at the + // edge otherwise alternates two writers within milliseconds. + // The blend keeps running through the dwell, chasing zero + // (the stock release semantics), so the whole episode has + // one continuous writer + if (state->stickQuietSinceMs == 0) { + state->stickQuietSinceMs = (in->nowMs != 0) ? in->nowMs : 1; + } + if (in->nowMs - state->stickQuietSinceMs < TERRAIN_NAV_HOLD_RETAKE_DWELL_MS) { + runBlend(state, in, out, 0.0f); + if (state->pullUpActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + } + break; + } // A release is a fresh capture - fresh floor-alarm phase too + state->blendActive = false; startCapture(state, in); } computeTarget(state, in, out); @@ -210,6 +284,15 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold // Resume the same held AGL, slew-limited by the funnel rate state->status = TERRAIN_NAV_HOLD_ACTIVE; updateFloorAlarm(state, in); + if (in->stickAdjusting) { + // Pilot is commanding at the moment of resume: yield + // immediately, the release becomes a fresh capture + state->reCapturePending = true; + if (state->pullUpActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + } + break; + } computeTarget(state, in, out); } else { out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; @@ -217,5 +300,6 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold break; } + state->stickWasAdjusting = in->stickAdjusting; out->status = state->status; } diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index 807103b2f9f..9b84b27e1a2 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -61,6 +61,9 @@ typedef struct { bool aglValid; // AGL answer available (may lag one query cycle behind healthy) int32_t aglCm; // current height above ground, valid only when aglValid float currentZCm; // current altitude in the local frame (same frame as the target funnel) + float actualClimbRateCmS; // the aircraft's real vertical speed (seeds the handover blend) + bool stickWishValid; // the pilot's stick climb-rate wish is known this cycle + float stickWishCmS; // what the stick currently asks for, manual-clamped by the caller bool lookaheadValid; // lookahead answer available float lookaheadClimbCm; // worst height deficit along the path ahead (>= 0) bool lookaheadDegraded; // lookahead wanted but unavailable for an abnormal reason (heading estimate invalid) - warn the pilot @@ -71,6 +74,8 @@ typedef struct { typedef struct { bool writeTarget; // when true the caller commands targetZCm through the funnel float targetZCm; + bool writeRate; // when true the caller commands rateCmS instead (handover blend window) + float rateCmS; // signed climb rate; bounded by the caller's clamp on the blend inputs terrainNavHoldStatus_e status; terrainNavHoldWarning_e warning; } terrainNavHoldOutput_t; @@ -84,6 +89,12 @@ typedef struct { bool minAglReached; // AGL has reached the minimum since this capture - arms the floor alarm int32_t climbBestAglCm; // best AGL seen while still below the minimum (the auto-climb phase) bool pullUpActive; // floor alarm latched: on below (min - margin), off at/above the minimum + bool stickWasAdjusting; // previous-cycle stick state (grab-edge detection for the blend) + bool blendActive; // handover blend running (stick grab through the retake dwell) + float blendRateCmS; // the blend's current climb-rate command + uint32_t blendStartMs; // when this blend began (hard lifetime bound) + uint32_t blendLastMs; // last blend update (dt for the first-order chase) + uint32_t stickQuietSinceMs; // stick back inside the deadband since (retake dwell); 0 = not quiet } terrainNavHoldState_t; // A momentary AGL gap (single cache miss) only pauses target updates; the hold @@ -100,5 +111,26 @@ typedef struct { // decides "losing height" during the initial automatic climb #define TERRAIN_NAV_HOLD_PULLUP_HYST_CM 500 +// Handover blend: when the pilot grabs the stick during an active hold the +// climb-rate command must not step (a large hold demand collapsing into a +// small stick demand pitches the nose the wrong way). The blend seeds from +// the aircraft's real climb rate and chases the stick's wish with this time +// constant - most of the pilot's wish arrives within the first tau +#define TERRAIN_NAV_HOLD_BLEND_TAU_MS 300 + +// The blend never outlives this: a hard bound on the shared-authority window, +// after which the stock stick control flies alone +#define TERRAIN_NAV_HOLD_BLEND_MAX_MS 1000 + +// The blend ends early once this close to the stick's wish - close enough +// that the switch to the stock writer is seamless +#define TERRAIN_NAV_HOLD_BLEND_DONE_CM_S 25.0f + +// Retake dwell: after the stick returns inside the deadband it must stay +// there this long before the hold captures and resumes writing - a stick +// hovering at the deadband edge otherwise alternates two writers within +// milliseconds (the LOG00010 whiplash) +#define TERRAIN_NAV_HOLD_RETAKE_DWELL_MS 250 + void terrainNavHoldCoreReset(terrainNavHoldState_t *state); void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out); From b6fe3a7b77e4a5ad9685d9c9efb8268c92669c26 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Sat, 1 Aug 2026 01:17:25 +0300 Subject: [PATCH 30/42] feat(terrain-nav): predictive TERRAIN AHEAD escape-test alarm + time-capped lookahead horizon --- src/main/io/osd.c | 3 + src/main/io/osd.h | 1 + src/main/terrain/terrain_nav.c | 13 ++++- src/main/terrain/terrain_nav.h | 6 +- src/main/terrain/terrain_nav_hold.c | 25 +++++++- src/main/terrain/terrain_nav_hold_core.c | 73 ++++++++++++++++++++++-- src/main/terrain/terrain_nav_hold_core.h | 15 +++++ 7 files changed, 127 insertions(+), 9 deletions(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 22269be5589..0f3db91d0e2 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6291,6 +6291,9 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter case TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_AUTO_CLIMB)); break; + case TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_AHEAD)); + break; case TERRAIN_NAV_HOLD_WARN_NONE: break; } diff --git a/src/main/io/osd.h b/src/main/io/osd.h index ebf84a12d33..1e5bb68e7e0 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -154,6 +154,7 @@ #define OSD_MSG_TERRAIN_PULL_UP "TERRAIN PULL UP" #define OSD_MSG_TERRAIN_NO_HEADING "TERRAIN LOOKAHEAD OFF" #define OSD_MSG_TERRAIN_AUTO_CLIMB "TERRAIN AUTO CLIMB TO MIN" +#define OSD_MSG_TERRAIN_AHEAD "TERRAIN AHEAD" #endif #ifdef USE_ADSB diff --git a/src/main/terrain/terrain_nav.c b/src/main/terrain/terrain_nav.c index aa30a589320..a99d722a14a 100644 --- a/src/main/terrain/terrain_nav.c +++ b/src/main/terrain/terrain_nav.c @@ -130,13 +130,14 @@ bool terrainNavGetHomeTerrainHeight(float *heightM) return terrainNavGetHeightAtLocation(&homeLoc, heightM); } -bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, terrainNavLookaheadResult_t *result) +bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, float escapeRatio, terrainNavLookaheadResult_t *result) { if (result == NULL || !terrainNavIsHealthy()) { return false; } result->climbNeededM = 0.0f; + result->escapeDeficitM = 0.0f; result->samplesTotal = 0; result->samplesMissed = 0; result->complete = true; @@ -151,8 +152,10 @@ bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, te const float stepNorthM = cosf(bearingRad) * TERRAIN_NAV_STEP_M; const float stepEastM = sinf(bearingRad) * TERRAIN_NAV_STEP_M; const float climbPerStepM = climbRatio * TERRAIN_NAV_STEP_M; + const float escapePerStepM = escapeRatio * TERRAIN_NAV_STEP_M; float achievableClimbM = 0.0f; + float escapeClimbM = 0.0f; for (float travelledM = TERRAIN_NAV_STEP_M; travelledM <= distanceM; travelledM += TERRAIN_NAV_STEP_M) { if (result->samplesTotal >= TERRAIN_NAV_LOOKAHEAD_MAX_SAMPLES) { @@ -161,14 +164,20 @@ bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, te offsetLatlng(&pos, stepNorthM, stepEastM); achievableClimbM += climbPerStepM; + escapeClimbM += escapePerStepM; result->samplesTotal++; float heightM; if (terrainNavGetHeightAtLocation(&pos, &heightM)) { - const float deficitM = (heightM - baseHeightM) - achievableClimbM; + const float riseM = heightM - baseHeightM; + const float deficitM = riseM - achievableClimbM; if (deficitM > result->climbNeededM) { result->climbNeededM = deficitM; } + const float escapeM = riseM - escapeClimbM; + if (escapeM > result->escapeDeficitM) { + result->escapeDeficitM = escapeM; + } } else { result->samplesMissed++; } diff --git a/src/main/terrain/terrain_nav.h b/src/main/terrain/terrain_nav.h index 93fbb937a5c..6d259e68cf1 100644 --- a/src/main/terrain/terrain_nav.h +++ b/src/main/terrain/terrain_nav.h @@ -39,6 +39,7 @@ typedef struct { float climbNeededM; // extra climb needed now to clear terrain along the path (>= 0) + float escapeDeficitM; // worst height deficit vs the escapeRatio slope (>= 0) - the escape test's raw input uint8_t samplesTotal; // points sampled along the path uint8_t samplesMissed; // points that had no terrain data (loads were scheduled) bool complete; // true when every sampled point had terrain data @@ -63,4 +64,7 @@ bool terrainNavGetHomeTerrainHeight(float *heightM); // and report the worst height deficit found. A descending path (negative // climbRatio) correctly increases the reported deficit. Sampled blocks not // yet cached are scheduled for loading and counted in samplesMissed. -bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, terrainNavLookaheadResult_t *result); +// One walk, two slopes: climbNeededM is the deficit vs climbRatio (the +// early-climb demand model), escapeDeficitM the deficit vs escapeRatio (the +// full-capability escape test) - same samples, no extra SD load +bool terrainNavLookahead(float bearingDeg, float distanceM, float climbRatio, float escapeRatio, terrainNavLookaheadResult_t *result); diff --git a/src/main/terrain/terrain_nav_hold.c b/src/main/terrain/terrain_nav_hold.c index 7bbfe74fb2c..33a61e3ffc4 100644 --- a/src/main/terrain/terrain_nav_hold.c +++ b/src/main/terrain/terrain_nav_hold.c @@ -69,6 +69,14 @@ PG_RESET_TEMPLATE(terrainNavConfig_t, terrainNavConfig, // Below this ground speed the course over ground is too noisy for a lookahead #define TERRAIN_NAV_HOLD_MIN_LOOKAHEAD_SPEED_CM_S 300 +// Time horizon of the lookahead scan: distance is additionally capped at +// groundspeed x this many seconds. Fixes both ends of the fixed-distance +// problem (LOG00010 analysis, docs/01 "Task 2"): slow flight stops climbing +// for peaks a minute away, and the escape test inherently reasons in time. +// At full nav_fw_auto_climb_rate the aircraft gains more height over this +// horizon than the largest demand ever seen in the logs +#define TERRAIN_NAV_HOLD_TIME_HORIZON_S 35 + static terrainNavHoldState_t holdState; static terrainNavHoldOutput_t holdOutput; @@ -78,6 +86,7 @@ static bool queryAglValid; static int32_t queryAglCm; static bool queryLookaheadValid; static float queryLookaheadClimbCm; +static float queryEscapeDeficitCm; // The hard-disengage conditions: launch, any landing, emergency landing, // VTOL transition, non-altitude platforms, degraded GPS, rangefinder SURFACE @@ -151,21 +160,31 @@ static void terrainNavHoldRunQueries(timeMs_t currentTimeMs) // query above uses the current block, so the lookahead may touch at most // cacheSize - 3 new blocks - its distance is capped accordingly const int32_t lookaheadBudgetM = (TERRAIN_GRID_BLOCK_CACHE_SIZE - 3) * TERRAIN_NAV_HOLD_M_PER_BLOCK_WORST; - const float lookaheadDistM = MIN((int32_t)terrainNavConfig()->lookaheadDistM, lookaheadBudgetM); + float lookaheadDistM = MIN((int32_t)terrainNavConfig()->lookaheadDistM, lookaheadBudgetM); // Conservative achievable climb slope: half the configured climb rate at // the current ground speed - underestimating it makes climbs start early const float groundSpeedCmS = MAX(gpsSol.groundSpeed, 800); const float climbRatio = 0.5f * navConfig()->fw.max_auto_climb_rate / groundSpeedCmS; + // Time cap on the scan (the fixed-distance fix): never look further ahead + // than the aircraft flies in the time horizon + lookaheadDistM = MIN(lookaheadDistM, groundSpeedCmS * 0.01f * TERRAIN_NAV_HOLD_TIME_HORIZON_S); + + // The escape slope: the FULL configured climb rate at the current ground + // speed - a deficit against it means "even climbing at maximum from this + // moment, that point ahead arrives below the minimum" + const float escapeRatio = (float)navConfig()->fw.max_auto_climb_rate / groundSpeedCmS; + const float bearingDeg = CENTIDEGREES_TO_DEGREES((float)posControl.actualState.cog); terrainNavLookaheadResult_t lookahead; - if (terrainNavLookahead(bearingDeg, lookaheadDistM, climbRatio, &lookahead)) { + if (terrainNavLookahead(bearingDeg, lookaheadDistM, climbRatio, escapeRatio, &lookahead)) { // A partial answer (samples missed at the far end) is still a valid // lower bound; the missed blocks are already scheduled for loading queryLookaheadValid = true; queryLookaheadClimbCm = lookahead.climbNeededM * 100.0f; + queryEscapeDeficitCm = lookahead.escapeDeficitM * 100.0f; } } @@ -194,6 +213,8 @@ void terrainNavCruiseHoldUpdate(void) in.currentZCm = navGetCurrentActualPositionAndVelocity()->pos.z; in.lookaheadValid = queryLookaheadValid; in.lookaheadClimbCm = queryLookaheadClimbCm; + in.escapeDeficitValid = queryLookaheadValid; + in.escapeDeficitCm = queryEscapeDeficitCm; in.lookaheadDegraded = terrainNavConfig()->lookaheadDistM != 0 && !isImuHeadingValid(); in.minAglCm = terrainNavConfig()->minAglCm; in.maxAltCm = navConfig()->general.max_altitude; diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index 0585233ca62..0de298eead7 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -40,6 +40,54 @@ void terrainNavHoldCoreReset(terrainNavHoldState_t *state) state->blendStartMs = 0; state->blendLastMs = 0; state->stickQuietSinceMs = 0; + state->terrainAheadActive = false; + state->escapeBadSinceMs = 0; + state->escapeClearSinceMs = 0; +} + +// TERRAIN AHEAD - the predictive escape test. Fails when even the FULL +// configured climb rate, applied from this moment, arrives below the +// minimum AGL at some point along the course ahead ("at this speed, this +// slope will beat you"). Persistence keeps single noisy samples silent; +// clearing needs the test to pass WITH margin for a while - normally the +// pilot turning away (the scan follows the course), slowing or climbing. +// Without a trusted view ahead the alarm cannot exist - the reactive floor +// alarm stands alone and TERRAIN LOOKAHEAD OFF explains why +static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in) +{ + if (!in->escapeDeficitValid) { + state->terrainAheadActive = false; + state->escapeBadSinceMs = 0; + state->escapeClearSinceMs = 0; + return; + } + + // How far below the minimum the aircraft arrives at the worst point + // ahead, climbing at full rate from now + const float shortfallCm = in->escapeDeficitCm + (in->minAglCm - in->aglCm); + + if (shortfallCm > 0.0f) { + state->escapeClearSinceMs = 0; + if (state->escapeBadSinceMs == 0) { + state->escapeBadSinceMs = (in->nowMs != 0) ? in->nowMs : 1; + } + if (in->nowMs - state->escapeBadSinceMs >= TERRAIN_NAV_HOLD_ESCAPE_PERSIST_MS) { + state->terrainAheadActive = true; + } + } else { + state->escapeBadSinceMs = 0; + if (shortfallCm < -(float)TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MARGIN_CM) { + if (state->escapeClearSinceMs == 0) { + state->escapeClearSinceMs = (in->nowMs != 0) ? in->nowMs : 1; + } + if (in->nowMs - state->escapeClearSinceMs >= TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MS) { + state->terrainAheadActive = false; + } + } else { + // Passing, but without margin: no fresh alarm, no clearing either + state->escapeClearSinceMs = 0; + } + } } // Handover blend: one continuous writer across the whole stick episode. @@ -150,7 +198,12 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu if (in->maxAltCm > 0 && targetZCm > in->maxAltCm) { out->warning = TERRAIN_NAV_HOLD_WARN_MAX_ALT; } - // The floor alarm outranks everything: too low and not recovering + // Predictive: the terrain ahead cannot be out-climbed at full rate - + // the pilot has time to act (turn, throttle, slow) and must use it + if (state->terrainAheadActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD; + } + // The floor alarm outranks everything: too low NOW and not recovering if (state->pullUpActive) { out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; } @@ -222,9 +275,13 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold if (in->nowMs - state->lastUsableMs > TERRAIN_NAV_HOLD_FREEZE_GRACE_MS) { // Freeze: stop commanding, the last target holds. // Never descend on dead data. A running handover blend - // dies with the writes - health gates every output + // and the predictive alarm die with the writes - health + // gates every output state->status = TERRAIN_NAV_HOLD_FROZEN; state->blendActive = false; + state->terrainAheadActive = false; + state->escapeBadSinceMs = 0; + state->escapeClearSinceMs = 0; out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; } // Inside the grace period: a single missed read just pauses @@ -232,11 +289,12 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold break; } updateFloorAlarm(state, in); + updateEscapeAlarm(state, in); if (in->stickAdjusting) { // Pilot input wins: stock climb-rate control runs, and the // AGL present at release becomes the new held target. The - // floor alarm stays live - it reports the aircraft's real - // state, not the hold's commands + // alarms stay live - they report the aircraft's real state, + // not the hold's commands state->reCapturePending = true; state->stickQuietSinceMs = 0; if (!state->blendActive && !state->stickWasAdjusting) { @@ -250,6 +308,9 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold state->blendLastMs = in->nowMs; } runBlend(state, in, out, in->stickWishValid ? in->stickWishCmS : 0.0f); + if (state->terrainAheadActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD; + } if (state->pullUpActive) { out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; } @@ -267,6 +328,9 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold } if (in->nowMs - state->stickQuietSinceMs < TERRAIN_NAV_HOLD_RETAKE_DWELL_MS) { runBlend(state, in, out, 0.0f); + if (state->terrainAheadActive) { + out->warning = TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD; + } if (state->pullUpActive) { out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; } @@ -284,6 +348,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold // Resume the same held AGL, slew-limited by the funnel rate state->status = TERRAIN_NAV_HOLD_ACTIVE; updateFloorAlarm(state, in); + updateEscapeAlarm(state, in); if (in->stickAdjusting) { // Pilot is commanding at the moment of resume: yield // immediately, the release becomes a fresh capture diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index 9b84b27e1a2..59ed2167844 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -51,6 +51,7 @@ typedef enum { TERRAIN_NAV_HOLD_WARN_PULL_UP, // below the minimum AGL and not recovering - fires with or without a ceiling TERRAIN_NAV_HOLD_WARN_NO_HEADING, // heading estimate invalid: lookahead off, reactive hold continues TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB, // engaged below the minimum AGL - automatic climb to the minimum in progress + TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD, // escape test failing: terrain ahead cannot be out-climbed at full rate } terrainNavHoldWarning_e; typedef struct { @@ -66,6 +67,8 @@ typedef struct { float stickWishCmS; // what the stick currently asks for, manual-clamped by the caller bool lookaheadValid; // lookahead answer available float lookaheadClimbCm; // worst height deficit along the path ahead (>= 0) + bool escapeDeficitValid; // escape-test answer available (same walk as the lookahead) + float escapeDeficitCm; // worst deficit vs the FULL climb rate along the path (>= 0) bool lookaheadDegraded; // lookahead wanted but unavailable for an abnormal reason (heading estimate invalid) - warn the pilot int32_t minAglCm; // configured minimum AGL int32_t maxAltCm; // nav_max_altitude in the same frame, 0 = no ceiling @@ -95,6 +98,9 @@ typedef struct { uint32_t blendStartMs; // when this blend began (hard lifetime bound) uint32_t blendLastMs; // last blend update (dt for the first-order chase) uint32_t stickQuietSinceMs; // stick back inside the deadband since (retake dwell); 0 = not quiet + bool terrainAheadActive; // predictive escape alarm latched + uint32_t escapeBadSinceMs; // escape test failing since (persistence); 0 = not failing + uint32_t escapeClearSinceMs;// escape test passing with margin since (clear hysteresis); 0 = not counting } terrainNavHoldState_t; // A momentary AGL gap (single cache miss) only pauses target updates; the hold @@ -132,5 +138,14 @@ typedef struct { // milliseconds (the LOG00010 whiplash) #define TERRAIN_NAV_HOLD_RETAKE_DWELL_MS 250 +// TERRAIN AHEAD (predictive escape test): the failing condition must hold +// this long before the alarm fires - no flicker on single noisy samples +#define TERRAIN_NAV_HOLD_ESCAPE_PERSIST_MS 1000 + +// ...and clears only after the test passes with the margin below for this +// long (typically because the pilot turned, slowed or climbed) +#define TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MS 2000 +#define TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MARGIN_CM 1000 + void terrainNavHoldCoreReset(terrainNavHoldState_t *state); void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out); From 0792b9ba2fe3da9db35159b171e040b7e199ce39 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 5 Aug 2026 10:35:18 +0300 Subject: [PATCH 31/42] feat(terrain-nav): honest alarm ladder - TERRAIN AHEAD on real forward threat only, PULL UP survives below-min captures, pass-at-minimum clear rule, TAWS callout texts Below the minimum the escape-test shortfall now counts only the real terrain-relative deficit (the altitude term is clamped out there; above the minimum it keeps acting as the honest cushion), so the automatic climb shows TERRAIN AUTO CLIMB TO MIN instead of a false TERRAIN AHEAD. A capture below the minimum no longer extinguishes an active PULL UP - the alarm clears only at/above the minimum, as always. TERRAIN AHEAD clears when the escape test passes with the aircraft at/above the minimum, both sustained 2 s; a returning threat re-fires the series (the spare-margin clear could never be met riding exactly at the floor). OSD texts become "TERRAIN AHEAD!" and "TERRAIN! PULL UP!". --- src/main/io/osd.h | 4 +-- src/main/terrain/terrain_nav_hold_core.c | 31 +++++++++++++++++------- src/main/terrain/terrain_nav_hold_core.h | 6 ++--- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/main/io/osd.h b/src/main/io/osd.h index 1e5bb68e7e0..1d976be1083 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -151,10 +151,10 @@ #define OSD_MSG_TERRAIN_NOT_READY "TERRAIN NOT READY" #define OSD_MSG_TERRAIN_DATA_LOST "TERRAIN LOST - ALT FROZEN" #define OSD_MSG_TERRAIN_VS_MAX_ALT "TERRAIN VS MAX ALT" -#define OSD_MSG_TERRAIN_PULL_UP "TERRAIN PULL UP" +#define OSD_MSG_TERRAIN_PULL_UP "TERRAIN! PULL UP!" #define OSD_MSG_TERRAIN_NO_HEADING "TERRAIN LOOKAHEAD OFF" #define OSD_MSG_TERRAIN_AUTO_CLIMB "TERRAIN AUTO CLIMB TO MIN" -#define OSD_MSG_TERRAIN_AHEAD "TERRAIN AHEAD" +#define OSD_MSG_TERRAIN_AHEAD "TERRAIN AHEAD!" #endif #ifdef USE_ADSB diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index 0de298eead7..e8b2f31d48d 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -48,9 +48,14 @@ void terrainNavHoldCoreReset(terrainNavHoldState_t *state) // TERRAIN AHEAD - the predictive escape test. Fails when even the FULL // configured climb rate, applied from this moment, arrives below the // minimum AGL at some point along the course ahead ("at this speed, this -// slope will beat you"). Persistence keeps single noisy samples silent; -// clearing needs the test to pass WITH margin for a while - normally the -// pilot turning away (the scan follows the course), slowing or climbing. +// slope will beat you"). Below the minimum, closeness alone is not a +// forward threat - AUTO CLIMB and PULL UP own that situation - so only the +// terrain-relative deficit counts there; above the minimum the altitude +// cushion legitimately buys escape headroom and stays in the sum. +// Persistence keeps single noisy samples silent; clearing needs the test +// passing AND the aircraft at/above the minimum, both sustained - normally +// the pilot turning away (the scan follows the course), slowing or +// climbing. A threat that returns simply re-fires the series. // Without a trusted view ahead the alarm cannot exist - the reactive floor // alarm stands alone and TERRAIN LOOKAHEAD OFF explains why static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in) @@ -62,9 +67,14 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold return; } - // How far below the minimum the aircraft arrives at the worst point - // ahead, climbing at full rate from now - const float shortfallCm = in->escapeDeficitCm + (in->minAglCm - in->aglCm); + // Altitude cushion above the minimum offsets the deficit; below the + // minimum the same term would fire the alarm over flat ground, so + // there it is clamped out and only a real forward deficit remains + float cushionCm = in->minAglCm - in->aglCm; + if (cushionCm > 0.0f) { + cushionCm = 0.0f; + } + const float shortfallCm = in->escapeDeficitCm + cushionCm; if (shortfallCm > 0.0f) { state->escapeClearSinceMs = 0; @@ -76,7 +86,7 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold } } else { state->escapeBadSinceMs = 0; - if (shortfallCm < -(float)TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MARGIN_CM) { + if (in->aglCm >= in->minAglCm) { if (state->escapeClearSinceMs == 0) { state->escapeClearSinceMs = (in->nowMs != 0) ? in->nowMs : 1; } @@ -84,7 +94,8 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold state->terrainAheadActive = false; } } else { - // Passing, but without margin: no fresh alarm, no clearing either + // Passing but still below the minimum: not clear yet - the + // aircraft is not where it should be until the minimum is back state->escapeClearSinceMs = 0; } } @@ -161,7 +172,9 @@ static void startCapture(terrainNavHoldState_t *state, const terrainNavHoldInput state->reCapturePending = false; state->minAglReached = false; state->climbBestAglCm = in->aglCm; - state->pullUpActive = false; + // An active PULL UP deliberately survives a capture below the minimum: + // releasing the stick at the lowest point must not extinguish the red + // alarm; updateFloorAlarm clears it the moment the minimum is back updateFloorAlarm(state, in); } diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index 59ed2167844..38e10728145 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -142,10 +142,10 @@ typedef struct { // this long before the alarm fires - no flicker on single noisy samples #define TERRAIN_NAV_HOLD_ESCAPE_PERSIST_MS 1000 -// ...and clears only after the test passes with the margin below for this -// long (typically because the pilot turned, slowed or climbed) +// ...and clears only after the test passes with the aircraft at/above the +// minimum, both sustained this long (typically because the pilot turned, +// slowed or climbed); a returning threat simply re-fires the series #define TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MS 2000 -#define TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MARGIN_CM 1000 void terrainNavHoldCoreReset(terrainNavHoldState_t *state); void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out); From 226abf338b2e46558edcfca8804939d2ad565c4a Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Sat, 8 Aug 2026 00:52:29 +0300 Subject: [PATCH 32/42] feat(terrain-nav): imperative alarms mean live threats only - a below-min capture hands over to the auto-climb info A capture below the minimum (low engagement or a deep stick release) starts a clean alarm phase again: the automatic climb shows TERRAIN AUTO CLIMB TO MIN, and TERRAIN! PULL UP! returns only when the climb is failing (losing height against the best achieved), when the pilot pushes below the margin, or when the floor is breached after having been reached. --- src/main/terrain/terrain_nav_hold_core.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index e8b2f31d48d..f00264d26ce 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -172,9 +172,11 @@ static void startCapture(terrainNavHoldState_t *state, const terrainNavHoldInput state->reCapturePending = false; state->minAglReached = false; state->climbBestAglCm = in->aglCm; - // An active PULL UP deliberately survives a capture below the minimum: - // releasing the stick at the lowest point must not extinguish the red - // alarm; updateFloorAlarm clears it the moment the minimum is back + // A fresh capture hands a below-minimum situation to the automatic climb + // (info message): the red alarm means a live threat only, and returns via + // the losing-ground ratchet, a pilot push below the margin, or a breached + // floor - never just for starting low + state->pullUpActive = false; updateFloorAlarm(state, in); } From 44809ae4954a1410088b13a26d899a94d82df69f Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Tue, 11 Aug 2026 02:23:14 +0300 Subject: [PATCH 33/42] feat(terrain-nav): honest red - TURN AWAY where pulling cannot help + cap-uncover fast clear --- src/main/io/osd.c | 3 ++ src/main/io/osd.h | 1 + src/main/terrain/terrain_nav_hold_core.c | 63 +++++++++++++++++++++--- src/main/terrain/terrain_nav_hold_core.h | 4 ++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 0f3db91d0e2..5cbdfb63743 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6285,6 +6285,9 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter case TERRAIN_NAV_HOLD_WARN_PULL_UP: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_PULL_UP)); break; + case TERRAIN_NAV_HOLD_WARN_TURN_AWAY: + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_TURN_AWAY)); + break; case TERRAIN_NAV_HOLD_WARN_NO_HEADING: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_NO_HEADING)); break; diff --git a/src/main/io/osd.h b/src/main/io/osd.h index 1d976be1083..63d8a3c0fa2 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -152,6 +152,7 @@ #define OSD_MSG_TERRAIN_DATA_LOST "TERRAIN LOST - ALT FROZEN" #define OSD_MSG_TERRAIN_VS_MAX_ALT "TERRAIN VS MAX ALT" #define OSD_MSG_TERRAIN_PULL_UP "TERRAIN! PULL UP!" +#define OSD_MSG_TERRAIN_TURN_AWAY "TERRAIN! TURN AWAY!" #define OSD_MSG_TERRAIN_NO_HEADING "TERRAIN LOOKAHEAD OFF" #define OSD_MSG_TERRAIN_AUTO_CLIMB "TERRAIN AUTO CLIMB TO MIN" #define OSD_MSG_TERRAIN_AHEAD "TERRAIN AHEAD!" diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index f00264d26ce..273e6f14c49 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -34,6 +34,9 @@ void terrainNavHoldCoreReset(terrainNavHoldState_t *state) state->minAglReached = false; state->climbBestAglCm = 0; state->pullUpActive = false; + state->pullUpRatchet = false; + state->pullUpCapFight = false; + state->uncoverCapFight = false; state->stickWasAdjusting = false; state->blendActive = false; state->blendRateCmS = 0.0f; @@ -64,6 +67,7 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold state->terrainAheadActive = false; state->escapeBadSinceMs = 0; state->escapeClearSinceMs = 0; + state->uncoverCapFight = false; return; } @@ -76,6 +80,20 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold } const float shortfallCm = in->escapeDeficitCm + cushionCm; + // A red that ran against the ceiling clamp just cleared: a latched + // caution pointing at sky the aircraft has already escaped must not + // linger - if the escape test passes RIGHT NOW it drops immediately. + // Every other uncover keeps the sustained clear below (anti-flicker) + if (state->uncoverCapFight) { + state->uncoverCapFight = false; + if (state->terrainAheadActive && shortfallCm <= 0.0f) { + state->terrainAheadActive = false; + state->escapeBadSinceMs = 0; + state->escapeClearSinceMs = 0; + return; + } + } + if (shortfallCm > 0.0f) { state->escapeClearSinceMs = 0; if (state->escapeBadSinceMs == 0) { @@ -136,7 +154,20 @@ static void runBlend(terrainNavHoldState_t *state, const terrainNavHoldInput_t * out->rateCmS = state->blendRateCmS; } -// Floor alarm: PULL UP must fire on real AGL, ceiling or not. Once the +// The red text is the honest instruction. Where pulling the stick genuinely +// fixes the lowness (a breached floor, floor breathing, stick pressure) it +// says PULL UP; where pulling cannot help and may delay the right reaction +// it says TURN AWAY - the losing auto-climb (a manual pull commands the +// MANUAL rate, by default LESS than the automatic climb already failing) +// and the fight pinned at the ceiling (the funnel clamps every nav climb) +static terrainNavHoldWarning_e pullUpWarning(const terrainNavHoldState_t *state) +{ + return (state->pullUpRatchet || state->pullUpCapFight) + ? TERRAIN_NAV_HOLD_WARN_TURN_AWAY + : TERRAIN_NAV_HOLD_WARN_PULL_UP; +} + +// Floor alarm: the red must fire on real AGL, ceiling or not. Once the // minimum has been reached, dropping below (min - margin) latches the alarm // and only climbing back to the minimum clears it. During the initial // automatic climb the aircraft is legitimately below the minimum, so there @@ -146,7 +177,14 @@ static void updateFloorAlarm(terrainNavHoldState_t *state, const terrainNavHoldI { if (in->aglCm >= in->minAglCm) { state->minAglReached = true; + // The uncover moment: a red that ran against the ceiling clamp just + // cleared - the escape alarm may drop its stale caution this cycle + if (state->pullUpActive && state->pullUpCapFight) { + state->uncoverCapFight = true; + } state->pullUpActive = false; + state->pullUpRatchet = false; + state->pullUpCapFight = false; return; } @@ -159,6 +197,7 @@ static void updateFloorAlarm(terrainNavHoldState_t *state, const terrainNavHoldI state->climbBestAglCm = in->aglCm; } else if (in->aglCm < state->climbBestAglCm - TERRAIN_NAV_HOLD_PULLUP_HYST_CM) { state->pullUpActive = true; + state->pullUpRatchet = true; } } } @@ -177,6 +216,8 @@ static void startCapture(terrainNavHoldState_t *state, const terrainNavHoldInput // the losing-ground ratchet, a pilot push below the margin, or a breached // floor - never just for starting low state->pullUpActive = false; + state->pullUpRatchet = false; + state->pullUpCapFight = false; updateFloorAlarm(state, in); } @@ -210,7 +251,8 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu // Ceiling arbitration: nav_max_altitude always wins - the funnel clamps // the target for real, here we only detect the conflict and warn. Warns // ahead of the pinch too, because the lookahead demand is in targetZCm - if (in->maxAltCm > 0 && targetZCm > in->maxAltCm) { + const bool capConflict = (in->maxAltCm > 0 && targetZCm > in->maxAltCm); + if (capConflict) { out->warning = TERRAIN_NAV_HOLD_WARN_MAX_ALT; } // Predictive: the terrain ahead cannot be out-climbed at full rate - @@ -218,9 +260,15 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu if (state->terrainAheadActive) { out->warning = TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD; } - // The floor alarm outranks everything: too low NOW and not recovering + // The floor alarm outranks everything: too low NOW and not recovering. + // Latched against the ceiling clamp the red sticks to TURN AWAY for the + // whole episode - a demand dipping under the cap mid-fight must not + // flip the instruction on screen if (state->pullUpActive) { - out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + if (capConflict) { + state->pullUpCapFight = true; + } + out->warning = pullUpWarning(state); } out->writeTarget = true; @@ -297,6 +345,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold state->terrainAheadActive = false; state->escapeBadSinceMs = 0; state->escapeClearSinceMs = 0; + state->uncoverCapFight = false; out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; } // Inside the grace period: a single missed read just pauses @@ -327,7 +376,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold out->warning = TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD; } if (state->pullUpActive) { - out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + out->warning = pullUpWarning(state); } break; } @@ -347,7 +396,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold out->warning = TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD; } if (state->pullUpActive) { - out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + out->warning = pullUpWarning(state); } break; } @@ -369,7 +418,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold // immediately, the release becomes a fresh capture state->reCapturePending = true; if (state->pullUpActive) { - out->warning = TERRAIN_NAV_HOLD_WARN_PULL_UP; + out->warning = pullUpWarning(state); } break; } diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index 38e10728145..d7c66dc709e 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -52,6 +52,7 @@ typedef enum { TERRAIN_NAV_HOLD_WARN_NO_HEADING, // heading estimate invalid: lookahead off, reactive hold continues TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB, // engaged below the minimum AGL - automatic climb to the minimum in progress TERRAIN_NAV_HOLD_WARN_TERRAIN_AHEAD, // escape test failing: terrain ahead cannot be out-climbed at full rate + TERRAIN_NAV_HOLD_WARN_TURN_AWAY, // floor alarm where pulling cannot help: the full-rate climb is losing, or the target is pinned at the ceiling } terrainNavHoldWarning_e; typedef struct { @@ -92,6 +93,9 @@ typedef struct { bool minAglReached; // AGL has reached the minimum since this capture - arms the floor alarm int32_t climbBestAglCm; // best AGL seen while still below the minimum (the auto-climb phase) bool pullUpActive; // floor alarm latched: on below (min - margin), off at/above the minimum + bool pullUpRatchet; // this red latched via the losing-auto-climb ratchet - pulling cannot help, the text says TURN AWAY + bool pullUpCapFight; // this red episode ran against the ceiling clamp - same TURN AWAY text, latched for the episode + bool uncoverCapFight; // one-shot: a cap-fight red just cleared at the minimum - the escape alarm may fast-clear this cycle bool stickWasAdjusting; // previous-cycle stick state (grab-edge detection for the blend) bool blendActive; // handover blend running (stick grab through the retake dwell) float blendRateCmS; // the blend's current climb-rate command From a51e0d761d36d288f76395c5ac649e51eb69e324 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 12 Aug 2026 00:07:06 +0300 Subject: [PATCH 34/42] feat(terrain-nav): honest red v2 - TURN AWAY on exhausted vertical channel + auto-climb info alternation --- src/main/io/osd.c | 11 +++- src/main/terrain/terrain_nav_hold.c | 11 ++++ src/main/terrain/terrain_nav_hold.h | 1 + src/main/terrain/terrain_nav_hold_core.c | 64 ++++++++++++++++++++---- src/main/terrain/terrain_nav_hold_core.h | 6 ++- 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 5cbdfb63743..6d2f25fb2e6 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6272,7 +6272,11 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter } #endif #ifdef USE_TERRAIN - switch (terrainNavHoldGetWarning()) { /* ADDS MAXIMUM OF 1 MESSAGE TO TOTAL */ + /* ADDS MAXIMUM OF 2 MESSAGES TO TOTAL: the worst warning, and - + * while the hold is actually climbing to the minimum - the + * auto-climb info alternating beneath it, so the pilot always + * sees both the danger and the action being taken */ + switch (terrainNavHoldGetWarning()) { case TERRAIN_NAV_HOLD_WARN_NOT_READY: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_NOT_READY)); break; @@ -6300,6 +6304,11 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter case TERRAIN_NAV_HOLD_WARN_NONE: break; } + if (terrainNavHoldAutoClimbRunning() + && terrainNavHoldGetWarning() != TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB + && terrainNavHoldGetWarning() != TERRAIN_NAV_HOLD_WARN_NONE) { + ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_AUTO_CLIMB)); + } #endif if (STATE(AIRPLANE)) { /* ADDS MAXIMUM OF 3 MESSAGES TO TOTAL */ #ifdef USE_FW_AUTOLAND diff --git a/src/main/terrain/terrain_nav_hold.c b/src/main/terrain/terrain_nav_hold.c index 33a61e3ffc4..147603f2af5 100644 --- a/src/main/terrain/terrain_nav_hold.c +++ b/src/main/terrain/terrain_nav_hold.c @@ -232,7 +232,13 @@ void terrainNavCruiseHoldUpdate(void) in.stickWishCmS = constrainf(posControl.desiredState.climbRateDemand, -(float)navConfig()->fw.max_manual_climb_rate, (float)navConfig()->fw.max_manual_climb_rate); + // Near-full pull = the pilot's vertical channel is maxed (for the + // red-text reserve question; 90% leaves room for stick noise) + in.stickFullPull = in.stickWishCmS >= 0.9f * (float)navConfig()->fw.max_manual_climb_rate; } + // Which side holds the unused climb reserve, from the two stock rates + in.pilotHasMoreClimb = navConfig()->fw.max_manual_climb_rate > navConfig()->fw.max_auto_climb_rate; + in.autoBeatsManual = navConfig()->fw.max_auto_climb_rate > navConfig()->fw.max_manual_climb_rate; terrainNavHoldCoreUpdate(&holdState, &in, &holdOutput); @@ -263,6 +269,11 @@ terrainNavHoldWarning_e terrainNavHoldGetWarning(void) return holdOutput.warning; } +bool terrainNavHoldAutoClimbRunning(void) +{ + return holdOutput.autoClimbRunning; +} + bool terrainNavHoldIsEngaged(void) { return holdState.status == TERRAIN_NAV_HOLD_ACTIVE || holdState.status == TERRAIN_NAV_HOLD_FROZEN; diff --git a/src/main/terrain/terrain_nav_hold.h b/src/main/terrain/terrain_nav_hold.h index d04012a41b8..65dc2783422 100644 --- a/src/main/terrain/terrain_nav_hold.h +++ b/src/main/terrain/terrain_nav_hold.h @@ -56,6 +56,7 @@ void terrainNavCruiseHoldUpdate(void); terrainNavHoldStatus_e terrainNavHoldGetStatus(void); terrainNavHoldWarning_e terrainNavHoldGetWarning(void); +bool terrainNavHoldAutoClimbRunning(void); // True while the hold owns the altitude target (active or frozen) bool terrainNavHoldIsEngaged(void); diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index 273e6f14c49..bb022bf0a19 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -154,12 +154,27 @@ static void runBlend(terrainNavHoldState_t *state, const terrainNavHoldInput_t * out->rateCmS = state->blendRateCmS; } -// The red text is the honest instruction. Where pulling the stick genuinely -// fixes the lowness (a breached floor, floor breathing, stick pressure) it -// says PULL UP; where pulling cannot help and may delay the right reaction -// it says TURN AWAY - the losing auto-climb (a manual pull commands the -// MANUAL rate, by default LESS than the automatic climb already failing) -// and the fight pinned at the ceiling (the funnel clamps every nav climb) +// The red text is the honest instruction: PULL UP while unused vertical +// reserve remains (release a push, deepen a pull, or - with manual rate +// above auto - pull at all); TURN AWAY once the vertical channel is +// exhausted and still losing, or clamped by the ceiling. The reserve +// question at the moment height is being LOST: +// - hands off: the hold already commands the full auto rate, so reserve +// exists only when a manual pull outclimbs it (manual > auto) +// - full pull: the pilot's channel is maxed, so reserve exists only when +// releasing hands the climb to a FASTER autopilot (auto > manual) +// - a push or partial pull always leaves reserve (release / pull deeper) +static bool noStickReserve(const terrainNavHoldInput_t *in) +{ + if (!in->stickAdjusting) { + return !in->pilotHasMoreClimb; + } + if (in->stickFullPull) { + return !in->autoBeatsManual; + } + return false; +} + static terrainNavHoldWarning_e pullUpWarning(const terrainNavHoldState_t *state) { return (state->pullUpRatchet || state->pullUpCapFight) @@ -190,16 +205,43 @@ static void updateFloorAlarm(terrainNavHoldState_t *state, const terrainNavHoldI if (state->minAglReached) { if (in->aglCm < in->minAglCm - TERRAIN_NAV_HOLD_PULLUP_HYST_CM) { - state->pullUpActive = true; + if (!state->pullUpActive) { + state->pullUpActive = true; + // The breach fight starts: track its best AGL to detect a + // losing climb (the field is otherwise idle in this phase) + state->climbBestAglCm = in->aglCm; + } + } + if (state->pullUpActive) { + if (in->aglCm > state->climbBestAglCm) { + state->climbBestAglCm = in->aglCm; + } else if (in->aglCm < state->climbBestAglCm - TERRAIN_NAV_HOLD_PULLUP_HYST_CM + && noStickReserve(in)) { + // Still losing height with nothing left to give vertically + state->pullUpRatchet = true; + } } } else { if (in->aglCm > state->climbBestAglCm) { state->climbBestAglCm = in->aglCm; } else if (in->aglCm < state->climbBestAglCm - TERRAIN_NAV_HOLD_PULLUP_HYST_CM) { + // The red fires regardless of cause (a pilot push below the + // margin re-arms it too - sealed semantics); TURN AWAY only + // when no vertical reserve remains state->pullUpActive = true; - state->pullUpRatchet = true; + if (noStickReserve(in)) { + state->pullUpRatchet = true; + } } } + + // Prospective upgrade, any red, any moment: a latched TERRAIN AHEAD has + // already proven that even the FULL climb rate loses the path ahead - + // releasing or pulling cannot save the margin, so the red must not + // promise it. Born-red-under-caution starts as TURN AWAY outright + if (state->pullUpActive && state->terrainAheadActive) { + state->pullUpRatchet = true; + } } // Every capture (engagement or stick release) starts a fresh floor-alarm @@ -244,9 +286,12 @@ static void computeTarget(terrainNavHoldState_t *state, const terrainNavHoldInpu out->warning = TERRAIN_NAV_HOLD_WARN_NO_HEADING; } // Engaged below the minimum: the hold itself is climbing to the floor - - // information, not an alarm, the pilot has nothing to do + // information, not an alarm, the pilot has nothing to do. The flag lets + // the OSD alternate this fact under a covering warning: the pilot must + // always see both the danger and the action already being taken if (!state->minAglReached && in->aglCm < in->minAglCm) { out->warning = TERRAIN_NAV_HOLD_WARN_AUTO_CLIMB; + out->autoClimbRunning = true; } // Ceiling arbitration: nav_max_altitude always wins - the funnel clamps // the target for real, here we only detect the conflict and warn. Warns @@ -297,6 +342,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold out->targetZCm = 0.0f; out->writeRate = false; out->rateCmS = 0.0f; + out->autoClimbRunning = false; out->warning = TERRAIN_NAV_HOLD_WARN_NONE; if (!in->eligible) { diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index d7c66dc709e..f07ebff645e 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -66,6 +66,9 @@ typedef struct { float actualClimbRateCmS; // the aircraft's real vertical speed (seeds the handover blend) bool stickWishValid; // the pilot's stick climb-rate wish is known this cycle float stickWishCmS; // what the stick currently asks for, manual-clamped by the caller + bool stickFullPull; // the pitch stick is at (or near) full pull - the pilot's vertical channel is maxed + bool pilotHasMoreClimb; // nav_fw_manual_climb_rate > nav_fw_auto_climb_rate: a pull adds climb the hold cannot + bool autoBeatsManual; // nav_fw_auto_climb_rate > nav_fw_manual_climb_rate: releasing the stick adds climb a pull cannot bool lookaheadValid; // lookahead answer available float lookaheadClimbCm; // worst height deficit along the path ahead (>= 0) bool escapeDeficitValid; // escape-test answer available (same walk as the lookahead) @@ -80,6 +83,7 @@ typedef struct { float targetZCm; bool writeRate; // when true the caller commands rateCmS instead (handover blend window) float rateCmS; // signed climb rate; bounded by the caller's clamp on the blend inputs + bool autoClimbRunning; // the hold is climbing to the minimum right now (for the OSD to alternate the info under a covering warning) terrainNavHoldStatus_e status; terrainNavHoldWarning_e warning; } terrainNavHoldOutput_t; @@ -91,7 +95,7 @@ typedef struct { uint32_t lastUsableMs; // last time data was usable (for the freeze grace period) bool reCapturePending; // stick released - capture a new target AGL on the next usable cycle bool minAglReached; // AGL has reached the minimum since this capture - arms the floor alarm - int32_t climbBestAglCm; // best AGL seen while still below the minimum (the auto-climb phase) + int32_t climbBestAglCm; // best AGL of the current below-minimum fight (the auto-climb phase, and the breach episode after the minimum was reached) bool pullUpActive; // floor alarm latched: on below (min - margin), off at/above the minimum bool pullUpRatchet; // this red latched via the losing-auto-climb ratchet - pulling cannot help, the text says TURN AWAY bool pullUpCapFight; // this red episode ran against the ceiling clamp - same TURN AWAY text, latched for the episode From 8bf3dbf791da31cf8b462209f5b54f495b253388 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 12 Aug 2026 02:17:33 +0300 Subject: [PATCH 35/42] feat(terrain-nav): blink the terrain floor warnings like the failsafe info text --- src/main/io/osd.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 6d2f25fb2e6..508488bede1 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6140,6 +6140,12 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter const char *messages[8]; unsigned messageCount = 0; #define ADD_MSG(msg) do { if (messageCount < ARRAYLEN(messages)) messages[messageCount++] = (msg); } while(0) +#ifdef USE_TERRAIN + /* The terrain floor warnings are crash-avoidance class: they blink + * like the failsafe info text below. Remembering the stored pointer + * lets the blink decision match the exact message on display */ + const char *terrainUrgentMessage = NULL; +#endif const char *failsafeInfoMessage = NULL; const char *invertedInfoMessage = NULL; @@ -6288,9 +6294,15 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter break; case TERRAIN_NAV_HOLD_WARN_PULL_UP: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_PULL_UP)); + if (messageCount) { + terrainUrgentMessage = messages[messageCount - 1]; + } break; case TERRAIN_NAV_HOLD_WARN_TURN_AWAY: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_TURN_AWAY)); + if (messageCount) { + terrainUrgentMessage = messages[messageCount - 1]; + } break; case TERRAIN_NAV_HOLD_WARN_NO_HEADING: ADD_MSG(OSD_MESSAGE_STR(OSD_MSG_TERRAIN_NO_HEADING)); @@ -6437,6 +6449,13 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter // a lost model, but might help avoiding a crash. // Blink to grab user attention. TEXT_ATTRIBUTES_ADD_BLINK(elemAttr); +#ifdef USE_TERRAIN + } else if (terrainUrgentMessage && message == terrainUrgentMessage) { + // The terrain floor warnings are the same class: act NOW to + // avoid the terrain. The no-blink note below protects + // recovery info, not urgent warnings + TEXT_ATTRIBUTES_ADD_BLINK(elemAttr); +#endif } else if (message == invertedInfoMessage) { TEXT_ATTRIBUTES_ADD_INVERTED(elemAttr); } From 0de1c84a5ce815b27840158fc57e292de4eb3a1c Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 12 Aug 2026 11:02:10 +0300 Subject: [PATCH 36/42] feat(terrain-nav): born-TURN-AWAY requires a deep (>=10 m) escape shortfall --- src/main/terrain/terrain_nav_hold_core.c | 14 ++++++++++++-- src/main/terrain/terrain_nav_hold_core.h | 8 ++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/terrain/terrain_nav_hold_core.c b/src/main/terrain/terrain_nav_hold_core.c index bb022bf0a19..db755ec40bb 100644 --- a/src/main/terrain/terrain_nav_hold_core.c +++ b/src/main/terrain/terrain_nav_hold_core.c @@ -46,6 +46,7 @@ void terrainNavHoldCoreReset(terrainNavHoldState_t *state) state->terrainAheadActive = false; state->escapeBadSinceMs = 0; state->escapeClearSinceMs = 0; + state->escapeDeepFail = false; } // TERRAIN AHEAD - the predictive escape test. Fails when even the FULL @@ -67,6 +68,7 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold state->terrainAheadActive = false; state->escapeBadSinceMs = 0; state->escapeClearSinceMs = 0; + state->escapeDeepFail = false; state->uncoverCapFight = false; return; } @@ -80,6 +82,11 @@ static void updateEscapeAlarm(terrainNavHoldState_t *state, const terrainNavHold } const float shortfallCm = in->escapeDeficitCm + cushionCm; + // Depth of the failure, stashed for the floor alarm's prospective + // upgrade: only a shortfall this deep proves the vertical channel + // truly exhausted (a marginal map-step deficit must not birth TURN AWAY) + state->escapeDeepFail = (shortfallCm >= TERRAIN_NAV_HOLD_ESCAPE_DEEP_CM); + // A red that ran against the ceiling clamp just cleared: a latched // caution pointing at sky the aircraft has already escaped must not // linger - if the escape test passes RIGHT NOW it drops immediately. @@ -238,8 +245,10 @@ static void updateFloorAlarm(terrainNavHoldState_t *state, const terrainNavHoldI // Prospective upgrade, any red, any moment: a latched TERRAIN AHEAD has // already proven that even the FULL climb rate loses the path ahead - // releasing or pulling cannot save the margin, so the red must not - // promise it. Born-red-under-caution starts as TURN AWAY outright - if (state->pullUpActive && state->terrainAheadActive) { + // promise it. Born-red-under-caution starts as TURN AWAY outright. + // Only a DEEP shortfall qualifies: a marginal deficit stays PULL UP + // (and escalates within a second if a real wall deepens it) + if (state->pullUpActive && state->terrainAheadActive && state->escapeDeepFail) { state->pullUpRatchet = true; } } @@ -391,6 +400,7 @@ void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHold state->terrainAheadActive = false; state->escapeBadSinceMs = 0; state->escapeClearSinceMs = 0; + state->escapeDeepFail = false; state->uncoverCapFight = false; out->warning = TERRAIN_NAV_HOLD_WARN_DATA_LOST; } diff --git a/src/main/terrain/terrain_nav_hold_core.h b/src/main/terrain/terrain_nav_hold_core.h index f07ebff645e..8f5cd5d196a 100644 --- a/src/main/terrain/terrain_nav_hold_core.h +++ b/src/main/terrain/terrain_nav_hold_core.h @@ -109,6 +109,7 @@ typedef struct { bool terrainAheadActive; // predictive escape alarm latched uint32_t escapeBadSinceMs; // escape test failing since (persistence); 0 = not failing uint32_t escapeClearSinceMs;// escape test passing with margin since (clear hysteresis); 0 = not counting + bool escapeDeepFail; // the current escape shortfall is at least the deep threshold - only such a failure births TURN AWAY prospectively } terrainNavHoldState_t; // A momentary AGL gap (single cache miss) only pauses target updates; the hold @@ -155,5 +156,12 @@ typedef struct { // slowed or climbed); a returning threat simply re-fires the series #define TERRAIN_NAV_HOLD_ESCAPE_CLEAR_MS 2000 +// A red born under a latched TERRAIN AHEAD says TURN AWAY outright only when +// the current escape shortfall is at least this deep: a marginal map-step +// deficit self-resolves in seconds and stays PULL UP, while the deficit of a +// genuine wall grows so fast at cruise closure that it crosses this line +// within a second of the alarm +#define TERRAIN_NAV_HOLD_ESCAPE_DEEP_CM 1000.0f + void terrainNavHoldCoreReset(terrainNavHoldState_t *state); void terrainNavHoldCoreUpdate(terrainNavHoldState_t *state, const terrainNavHoldInput_t *in, terrainNavHoldOutput_t *out); From 06377fc816dd10a0b818d6a1e232e09e27143789 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Sat, 15 Aug 2026 17:05:06 +0300 Subject: [PATCH 37/42] docs: update Settings.md (terrain_nav settings) --- docs/Settings.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/Settings.md b/docs/Settings.md index ffd58c96604..f90cf010987 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -6948,6 +6948,26 @@ Enable load terrain data from SD card --- +### terrain_nav_lookahead + +TERRAIN AGL HOLD: check terrain up to this distance ahead along the flight path and climb early for rising ground [m]. 0 disables the lookahead. The effective distance is also limited by the terrain block cache of the flight controller + +| Default | Min | Max | +| --- | --- | --- | +| 1000 | 0 | 2000 | + +--- + +### terrain_nav_min_agl + +TERRAIN AGL HOLD: minimum held height above ground [cm]. Engaging below it commands a gentle climb to it. Covers worst-case terrain map error on steep slopes plus canopy - lowering it below the default is not recommended + +| Default | Min | Max | +| --- | --- | --- | +| 6000 | 3000 | 12000 | + +--- + ### thr_comp_weight Weight used for the throttle compensation based on battery voltage. See the [battery documentation](Battery.md#automatic-throttle-compensation-based-on-battery-voltage) From a542c09553433f98ea22f351e052c998f7dfb92b Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Sun, 16 Aug 2026 00:05:51 +0300 Subject: [PATCH 38/42] feat(terrain-nav): raise terrain_nav_min_agl minimum to 5000 (50 m) At a 30 m floor the alarm point (floor - 5 m) minus the worst-case map error budget (30 m on steep slopes) minus the tracking band leaves no real clearance. 50 m is the lowest floor where the worst-case stack still clears with margin. Default unchanged at 6000. --- docs/Settings.md | 4 ++-- src/main/fc/settings.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index f90cf010987..b92632ec3ba 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -6960,11 +6960,11 @@ TERRAIN AGL HOLD: check terrain up to this distance ahead along the flight path ### terrain_nav_min_agl -TERRAIN AGL HOLD: minimum held height above ground [cm]. Engaging below it commands a gentle climb to it. Covers worst-case terrain map error on steep slopes plus canopy - lowering it below the default is not recommended +TERRAIN AGL HOLD: minimum held height above ground [cm]. Engaging below it commands a gentle climb to it. The floor absorbs worst-case terrain map error on steep slopes plus canopy - the minimum is the lowest value where that error budget still clears | Default | Min | Max | | --- | --- | --- | -| 6000 | 3000 | 12000 | +| 6000 | 5000 | 12000 | --- diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 1223442cb6e..f727b5b84c1 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -4659,10 +4659,10 @@ groups: condition: USE_TERRAIN members: - name: terrain_nav_min_agl - description: "TERRAIN AGL HOLD: minimum held height above ground [cm]. Engaging below it commands a gentle climb to it. Covers worst-case terrain map error on steep slopes plus canopy - lowering it below the default is not recommended" + description: "TERRAIN AGL HOLD: minimum held height above ground [cm]. Engaging below it commands a gentle climb to it. The floor absorbs worst-case terrain map error on steep slopes plus canopy - the minimum is the lowest value where that error budget still clears" default_value: 6000 field: minAglCm - min: 3000 + min: 5000 max: 12000 - name: terrain_nav_lookahead description: "TERRAIN AGL HOLD: check terrain up to this distance ahead along the flight path and climb early for rising ground [m]. 0 disables the lookahead. The effective distance is also limited by the terrain block cache of the flight controller" From e6e5529b017d0b689afaca160dab0469a1302bf6 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 19 Aug 2026 19:18:07 +0300 Subject: [PATCH 39/42] feat(terrain-nav): decode terrain heights via the 10-bit packed grid format The terrain data layer now stores heights as a 10-bit packed heightOffset[] plus an int16 heightBase instead of a plain int16 height[x][y] grid. Read the four interpolation corners through getHeightOffsetByIndex() + heightBase, scaled by TERRAIN_HEIGHT_OFFSET_RESOLUTION_M (2 m), the same way terrain.c decodes a sample; the interpolation math is unchanged. --- src/main/terrain/terrain_nav.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/terrain/terrain_nav.c b/src/main/terrain/terrain_nav.c index a99d722a14a..2a98f955f10 100644 --- a/src/main/terrain/terrain_nav.c +++ b/src/main/terrain/terrain_nav.c @@ -103,10 +103,10 @@ bool terrainNavGetHeightAtLocation(const gpsLocation_t *loc, float *heightM) return false; } - const int16_t h00 = grid->height[info.idx_x][info.idx_y]; - const int16_t h01 = grid->height[info.idx_x][info.idx_y + 1]; - const int16_t h10 = grid->height[info.idx_x + 1][info.idx_y]; - const int16_t h11 = grid->height[info.idx_x + 1][info.idx_y + 1]; + const int16_t h00 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x, info.idx_y); + const int16_t h01 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x, info.idx_y + 1); + const int16_t h10 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x + 1, info.idx_y); + const int16_t h11 = grid->heightBase + TERRAIN_HEIGHT_OFFSET_RESOLUTION_M * getHeightOffsetByIndex(grid, info.idx_x + 1, info.idx_y + 1); const float avg1 = (1.0f - info.frac_x) * h00 + info.frac_x * h10; const float avg2 = (1.0f - info.frac_x) * h01 + info.frac_x * h11; From c2ce3b48d571288a66feb32ceddbaa098cca4ba0 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Wed, 19 Aug 2026 19:18:07 +0300 Subject: [PATCH 40/42] feat(SITL): mirror the new small-RAM terrain cache tier (4) --- src/main/target/SITL/target.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/target/SITL/target.h b/src/main/target/SITL/target.h index ba2d6f1d818..7052b29e978 100644 --- a/src/main/target/SITL/target.h +++ b/src/main/target/SITL/target.h @@ -76,7 +76,7 @@ #define USE_SDCARD #define USE_SDCARD_SITL #define USE_TERRAIN -#define TERRAIN_GRID_BLOCK_CACHE_SIZE 5 // 2048 bytes = 1 grid block; 5 mirrors the small-cache (F405 class) tier +#define TERRAIN_GRID_BLOCK_CACHE_SIZE 4 // mirror the small-RAM (F7/AT32) terrain cache tier of the new base (each entry ~1.1 KB packed) #define USE_DRONECAN #define DRONECAN_SITL_INTERFACE "vcan0" From daea1b73ce966008460e3a55deded27c88c8bb39 Mon Sep 17 00:00:00 2001 From: Emiliyan Martinov Date: Thu, 20 Aug 2026 01:51:55 +0300 Subject: [PATCH 41/42] docs: add Terrain AGL Hold pilot guide --- docs/Navigation Terrain Following.md | 87 ++++++++++++++++++ .../images/Terrain-AGL-Hold-diagram.png | Bin 0 -> 412645 bytes 2 files changed, 87 insertions(+) create mode 100644 docs/Navigation Terrain Following.md create mode 100644 docs/assets/images/Terrain-AGL-Hold-diagram.png diff --git a/docs/Navigation Terrain Following.md b/docs/Navigation Terrain Following.md new file mode 100644 index 00000000000..8bcc42c0e11 --- /dev/null +++ b/docs/Navigation Terrain Following.md @@ -0,0 +1,87 @@ +# Terrain AGL Hold + +Terrain AGL Hold is INAV's **terrain-following** mode for fixed wing. It is a **3D Cruise** enhancement that holds a set height **above the ground** instead of above the take-off point — *Cruise with a moving target*. As the ground rises and falls under you, the altitude target rides with it, so the aircraft keeps its clearance over hills and valleys with no stick input: downhill it descends with the valley, uphill it climbs with the ridge. + +It reads the ground elevation from the terrain elevation data on the SD card (see **[Terrain](Terrain.md)** for generating and loading tiles); it never uses a physical rangefinder. With the mode switched off, the aircraft flies exactly like stock 3D Cruise — everything here is opt-in, and **the pitch stick always wins** (the mode box is an instant in-flight kill switch). + +## How it works + +![How Terrain AGL Hold works](assets/images/Terrain-AGL-Hold-diagram.png) + +*One navigation cycle — inputs, health gate, the moving target, the alarm ladder, and the single gate into the altitude target path (the stock controller, untouched).* + +## Requirements + +- A flight controller with terrain support (SD card + barometer): **STM32H7, STM32F7 (F745 / F746 / F765), or AT32F43x**. F4 boards are not enabled for terrain by default. +- **Fixed-wing only.** A valid **GPS fix** and a **barometer**. +- **Terrain tiles** (`.TER`) on the SD card, **including the tile for your take-off site** — see [Terrain](Terrain.md). +- **3D Cruise must be active.** At this stage Terrain AGL Hold works **only together with NAV CRUISE** — this is a mandatory safety condition for now, not just a recommendation. + +## Not the same as copter SURFACE mode + +Multirotor **SURFACE** mode follows the ground *reactively* from a physical rangefinder — the instantaneous distance straight below. Terrain AGL Hold is the fixed-wing equivalent taken from the SD terrain map, and crucially it is **predictive**: it scans **up to 2 km ahead** along your track and climbs/warns *early* for terrain rising in front of you — something a downward rangefinder cannot do. The two never stack (this mode refuses to engage while SURFACE is active), and SURFACE mode itself is multirotor-only. + +## What it does + +- **Captures** your current height above ground when you switch it on, and holds it. Move the **pitch stick** to set a new height; centre the stick and it holds the new one. +- Engage **below the safety floor** → it climbs smoothly up to the floor first. +- **Looks ahead** along your track (up to 2 km, 1 km by default) with a per-airframe **escape test** — *"at this speed, with your configured climb rate, will you clear what's ahead?"* — and warns you **early, while there's still time**. +- If the terrain data is lost mid-flight, it **holds the last valid altitude target and warns** on the OSD — it never descends on dead data. +- The stock 3D Cruise altitude controller is **untouched**; this only feeds it a moving target. +- **Not used for autonomous navigation.** RTH and waypoint missions do **not** use it yet — the mode yields to them and they fly stock (terrain-blind). + +## Setup + +1. **Prepare and enable terrain data** — see **[Terrain](Terrain.md)**: generate `.TER` tiles for your area (include the take-off tile), copy them to the SD card root, then in the CLI: + ``` + set terrain_enabled = ON + save + ``` +2. **Assign the mode.** In the Configurator **Modes** tab (or via CLI `aux`), put **TERRAIN AGL HOLD** on a spare, **deliberately guarded** switch — not next to ARM. It is active only inside 3D Cruise. + > **Hint (once you are confident with the mode):** you *can* put **NAV CRUISE** and **TERRAIN AGL HOLD** on the **same switch** so one flip arms both. This is **not** the recommended default — keeping them on **separate** switches lets you enter Cruise first, confirm it, and engage terrain hold as a deliberate second step. +3. **Set the floor and look-ahead** if you want (defaults are sensible — see *Settings*). +4. **OSD:** add the **Rangefinder** element to see your height above ground (AGL) in flight. The flight-mode field shows **TERR** while the hold is engaged. +5. **Fly** in 3D Cruise, then flip the switch — it captures and holds your current AGL. **Panic rule: box off = stock cruise instantly; the pitch stick always wins.** + +## Settings + +| Setting | Default | Range | Meaning | +|---|---|---|---| +| `terrain_nav_min_agl` | 60 m | 50 – 120 m | The **safety floor** — the lowest height above ground the mode will hold. Engage below it and it climbs up to it. (Stored in centimetres: 6000 = 60 m.) | +| `terrain_nav_lookahead` | 1000 m | 0 – 2000 m | How far ahead along your track the **forward scan** looks for rising terrain. `0` disables the early *TERRAIN AHEAD!* warning, leaving only the reactive floor. | + +> **NB — keep your climb rates honest.** The climb authority is your existing **`nav_fw_auto_climb_rate`** — there is no separate terrain climb setting, and the *TERRAIN AHEAD!* escape warning **trusts that number**, so set it to a rate your model genuinely sustains. Keep **`nav_fw_manual_climb_rate` at least equal to `nav_fw_auto_climb_rate`** (they default to 300 / 500) — or **greater**, if you want pulling the stick to actually add climb. If `nav_fw_manual_climb_rate` is **lower** than `nav_fw_auto_climb_rate`, grabbing pitch during an auto-climb commands the lower rate: the nose visibly eases and it can even **slow** the climb. + +## Behaviour in the air + +- **Engage at or above the floor** → captures your current AGL and holds it. +- **Engage below the floor** → climbs to the floor (*TERRAIN AUTO CLIMB TO MIN*). +- **Pitch stick** → sets a new held AGL. The stick always wins: it pauses the hold the moment it moves (smooth blend, no jump); centre it and the current height is re-captured. +- **Hands off over a hill** → the target rides the terrain up and back down. +- **Sharp turns while riding the floor** clip the safety margin — bank with height in hand. + +## OSD messages — the alarm ladder + +The mode narrates itself on the OSD. Higher-priority messages take the slot; while the autopilot is auto-climbing under a warning, the two alternate so you always see both the danger and the action. + +| Message | Kind | Meaning | What to do | +|---|---|---|---| +| **TERRAIN! TURN AWAY!** | Warning | Too low **and** climbing can no longer save you (full climb still loses, or you're sinking, or pinned at your altitude ceiling). | **Turn away immediately** (manual + full throttle). Don't count on pulling up. | +| **TERRAIN! PULL UP!** | Warning | Too low, but climbing **still works**. | Release the stick — the autopilot climbs at full rate — or pull **if `nav_fw_manual_climb_rate` > `nav_fw_auto_climb_rate`**; if in doubt, turn as well. | +| **TERRAIN AHEAD!** | Caution | The forward scan says that at this speed the slope ahead beats a full-rate climb — **you have time now**. | Turn, slow down, or climb early — while it's cheap. | +| **TERRAIN VS MAX ALT** | Caution | The terrain needs more altitude than your `nav_max_altitude` ceiling allows (only if a ceiling is set). | Raise/remove the ceiling, or accept reduced clearance at the wall. | +| **TERRAIN AUTO CLIMB TO MIN** | Info | The autopilot is already climbing you to the floor. | Nothing — it's handled. | +| **TERRAIN NOT READY** | Info | Engagement refused — no usable terrain data yet. | Fly stock cruise; check tiles / SD card if it persists. | +| **TERRAIN LOST - ALT FROZEN** | Caution | Data lost mid-hold — the altitude target is **frozen at the last valid value**. | Never descend blind; navigate out on the frozen altitude (it resumes after a few seconds of good data). | +| **TERRAIN LOOKAHEAD OFF** | Info | The forward scan is unavailable (no heading estimate). | Only the reactive floor guards you now. | + +> **About `nav_max_altitude`:** that ceiling is **barometric altitude above your Home point** — it is *not* terrain-AGL aware. So over rising ground the terrain can legitimately need more altitude than the ceiling allows; that is exactly when *TERRAIN VS MAX ALT* appears. + +## Safety & limitations + +- **Fixed-wing only.** It never runs on multirotors, rovers, or boats, and it works **only inside 3D Cruise** (mandatory at this stage). +- **Not for autonomous navigation yet.** RTH and waypoint missions do not use it — the mode **yields cleanly to stock behaviour** (hands the aircraft back) during launch, autoland, emergency landing, RTH, and waypoint missions, and on **GPS loss**. Stock RTH is terrain-blind: it does not follow the ground. +- The **floor is deliberately high** (default 60 m, minimum 50 m). Terrain data and GPS each carry a few metres of error, so keep clearance in hand — don't set the floor low. +- **Not a rangefinder replacement.** The value is the ground elevation at your GPS position from the map — it does **not** see trees, buildings, wires, or the actual nearest object below you. +- **Never stacks with the rangefinder** — it refuses to engage while SURFACE mode is active. +- If terrain data is lost while engaged, the target **freezes at the last valid altitude** (*TERRAIN LOST - ALT FROZEN*) — it never descends on dead data. diff --git a/docs/assets/images/Terrain-AGL-Hold-diagram.png b/docs/assets/images/Terrain-AGL-Hold-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..780dec211de6c88d25ef6033c29526f34c742914 GIT binary patch literal 412645 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

Fnf_VA?!r-AhFf#fp%R{Fbm?x4QpGObWy4yAF- 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


f_z!>J87|;L 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�pB+htGtb&e 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 Date: Fri, 21 Aug 2026 00:42:37 +0300 Subject: [PATCH 42/42] fix(sitl): cast blackbox log number for %ld in OSD stats (64-bit hosts) The blackbox log-number stat compiles on SITL only once USE_SDCARD is enabled (added for SITL SD-card support in this branch). On 64-bit hosts int32_t is not long, and clang on macOS errors on the format mismatch with warnings-as-errors. Cast the argument to match the format. --- src/main/io/osd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 508488bede1..a065675ac43 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -5459,7 +5459,7 @@ static void osdShowStats(bool isSinglePageStatsCompatible, uint8_t page) int32_t logNumber = blackboxGetLogNumber(); if (logNumber >= 0) { - tfp_sprintf(buff, ": %05ld ", logNumber); + tfp_sprintf(buff, ": %05ld ", (long)logNumber); } else { strcat(buff, ": INVALID"); }