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 = w*h;
int px[w*h];                        // 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 * ((3*w + 3)/4);      // how many bytes in the row (used to create padding)
  int fileSize = 54 + h*rowSize;        // 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[(y*w + x)*3+0] = (unsigned char)(colorVal);    // R
      img[(y*w + x)*3+1] = (unsigned char)(colorVal);    // G
      img[(y*w + 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 = w*h;
int px[w*h];                        // 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 < amount_CT_samples; i++){
      if(Xarr[i] == x && Yarr[i] == (h - y))return green;
    }
    return black;
}

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

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

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

    // Write image data.
    uint8_t row[rowSize];
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < 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 < amount_CT_samples; i++){
        Xarr[i] = i;
        Yarr[i] = i;
    }

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

  // 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() { }
4
  • 2
    Would it be doable to create and write the headers first, and then generate the image row by row, and save each row to to the SD card before generating the next one? Should be under 3kB/row. The for loops suggest this could be doable. Don't know where the padding goes, though. Also, maybe you could generate directly to the flash memory instead of to the RAM, but I don't know what can be done there on the Due. – ocrdu Nov 12 '20 at 18:31
  • @ocrdu, thanks for your comment. Indeed a line by line write or a direct write to flash would solve my problem. But I don't know how I could practically implement this. – Sven Onderbeke Nov 12 '20 at 20:58
  • 2
    Well, roughly, you would use a int px[] the size of one row, fill it in the inner for loop, so for one value of h, then write it to the SD card at the end of the outer for loop, and then do the same for the next row i.e. the next h in the outer loop. Grofweg. – ocrdu Nov 12 '20 at 21:04
  • 1
    Thanks this is indeed a good idea! – Sven Onderbeke Nov 14 '20 at 18:24
4

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 * ((3*w + 3)/4);  // padded to multiple of 4
    size_t fileSize = 54 + h*rowSize;    // includes header

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

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

    // Write image data.
    uint8_t row[rowSize] = {0};
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < 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.

2
  • 2
    @SvenOnderbeke, this is a good starting point for you. Just so you're not caught off guard, I wanted to mention that the BMP format stores its images in a way that most of us would call up-side-down. The rows appearing first in the file are content that appears at the bottom of the image when displayed. – timemage Nov 13 '20 at 21:44
  • Perfect, thanks a lot! This answer solved my problem, I also edit my question so it now contains the solution. – Sven Onderbeke Nov 14 '20 at 18:29
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 && i == poi.y;         
        if (coordinate_of_interest) {
          break;
        }
      }                      

      if (coordinate_of_interest) {
        Serial.print("XX");
      } else {
        Serial.print("..");
      }
    }
    
    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.

2
  • Thank you for that advice! The solutions given here really helped me to generate the code I needed. (I edited my question so it now contains the solution). – Sven Onderbeke Nov 14 '20 at 18:34
  • @SvenOnderbeke, Nice. If this was actually helpful you can mark it up. But as I understand it, it is fitting that you should mark Edgar's answer as "accepted" because you have described it as solving your problem. – timemage Nov 14 '20 at 19:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.