Error414/feature/inav terrain - #11438
Conversation
|
Test firmware build ready — commit Download firmware for PR #11438 240 targets built. Find your board's
|
f26cbdc to
b34e46e
Compare
|
Tested again with a H743. Now with the grid loading fix, it works well. |
e13d24b to
505c2d3
Compare
0437374 to
b4f054c
Compare
|
Definitely looks interesting. Rather than modifying each and every target individually, would it make sense to do something like: |
|
you are right, it would be better, I will do it as you said |
|
I tried to add it to src/main/target/common.h, but I have no idea how to do that. I would need check if FC has SD card and Baro, but common.h file is loaded before target.h so there is not information about target. the order common.h -> platform.h -> FC_TARGET_NAME/target.h Do you see any solution? |
I will work on that. |
…ature/inav_terrain # Conflicts: # docs/Settings.md
…ature/inav_terrain
|
@error414 I've also been using this with F405 controllers. And it works just as well as it does with the H743. I look forward to it being merged. |
|
How about in common_post.h ? In platform.h I see: So maybe in common_post.h: Then in terrain_utils.h: Obviously any ideas or suggestions I have are just ideas without me understanding your code as well as you do. |
|
good catch, it works. targets for which is terrain enabled |
…ature/inav_terrain # Conflicts: # .gitignore
…ature/inav_terrain
…rd is not in idle
…add new DEBUG LOG type "terrain"
|
I improved terrain_io a little bit
|
# Conflicts: # src/test/unit/CMakeLists.txt
|
Hi @error414 — first off, really nice feature, the grid caching design is clean. I was digging into why CI is currently failing to link on some targets (BLUEBERRYF405, and likely other "big flash, small RAM" F405-class boards) — Most of that is the grid cache ( Could the I/O scratch buffer be eliminated? In uint32_t readNow = afatfs_fread(terrainIoState.datFile,
(uint8_t*)&terrainIoState.ioBlock + terrainIoState.bytesRead,
sizeof(terrainIoState.ioBlock) - terrainIoState.bytesRead);
...
memcpy(terrainIoState.gridBlock, &terrainIoState.ioBlock.block, sizeof(gridBlock_t));But Something like this — read directly into the destination, and stash the expected idx before overwriting it (since the compare currently relies on the destination's old idx still being there): // before starting the read (e.g. when entering TERRAIN_IO_SEEK):
terrainIoState.expectedIdxX = terrainIoState.gridBlock->grid_idx_x;
terrainIoState.expectedIdxY = terrainIoState.gridBlock->grid_idx_y;
// TERRAIN_IO_READ, reading straight into the cache slot instead of ioBlock:
uint32_t readNow = afatfs_fread(terrainIoState.datFile,
(uint8_t*)terrainIoState.gridBlock + terrainIoState.bytesRead,
sizeof(gridBlock_t) - terrainIoState.bytesRead);
terrainIoState.bytesRead += readNow;
...
if (terrainIoState.bytesRead == sizeof(gridBlock_t)) {
if (terrainIoState.gridBlock->grid_idx_x != terrainIoState.expectedIdxX ||
terrainIoState.gridBlock->grid_idx_y != terrainIoState.expectedIdxY) {
markGridBlockInvalid(terrainIoState.gridBlock);
cleanUp();
return;
}
markGridBlockAsRead(terrainIoState.gridBlock);
finishGridBlockRead();
}This only reads Separately, and maybe as a quicker stopgap if that turns out to need more thought: would it be reasonable to drop |
|
F405 had enought room for terrain, as I understand there is not enought room when you merged to maintanence-10.x . It makes sense. If we need reduce using memory, your suggestion is correct. We can read directly to cache without any problem. I used temp gridIoBlock_t just because I like clean aproach, read all and then to have clean data in cache. So you understand code very well :) Even as you mentioned we don't have to read whole 2048 bytes, 1821 B is enought, rest of bytes is only zero. Ardupilot uses 2048 because this value is aligned with some data in telemetry. I don't remember exactly now. For your solution must be adjusted src/main/terrain/terrain_utils.c:182 findGridCache function as well, not to return dirty block. It's easy change. For F405 CPUs we can reduce cache to 4. It cover situation if you fly near corners. One block is area cca 900 * 1100m so next reading would be read when you fly 900m. I'm on vacation now, so I can fix and test it next week, if it's OK. |
|
Great. I also came up with two other ideas which should be able to further reduce the RAM usage by a further 75% without losing much of anything. I'll probably write those up as pull requests. It would also cut in half the time spent reading the data from flash. Idea 1 - block pointThe first one is cutting the RAM usage (and flash read time) in half by adjusting how the data is stored. It might mean doing a conversion while loading it, but I think it will be worth it, if my understanding is correct. It's my understanding the data is currently stored as int_16, with 1-meter units. Actual accuracy of the source data is around 12 meters. Each point uses 16 bits. Consider this scheme: Each BLOCK gets a 16-bit value telling the minimum elevation in that block. That preserves the full resolution of the source data, with only 8 bits per point instead of 16, right? Cuts RAM usage in half, and cuts in half the number of bytes that need to be read from flash. Idea 2 - findGridCache() reads overlap dataThe other idea is you mentioned you don't suggest read-ahead, which is cool, but what about using the data we already read and already have cached? Suppose I am leaving block 1 and entering block 2. So far, I'm still within the 120-meter overlap zone as my new block coordinate moves to block 2. I think the current code will cache miss and not return any data for that point, even though that point is ALSO within block 1? It's already loaded. findGridCache() could use the data we already have in block 1 while waiting for block 2 to load, right, since the current position is in both blocks? Suppose I'm flying at 100 km/h. After entering block 2, I still have overlap data for 60 meters (120 meters?), or a little over two seconds. Figure it takes 100-200ms to load the uncached (but now smaller) new block I just entered. That's fine -- I'm in no hurry to load the new block because I have two (or four?) seconds of data in the overlap! Cache hits would be less of an issue, so I could cache maybe two rather than five? |
|
Interesting ideas! Two data points from the tile-accuracy side (I've been measuring these maps against ICESat-2 and a radar altimeter together with error414): — the 8-bit / 4 m scheme has a hard ceiling of 255 × 4 = 1020 m of relief per block. I just scanned my Bulgarian tiles: Rila already hits 804 m in a single block — 79% of that ceiling. Alpine terrain (Eiger, Lauterbrunnen: 1400+ m of relief per km) will exceed it and clip — exactly the places where terrain data matters most. — in our measurements the sources agree to ~6 m (P95), and we've been working to make that better; 4 m quantization would eat a good part of it back. And one important thing if this would change the .DAT format itself: that's the ArduPilot terrain format — every existing tile generator (ArduPilot's own tools, terrain servers, and my generator) produces it, and people already have cards full of these tiles. A new format would break all of that and split the ecosystem — conversion-on-load into whatever RAM layout you like keeps the files compatible. The overlap idea for boundary crossings sounds neat though. Maybe land the simple buffer fix first so CI is green, and explore the bigger reworks as follow-up PRs? |
|
I think 1024 would cover all but maybe two or three places on earth. Thinking about it more though, we may want the base to be the HIGHEST point in the block - we're flying over things. If the low point clips at three specific points on earth - well that accounts for the trees that grow in low (moist) points that aren't represented on the radar scan. 😁 One COULD do FP8 to include martian terrain, but that would mean extra CPU that probably isn't worth it. Regarding compatibility - we could have a tool that converts on the SD card, BUT we could also do the conversion as it's loaded into RAM. (Where conversion means one subtraction and one divide / shift operation). So the format on the SD card would remain the same. We would just down sample each block as it's read to save RAM. Four is a good divisor because the hardware can shift by two bits in a single cycle. What I think is good about the overlap is it kinda gives you a smart lookahead "for free". If you always load the one or TWO blocks you're in, you're never waiting for a load. And therefore don't need to cache more than two on a low-RAM chip? |
|
I'm just thinking laud :).
a) our format b) convering durring reading Current fread function reads data in 512bits junks, so read 2048 bytes needs 4 cycles. So if we would useb our format, then we would need only two reading cycles. More over our new fread function would have to find the highest point (the base).
For your suggestion overlaping is not needed. I can just read neighboring grid if I'm close to border of current grid. Or I miss something :D Do you want to do these changes only for f4 with small RAM or for H7 as well ? Btw: i can verify if overlaping functionality works properly and then f4 cpu can run only with 1 grid cache. In the worse case if pilot would fly zig-zag then reading new grid would be each 120m. |
|
@sensei-hacker I made two changes which reduces RAM, quite a lot :) So far it's in extra branch https://github.com/error414/inav/tree/error414/feature/inav_terrain_RAM I have to test on the field first.
What do you think? Result:
Differences relative to the baseline (10-bit / 4 cache):
|




Terrain AGL
Terrain is a system that can show AGL (Above Ground Level) in the "distance" OSD element. It loads pre-generated data from an SD card and uses GPS data and barometric altitude for AGL calculation.
Features
Limitations
Technical details
Terrain uses two tasks: an IO task and a GPS task.
The GPS task reads GPS data and creates requests for the IO task. The IO task reads requests from cache and loads grid data into cache.
The two-task design is needed because AGL data must already be loaded in cache by the time it is requested. In some conditions, AGL data requests should stop, but the IO task must keep running to close files and directories, free memory, and release the blackbox lock.
The IO task needs approximately 10 cycles to load data from the SD card. Task frequency can be adjusted to reduce gaps in the blackbox log.
Protection
When arming, the system checks whether the SD card can be read. If the read fails due to a faulty SD card or missing grid data, the terrain system is disabled for the entire flight. This prevents the first SD card read from happening mid-flight.
Inav Configurator PR: iNavFlight/inav-configurator#2651
Testing
I tested it for few months on two planes
Hewing T1, MatekH743Wlite (blackbox on)
Hewing T2, MatekH743Wlite + dedicated rangerfinder NRA15 (blackbox on)
other testers
Hewing T1, SpeedyBeeF4Wing (blackbox on)
Beluga, SpeedyBeeF4Wing
For testers
Documentation:
https://github.com/error414/inav/blob/4fb0fdb9e28d899f03293d34b60907cadd1dbd2b/docs/Terrain.md
Use configuratior:
https://github.com/iNavFlight/inav-configurator/tree/maintenance-10.x