3

My goal is to have an Arduino Due (96 KB RAM) write a +-1MB bitmap to an SD card.

I have an array of a several coordinates on my Arduino Due and I would like to generate the bitmap where all the coordinates have a certain color and all the other pixels/background have another color.

I do have code to write a bitmap to an SD card (see below) but the problem is that the bitmap is dynamically generated and allocated in ram and then written to the SD card. So as soon as the bitmap I do want to write to the SDcard becomes bigger than the RAM available I get an error: "xx.elf section '.bss' is not within region 'ram'.

Could anyone advice me on how to put parts of the bitmap in ram and write them to the sdcard one after another. Or would there maybe be another better approach?

#include <SPI.h>
#include <SD.h>

const int chipSelect = 10;

char name[] = "9px_0000.bmp"; // filename convention (will auto-increment) const int w = 800; // image width in pixels const int h = 400; // " height const boolean debugPrint = true; // print details of process over serial?

const uint32_t imgSize = wh; int px[wh]; // actual pixel data (grayscale - added programatically below)

File file;

const int amount_CT_samples = 20; int Yarr[amount_CT_samples]; int Xarr[amount_CT_samples];

void setup() {

// SD setup SerialUSB.begin(9600); while (!SerialUSB) { ; // wait for serial port to connect. Needed for native USB port only }

SerialUSB.print("Initializing SD card...");

if (!SD.begin(chipSelect)) { SerialUSB.println("initialization failed!"); return; } SerialUSB.println("initialization done.");

// if name exists, create new filename for (int i=0; i<10000; i++) { name[4] = (i/1000)%10 + '0'; // thousands place name[5] = (i/100)%10 + '0'; // hundreds name[6] = (i/10)%10 + '0'; // tens name[7] = i%10 + '0'; // ones file = SD.open(name, O_CREAT | O_EXCL | O_WRITE); if (file) { break; } }

// set fileSize (used in bmp header) int rowSize = 4 * ((3w + 3)/4); // how many bytes in the row (used to create padding) int fileSize = 54 + hrowSize; // headers (54 bytes) + pixel data

// create image data; heavily modified version via: // http://stackoverflow.com/a/2654860 unsigned char img = NULL; // image data if (img) { // if there's already data in the array, clear it free(img); } img = (unsigned char )malloc(3*imgSize);

for (int y=0; y<h; y++) { for (int x=0; x<w; x++) { int colorVal = px[y*w + x]; // classic formula for px listed in line img[(yw + x)3+0] = (unsigned char)(colorVal); // R img[(yw + x)3+1] = (unsigned char)(colorVal); // G img[(yw + x)3+2] = (unsigned char)(colorVal); // B // padding (the 4th byte) will be added later as needed... } }

// create padding (based on the number of pixels in a row unsigned char bmpPad[rowSize - 3*w]; for (int i=0; i<sizeof(bmpPad); i++) { // fill with 0s bmpPad[i] = 0; }

// create file headers (also taken from StackOverflow example) unsigned char bmpFileHeader[14] = { // file header (always starts with BM!) 'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0 }; unsigned char bmpInfoHeader[40] = { // info about the file (size, etc) 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0 };

bmpFileHeader[ 2] = (unsigned char)(fileSize ); bmpFileHeader[ 3] = (unsigned char)(fileSize >> 8); bmpFileHeader[ 4] = (unsigned char)(fileSize >> 16); bmpFileHeader[ 5] = (unsigned char)(fileSize >> 24);

bmpInfoHeader[ 4] = (unsigned char)( w ); bmpInfoHeader[ 5] = (unsigned char)( w >> 8); bmpInfoHeader[ 6] = (unsigned char)( w >> 16); bmpInfoHeader[ 7] = (unsigned char)( w >> 24); bmpInfoHeader[ 8] = (unsigned char)( h ); bmpInfoHeader[ 9] = (unsigned char)( h >> 8); bmpInfoHeader[10] = (unsigned char)( h >> 16); bmpInfoHeader[11] = (unsigned char)( h >> 24);

// write the file (thanks forum!) file.write(bmpFileHeader, sizeof(bmpFileHeader)); // write file header

}

void loop() { }

UPDATE

With the following code my problem was solved. Credits to those who answered my question below!

#include <SPI.h>
#include <SD.h>
const int chipSelect = 10;

struct Pixel { uint8_t r, g, b; };

char name[] = "CT_0000.bmp"; // filename convention (will auto-increment) const int w = 800; // image width in pixels const int h = 400; // " height const boolean debugPrint = true; // print details of process over serial?

const uint32_t imgSize = wh; int px[wh]; // actual pixel data (grayscale - added programatically below)

File file;

const int amount_CT_samples = 50; int Xarr[amount_CT_samples]; int Yarr[amount_CT_samples];

Pixel getPixel(int x, int y) { const Pixel black = {0, 0, 0}, green = {0, 255, 0};

for(int i=0; i &lt; amount_CT_samples; i++){
  if(Xarr[i] == x &amp;&amp; Yarr[i] == (h - y))return green;
}
return black;

}

void writeBitmap(File &file, int w, int h) { size_t rowSize = 4 * ((3w + 3)/4); // padded to multiple of 4 size_t fileSize = 54 + hrowSize; // includes header

// Write image header.
uint8_t header[54] = {
    // File header.
    'B','M',
    (uint8_t)(fileSize &gt;&gt;  0),
    (uint8_t)(fileSize &gt;&gt;  8),
    (uint8_t)(fileSize &gt;&gt; 16),
    (uint8_t)(fileSize &gt;&gt; 24),
    0,0, 0,0, 54,0,0,0,

    // Image info header.
    40,0,0,0,
    (uint8_t)(w &gt;&gt;  0),
    (uint8_t)(w &gt;&gt;  8),
    (uint8_t)(w &gt;&gt; 16),
    (uint8_t)(w &gt;&gt; 24),
    (uint8_t)(h &gt;&gt;  0),
    (uint8_t)(h &gt;&gt;  8),
    (uint8_t)(h &gt;&gt; 16),
    (uint8_t)(h &gt;&gt; 24),
    1,0, 24,0
};
file.write(header, sizeof header);

// Write image data.
uint8_t row[rowSize];
for (int y = 0; y &lt; h; y++) {
    for (int x = 0; x &lt; w; x++) {
        Pixel pix = getPixel(x, y);
        row[3*x + 0] = pix.b;
        row[3*x + 1] = pix.g;
        row[3*x + 2] = pix.r;
    }
    file.write(row, sizeof row);
}

file.close();  

}

void setup() { Serial.begin(9600); Serial.print("Initializing SD card...");

//create dummy X and Y points
for(int i = 0; i &lt; amount_CT_samples; i++){
    Xarr[i] = i;
    Yarr[i] = i;
}

if (!SD.begin(chipSelect)) {
  Serial.println(&quot;initialization failed!&quot;);
  return;
}
Serial.println(&quot;initialization done.&quot;);

// if name exists, create new filename for (int i=0; i<10000; i++) { name[3] = (i/1000)%10 + '0'; // thousands place name[4] = (i/100)%10 + '0'; // hundreds name[5] = (i/10)%10 + '0'; // tens name[6] = i%10 + '0'; // ones file = SD.open(name, O_CREAT | O_EXCL | O_WRITE); if (file) { break; } }

Serial.println("start writing bitmap."); writeBitmap(file, w, h); Serial.println("done writing bitmap.");

}

void loop() { }

Sven Onderbeke
  • 155
  • 1
  • 7

2 Answers2

3

You can write the file in chunks. As suggested by ocrdu in a comment, writing line by line is a good strategy. In order to keep the complexity of the code manageable, I would put the logic of deciding the color of a pixel in its own function, and use another function for managing writing the bitmap. For example:

struct Pixel {
    uint8_t r, g, b;
};

// Dummy example. Replace with your own logic. Pixel getPixel(int x, int y) { const Pixel black = {0, 0, 0}, green = {0, 255, 0}; if (((x - y) & 0x0f) == 0) return green; return black; }

void writeBitmap(File &file, int w, int h) { size_t rowSize = 4 * ((3w + 3)/4); // padded to multiple of 4 size_t fileSize = 54 + hrowSize; // includes header

// Write image header.
uint8_t header[54] = {
    // File header.
    'B','M',
    (uint8_t)(fileSize &gt;&gt;  0),
    (uint8_t)(fileSize &gt;&gt;  8),
    (uint8_t)(fileSize &gt;&gt; 16),
    (uint8_t)(fileSize &gt;&gt; 24),
    0,0, 0,0, 54,0,0,0,

    // Image info header.
    40,0,0,0,
    (uint8_t)(w &gt;&gt;  0),
    (uint8_t)(w &gt;&gt;  8),
    (uint8_t)(w &gt;&gt; 16),
    (uint8_t)(w &gt;&gt; 24),
    (uint8_t)(h &gt;&gt;  0),
    (uint8_t)(h &gt;&gt;  8),
    (uint8_t)(h &gt;&gt; 16),
    (uint8_t)(h &gt;&gt; 24),
    1,0, 24,0
};
file.write(header, sizeof header);

// Write image data.
uint8_t row[rowSize] = {0};
for (int y = 0; y &lt; h; y++) {
    for (int x = 0; x &lt; w; x++) {
        Pixel pix = getPixel(x, y);
        row[3*x + 0] = pix.b;
        row[3*x + 1] = pix.g;
        row[3*x + 2] = pix.r;
    }
    file.write(row, sizeof row);
}

}

Then after opening the file, you just have to:

writeBitmap(file, 800, 400);

Note that the getPixel() function above is just a dummy example that draws green diagonal lines across the image. See timemage's answer for ideas on how to implement your own pixel-generating function.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
1

Normally I'd write this a differently, but I've kept in relatively simple just to illustrate the basic idea:

struct point {
  int x;
  int y;
};

const point points_of_interest[] = { { 7, 11}, {23, 17}, {13, 19}, { 3, 5},
{17, 3}, };

void setup() { Serial.begin(9600); }

void loop() { delay(4000); Serial.println("\n\n\n");

for (int i = 0; i < 24; ++i) { for (int j = 0; j < 24; ++j) { bool coordinate_of_interest = false;

  for (const auto poi: points_of_interest) {       
    coordinate_of_interest = j == poi.x &amp;&amp; i == poi.y;         
    if (coordinate_of_interest) {
      break;
    }
  }                      

  if (coordinate_of_interest) {
    Serial.print(&quot;XX&quot;);
  } else {
    Serial.print(&quot;..&quot;);
  }
}

Serial.println();

} }

It's printing a 24x24 or 576 "pixel" grid. But only five coordinates are stored. It would matter if it were a 2400x2400 grid, but it would not print nicely on a maximized Serial Monitor for example purposes.

With your bitmap, you could get more sophisticated. E.g. checking that distance (or more efficiently distance squared; no sqrt() operation) from the pixel under consideration (i, j) to any point in the list is less than or equal to some amount, and coloring based on that. This would result in solid circles centered on your points in your bitmap. Or color gradient around the point if you base the color on the distance.

If you have a lot of points you will need to do something like sort them by their vertical coordinates and advance an index that represents the points on or below your horizontal sweep line, etc. That is, if you have a lot of points you don't want to have to be taking the distance to all of them for every pixel under consideration.

timemage
  • 5,639
  • 1
  • 14
  • 25