I'm evaluating the Renesas RX65N MCU EV Kit. In this post, a mechanism to reserve memory for the LCD module frame buffer with the GCC toolchain |
LCD Frame Buffer
The Renesas library for LCD, r_glcdc_rx, supports two frame buffers.
These are memory areas where you can draw your graphics, then activate the refresh of the LCD from that buffer.
There's no specific requirement from the library where the buffers should reside in RAM. You just need to know what address it's located. And preferably, prevent other parts of your design of taking in that chunk of memory.
Telling C code where an address is, is easy. You can just define it.
#define FRAME_BUF_BASE_ADDR 0x00800000
That will work, but it will not prevent the toolchain to place other variables or code in that same memory area. And that would lead to corrupted data or code.
The GCC linker allows us to safeguard the chunk of memory for the buffer, so that this will not be occupied by other artifacts (if you behave ).
There's no restriction on what parts of code can read and write to it. That's not the goal of this block. Just take care that the loader, compiler and future developers are aware where the buffers are.
All 3 of these will get an alert if something else tries to occupy the area, if they accidentally try and place another variable or function in that RAM.
It will not prevent developers from explicitly - by hardcode - putting a variable in the address space. It's expected that someone who occupies a piece of memory by hard coding knows what and why.
The added advantage of using the linker file is that it's documentation for the future developers. The allocated space is clearly defined and Eclipse / e2 studio have a memory visualisation display.
See the header image of this post for the graphical representation that's available.
Manage the Buffers with GCC Linker
The linker script describes the controller's memory areas and dictates what pieces of firmware go in what areas.
In e2 studio, if you create a Renesas GCC project, you'll find the script - linker_script.ld - in the src folder.
By default, a set of blocks are predefined:
MEMORY { RAM : ORIGIN = 0x0, LENGTH = 262144 RAM2 : ORIGIN = 0x00800000, LENGTH = 393216 ROM : ORIGIN = 0xFFF00000, LENGTH = 1048576 OFS : ORIGIN = 0xFE7F5D00, LENGTH = 128 }
(actually, RAM2 is defined as RAM2 : ORIGIN = 0x00080000, but I think that this is incorrect.)
For this exercise, let's use the 2nd block of RAM available in this controller, called On-chip expansion RAM in the datasheet extract below:
image source: annotated from Renesas RX65N data sheet
In the linker file, this memory area is named RAM2, and not used to place any objects by default. It's an unmanaged area out-of-box.
I'll be using it to fix an area for the frame buffer. Anything else placed in that memory part in later developments (E.g.: an additional frame buffer, a chunk of code, data, freely allocatable memory) will be placed after that chunk.
SECTIONS { // ... .FRAME_BUFFER (NOLOAD) : { "_FRAME_BUFFER2" = .; . += 480 * 272 * 2; /* "_FRAME_BUFFER1" = .; . += 480 * 272 * 2; */ "FRAME_BUFFER_end" = .; } > RAM2 .ERAM FRAME_BUFFER_end : { } > RAM2 }
The .FRAME_BUFFER section will take the first chunk of the expansion RAM. It's the first section that's told to place itself in RAM2 (> RAM2).
So it's guaranteed to be at the memory address 0x008000000.
I declared a label called _FRAME_BUFFER_2, with address . (current).
That can later be used (it becomes a variable name with known address) in our C firmware to tell the LCD library where the buffer is allocated, without hard-coding the address in the C code. I'll come back on that in the Firmware description.
Then I tell the linker to skip (fill) 480 * 272 * 2 bytes. This represents a LCD size of 480 * 272, and 2 bytes per pixel for the colour value.
It doesn't actually physically do anything with that area, not at loading and not during runtime. The area is not loaded nor initialised. But it's occupied and won't be taken by something else.
I've placed a commented out placeholder for the other buffer. My program does not use tha buffer so I didn't reserve it.
Note that there is actually not enough expansion RAM to store a second buffer with that size. Just comment out the declaration and try to build the program.
The linker will fail with a message that you are overrunning the RAM2 area. That was one of the purposes of this blog - having the linker support and guard memory management.
If you'd just hardcode the start addresses for the 2 buffers in your C program, nothing would warn you that te second chunk overruns into the reserved memory area at 0x00108000. correction - thank you Andrew J : 0x00860000.
The datasheet says "Note 2. Reserved areas should not be accessed" but you'd find that out by the program crashing during paint commands instead of the build process flagging it.
Then I've created a label .FRAME_BUFFER_end, that's representing the first address after the memory space taken by the buffer(s).
That's used as a start point for the next section, named .ERAM by me. This section is the remainder of the expansion RAM, free to use for other purposes.
Use the Buffer in Firmware
The label _FRAME_BUFFER_2 that I created in the linker file in the previous section, automagically becomes a C external variable.
It doesn't have a type or size, but it has a known address - the adders 0x00800000 that we wanted.
Here's how we get to know the address reserved for frame buffer 2:
/* Frame buffer address of graphics 2 is the FRAME_BUFFER section top address */ extern uint32_t FRAME_BUFFER2[]; #define FRAME_BUF_BASE_ADDR FRAME_BUFFER2
The type of the variable isn't critical because it isn't allocated. I've used the type that's expected by the library. If the buffer is an array of consistent types, it's useful to declare it as that.
It's good for the understanding and you will not have to cast the definition to the expected type later.
/* ==== User setting parameter ==== */ /* ---- Graphic 2 setting ---- */ /* Image format */ gs_glcdc_init_cfg.input[GLCDC_FRAME_LAYER_2].p_base = FRAME_BUF_BASE_ADDR;
The end result is that the buffer area is managed by the linker and that we don't have to hardcode buffer addresses in the C code.
For reference, here's the relevant part of the linker map output file:
.FRAME_BUFFER 0x00800000 0x3fc00 0x00800000 _FRAME_BUFFER2 = . 0x0083fc00 . = (. + 0x3fc00) *fill* 0x00800000 0x3fc00 0x0083fc00 FRAME_BUFFER_end = . .ERAM
As added advantage, the location and size is documented - that helps when doing later maintenance to the code.
And if needed, the buffer can be moved to another location without having to adapt the code. It's good to have the linker manage what goes where.
Top Comments