MMU - Adjust the Ratio of ICACHE to IRAM¶
Overview¶
The ESP8266 has a total of 64K of instruction memory, IRAM. This 64K of IRAM is composed of one dedicated 32K block of IRAM and two 16K blocks of IRAM. The last two 16K blocks of IRAM are flexible in the sense that it can be used as a transparent cache for external flash memory. These blocks can either be used for IRAM or an instruction cache for executing code out of flash, ICACHE.
The code generated for a sketch is divided up into two groups, ICACHE and IRAM. IRAM offers faster execution. It is used for interrupt service routines, exception handling, and time-critical code. The ICACHE allows for the execution of up to 1MB of code stored in flash. On a cache miss, a delay occurs as the instructions are read from flash via the SPI bus.
There is 98KB of DRAM space. This memory can be accessed as byte, short, or a 32-bit word. Access must be aligned according to the data type size. A 16bit short must be on a multiple of 2-byte address boundary. Likewise, a 32-bit word must be on a multiple of 4-byte address boundary. In contrast, data access in IRAM or ICACHE must always be a full 32-bit word and aligned. We will discuss a non32-bit exception handler for this later.
Option Summary¶
Miscellaneous¶
For calls to umm_malloc
with interrupts disabled.¶
malloc
will always allocate from theDRAM
heap when called with interrupts disabled.realloc
with a NULL pointer will usemalloc
and return aDRAM
heap allocation. Note, callingrealloc
with interrupts disabled is not officially supported. You are on your own if you do this.
If you must use IRAM memory in your ISR, allocate the memory in your init code. To reduce the time spent in the ISR, avoid non32-bit access that would trigger the exception handler. For short or byte access, consider using the inline functions described in section “Performance Functions” below.
How to Select Heap¶
The MMU
selection 16KB cache + 48KB IRAM and 2nd Heap (shared)
allows you to use the standard heap API function calls (malloc
,
calloc
, free
, … ). to allocate memory from DRAM or IRAM. This
selection can be made by instantiating the class HeapSelectIram
or
HeapSelectDram
. The usage is similar to that of the
InterruptLock
class. The default/initial heap source is DRAM. The
class is in umm_malloc/umm_heap_select.h
.
...
char *bufferDram;
bufferDram = (char *)malloc(33);
char *bufferIram;
{
HeapSelectIram ephemeral;
bufferIram = (char *)malloc(33);
}
...
free(bufferIram);
free(bufferDram);
...
free
will always return memory to the correct heap. There is no need
for tracking and selecting before freeing.
realloc
with a non-NULL pointer will always resize the allocation
from the original heap it was allocated from. When the supplied pointer
is NULL, then the current heap selection is used.
Low-level primitives for selecting a heap. These are used by the above Classes:
umm_get_current_heap_id()
umm_set_heap_by_id( ID value )
Possible ID values
UMM_HEAP_DRAM
UMM_HEAP_IRAM
Also, an alternate stack select method API is available. This is not as easy as the class method; however, for some small set of cases, it may provide some additional control:
ESP.setIramHeap()
Pushes current heap ID onto a stack and sets Heap API for an IRAM selection.ESP.setDramHeap()
Pushes current heap ID onto a stack and sets Heap API for a DRAM selection.ESP.resetHeap()
Restores previously pushed heap. ### Identify Memory
These always inlined functions can be used to determine the resource of a pointer:
bool mmu_is_iram(const void *addr);
bool mmu_is_dram(const void *addr);
bool mmu_is_icache(const void *addr);
Performance Functions¶
While these always inlined functions, will bypass the need for the exception handler reducing execution time and stack use, it comes at the cost of increased code size.
These are an alternative to the pgm_read
macros for reading from
IRAM. When compiled with ‘Debug Level: core’ range checks are performed
on the pointer value to make sure you are reading from the address range
of IRAM, DRAM, or ICACHE.
uint8_t mmu_get_uint8(const void *p8);
uint16_t mmu_get_uint16(const uint16_t *p16);
int16_t mmu_get_int16(const int16_t *p16);
While these functions are intended for writing to IRAM, they will work with DRAM. When compiled with ‘Debug Level: core’, range checks are performed on the pointer value to make sure you are writing to the address range of IRAM or DRAM.
uint8_t mmu_set_uint8(void *p8, const uint8_t val);
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_t val);
int16_t mmu_set_int16(int16_t *p16, const int16_t val);