const
means different things in different contexts as far as storage goes.
For a simple numeric value the compiler will generally replace the constant with the literal value. Any mathematics using purely constants or literals will be replaced at compile time with the result.
For instance, the code:
const int a = 3;
const int b = 4;
void setup() {
int c = a + b;
Serial.println(c);
}
void loop() {
}
results in the AVR assembly:
be: 4a e0 ldi r20, 0x0A ; 10
c0: 50 e0 ldi r21, 0x00 ; 0
c2: 67 e0 ldi r22, 0x07 ; 7
c4: 70 e0 ldi r23, 0x00 ; 0
c6: 80 e2 ldi r24, 0x20 ; 32
c8: 91 e0 ldi r25, 0x01 ; 1
ca: 0c 94 4a 01 jmp 0x294 ; 0x294 <_ZN5Print7printlnEii>
Registers r22
and r23
store the second integer parameter to the Serial.println() - the actual function call generated is _ZN5Print7printlnEii(&Serial, 7, 10)
.
So you see that the literal value 7 has replaced both the sum of the constants and the variable it is placed in.
However it is very different when more complex values are called for - such as arrays (either numeric or character arrays, AKA "C Strings"). In this case yes the values are stored in Flash, however the startup code crt0.o
copies that data into SRAM so that the software can access it easily.
Generally flash is hard for software to read from. In some architectures it requires special instructions to be executed. In others it requires setting values in SFRs (Special Function Registers) and reading from other SFRs to get the data. In other words it's far harder than just reading from a memory location (a limitation of Harvard Architecture). So it's copied at boot time into SRAM so that it then becomes easy to access, as it's just now accessing a memory location.
However that then defeats one of the objectives of using a constant value - reduction of SRAM usage by placing the data in Flash. So the Arduino has a specific macro PROGRMEM
, which sets attributes on the variable to say "Never copy this data to SRAM".
More specifically it's actually provided by the file avr/pgmspace.h
in the avr-gcc compiler:
#define __ATTR_PROGMEM__ __attribute__((__progmem__))
#define PROGMEM __ATTR_PROGMEM__
But now of course you can't access the data using simple memory accesses, so there are special functions to allow it to be done. Some are alternate versions of standard C library routines, for instance strcmp_P()
shadows strcmp()
with the second parameter being a PROGMEM variable. Others are special functions for reading bytes or words from locations in flash, such as pgm_read_byte_near()
.
The Arduino API also provides an extra little trick, called the __FlashStringHelper
which is an empty class used to identify strings held in flash for the purpose of function overloading so that the F()
macro can be used to call the right variant of a function to use the flash reading functions instead of assuming the string is in SRAM.
So as you can see it's not as straight forward as "consts are in Flash, non-consts are in RAM".
Highlighting the differences between how the AVR accesses Flash and how the various PIC sub-families access Flash:
- The AVR has one single instruction that can read Flash - LPM:
There is only one instruction for the read access to the program storage space. It is defined for the pointer pair Z and it is named LPM (Load from Program Memory). The instruction copies the byte at program flash address Z to the register R0. As the program memory is organized word-wise (one instruction on one address consists of 16 bits or two bytes or one word) the least significant bit selects the lower or upper byte (0=lower byte, 1= upper byte). Because of this the original address
must be multiplied by 2 and access is limited to 15-bit or 32 kB program memory. Like this:
LDI ZH,HIGH(2*address)
LDI ZL,LOW(2*address)
LPM
- PIC10, PIC12 and PIC16 chips have no way of reading the flash.
Instead a "lookup table" of "RETLW" calls is used. For instance the string "Hello" would be stored in flash as:
RETLW 72 ; H
RETLW 101 ; e
RETLW 108 ; l
RETLW 108 ; l
RETLW 111 ; o
RETLW 0 ; NULL
RETLW
returns from a CALL
with the W
register set to the literal value in the instruction. The entire command, that is both the RETLW
and the 8 bit literal value, fits into a single 14-bit opcode.
PIC18 uses SFRs to access the Flash memory. Set the three registers TBLPTRU
, TBLPTRH
and TBLPTRL
to the three bytes of the address in flash you want to read, a TBLRD
instruction is issued through the EECON1
register, then you can read the byte from TABLAT
.
PIC24 / dsPIC33 uses PSV - Program Space Visibility. This gives you the ability to map a 32kB chunk of the flash memory into the upper 32kB portion of the data space. This allows direct reading of data from within that 32kB chunk as if it were in RAM.
PIC32 uses a Von Neumann architecture which means that Flash and SRAM both share the same 32-bit address space. There is no difference reading from Flash or SRAM, so no special instructions are needed.
The only chip from those above where const
really means const
is the PIC32 where there is no need to copy from Flash to SRAM during startup since all the Flash memory can be directly accessed with normal MIPS LW
and LB
etc instructions.