ATmega2560, 64k barrier, use both data AND size as parameters

For an audio project, I need to fill nearly all the FLASH memory of an ATmega2560 with arrays of different sizes. Lines like this

adrLen[0] = { pgm_get_far_address(rawData0), sizeof rawData0 };

do their job, but it requires to use the same parameter twice, which is prone to errors. Calls like this

initA(0, rawData0);

with a function like

void initA(int dest, const char source) {
  adrLen[dest] = { pgm_get_far_address(source), sizeof source };
}

would be much better, but it does not compile. I tried a lot of types and type castings, no way. Could it be, what I want is completely impossible?
(see MCVE fragment at https://clauduino.de/audioOut2560_6b/index.html)
For some reason, this project cannot be transferred to UNO-R4.

You will not be able to use sizeof for an array passed into a function, regardless of whether it is in flash memory or ram.

You can define a macro to expand the array into the struct in the function call, and pass the struct to the function.

/*
  several huge char arrays rawData0, 1, 2
*/

#define TRY

const PROGMEM
#include "0_44M8.h"
const PROGMEM
#include "1_44M8.h"

const int N = 8;

struct adrLen_t {
  uint_farptr_t address;
  word length;
};

//function prototype must be after definition of struct
void initA(int dest, const adrLen_t source);
 
#define flashArray(x) { pgm_get_far_address(x), sizeof(x)  }
 
void setup() {
  Serial.begin(9600);
  Serial.println(__FILE__);
#ifdef TRY
  // it would be nice if this was compiling and working properly:
  initA(0, flashArray(rawData0));
  initA(1, flashArray(rawData1));
  // and so on
#endif
}

void loop() {}

#ifdef TRY // sorry, not compiling
void initA(int dest, const adrLen_t source) {
  Serial.println((uint32_t)source.address);
  Serial.println(source.length);
}
#endif

Hi, David,
I have to confess: you are completely right. I took a look at the generated code:

adrLen[0] = {pgm_get_far_address(rawData0), sizeof rawData0};
/// this one for the array address

   325ee:	85 ed       	ldi	r24, 0xD5	; 213
   325f0:	99 ea       	ldi	r25, 0xA9	; 169
   325f2:	a2 e0       	ldi	r26, 0x02	; 2
   325f4:	bb 27       	eor	r27, r27
   325f6:	80 93 8f 02 	sts	0x028F, r24
   325fa:	90 93 90 02 	sts	0x0290, r25
   325fe:	a0 93 91 02 	sts	0x0291, r26
   32602:	b0 93 92 02 	sts	0x0292, r27

/// and this for the length

   32606:	8a e0       	ldi	r24, 0x0A	; 10
   32608:	95 e7       	ldi	r25, 0x75	; 117
   3260a:	90 93 94 02 	sts	0x0294, r25
   3260e:	80 93 93 02 	sts	0x0293, r24

It is true: the compiler handles both in a very diferent way. I have to give it up :frowning:

Is it line compiled? I think you have a several problems in it.

It looks like you forget to put the parenthesis around parameter in sizeof operator.
Moreover, the return value of code block { ... } is a result of last operation. So your line abobe is perfectly equivalent just to

 adrLen[dest] = sizeof(source) ;

Also It seems to me that the type of the source variable is incorrect. I don't know what the data it is, but it obviously can't be a char. Is it classic string (char array) ?

b707
can you please enlighten me, how can you get an avr-objdump.exe output when the compiler had not done its work?

The parenthesis are not required here. The parenthesis are sometimes necessary, particularly when evaluating the size of a type, such as sizeof (float).

That is not a code block. Unfortunately the OP posted a link to the code instead of putting it in the question, see my previous reply for a modified version of OPs code. adrLen is an array of struct, the line of code is setting the value of an element of the array.

Source is an array of unsigned char (again the OP did not post the code here, only a link).

So it's type should be unsigned char* rather than char?
So I think it was a mistake to discuss the code without seeing it

It gets complicated in this case.

The source array is an array of unsigned char stored in PROGMEM, and in this case with the possibility of the location in PROGMEM being above the 64K boundary on an atmega2560.

That necessitates the use of the _far version of the PROGMEM functions, such as pgm_read_byte_far().

The type of the pointer is uint_farptr_t, a 24-bit address stored as a uint32_t. The compiler is only able to operate on 16-bit addresses for the atmega2560, necessitating the use of pgm_get_far_address() to produce the pointer at run-time.

Thank you all for your comments.
Yes it is getting complicated.
Initially, I should have made it more clear that the code is compiling, uploading, and working as it should.
So my question was not about accessing data in high memory areas but producing robust code.
That is why I did not waste the forum's precious space by uploading 256 kB of code.
My concern is, it does not look very elegant and above all is less maintainable.
The compiler would still accept a line like this

adrLen[XXX] = { pgm_get_far_address(rawDataYYY), sizeof rawDataZZZ };

but one fine day it would give wrong results - and nobody would notice it.
The proposal to use a macro that accepts one parameter and passes it to functions sounds good, but is not easy to define.

If its the macro you are concerned about, you can type out the full code yourself:

  initA(0, { pgm_get_far_address(rawData0), sizeof(rawData0) });
  initA(1, { pgm_get_far_address(rawData1), sizeof(rawData1) });

You don't really need the struct, or a variable like adrLen[], the individual parameters can be passed to the function:

  initA(0, pgm_get_far_address(rawData0), sizeof(rawData0));
  initA(1, pgm_get_far_address(rawData1), sizeof(rawData1));

Is it that difficult?

#define loadAdrLen(z) adrLen[z] = pgm_get_far_address(rawData##z, sizeof rawData##z)

loadAdrLen(0);
loadAdrLen(1);
loadAdrLen(2);
loadAdrLen(3);

Produces:

adrLen[0] = pgm_get_far_address(rawData0, sizeof rawData0);
adrLen[1] = pgm_get_far_address(rawData1, sizeof rawData1);
adrLen[2] = pgm_get_far_address(rawData2, sizeof rawData2);
adrLen[3] = pgm_get_far_address(rawData3, sizeof rawData3);

I'm not sure what the extra braces were for, though. Maybe you're doing something more complicated. But the pre-processor concatenation operator ("##") looks like it should be your friend.

Thank you for taking the time to help.
Actually, I had to do a small modification as pgm_get_far_address takes only one parameter. Now I am struggeling with

 for (int i = 0; i < sounds; i++)
       adrLen[i] = loadAdrLen(i);

which does not compile as I keep getting the "lvalue required as unary '&' operand" message, whatever I try the macro expands to "rawDatai" which obviously is not declared.
I keep looking for a really good macro tutorial.

The braces were there because adrLen is an array of struct, not a single value per element.

An array of structs, or an array of pointers to structs?
Might be a lot easier if the latter...

From the OP's code:

const int N = 8;

struct adrLen_t {
  uint_farptr_t address;
  word length;
} adrLen[N];

So does C++ actually allow run-time construction of structures like that?
(I'm pretty sure you can't do it in C.)

If you're going to use a macro anyway (which seems like a good idea?) You can get rid of the questionable (?) syntax:

#define loadData(z) do { adrLen[z].address = pgm_get_far_address(rawData##z);    adrLen[z].length = sizeof rawData##z) } while (0)

You won't be able to do that. The macro is expanded before the code is compiled, so there is no way to code a variable into the array name.

It might be possible to write a macro that would generate multiple lines of code with incrementing numbers in the variable name, but it is beyond my skills and would be easier done with a text editor.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.