I am using a Mega 2560 with an external memory card.
The data arrays I am using are quite large (500 by 500). The data stored in the arrays will be bytes.
As far as feeding a 2 dimensional array, I understand that and do it using 2 "for loops".
I have mastered the concept of assigning single point Vars to some memory location. I have also figured out how to assign a single point array to some memory location.
A 2 dimensional array????
Also I need or would like to access the data in the usual way "myArray[x][y]".
My thoughts are as follows:
int *DataArray_2d = 8704;
byte int DataArray_2d[500][500];
Main()
DataArray_2d[x][y] = *DataArray_2d;
The problem is similar to "how do I malloc() a two-dimensional array in C", for which I see a lot of internet discussion (most proposing somewhat questionable an ugly solutions )
I have also been thinking about how I could get the job done using single dimensional arrays but it kind of looks messy when you consider 500, 500 data point arrays.
It might be easier to use a single 32,k point single dimensional array and access the data using a fancy offset algorithm.
Well, this caused me to do some more studying again. Not a bad thing. I now know what the term "Struct" means, how they work and how to set them up. Very Valuable!!
I could not get your example to compile exactly as written but I did figure it out and it works.
struct {
byte array[500][500];
} *ap = (byte *)8704; // I had to take the "(byte *) out. Once I did that, it compiled and worked
For those others who may follow, in this case the array is a 5 by 5 2 dimensional array who's base address is at memory location 9,000.
This is what I did:
int x=0; int y =0; int i;
unsigned int ii;
struct {
byte DataArray[5][5];
}*PtrDataArray_2d = 9000;
Main()
Serial.println (""); Serial.println ("2 dimensional Array");
ii = PtrDataArray_2d;
Serial.print("PtrDataArray_2d= "); Serial.println(ii);
i = 0;
//Write to the 2 dimensional array
for (y =0; y <= 1; y++){
for (x =0; x<= 4; x++){
PtrDataArray_2d->DataArray[y][x] = i;
Serial.print("i= "); Serial.println(i);
i = i + 1;
}
i = 10; // add 10 to row 2 of the array
}
//Read from the 2 dimensional array
for (y =0; y<= 1; y++){
for (x =0; x<= 4; x++){
ii = PtrDataArray_2d->DataArray[y][x];
Serial.print("y= "); Serial.print(y); Serial.print(" ,x= "); Serial.print(x);
Serial.print(" Data= "); Serial.println(ii);
}
}
}
I don't think you need to resort to pointers, if I understand the problem correctly. This was tested on an Uno but I see no reason why it wouldn't scale up.
const int mySize = 20;
byte array[mySize][mySize];
int x, y;
int myLoc;
void setup() {
Serial.begin(9600);
// zero out both dimensions
for ( x = 0; x < mySize; x++) {
for ( y = 0; y < mySize; y++) {
array[x][y] = 0;
}
}
array[15][15] = 54;
Serial.println(array[15][15]);
myLoc = 315; // location 15,15
x = myLoc / mySize;
y = myLoc % mySize;
Serial.println(array[x][y]);
array[2][0] = 63;
Serial.println(array[2][0]);
myLoc = 40; // location 2,0
x = myLoc / mySize;
y = myLoc % mySize;
Serial.println(array[x][y]);
}
void loop() {
}
What I am doing is using a Mega2560 with an external memory card. The mega can directly address 64k of ram.
The memory card has 512k of RAM arranged in 8 pages. The programmer has to control which page is being written to or read from.
The data arrays I am using are 500 by 62. That works out to be 31k bytes each. I plan on loading 8 arrays in the memory card.
The problem with the compiler/linker is that they don't know how to use the external memory in the normal way. As such the programmer (me) have to tell the processor where to put the data and where to go and get the data.
The task of assigning a single point VAR or even a single dimensional array to a memory location is easy. You just use a pointer and tell to program where to write or read the data. With a single dimensional array, the pointer points to the base of the array and you just index the pointer to move from cell to cell.
The 2 dimensional array on the other hand had me baffled. Westfw was kind enough to turn me on to the concept of "Structs". Using a "Struct" I (Westfw) built a 2 dimensional array as a member of the "struct" and the pointer points to the member. From there you just access the data in the 2 dimensional array as you normally would.
The reason for using pointers is to allow me to manage where the arrays are put as the compiler in its current configuration can't. I am sure if you were good enough (and I am NOT), the compiler/linker could be instructed handle the external memory.
I could not get your example to compile exactly as written but I did figure it out and it works.
Excellent. It was off the top of my head, and I didn't really expect it to work exactly as posted, but I'm pleased that you were able to convert the "hint" into something that worked.
I am sure if you were good enough (and I am NOT), the compiler/linker could be instructed handle the external memory.
It's not so much having the linker "handle" the external memory that I was thinking, of, but rather "tricking" it into thinking that was where the data was.
If you have "extern int myarray[500][28]" in your code, it will all get compiled to correctly access the array. Normally you'd have some other C program that would contain the actual array, and the linker would carefully fit that array somewhere in RAM, and resolve the actual addresses needed to access the array when they were linked together.
But in theory, you could also MANUALLY assign values to the symbols using mysterious ld commands, custom linker scripts, and/or named sections. But I'm not sure exactly how that would work, especially with the separate RAM/Flash address spaces in the AVR, and it would end up being less portable and understandable than the structure-based method.
This works... but you loose the nice array notation of "array[dx][dy]".
If you must have the bracketed notation, perhaps you can use westfw's idea of "extern int myarray[500][62]" to create a zero origin reference to the array and then you can use the previous code I posted (after adding the address offset).
// assumes we have "byte myArray[500][62]" created in external memory by the programmer
// call with offset = the base address of the memory block in the current bank
// x = the first array dimension
// y = the second array dimension
// returns the contents of memory at the array location
const int mySize = 62;
byte myArrayGet(uint16_t offset, uint16_t x, uint16_t y)
{
uint8_t *addr;
addr = (uint8_t*)(offset + (x*mySize) + y);
return *addr;
}
you lose the nice array notation of "array[dx][dy]".
Since you're going to be using bank-switching to deal with >64k of memory anyway, you could just dispense with using arrays indices and go with get/put functions that do their own index calculations:
int getarray(int bank, int x, int y) {
PORTL = bank;
int * index = sizeof(int) * (x * 500 + y);
return *index;
}
PS: you might want to make your "500" size be 512 instead; you (or the compiler) can be smart enough to get rid of a multiply...
We used to do this all the time in the 80's to access video memory in CGI cards. We declared memory at 0xB8000000 to be a two dimensional array of 80 unsigned ints. The high byte of each int was colour, the low byte was the character, and the screen was 80 columns wide, so [nobbc]scr[2][5] = 'A' | 0x1F00;[/nobbc] would put a bright nonblinking white uppercase 'A' on a blue background at row 3, column 6 on the screen.
The memory card I am using is a Rugged Circuits card. Andy's Workshop wrote the library to run the memory card. Andy has a function built into his library to switch 64k memory blocks. This makes it very easy to switch 64k memory banks. Rugged Circuits provides a piece of code that allows you to switch 32k memory blocks to get around the issue of losing 8k at the bottom of each 64k block due to avr's use of the address locations for internal RAM. In my case (for now) I will load eight arrays in memory (nine would be better) and be happy with that. I will be loading the database into RAM from an SD card.
Paul:
I am using the arrays pretty much as you were using them in the 80's. The arrays I am using contain monochrome graphic data using a modified XBM format. Each bit represent a pixel. In the array (array[y][x]) the y represents vertical lines on the display and the x represents the horizontal lines.
The display I am using currently is a 128 by 64 LCD display. Using 2 simple "for loops" I can display any 128 by 64 section within the array I want and move around the database at will. Vertically I can move the display one pixel at a time while horizontally I have to move the display 8 pixels at a time (more than good enough).
With respect to locating the array in memory, I do not want the arrays to cross a 64k block. Assuming that the compiler/linker could assign the arrays to memory areas outside of the 8k resident RAM, it does not go a good job when the database crosses a 64k boundary. I had a lot of trouble with the database getting corrupted when I was loading the data into PROGMEM. I had to use the late function to force the compiler to load the data after the program functions using the following but the data was still getting corrupted.
The use of the statement above did solve the problem with the program itself being corrupted but did not correct the problems with the database. To date, I have not found a way to force the compiler to place the arrays where I want them. I see in oqibidipo's post that there is an "attribute" function that may help with this. I will try that soon.
oqibidipo:
Is there a list of these "Attribute" functions?? I use CPlusPlus.com as a "C" reference and the "avr" website for "avr" related issues. The "Attribute" option is mention but I have never seen a list of them and or a manual for them (so to speak).
KenK:
Is there a list of these "Attribute" functions?? I use CPlusPlus.com as a "C" reference and the "avr" website for "avr" related issues. The "Attribute" option is mention but I have never seen a list of them and or a manual for them (so to speak).
(Scroll down to the AVR section.)
The address attribute is not mentioned in GCC 4.9 documentation, but it does work in 4.9.2.