A common question asked is why code written for a PC cannot be used on microcontrollers. When running a program on a x86 style PC, the program is loaded into memory and begins executing. When that program needs data, it allocates more memory in the same RAM address space. Even the ROM BIOS, video RAM and more, is all in the same address space differentiated only by its address. The PC does have a second separate address space for I/O usually only used to communicate with peripherals.
On a PIC® MCU the memory is different. There are two buses, one for program memory and the other for RAM, peripherals and special registers. That means there are two address 0's. A 0 in program memory and the other in RAM. This is referred to as a Harvard architecture.
Note that the buses on each side of the above diagram are different data width sizes. On the left, is 12,14,16 or 24 bits depending on the PIC® MCU. On the right it is 8 or 16 bit. One advantage to this architecture is both memory spaces can be accessed at the same time.
Look at the following C:
char s[10];
strcpy(s,"Hi There");
On a PC the "Hi There" is loaded with the program into memory. The s is allocated to another area of the RAM. For example if "Hi There" starts at location 0x1108 and s is allocated at 0x2004. The function call looks like this:
strcpy(0x2004, 0x1108);
On a PIC® MCU, by default the "Hi There" is in program memory, such as 0x108. s is in the RAM address space like maybe 0x34. The call would look like this:
strcpy(0x34, 0x108);
The problem is how does strcpy() know if 0x108 is in the program memory space or RAM space? Remember there is a 0x108 address in both.
This problem can come up whenever a pointer is used. In the CCS C compiler the following syntax is used to declare pointers. ROM refers to the left side memory above and RAM to the right side.
char id; id is stored in RAM
char * id; id is stored in RAM, is a pointer to RAM
rom char id; id is stored in ROM
char rom * id; id is stored in RAM, is a pointer to ROM
char * rom id; id is stored in ROM, is a pointer to RAM
rom char * rom id; id is stored in ROM, is a pointer to ROM
Consider this declaration:
char * a;
char rom * b;
When the compiler encounters *a in the code it uses the address in a and grabs data from the RAM address space (right side). When the compiler encounters *b in the code, it uses the address in b and grabs data from the ROM address space (left side). The following would cause nothing but trouble:
a=b;
For function calls the parameters act like assignments so care must be taken not to mix pointers from different address spaces. The same kind of care is not needed on some other architectures like PC's and that is why some ported code does not work.
Back to the strcpy() the reason it works in the CCS compiler is there are two overloaded functions defined like this:
strcpy( char * dest, char * src);
strcpy( char * dest, char rom * src);
In the above example, the second function was called. Not all compiler functions have multiple versions defined. For example, there is only one version of strcat() defined, for RAM only pointers. This can be annoying if your code uses both RAM and constant strings in function calls. One way around this is to always pass RAM strings like this:
write_to_log(char * string);
...
char temp[10];
strcpy(temp, "Hi There");
write_to_log(temp);
The compiler has a feature where you can tell it to always copy constant strings to a temporary RAM area and then pass the RAM pointer. Here is how it looks:
#device pass_strings=in_ram
...
write_to_log(char * string);
...
write_to_log("Hi There");
Users need to be careful because the same RAM area is used the next time a function call is made where it needs it, so the pointer is only valid until the next call.
For some users the rom keyword above may seem new. A common approach to putting data in rom is like this:
const char message[10] = "Hi There";
By default, the compiler saves message in ROM, however, the format is not straightforward. Depending on the chip, the format is different so the data can be saved in the most efficient way possible. That saves memory, however users can not create pointers to const data. In the above users can do message[i] but not &message.
This can cause trouble porting code from other architectures so the compiler provides an alternative interpretation of const. In ANSI C const data is in RAM and the compiler throws an error if you try to change the data. To get that in CCS C do this:
#device const=read_only
As for the default const data the format varies depending on the chip and sometimes the data itself. For example, on some chips the assembly equivalent of this is used:
char lookup_const123(int index) {
switch(index) {
case 0 : return 'H';
case 1 : return 'i';
case 2 : return ' ';
case 3 : return 'T';
case 4 : return 'h';
case 5 : return 'e';
case 6 : return 'r';
case 7 : return 'e';
case 8 : return 0; } }
On a 14 bit PIC® MCU, if the constant data is all under 128, then two items can be packed into each 14 bit word. On a 24 bit part we can pack in 3 bytes of data in each word. None of these methods can provide an ANSI compliant pointer. When the rom is used, the data is always saved with one item per pointer value so pointers are fully supported. Users should be aware on 24 bit parts rom also gets packed three bytes per instruction. Instructions take two addresses. The compiler does an internal translation from a byte address to the device address.
Be aware, although the pointer works as it should, users should not expect to use the pointer for something other than allowing the compiler to access the data.
This article covered the two major address spaces used by the CCS C compiler. The compiler also allows for user defined address spaces. That will be covered in detail in part 2 of this article. Part 3 will cover PIC® MCU chips that have multiple ways of configuring their address space.
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 https://www.ccsinfo.com.
PIC® MCU, MPLAB® IDE, MPLAB® ICD2, MPLAB® ICD3 and dsPIC® are registered trademarks of Microchip Technology Inc. in the U.S. and other countries.
|