Reducing PROGMEM and RAM Usage: Difference between revisions
(8 intermediate revisions by 2 users not shown) | |||
Line 11: | Line 11: | ||
** In our example, there are a huge amount of float functions that are taking up a lot of space. | ** In our example, there are a huge amount of float functions that are taking up a lot of space. | ||
** How can we fix this? In our source code, we can change x from a float to a uint8_t: | ** How can we fix this? In our source code, we can change x from a float to a uint8_t: | ||
<pre> | <pre>#include <Arduboy2.h> | ||
#include <Arduboy2.h> | |||
Arduboy2 arduboy; | Arduboy2 arduboy; | ||
// float x; << try to avoid floats | |||
uint8_t x; | |||
void setup() { | void setup() { | ||
Line 37: | Line 36: | ||
arduboy.display(); | arduboy.display(); | ||
} | }</pre> | ||
</pre> | |||
* That small modification will save us thousands of bytes! | |||
* Use | * Use https://godbolt.org/z/8vhWqj8KE (pre-configured with correct settings) to see the assembly that the provided code will generate. You don't need to know assembly, the most important thing is if you see a function call to do an arithmetic operation, you may be able to avoid it. This is helpful to avoid division and mod, as discussed below. | ||
* Use <code>arduboy.boot();</code> and <code>arduboy.safeMode();</code> instead of <code>arduboy.begin();</code>. <code>arduboy.systemButtons();</code> and <code>arduboy.waitNoButtons();</code> can also be added for convenience (turning on and off sound at bootup). Note that this will get rid of the scrolling logo. | * Use <code>arduboy.boot();</code> and <code>arduboy.safeMode();</code> instead of <code>arduboy.begin();</code>. <code>arduboy.systemButtons();</code> and <code>arduboy.waitNoButtons();</code> can also be added for convenience (turning on and off sound at bootup). Note that this will get rid of the scrolling logo. | ||
* Use <code>Arduboy2Base</code> instead of <code>Arduboy2</code> if you don’t use text. | * Use <code>Arduboy2Base</code> instead of <code>Arduboy2</code> if you don’t use text. | ||
Line 53: | Line 51: | ||
== Tips for Reducing RAM == | == Tips for Reducing RAM == | ||
* Declare variables <code>constexpr</code> if they do not change. | * Declare variables <code>constexpr</code> if they do not change. | ||
* Put string literals in PROGMEM by surrounding them with the <code>F("string literal here")</code> macro. Pharap's [https://github.com/Pharap/FlashStringHelperDemo FlashStringHelper] library is very helpful when dealing with strings in PROGMEM. | * Put string literals in PROGMEM by surrounding them with the <code>F("string literal here")</code> macro. Pharap's [https://github.com/Pharap/FlashStringHelperDemo FlashStringHelper] library is very helpful when dealing with strings in PROGMEM. | ||
* Use locals instead of globals when possible. Locals are stored on the stack and exist only in a scope, while globals are stored for the entire lifetime of the program. | * Use locals instead of globals when possible. Locals are stored on the stack and exist only in a scope, while globals are stored for the entire lifetime of the program. | ||
* Pack variables into bits as much as possible. For example, instead of using an array of 8 bools, use 1 byte with each bit representing a bool. Reading and writing bits can be done with <code>bitRead(number, bit)</code> and <code>bitWrite(number, bit, value)</code>. | * Pack variables into bits as much as possible. For example, instead of using an array of 8 bools, use 1 byte with each bit representing a bool. Reading and writing bits can be done with <code>bitRead(number, bit)</code> and <code>bitWrite(number, bit, value)</code>. | ||
== Storing and Retrieving Static Numerical Data == | |||
Declaring an array of static data takes up both RAM and PROGMEM on the Arduboy as the data needs to be stored in the program and copied into RAM as the program starts. If the data is purely static, you can remove the need to store the data in RAM and minimise the PROGMEM usage by using the <code>PROGMEM</code> keyword on the array and the helper functions to retrieve the data. | |||
A simple example is shown below: | |||
<pre>const uint8_t numbers[] PROGMEM = { 1, 2, 3, 4, 5 }; | |||
uint8_t aNumber = 0; | |||
for (uint8_t i = 0; i < 5; i++) { | |||
aNumber = pgm_read_byte(&numbers[i]); | |||
Serial.println(aNumber); | |||
}</pre> | |||
The <code>pgm_read_byte()</code> function is provided by the [https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html pgmspace] library. This library includes numerous functions for reading other data: | |||
* read a <code>uint8_t</code> using <code>pgm_read_byte(address)</code> | |||
* read a <code>uint16_t</code> using <code>pgm_read_word(address)</code> | |||
* read a <code>uint32_t</code> using <code>pgm_read_dword(address)</code> | |||
* read a <code>float</code> using <code>pgm_read_float(address)</code> | |||
Data can be copied from PROGMEM into a structure in a single action, as shown below. This approach alleviates the need to copy each element of the room data (width, height and player position) individually. | |||
<pre>struct Room { | |||
uint8_t roomWidth; | |||
uint8_t roomHeight; | |||
uint8_t playerX; | |||
uint8_t playerY; | |||
}; | |||
constexpr Room rooms[] PROGMEM { | |||
16, 16, 3, 3, // Room 1 | |||
32, 32, 5, 6, // Room 2 | |||
... | |||
}; | |||
Room currentRoom; | |||
void loadRoom(uint8_t index) { | |||
memcpy_P(¤tRoom, &rooms[index], sizeof(Room)); | |||
}</pre> |
Latest revision as of 12:03, 14 August 2024
Overview
The Arduboy only has 28KiB or 29KiB (FX) of PROGMEM and 2.5KiB of RAM. While making a game, these can run out very quickly. This page aims to help you recover some of your precious PROGMEM and RAM.
Tips for Reducing PROGMEM
- Look through your code in Ardens for large functions that shouldn't be there. Here is a step-by-step example: (Note: this will only work with .elf files, .hex files don't provide enough information.)
- Launch Ardens and drag your .elf file on it.
- Open the disassembly tab (Windows>Debugger>Disassembly)
- Click on the "Jump to Function" dropdown.
- Scan the dropdown for anything unexpected.
- In our example, there are a huge amount of float functions that are taking up a lot of space.
- How can we fix this? In our source code, we can change x from a float to a uint8_t:
#include <Arduboy2.h> Arduboy2 arduboy; // float x; << try to avoid floats uint8_t x; void setup() { arduboy.begin(); arduboy.setFrameRate(10); } void loop() { if (!arduboy.nextFrame()) return; arduboy.clear(); x++; arduboy.print(x / 10); arduboy.display(); }
- That small modification will save us thousands of bytes!
- Use https://godbolt.org/z/8vhWqj8KE (pre-configured with correct settings) to see the assembly that the provided code will generate. You don't need to know assembly, the most important thing is if you see a function call to do an arithmetic operation, you may be able to avoid it. This is helpful to avoid division and mod, as discussed below.
- Use
arduboy.boot();
andarduboy.safeMode();
instead ofarduboy.begin();
.arduboy.systemButtons();
andarduboy.waitNoButtons();
can also be added for convenience (turning on and off sound at bootup). Note that this will get rid of the scrolling logo. - Use
Arduboy2Base
instead ofArduboy2
if you don’t use text. - Use smaller integer sizes when possible. For example, instead of using
int
/int16_t
for a screen position, you can useint8_t
. Because the Arduboy has an 8-bit CPU, anything larger adds extra instructions/functions. - Avoid using floats! The Arduboy has no hardware floating point, so 5+KiB can be included to do it in software. Fixed point is much faster and smaller
- Avoid using division/mod whenever possible; the Arduboy does not have hardware division, so code is included to do it in software. This not only increases PROGMEM, it slows your program down. Here are some alternatives to division/mod:
- Use powers of 2 when dividing/using mod; the compiler can optimize these into shifts/bitwise ands, which are very fast. For example, try to use
x /= 4;
instead of ofx /= 3;
This becomes especially important when you are working with a tile map: It's best to use tiles with powers of 2. - Instead of using mod to wrap a number (unless you can do it with a power of 2), use an if statement. For example, instead of writing
x %= 3
, writeif (x > 2) x = 0;
.
- Use powers of 2 when dividing/using mod; the compiler can optimize these into shifts/bitwise ands, which are very fast. For example, try to use
- As a last resort, you can remove the USB stack by adding the
ARDUBOY_NO_USB
macro in your code in global scope. This gains you an extra 3KiB of PROGMEM to use. Be aware of the fact that this makes it very hard to upload a new game onto a non-FX Arduboy, as the uploader cannot put it into bootloader mode. TheARDUBOY_NO_USB
macro helps by giving the option to put the Arduboy back into bootloader mode when the down button is held as the Arduboy is turned on. There also is a function in the Arduboy2 library calledarduboy.exitToBootloader()
. FX-enabled Arduboys do not have this problem as by holding up and down for multiple seconds or turning off and on again will put it into bootloader mode and allow uploads to continue. It is highly recommended when posting a game to tell others in bold letters: "This game removes the USB stack. [If FX is not required] Press down while turning on to go into bootloader mode."
Tips for Reducing RAM
- Declare variables
constexpr
if they do not change. - Put string literals in PROGMEM by surrounding them with the
F("string literal here")
macro. Pharap's FlashStringHelper library is very helpful when dealing with strings in PROGMEM. - Use locals instead of globals when possible. Locals are stored on the stack and exist only in a scope, while globals are stored for the entire lifetime of the program.
- Pack variables into bits as much as possible. For example, instead of using an array of 8 bools, use 1 byte with each bit representing a bool. Reading and writing bits can be done with
bitRead(number, bit)
andbitWrite(number, bit, value)
.
Storing and Retrieving Static Numerical Data
Declaring an array of static data takes up both RAM and PROGMEM on the Arduboy as the data needs to be stored in the program and copied into RAM as the program starts. If the data is purely static, you can remove the need to store the data in RAM and minimise the PROGMEM usage by using the PROGMEM
keyword on the array and the helper functions to retrieve the data.
A simple example is shown below:
const uint8_t numbers[] PROGMEM = { 1, 2, 3, 4, 5 }; uint8_t aNumber = 0; for (uint8_t i = 0; i < 5; i++) { aNumber = pgm_read_byte(&numbers[i]); Serial.println(aNumber); }
The pgm_read_byte()
function is provided by the pgmspace library. This library includes numerous functions for reading other data:
- read a
uint8_t
usingpgm_read_byte(address)
- read a
uint16_t
usingpgm_read_word(address)
- read a
uint32_t
usingpgm_read_dword(address)
- read a
float
usingpgm_read_float(address)
Data can be copied from PROGMEM into a structure in a single action, as shown below. This approach alleviates the need to copy each element of the room data (width, height and player position) individually.
struct Room { uint8_t roomWidth; uint8_t roomHeight; uint8_t playerX; uint8_t playerY; }; constexpr Room rooms[] PROGMEM { 16, 16, 3, 3, // Room 1 32, 32, 5, 6, // Room 2 ... }; Room currentRoom; void loadRoom(uint8_t index) { memcpy_P(¤tRoom, &rooms[index], sizeof(Room)); }