Problem with array declared in PROGMEM

I have a problem with this bit of code causing some strange runtime errors:

const uint8_t CProgram::m_arrayRelayPinNums[] PROGMEM = {22, 24, 26, 28, 30, 32, 34, 36};
const uint8_t CProgram::m_arrayProbePinNums[] PROGMEM = {A8, A9, A10, A11, A12, A13, A14, A15};


void CProgram::begin()
{
  for (uint8_t nI = 0; nI < MAX_STATIONS; nI++)
  {
    pinMode(m_arrayRelayPinNums[nI], OUTPUT);
    pinMode(m_arrayProbePinNums[nI], INPUT);
    digitalWrite(m_arrayRelayPinNums[nI], HIGH);
  }
}

I have changed my array to occupy PROGMEM but I have neglected to alter this chunk of code accordingly.

Except that I don’t really know the correct way to dereference an array in PROGMEM.

Can anyone enlighten me?

https://www.arduino.cc/en/Reference/PROGMEM

jremington:
PROGMEM - Arduino Reference

Ahhhh these sorts of functions: pgm_read_word_near(...)

Yes I remember now seeing them when I was trying to figure out how to use PROGMEM properly but promptly forgot all about them.

    pinMode(m_arrayRelayPinNums[nI], OUTPUT);
    pinMode(m_arrayProbePinNums[nI], INPUT);

Just remember that the compiler only remembers the address, not the address space. Unless you use the special functions it will assume that every address you use is in SRAM. If your address is in FLASH/PROGMEM or EEPROM then you have to call a function to fetch the data from the correct address space.

I hope that some day the compiler will be improved to actually remember the address space for each variable.

johnwasser:
I hope that some day the compiler will be improved to actually remember the address space for each variable.

While that may be in the works, it seems more likely that people will leave the AVR behind for other often more powerful processors that don't have this issue, which are now starting to be at the same or similar price points.

The PROGMEM stuff is a Atmel proprietary kludge to offer the ability to store data in flash since the h/w can't offer direct access to it, which is what the C language expects as it has no concept of multiple address spaces.

None of the other new generation processors out there like ARM or PIC32 have this issue.
IMO, the AVR designers really pulled a boner in their h/w architecture. Having been involved with ASICs that have embedded processors inside them, I understand the seduction of going full harvard as it can reduce complexity and potentially cost; however, when that means that the C language can't really fully work with it, it seems like a bad decision.
i.e. The the split address space h/w architecture, with no built in h/w capability to access the code space from the data space side, traded a short term h/w efficiency for a permanent long term s/w issue that can potentially increase the cost of development and support of the final project/product.

--- bill

johnwasser:

    pinMode(m_arrayRelayPinNums[nI], OUTPUT);

pinMode(m_arrayProbePinNums[nI], INPUT);




Just remember that the compiler only remembers the address, not the address space. Unless you use the special functions it will assume that every address you use is in SRAM. If your address is in FLASH/PROGMEM or EEPROM then you have to call a function to fetch the data from the correct address space.

I hope that some day the compiler will be improved to actually remember the address space for each variable.

Wouldn't that be convenient! Also the ability to cancel a compile would be nice.

If MAX_STATIONS isn't declared to be 8 you'll have problems. You should use MAX_STATIONS to declare the size of the two arrays. And if MAX_STATIONS really is just the maximum number but there can be fewer than that in the array, you need to fix up the for loop so that you detect when you've reached the end of the actual number of pins e.g. if pin zero is never used, you can exit the for loop when pin zero is read from the array.

Pete

el_supremo:
If MAX_STATIONS isn't declared to be 8 you'll have problems. You should use MAX_STATIONS to declare the size of the two arrays. And if MAX_STATIONS really is just the maximum number but there can be fewer than that in the array, you need to fix up the for loop so that you detect when you've reached the end of the actual number of pins e.g. if pin zero is never used, you can exit the for loop when pin zero is read from the array.

Pete

Array is fixed at MAX_STATIONS. They are watering stations with 8 digital pins controlling a fixed eight relays. To each relay you can attach a solenoid or not. If no solenoid is attached to a relay then activating it is of no consequence.

It's still bad programming practice.

Pete

el_supremo:
It's still bad programming practice.

Pete

Under normal circumstances probably but not for arduino - all the advice seems to be that using the heap with arduino is bad practice (e.g. String)

Hence I did not implement the array as dynamic....and it is of no consequence on a Mega in that there is enough memory to accommodate it.

Uno is out of the question because it does not have enough memory to accommodate all the libraries I require, let alone a fixed array.

Clearly I would not implement fixed size arrays if I was writing a program under Visual Studio.

I think you are missing my point. I'm not referring to the use of PROGMEM.
Those two declarations should be:

const uint8_t CProgram::m_arrayRelayPinNums[MAX_STATIONS] PROGMEM = {22, 24, 26, 28, 30, 32, 34, 36};
const uint8_t CProgram::m_arrayProbePinNums[MAX_STATIONS] PROGMEM = {A8, A9, A10, A11, A12, A13, A14, A15};

If less than MAX_STATIONS entries are used to initialize the array, the remainder will be zero and the code in the for loop should test for that. If more than MAX_STATIONS are defined, the compiler will throw an error message. That way you don't get any mysterious crashes or oddball behaviour when you, or someone else, change the code weeks or months down the road.

Pete

I hope that some day the compiler will be improved to actually remember the address space for each variable.

I think that the current version of avr-gcc has an __flash variable modifier as part of the "named address space" feature, along with a "universal" 24bit pointer. Unfortunately, C++ doesn't seem to believe in the concept :-(, none of the avr-libc functions support the universal pointers, and it's usually easier and more efficient to deal with the two memories manually.

johnwasser:
I hope that some day the compiler will be improved to actually remember the address space for each variable.

A reasonable compromise...

https://www.google.com/search?q=mikal+hart's+flash+library
http://arduiniana.org/libraries/flash/

westfw:
__flash variable modifier

https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Named-Address-Spaces.html