avr-gcc read flash across 64KB boundary - Page 1
Forum section: electronics / general

author: Svuppe
author_rank: Regular Contributor
timestamp: September 21, 2024, 10:18:56 am
content: I am working on an AVR program that has a lot of const data placed in flash (think big tables). For now I am using "const __flash", and all is well. However, I foresee that one of these tables some day will cross the 64 KB boundary (I am using an AVR with 128 KB flash), or even worse: One of the tables will be bigger than 64 KB in itself.I know __flash pointers are only 16-bit and won't work above 64 KB. I have just read up on __memx and have started to experiment with it a little. __memx pointers are 24-bit wide, but the msb signals if it address flash or ram. This seems to give some overhead when using them, especially when traversing lots of data in big tables.Is there something I have missed? Is there a way, in avr-gcc, to have a pointer to flash only, that is wider than 16 bit?

author: T3sl4co1l
author_rank: Super Contributor
timestamp: September 21, 2024, 01:53:23 pm
content: You may find this relevant:https://gcc.gnu.org/wiki/avr-gcc#Address_SpacesWhen overflow is anticipated, you could plan on putting all such objects into the __flashNs, and make note to only access them with specific RAMPZ value, or other means (e.g. NVMCTRL when applicable).GCC may handle it automatically, but do check the output to see what it's doing.Tim

author: Svuppe
author_rank: Regular Contributor
timestamp: September 21, 2024, 03:46:56 pm
content: I did have a look at __flashN, but they are unfortunately also 16-bit, so no good across a 64KB boundary.Perhaps I should mention that I am not in control of these tables or their sizes. They are created by the linker from data in (potentially many) source files. I prefer to have a solution where I don't need to manually manage the placement of the tables into segments in flash.I have tested some more with __memx, and it does what it's supposed to. I guess I'll start with that, and then I can try to optimize the flash access with inline assembler when I get too annoyed about the overhead.

author: westfw
author_rank: Super Contributor
timestamp: September 21, 2024, 10:57:52 pm
content: since pointers on AVR are 16 bits, data structure size is limited to 32kbytes.(there may be a separate problem with smaller data crossing the 64k boundaries in flash, but you'll need to reconsider tables that you expect might exceed 64kbytes.)https://www.avrfreaks.net/s/topic/a5C3l000000UAVLEA4/t054177?comment=P-266422

author: Svuppe
author_rank: Regular Contributor
timestamp: September 22, 2024, 08:55:55 am
content: Thank you for that link. That is very interesting reading.I knew there was a limit to array sizes, but I didn't know what that limit was.I've just made a test where I got one of my flash tables to be about 80KB. It worked fine using a __memx pointer.

author: westfw
author_rank: Super Contributor
timestamp: September 23, 2024, 02:02:58 am
content: QuoteGood to know.How did you create the table? Linker magic?avr-gcc doesn't let you make really large structures...

author: Svuppe
author_rank: Regular Contributor
timestamp: September 23, 2024, 05:38:11 am
content: Quote from: westfw on September 23, 2024, 02:02:58 amHehehe yes, "linker magic" is the short versionThe long version is that I have a macro that puts data into a custom section in flash. Well ok. I have multiple macros and corresponding multiple sections. But that doesn't matter. Each invocation of the macro creates one element in the table, and the linker puts them all together (and even sorts them if I need that). I just got linker script augmentation to work the other day, which is something I've tried to do earlier, but failed. It appears that I was using a bad example.Here is one such macro (the most complex one of course):https://github.com/ejbergdk/loconet-routectrl3/blob/5b79e1fd5d28f4487d9ec86e8af7c40b931f97d3/routectrl3/route.h#L53And this file demonstrates how the macro is used:https://github.com/ejbergdk/loconet-routectrl3/blob/5b79e1fd5d28f4487d9ec86e8af7c40b931f97d3/routectrl3/ctrl_routetables.c#L45And in case anyone wonders... This code is used to control my model train layout. A route is the process of a train moving from one point to another on the layout. Requesting routes that would conflict with another (causing trains to collide) are postponed until the conflict is gone.Now, my layout is nowhere big enough to cause me to hit the 64KB limit, but as I've recently made my train controller project public, someone else might do that. And that is what prompted me to make sure the 64KB won't be a problem.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 23, 2024, 05:39:11 am
content: You can find the AVR GCC documentation for_memxaddress space here. It requires GCC version 4.7 or later.To collect elements into a single large_memxarray, annotate the variables with__attribute__((section (".progmemx.data"))). Note that the default alignment is two bytes, so the size of each variable/object should be even, or you'll get surprising padding with default linker scripts. Also, the order of such variables/objects is semi-random.(I've had to do post-processing passes to reorder the data in some cases, for example to sort a command array for binary search or hash table access. It is easy to do using binutils'avr-objdump,avr-objcopyif and only if the data does not contain relocations (pointers to other objects); relocations can be retained using a number of different methods, with fixed structure formatted data being relatively easy.)To find out what linker script is in use, runavr-gcc -O2 -Wl,--verbose -mmcu=atmega32u4replacing the MCU (atmega32u4) with the specific one you're using. (This specific one will typically refer to/usr/lib/avr/bin/../lib/ldscripts/avr5.xn=/usr/lib/avr/lib/ldscripts/avr5.xnon non-Windows machines.)

author: Doctorandus_P
author_rank: Super Contributor
timestamp: September 23, 2024, 06:08:52 pm
content: I'd say you may be going in the wrong direction.I've had lots of fun with the AVR microcontrollers years ago, and I still use them for simple projects (my programs rarely exeed 8kB, but if they don't fit in the old ATMEGA8 I can easly swap to an ATMEGA328. But the 8-bit architecture simply has it's limits. There was a blue monday that I was interested in the Xmega's, and they have lots of nice and new features. But after realizing how different they were from the normal ATMega's I realized it would be foolish to not look further, so I finally settled on STM32. I don't know how far they go, but I guess that a 1MB lookup table is no problem at all. And maybe STM32 was not a very good choice. I choose it because lots of chinese use it in hobby equipment, so I can reprogram such existing gadgets to do something more useful. Documentation for STM32 is mindboggingly overwhelming, and not well organized. It's not a friendly processor to begin with, but there are plenty of other Cortex-M3 and similar processor families. I've heard rumors that the LPC variant is easier to get started in (also used in Teensy's).But if you're stuck with the processor you have now. There is probably a way to force it to work, and it's the way of least effort. But a respin of a PCB (for new products) is not such a big deal either.

author: gjl
author_rank: Newbie
timestamp: December 21, 2024, 11:19:16 am
content: To date, the __memx named address space is the only one that supports reading across the 64 KiB flash segment boundaries.In avr-gcc v15, __flashx has been added as 24-bit flash address space. It supports reading across the flash segment boundaries. The overhead caused by pointer handling is the same like for __memx, but the overhead to access locations might be less than imposed by __memx. Just like __memx, __flashx allocates its objects in input section.progmemx.data.Then there is AVR-LibC v2.2'savr/pgmspace.hwhich supportsPROGMEM_FAR(similar toPROGMEM),PSTR_FAR(similar toPSTR) and lots of macros likepgm_read_int_far(similar topgm_read_int). Notice thatavr/pgmspace.himplements all these features in that header without library support, so it should be straight forward to use these features with an older toolchain.QuoteIn that case, even __flashx won't work (because offsets are still 16 bits wide) and you'll have to use inline assembly resp.avr/pgmspace.h.

author: Svuppe
author_rank: Regular Contributor
timestamp: December 21, 2024, 04:14:31 pm
content: Quote from: gjl on December 21, 2024, 11:19:16 amHoly smoke. That's exactly what I needed. Impressive that there's still being developed new features for avr-gcc.Unfortunately, that means that I now need to look at a new development setup. I am currently still using the aging Atmel/Microchip Studio 7, and I know perfectly well that it won't be upgraded. I don't feel like tormenting myself with MPLAB (I've heard plenty of bad things about it), so I'll probably go full Linux instead.Meanwhile, I did get __memx to work. It does have a little overhead, but it isn't as bad as I first imagined.

author: gjl
author_rank: Newbie
timestamp: December 21, 2024, 07:19:17 pm
content: Quote from: Svuppe on December 21, 2024, 04:14:31 pmYou can still goavr/pgmspace.hwithout the need for new avr-gcc or AVR-LibC.https://github.com/avrdudes/avr-libc/blob/main/include/avr/pgmspace.hPerformance-wise, the best might be__flash1, though you need an amendment to the linker description file as outlined in the avr-gcc Wiki:https://gcc.gnu.org/wiki/avr-gcc#Address_Spaces

author: Svuppe
author_rank: Regular Contributor
timestamp: December 22, 2024, 12:42:03 pm
content: Quote from: gjl on December 21, 2024, 07:19:17 pmI can, but I suspect that it will create more overhead, as RAMPZ is reset after every far read. And the code won't be as "clean" and readable as it is now, using __memx.QuoteThat will bring me back to the 64KB segment limit that I wanted to avoid in the first place.I just might try to make 3 versions (using __memx, avr/pgmspace.h and homemade inline assembler), and measure their performance against each other. That could be a fun little exercise.

author: Svuppe
author_rank: Regular Contributor
timestamp: December 22, 2024, 03:53:44 pm
content: Quote from: Svuppe on December 22, 2024, 12:42:03 pmI misread that in pgmspace.h. The RAMPZ reset only applies to AVR's with EBI (which I don't have).QuoteI have now done a small test, based on data I already use in a project. This is a table in flash with 74 entries.Using __memx pointer to traverse the table takes 104.5 us.Using avr/pgmspace.h and its pgm_read_xxx_far takes 79.8 us.Using a hand-crafted inline asm loop takes 66.2 us.

author: gjl
author_rank: Newbie
timestamp: December 23, 2024, 01:44:07 pm
content: When you want a loop, here is also memcpy_PF. Though that will cost a function call and argument shuffling.https://avrdudes.github.io/avr-libc/avr-libc-user-manual/group__avr__pgmspace.html#ga13cc6cd0692aeccaac2789350a16412bhttps://github.com/avrdudes/avr-libc/blob/main/libc/pmstring/memcpy_PF.SIn many cases, avr-gcc's code generation for address-space access is quite suboptimal, hence better consider the inline asm macros / inline functions provided by AVR-LibC.

author: gjl
author_rank: Newbie
timestamp: September 21, 2025, 08:19:50 pm
content: Quote from: Svuppe on September 21, 2024, 10:18:56 amYou can use named address space __memx (since GCC 4.7) or __flashx (since GCC 15). Or you could just place that object in __flash1, so it will never cross a 64 KiB boundary. Notice that __flash1 is not handled in the default LD script, so you have some work to do amending the LD script according to your preferences.Quote

author: Svuppe
author_rank: Regular Contributor
timestamp: September 22, 2025, 05:46:59 am
content: Quote from: gjl on September 21, 2025, 08:19:50 pmNot when using "linker magic" as described earlier.I made a test, where my table ended up being 80 KB, and I had no problem creating or reading it.

author: gjl
author_rank: Newbie
timestamp: September 22, 2025, 09:54:09 am
content: Quote from: Svuppe on September 21, 2024, 03:46:56 pmGCC 15 has __flashx which has less overhead when reading, though it can read from flash only, whereas __memx can also read from RAM.https://gcc.gnu.org/gcc-15/changes.html#avrBetter still is __flash1 since it has 16-bit pointers, whereas __flashx invokes 24-bit addresses and computations. The avr-gcc Wiki has examples for how to augment the default ld script:https://gcc.gnu.org/wiki/avr-gcc#Address_Spaces