Skip to content

Commit f26cbdc

Browse files
committed
terrain data from SD card functionality to inav
1 parent c428cc9 commit f26cbdc

65 files changed

Lines changed: 1898 additions & 31 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ launch.json
4646
# Assitnow token and files for test script
4747
tokens.yaml
4848
*.ubx
49+
/.idea
50+
51+
/testing/
4952

5053
# Local development files
5154
.semgrepignore

docs/Settings.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6372,6 +6372,16 @@ Which aux channel to use to change serial output & baud rate (MSP / Telemetry).
63726372

63736373
---
63746374

6375+
### terrain_enabled
6376+
6377+
Enable load terrain data from SD card
6378+
6379+
| Default | Min | Max |
6380+
| --- | --- | --- |
6381+
| OFF | OFF | ON |
6382+
6383+
---
6384+
63756385
### thr_comp_weight
63766386

63776387
Weight used for the throttle compensation based on battery voltage. See the [battery documentation](Battery.md#automatic-throttle-compensation-based-on-battery-voltage)

docs/Terrain.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Terrain
2+
```
3+
┌─────────────────────────────────────────────────┐
4+
│ ===Experimental=== │
5+
│ This feature is experimental. Use it with │
6+
│ caution. │
7+
└─────────────────────────────────────────────────┘
8+
```
9+
10+
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
11+
and SD card read speed requirements of terrain data processing, it is not supported on weaker hardware.
12+
13+
This feature in iNav determines the model’s altitude above ground level using preloaded elevation maps
14+
(terrain / SRTM data) stored on an SD card, without requiring a physical rangefinder. Based on the current GPS position, the
15+
flight controller identifies the corresponding point in the terrain map, calculates the ground elevation, and derives the
16+
altitude above terrain.
17+
18+
In this first implementation, the calculated value is used **only for informational display in the OSD**. It is indicative
19+
only, does not yet behave as a true virtual rangefinder, and is not used for navigation or automatic altitude control. If the
20+
terrain data are unavailable or a read error occurs, the feature is automatically disabled.
21+
22+
# SD Card Preparation
23+
24+
For proper operation, the SD card must be prepared in advance. It is recommended to create a **partition with a maximum size
25+
of 4 GB** and format it. Formatting should be done using the official
26+
[**SD Memory Card Formatter**](https://www.sdcard.org/downloads/formatter/sd-memory-card-formatter-for-windows-download/) tool from the SD Association,
27+
which ensures correct alignment and a compatible file system structure. Using this tool minimizes the risk of terrain data
28+
read issues during flight and is considered the recommended method for preparing an SD card for iNav.
29+
30+
# Data Generation and Copying
31+
32+
To generate elevation maps, use the terrain generator web tool available at https://terrain.ardupilot.org/
33+
34+
In iNav, **only 30 m resolution (SRTM1)** is currently supported, so this option must be selected during data generation.
35+
The generated files are then copied to the SD card into the appropriate directory structure.
36+
37+
Copying can be done via **iNav MSC (Mass Storage Class)**, but this method is very slow, so using an **external SD card reader**
38+
is strongly recommended. Before copying the data, the file **`FREESPAC.E`** must be deleted from the root directory of the SD
39+
card. iNav uses this file to track available disk space, and without deleting it, the card may appear to be full. After the
40+
copying process is complete, iNav will automatically recreate this file on the next startup.
41+
42+
# Enabling and Displaying Terrain Data
43+
44+
To display altitude above terrain in iNav, the OSD element **“Rangefinder distance”** must be enabled. If terrain data are
45+
available on the SD card and no valid data are available from a dedicated rangefinder, the value calculated by the terrain
46+
system will be displayed. If a rangefinder is present and providing valid data, its measurements always take priority and the
47+
actual distance to the ground will be shown.
48+
49+
Loading terrain data from the SD card is enabled via the CLI using the following command:
50+
51+
```text
52+
set terrain_enabled = ON
53+
save
54+
```
55+
56+
After restarting the flight controller, iNav will automatically start loading terrain data and, when conditions are met, use
57+
them to display altitude above terrain.
58+
59+
Finally, it is **strongly recommended to use only high-quality, branded SD cards** from reputable manufacturers. The terrain
60+
system is sensitive to SD card read speed and reliability, and low-quality or counterfeit cards may cause read errors,
61+
display dropouts, or automatic disabling of the feature during flight. Using a quality SD card significantly improves the
62+
stability and reliability of the terrain feature.
63+
64+
# TL;DR
65+
66+
- **H7/F4 flight controller** with SD card required
67+
- 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)
68+
- Generate terrain data at https://terrain.ardupilot.org/ — select **SRTM1 (30 m)**
69+
- Delete `FREESPAC.E` from SD card root before copying files (use external card reader, not MSC)
70+
- Enable via CLI: `set terrain_enabled = ON` + `save`
71+
- Enable **Rangefinder distance** OSD element to see altitude above terrain
72+
- ⚠️ Displayed value is **informational only** — not used for navigation or altitude control
73+
- Use only **high-quality branded SD cards**

src/main/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,15 @@ main_sources(COMMON_SRC
626626
telemetry/sim.h
627627
telemetry/telemetry.c
628628
telemetry/telemetry.h
629+
630+
terrain/terrain.h
631+
terrain/terrain.c
632+
terrain/terrain_utils.h
633+
terrain/terrain_utils.c
634+
terrain/terrain_io.h
635+
terrain/terrain_io.c
636+
terrain/terrain_location.h
637+
terrain/terrain_location.c
629638
)
630639

631640
add_subdirectory(target)

src/main/blackbox/blackbox.c

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565

6666
#include "io/beeper.h"
6767
#include "io/gps.h"
68+
#include "io/asyncfatfs/asyncfatfs.h"
69+
6870

6971
#include "navigation/navigation.h"
7072

@@ -84,6 +86,8 @@
8486
#include "flight/wind_estimator.h"
8587
#include "sensors/temperature.h"
8688

89+
#include "terrain/terrain.h"
90+
8791

8892
#if defined(ENABLE_BLACKBOX_LOGGING_ON_SPIFLASH_BY_DEFAULT)
8993
#define DEFAULT_BLACKBOX_DEVICE BLACKBOX_DEVICE_FLASH
@@ -196,6 +200,54 @@ typedef struct blackboxDeltaFieldDefinition_s {
196200
uint8_t condition; // Decide whether this field should appear in the log
197201
} blackboxDeltaFieldDefinition_t;
198202

203+
static bool canUseBlackboxWithCurrentConfiguration(void)
204+
{
205+
return feature(FEATURE_BLACKBOX);
206+
}
207+
208+
#ifdef USE_TERRAIN
209+
//state machine to get access to other device, it's designed only for one other device, in this case for terrain
210+
//if you would like to have more devices, some queue must be implemented
211+
static struct blackboxSDCardAccessStatus_s {
212+
bool blackboxAccessToSDGrantedToOtherDevice;
213+
bool requestToSdCardAccessState;
214+
215+
} blackboxSDCardAccessStatus = {
216+
.blackboxAccessToSDGrantedToOtherDevice = false,
217+
.requestToSdCardAccessState = false
218+
};
219+
220+
221+
/**
222+
* request access from terrain subsystem
223+
* @return
224+
*/
225+
bool requestToSdCardAccess(void)
226+
{
227+
if(blackboxConfig()->device != BLACKBOX_DEVICE_SDCARD || !canUseBlackboxWithCurrentConfiguration()){
228+
return true;
229+
}
230+
231+
if(blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice){
232+
return true;
233+
}
234+
235+
blackboxSDCardAccessStatus.requestToSdCardAccessState = true;
236+
return false;
237+
}
238+
239+
/**
240+
* release access from terrain subsystem
241+
* @return
242+
*/
243+
void releaseSdCardAccess(void)
244+
{
245+
blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice = false;
246+
blackboxSDCardAccessStatus.requestToSdCardAccessState = false;
247+
}
248+
#endif
249+
250+
199251
/**
200252
* Description of the blackbox fields we are writing in our main intra (I) and inter (P) frames. This description is
201253
* written into the flight log header so the log can be properly interpreted (but these definitions don't actually cause
@@ -2176,6 +2228,24 @@ static void blackboxLogIteration(timeUs_t currentTimeUs)
21762228
*/
21772229
void blackboxUpdate(timeUs_t currentTimeUs)
21782230
{
2231+
#ifdef USE_TERRAIN
2232+
if(blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD){
2233+
//access to SD card is given to other device
2234+
if(blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice){
2235+
return;
2236+
}
2237+
2238+
//incooming request to get access to SD card
2239+
if(blackboxSDCardAccessStatus.requestToSdCardAccessState && (blackboxState == BLACKBOX_STATE_RUNNING || blackboxState == BLACKBOX_STATE_STOPPED)){
2240+
//we have to be sure that all writes are already processed and SD card is in idle
2241+
if(afatfs_isIdle()){
2242+
blackboxSDCardAccessStatus.requestToSdCardAccessState = false;
2243+
blackboxSDCardAccessStatus.blackboxAccessToSDGrantedToOtherDevice = true;
2244+
}
2245+
}
2246+
}
2247+
#endif
2248+
21792249
if (blackboxState >= BLACKBOX_FIRST_HEADER_SENDING_STATE && blackboxState <= BLACKBOX_LAST_HEADER_SENDING_STATE) {
21802250
blackboxReplenishHeaderBudget();
21812251
}
@@ -2306,11 +2376,6 @@ void blackboxUpdate(timeUs_t currentTimeUs)
23062376
}
23072377
}
23082378

2309-
static bool canUseBlackboxWithCurrentConfiguration(void)
2310-
{
2311-
return feature(FEATURE_BLACKBOX);
2312-
}
2313-
23142379
BlackboxState getBlackboxState(void)
23152380
{
23162381
return blackboxState;

src/main/blackbox/blackbox.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,6 @@ void blackboxIncludeFlagSet(uint32_t mask);
7575
void blackboxIncludeFlagClear(uint32_t mask);
7676
bool blackboxIncludeFlag(uint32_t mask);
7777
BlackboxState getBlackboxState(void);
78+
79+
bool requestToSdCardAccess(void);
80+
void releaseSdCardAccess(void);

src/main/blackbox/blackbox_io.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,16 @@ static bool blackboxSDCardBeginLog(void)
435435
if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY) {
436436
blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
437437

438-
afatfs_mkdir("logs", blackboxLogDirCreated);
438+
if(afatfs_isCurrentDirRoot()){
439+
//we are in root of SD card, we have to create or move to log directory
440+
afatfs_mkdir("logs", blackboxLogDirCreated);
441+
}
442+
else
443+
{
444+
//we are already in log directory
445+
blackboxSDCard.logDirectory = NULL;
446+
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
447+
}
439448
}
440449
break;
441450

@@ -444,6 +453,12 @@ static bool blackboxSDCardBeginLog(void)
444453
break;
445454

446455
case BLACKBOX_SDCARD_ENUMERATE_FILES:
456+
457+
if (blackboxSDCard.logDirectory == NULL) {
458+
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
459+
break;
460+
}
461+
447462
while (afatfs_findNext(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder, &directoryEntry) == AFATFS_OPERATION_SUCCESS) {
448463
if (directoryEntry && !fat_isDirectoryEntryTerminator(directoryEntry)) {
449464
// If this is a log file, parse the log number from the filename
@@ -469,6 +484,12 @@ static bool blackboxSDCardBeginLog(void)
469484
break;
470485

471486
case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY:
487+
//if logDirectory is NULL, it would mean change directory to ROOT, we don't want to do that
488+
if (blackboxSDCard.logDirectory == NULL) {
489+
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
490+
break;
491+
}
492+
472493
// Change into the log directory:
473494
if (afatfs_chdir(blackboxSDCard.logDirectory)) {
474495
// We no longer need our open handle on the log directory
@@ -535,7 +556,7 @@ bool blackboxDeviceEndLog(bool retainLog)
535556
) {
536557
// Don't bother waiting the for the close to complete, it's queued now and will complete eventually
537558
blackboxSDCard.logFile = NULL;
538-
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
559+
blackboxSDCard.state = BLACKBOX_SDCARD_INITIAL;
539560
return true;
540561
}
541562
return false;

src/main/config/parameter_group_ids.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
// #define PG_ELERES_CONFIG 55
7878
#define PG_TEMP_SENSOR_CONFIG 56
7979
#define PG_CF_END 56
80+
#define PG_TERRAIN_CONFIG 57
8081

8182
// Driver configuration
8283
//#define PG_DRIVER_PWM_RX_CONFIG 100

src/main/fc/fc_init.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@
151151

152152
#include "telemetry/telemetry.h"
153153

154+
#include "terrain/terrain.h"
155+
154156
#if defined(SITL_BUILD)
155157
#include "target/SITL/serial_proxy.h"
156158
#endif
@@ -611,6 +613,19 @@ void init(void)
611613
}
612614
#endif
613615

616+
#ifdef USE_SDCARD
617+
618+
#ifdef USE_TERRAIN
619+
if (blackboxConfig()->device == BLACKBOX_DEVICE_SDCARD)
620+
#endif
621+
{
622+
sdcardInsertionDetectInit();
623+
sdcard_init();
624+
afatfs_init();
625+
}
626+
#endif
627+
628+
614629
#ifdef USE_BLACKBOX
615630

616631
//Do not allow blackbox to be run faster that 1kHz. It can cause UAV to drop dead when digital ESC protocol is used
@@ -635,20 +650,15 @@ void init(void)
635650
}
636651
break;
637652
#endif
638-
639-
#ifdef USE_SDCARD
640-
case BLACKBOX_DEVICE_SDCARD:
641-
sdcardInsertionDetectInit();
642-
sdcard_init();
643-
afatfs_init();
644-
break;
645-
#endif
646653
default:
647654
break;
648655
}
649656

650657
blackboxInit();
651658
#endif
659+
#ifdef USE_TERRAIN
660+
terrainInit();
661+
#endif
652662

653663
gyroStartCalibration();
654664

src/main/fc/fc_tasks.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@
9595
#include "telemetry/telemetry.h"
9696
#include "telemetry/sbus2.h"
9797

98+
#include "terrain/terrain.h"
99+
#include "terrain/terrain_io.h"
100+
98101
#include "config/feature.h"
99102

100103
#if defined(SITL_BUILD)
@@ -370,6 +373,10 @@ void fcTasksInit(void)
370373
#ifdef USE_GPS
371374
setTaskEnabled(TASK_GPS, feature(FEATURE_GPS));
372375
#endif
376+
#ifdef USE_TERRAIN
377+
setTaskEnabled(TASK_TERRAIN, terrainConfig()->terrainEnabled);
378+
setTaskEnabled(TASK_TERRAIN_IO, terrainConfig()->terrainEnabled);
379+
#endif
373380
#ifdef USE_MAG
374381
setTaskEnabled(TASK_COMPASS, sensors(SENSOR_MAG));
375382
#if defined(USE_MAG_MPU9250)
@@ -760,4 +767,19 @@ cfTask_t cfTasks[TASK_COUNT] = {
760767
},
761768
#endif
762769

770+
#ifdef USE_TERRAIN
771+
[TASK_TERRAIN] = {
772+
.taskName = "TERRAIN",
773+
.taskFunc = terrainUpdateTask,
774+
.desiredPeriod = TASK_PERIOD_HZ(TERRAIN_TASK_RATE_HZ),
775+
.staticPriority = TASK_PRIORITY_LOW,
776+
},
777+
[TASK_TERRAIN_IO] = {
778+
.taskName = "TERRAIN_IO",
779+
.taskFunc = loadGridToCacheTask,
780+
.desiredPeriod = TASK_PERIOD_HZ(TERRAIN_IO_TASK_RATE_HZ),
781+
.staticPriority = TASK_PRIORITY_LOW,
782+
},
783+
#endif
784+
763785
};

0 commit comments

Comments
 (0)