CCS News

PIC® MCU Address Spaces - Part 2

Friday 19 February, 2021

In part one, we covered the two hardware address spaces in the PIC® MCU and how they can be used from C. This article will explain how a developer defines virtual address spaces. These user defined spaces can be used to add a layer of abstraction between "normal" C variables and the actual hardware memory implementation. We will detail implementations for three example address spaces:

* External serial EEPROM
* Display memory in a graphic LCD module
* Dual copy safety variables for a safety critical application

External Serial EEPROM
For the first example, we will use an external serial EEPROM that uses I2C to access the data. CCS provides a simple driver that gives users write_ext_eeprom() and read_ext_eeprom() for byte access to the external memory. To use that memory like they would use regular RAM users need to create a new address space that the variable can be declared in.

Before users can define the address space, users need to provide some functions to read and write to the memory. For example:

void DataEE_Read(int32 addr, int8 * ram, int bytes) {

for(int i=0;i<bytes;i++,ram++,addr++)

void DataEE_Write(int32 addr, int8 * ram, int bytes) {
for(int i=0;i<bytes;i++,ram++,addr++)

When the embedded C ANSI specification was first proposed they called this typemod and by the time the specification was approved it was called addressmod with a new syntax. The CCS C compiler accepts both syntax forms. This is everything needed to define the space:

addressmod (DataEE, DataEE_read,DataEE_write,0,0x3ff);

The first parameter give the new space a name, then we have the read and write functions and finally we have the address range of the new space. Here is how it can be used:

DataEE int32 serial_number;


printf("S/N = %8LXrn", serial_number);

In this case, the variable serial number actually resides in the external EEPROM. Suppose the user wants to make the EEPROM access more efficient.They could crate a simple RAM array with a copy of all the EE data that gets loaded on power up.Then the DataEE_Read() function reads from the RAM array and DataEE_Write() writes to the array while marking a dirty bit. In the background as time permits some task could copy the dirty data back to the EE.The details of this implementation are separate from the main program and the algorithm can be changed without affecting the main program.For example, maybe the user want to keep a CRC of the EE data in the last EE location, or they want to encrypt the data or keep three copies of each byte in the EE in case of corruption.This can be completely handled in DataEE_Write(). Data can be declared with the DataEE qualifier with arrays, structures or any other data type.

Display Memory in a Graphic LCD Module
Consider an array where each element in the array represents a color of a pixel on a display. If the processor shares memory with the display then this is a given. However, for displays with a serial interface or requires commands to access the display users can use a user defined space to have the same capability. The functions might look like this:

void LCD_Read(int32 addr, int8 * ram, int bytes) {
glcd_ReadPixels(addr*LCD_WIDTH*sizeof(color_t), // X
addr % (LCD_WIDTH*sizeof(color_t)), // Y

void LCD_Write(int32 addr, int8 * ram, int bytes) {
glcd_DrawPixels(addr*LCD_WIDTH*sizeof(color_t), // X
addr % (LCD_WIDTH*sizeof(color_t)), // Y

addressmod (LCD_screen, LCD_read,LCD_write,0,0x3ff);

LCD_screen char screen[180][240];

A function to draw a line might look like this:

void draw_line( int16 x, int16 y, int16 length, color_t color){
for(int16 i=x; i<(x+length); i++)

Dual Copy Safety Variables for a Safety Critical Application
Consider an application where to get certified users are required to save all critical variables in two memory locations and to verify they match before using them. Users can add logic to the hundreds of places they access the variables. It would be easier with macros however it will still make the code hard to read and if the variables considered critical changes then it can be a mess to update the code. Instead try this:

void Safety_Read(int32 addr, int8 * ram, int bytes) {
for(int i=0;i<bytes;i++,ram++,addr++)
trigger_system_fault('Bad RAM');

void Safety_Write(int32 addr, int8 * ram, int bytes) {
for(int i=0;i<bytes;i++,ram++,addr++) {

Then the critical variables can be declared like this:

SafetyRAM int16 motor_speed;
SafetyRAM int16 recent_presures[10];
SafetyRAM struct {
int32 target_voltage; // tenths of a volt
int16 time_since_change; // seconds
int8 last_adjustment; // tenths of a volt
} drive_data;

In the above discussion, we covered how to implement user defined address spaces. The next part details how some PIC® MCUs have their own alternative address spaces and how the C compiler deals with them. For example the enhanced PIC16 parts have both a physical address space in the traditional memory banks and they have an alternate linear address space that includes only the user RAM locations and no Special Function Registers. Some PIC18's and PIC24's also have alternate addressing schemes.

Like us on Facebook. Follow us on Twitter.

About CCS:

CCS is a leading worldwide supplier of embedded software development tools that enable companies to develop premium products based on Microchip PIC® MCU and dsPIC® DSC devices. Complete proven tool chains from CCS include a code optimizing C compiler, application specific hardware platforms and software development kits. CCS' products accelerate development of energy saving industrial automation, wireless and wired communication, automotive, medical device and consumer product applications. Established in 1992, CCS is a Microchip Premier 3rd Party Partner. For more information, please visit

PIC® MCU, MPLAB® IDE, MPLAB® ICD2, MPLAB® ICD3 and dsPIC® are registered trademarks of Microchip Technology Inc. in the U.S. and other countries.