|
| 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