Skip to content

Commit 14e4466

Browse files
KerogitAlan C. Assis
authored andcommitted
Documentation/platforms/avr: document options of keeping const vars in flash
This patch adds a document that describes why const variables need to be copied into the RAM in AVR architecture for normal program flow in NuttX. It then describes options of accessing them directly from the flash without need to do any copying. Patch was tested by building the documentation. Signed-off-by: Kerogit <[email protected]>
1 parent 5710b54 commit 14e4466

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
===================================
2+
Keeping constants in program memory
3+
===================================
4+
5+
By default, all constants in a program running on AVR device are copied
6+
into RAM. This document describes the reasons for doing that and options
7+
to keep them in program memory.
8+
9+
Introduction
10+
============
11+
12+
AVR architecture is a Harvard architecture, program and data memories are
13+
accessed over separate buses with their own address spaces. While this approach
14+
has its advantages, it does not match well with C programs which expect
15+
to be able to access everything in the same way. (That is - using
16+
the same instructions.) In fact, unless some measures are taken, C program
17+
is completely unable to access initialized variables.
18+
19+
Consider this variable declaration and function call:
20+
21+
::
22+
23+
const char hello[] = "Hello, world\n";
24+
printf(hello);
25+
26+
When this code is compiled, the string is stored somewhere within
27+
the program and uploaded to the program memory (flash.) Pointer
28+
variable ``hello`` then contains address of the string. Call
29+
to ``printf`` receives this address and attempts to load and process
30+
characters of this string - on AVR, indirect load from memory
31+
instructions would be used for that.
32+
33+
On a von Neumann architecture, which has single address space
34+
for both program and data, this would pose no problem. Since
35+
the program memory is part of the overall address space, the load
36+
instructions can reach the data contained within it. This is,
37+
however, not the case for AVR. Anything stored
38+
in program memory is inaccessible for regular load instruction
39+
and the ``printf`` call would fail.
40+
41+
Solution of the problem
42+
=======================
43+
44+
AVR provides instructions that are able to load data from program memory.
45+
These instructions can be used to copy all the constants into the RAM,
46+
the copy is then available to regular load instructions and the program
47+
can work correctly.
48+
49+
Internally, variable ``hello`` is altered to contain address of the copy
50+
of the string with the data address space . It can be passed freely into
51+
``printf`` or any other function.
52+
53+
All that is needed is a code that performs that copy and that code
54+
is present in NuttX. It is executed automatically at program startup.
55+
If the application is being developed for a supported board, everything
56+
happens automatically. (If the application is being developed for a custom board, the board's
57+
linker script only needs to provide some variables - common architecture
58+
code then takes care of the rest.)
59+
60+
In other words - by default, any application running on NuttX is able
61+
to freely pass any variable to any NuttX interface.
62+
63+
Problem of the solution
64+
=======================
65+
66+
As described, this solution works reliably and correctly. However, there
67+
is a significant cost to it - it consumes RAM, which is a limited resource.
68+
For example, one of the supported chips is ATmega128 featuring 4kB of SRAM.
69+
Even a simple program will quickly consume significant part of it,
70+
especially if it contains a lot of strings.
71+
72+
Constants in program memory
73+
===========================
74+
75+
As its name suggests, this document describes techniques used
76+
to make the program work without the need to copy constants to RAM.
77+
78+
Using PROGMEM
79+
-------------
80+
81+
PROGMEM is a macro defined GNU's C library for AVR architecture.
82+
It translates to a qualifier that instructs the compiler not to move
83+
the variable from program memory to RAM. It is used like this:
84+
85+
::
86+
87+
const uint8_t values[4] PROGMEM = { 0, 2, 4, 6 };
88+
89+
When the variable is declared this way, it will not be copied to RAM
90+
by the initialization code.
91+
It cannot be used by normal means in this state though. As a pointer,
92+
it holds address of data in program memory. This makes anything like
93+
the following impossible:
94+
95+
::
96+
97+
function(values);
98+
99+
The issue here is described at the beginning of the document. Contents
100+
of ``values`` is not present in the RAM and therefore cannot be reached
101+
using regular load instructions.
102+
103+
Instead, the compiler needs to be explicitly instructed to read from
104+
program memory. The C library provides functions to achieve that. Those
105+
functions will accept the pointer to program memory and are at least
106+
partially written in AVR assembly, making sure that LPM (load from
107+
program memory) instructions are used.
108+
109+
Main drawback of this method manifests itself when a function needs
110+
to be able to accept a parameter which may be stored either in RAM
111+
or in the program memory. It either needs to have an additional parameter
112+
to distinguish where to read from, or the function needs to be provided
113+
in two variants. (Or even more variants if it accepts more than one
114+
such parameter.)
115+
116+
This is unsupported in NuttX altogether. The application may use variables
117+
declared with PROGMEM qualifier freely but must not pass them to any NuttX
118+
interface.
119+
120+
Using __memx/__flashx
121+
=====================
122+
123+
An example usage of the ``__memx`` qualifier would be:
124+
125+
::
126+
127+
const __memx char hello[] = "Hello, world\n";
128+
129+
In this context, the qualifier has the same same meaning as PROGMEM above,
130+
the variable will not be copied into the RAM by the initialization code.
131+
(``__flashx`` has the same meaning but this qualifier is a feature of relatively
132+
new - relatively of when this document is written - release of GCC
133+
and is not used in NuttX.)
134+
135+
The meaning changes when the variable is used, for example:
136+
137+
::
138+
139+
void function(const __memx char *arg)
140+
141+
In this case, ``__memx`` signals the compiler that the pointer may
142+
be dereferenced to either the data or the program memory address space.
143+
That needs to be determined during run-time.
144+
145+
Internally, this is achieved by extending the pointer to 24-bit length;
146+
this also accommodates devices with more than 64kB program memory.
147+
Most significant bit in the pointer then determines which address space
148+
needs to be used when dereferencing the pointer. This bit is set
149+
for data address space and cleared for program memory address space.
150+
151+
There is a significant run-time cost of using this method. Essentially,
152+
every memory access to a variable with this qualifier is replaced with
153+
a function call that determines memory type and reads from appropriate
154+
address space. This call can take around 15 clock cycles for single
155+
byte read. It is also entirely possible that unoptimized call
156+
to eg. strlen will call this function for each byte in the string.
157+
158+
NuttX supports these qualifiers using IOBJ and IPTR macros like this:
159+
160+
::
161+
162+
const IOBJ char hello[] = "Hello, world\n";
163+
void function(const IPTR char *arg);
164+
165+
``IOBJ`` denotes variable that should remain in program memory.
166+
It currently translates to ``__memx`` but may eventually be switched
167+
to ``__flashx``. ``IPTR`` always translates to ``__memx``.
168+
169+
This method of keeping constants in program memory
170+
has a very limited support in NuttX. Essentially, it was
171+
added as a debugging feature to support format strings with debug
172+
messages. What this means is that functions related to logging
173+
tend to have the ``IPTR`` qualifier in their declaration. Other functions
174+
don't - most interactions with the kernel will not accept these pointers.
175+
176+
Note that both ``IOBJ`` and ``IPTR`` need to be activated by
177+
:menuselection:`System Type --> Mark const variables with __memx`.
178+
If this configuration option is not set, both macros are defined
179+
to be empty and all strings will be copied to RAM (performance penalty
180+
discussed above is therefore removed as well.)

0 commit comments

Comments
 (0)