Writing binary file to SD (creating BMP files)

Hi all, I'm working with the Sparkfun MicroSD shield and the SdFat library to write files to the card. There are tons of examples on how to write ascii data to text files, but I'm having a hard time figuring out how to write a file from an array of bytes. My code is below - mostly pulled from the SdFat examples and a Stack Overflow example on creating bmp files.

The SD stuff works (writes to the card, creates files, etc) and my computer even sees them as bmp files (not sure if the header is correct, or that the extension is all it needs), but I'm not getting a readable image.

Any help would be fantastic!

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h>

char name[] = "9px_00.bmp";
int w = 3;
int h = 2;
int px[] = {
255, 0, 255, 0, 255, 0 };
boolean debugPrint = true;

SdFat sd;
SdFile file;

void setup() {

// SD setup
Serial.begin(9600);
if (!sd.init(SPI_FULL_SPEED, 8)) {
sd.initErrorHalt();
}

// if name exists, create new filename
for (int i=0; i<1000; i++) {
name[4] = i/10 + '0';
name[5] = i%10 + '0';
if (file.open(name, O_CREAT | O_EXCL | O_WRITE)) {
break;
}
}

// create image data
// heavily modified version via: Writing BMP image in pure c/c++ without other libraries - Stack Overflow
unsigned char img = NULL; // image data
int filesize = 54 + 3 * w * h; // w is image width, h is image height
if (img) {
free(img);
}
img = (unsigned char )malloc(3w
h);
//memset(img,0,sizeof(img)); // not sure if I really need this; runs fine without...

for (int y=0; y<h; y++) {
for (int x=0; x<w; x++) {
int colorVal = px[yw + x];
img[(y
w + x)3+2] = (unsigned char)(colorVal);
img[(y
w + x)3+1] = (unsigned char)(colorVal);
img[(y
w + x)*3+0] = (unsigned char)(colorVal);
}
}

// print px and img data for debugging
if (debugPrint) {
Serial.print("Writing "");
Serial.print(name);
Serial.print("" to file...\n");
for (int i=0; i<wh; i++) {
Serial.print(px
);*
_ Serial.print(" ");_

  • }*

  • }*

  • // create file headers (also taken from above example)*

  • unsigned char bmpFileHeader[14] = {*

  • 'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0 };*

  • unsigned char bmpInfoHeader[40] = {*

  • 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0 };*

  • unsigned char bmpPad[3] = {*

  • 0,0,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!*

  • // this is a combination of the bmp example above and*

  • // one from the SdFat library (it doesn't create a usable*

  • // bmp file, though)...*

  • file.write(bmpFileHeader, sizeof(bmpFileHeader)); // write file header*

  • file.write(bmpInfoHeader, sizeof(bmpInfoHeader)); // " info header*

  • for (int i=0; i<sizeof(img); i++) { // iterate image array*
    _ file.write(img+(w*(h-i-1)3), w); // write px data_
    _ file.write(bmpPad, (4-(w
    3)%4)%4); // and padding as needed_

  • }*

  • if (debugPrint) {*
    _ Serial.println("\n---");_

  • }*
    }
    void loop() { }
    [/quote]

Try this:

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h>

const uint8_t csPin = 8;

char name[] = "9px_00.bmp";
int w = 3;
int h = 2;
int px[] = {
  255, 0, 255, 0, 255, 0 };
boolean debugPrint = true;

SdFat sd;
SdFile file;


void setup() {

  // SD setup
  Serial.begin(9600);
  if (!sd.init(SPI_FULL_SPEED, csPin)) {
    sd.initErrorHalt();
  }

  // if name exists, create new filename
  for (int i=0; i<1000; i++) {
    name[4] = i/10 + '0';
    name[5] = i%10 + '0';
    if (file.open(name, O_CREAT | O_EXCL | O_WRITE)) {
      break;
    }
  }

  // create image data
  // heavily modified version via: http://stackoverflow.com/a/2654860
  unsigned char *img = NULL;          // image data
//  int filesize = 54 + 3 * w * h;      //  w is image width, h is image height
  int filesize = 54 + 4 * w * h;      //  w is image width, h is image height  
  if (img) {
    free(img);
  }
  img = (unsigned char *)malloc(3*w*h);
  //memset(img,0,sizeof(img));        // not sure if I really need this; runs fine without...

  for (int y=0; y<h; y++) {
    for (int x=0; x<w; x++) {
      int colorVal = px[y*w + x];
      img[(y*w + x)*3+2] = (unsigned char)(colorVal);
      img[(y*w + x)*3+1] = (unsigned char)(colorVal);
      img[(y*w + x)*3+0] = (unsigned char)(colorVal);
    }
  }

  // print px and img data for debugging
  if (debugPrint) {
    Serial.print("Writing \"");
    Serial.print(name);
    Serial.print("\" to file...\n");
    for (int i=0; i<w*h; i++) {
      Serial.print(px[i]);
      Serial.print("  ");
    }
  }

  // create file headers (also taken from above example)
  unsigned char bmpFileHeader[14] = {
    'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0             };
  unsigned char bmpInfoHeader[40] = {
    40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0             };
  unsigned char bmpPad[3] = {
    0,0,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!
  // this is a combination of the bmp example above and
  // one from the SdFat library (it doesn't create a usable
  // bmp file, though)...
  file.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
  file.write(bmpInfoHeader, sizeof(bmpInfoHeader));    // " info header
  /*
  // sizeof(img) is always 2
  for (int i=0; i<sizeof(img); i++) {                  // iterate image array
    file.write(img+(w*(h-i-1)*3), w);                  // write px data
    file.write(bmpPad, (4-(w*3)%4)%4);                 // and padding as needed
  }
  */
  int n = w*h;
  for (int i = 0; i < n; i++) {
    file.write(img + 3*i, 3);
    file.write((uint8_t)0);
  }
  file.close();
  if (debugPrint) {
    Serial.println("\n---");
  }
}

void loop() { }

This make a valid bmp but not what you intended. The write is still not correct.

Here is a sketch that will dump a small bmp file:

#include <SdFat.h>
const uint8_t csPin = 8;
SdFat sd;
SdFile file;
char* fileName = "9PX_03.BMP";

class BmpHeader {
public:
  char signature[2];
  uint32_t fileSize;
  uint32_t reserved;
  uint32_t dataOffset;
};
class BmpInfo {
public:
  uint32_t size;  // 40
  uint32_t width;
  uint32_t height;
  uint16_t planes;
  uint16_t bitsPerPixel;
  uint32_t compression;
  uint32_t imageSize;  // compressed size 
  uint32_t xPixelsPerM;
  uint32_t yPixelsPerM;
  uint32_t colorsUsed;
  uint32_t importantColors;
};
BmpHeader header;
BmpInfo info;

void printHexU8(uint8_t h) {
  if (h < 16) Serial.write('0');
  Serial.print(h, HEX);
}
void setup() {
  Serial.begin(9600);
  if (!sd.init(SPI_FULL_SPEED, csPin)) sd.initErrorHalt();
  if (!file.open(fileName, O_READ)) {
    Serial.println("openError");
    return;
  }
  if (file.read(&header, sizeof(header)) != sizeof(header) ||
    file.read(&info, sizeof(info)) != sizeof(info)) {
    Serial.println("read error");
    return;
  }
  Serial.print("header.fileSize: ");
  Serial.println(header.fileSize);
  Serial.print("header.dataOffset: ");  
  Serial.println(header.dataOffset);
  Serial.print("info.size: ");  
  Serial.println(info.size);
  Serial.print("info.width: ");   
  Serial.println(info.width);
  Serial.print("info.height: ");   
  Serial.println(info.height);
  Serial.print("info.planes: ");   
  Serial.println(info.planes);  
  Serial.print("info.bitsPerPixel: ");   
  Serial.println(info.bitsPerPixel);  
  Serial.print("info.compression: ");   
  Serial.println(info.compression);  
  Serial.print("info.imageSize: ");   
  Serial.println(info.imageSize);   
  Serial.print("info.xPixelsPerM: ");   
  Serial.println(info.xPixelsPerM);     
  Serial.print("info.yPixelsPerM: ");   
  Serial.println(info.yPixelsPerM);   
  Serial.print("info.colorsUsed: ");   
  Serial.println(info.colorsUsed);   
  Serial.print("info.importantColors: ");   
  Serial.println(info.importantColors);
  int b;
  int i = 0;
 while ((b = file.read()) >= 0) {
   printHexU8(i++);
   Serial.write(' ');
   printHexU8(b);
   Serial.println();
 }
   
}
void loop() {
}

9PX_05.BMP (78 Bytes)

Ah, fantastic! I'm quite used to Java, etc where big libraries can handle writing files, so looking through the BMP doc is really confusing.

I get the same result as your attached image, but my intention was alternating white and black pixels - what I get is black, yellow, blue, white, black, red... I experimented with a few places where a 3 maybe should be a 4 (for RGB0). I get different, but results that appear more wrong :slight_smile:

[ the BMP reader is really nice too - one to tuck away for another project ]

This seems to do the alternating white and black pixels.
You will need to fix the file size to include padding.

  int filesize = 54 + 24;// 3 * w * h;      //  w is image width, h is image height

Here is the sketch

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h>

const uint8_t csPin = 8;

char name[] = "9px_00.bmp";
int w = 3;
int h = 2;
int px[] = {
  255, 0, 255, 0, 255, 0 };
boolean debugPrint = true;

SdFat sd;
SdFile file;


void setup() {

  // SD setup
  Serial.begin(9600);
  if (!sd.init(SPI_FULL_SPEED, csPin)) {
    sd.initErrorHalt();
  }

  // if name exists, create new filename
  for (int i=0; i<1000; i++) {
    name[4] = i/10 + '0';
    name[5] = i%10 + '0';
    if (file.open(name, O_CREAT | O_EXCL | O_WRITE)) {
      break;
    }
  }

  // create image data
  // heavily modified version via: http://stackoverflow.com/a/2654860
  unsigned char *img = NULL;          // image data
  // need to fix this so padding is included
  int filesize = 54 + 24;// 3 * w * h;      //  w is image width, h is image height
  if (img) {
    free(img);
  }
  img = (unsigned char *)malloc(3*w*h);
  //memset(img,0,sizeof(img));        // not sure if I really need this; runs fine without...

  for (int y=0; y<h; y++) {
    for (int x=0; x<w; x++) {
      int colorVal = px[y*w + x];
      img[(y*w + x)*3+2] = (unsigned char)(colorVal);
      img[(y*w + x)*3+1] = (unsigned char)(colorVal);
      img[(y*w + x)*3+0] = (unsigned char)(colorVal);
    }
  }

  // print px and img data for debugging
  if (debugPrint) {
    Serial.print("Writing \"");
    Serial.print(name);
    Serial.print("\" to file...\n");
    for (int i=0; i<w*h; i++) {
      Serial.print(px[i]);
      Serial.print("  ");
    }
  }

  // create file headers (also taken from above example)
  unsigned char bmpFileHeader[14] = {
    'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0             };
  unsigned char bmpInfoHeader[40] = {
    40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0             };
  unsigned char bmpPad[3] = {
    0,0,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!
  // this is a combination of the bmp example above and
  // one from the SdFat library (it doesn't create a usable
  // bmp file, though)...
  file.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
  file.write(bmpInfoHeader, sizeof(bmpInfoHeader));    // " info header
  
  // sizeof(img) is always 2 must want h
  for (int i=0; i<h; i++) {                  // iterate image array
    file.write(img+(w*(h-i-1)*3), 3*w);                // write px data
    file.write(bmpPad, (4-(w*3)%4)%4);                 // and padding as needed
  }
  file.close();
  if (debugPrint) {
    Serial.println("\n---");
  }
}

void loop() { }

Yes! Maybe miscommunication on what the pixel values represented (RGB colors or grayscale). Instead of hard-coding the number of bytes for pixels, can't I just change it to four here?

int filesize = 54 + 4 * w * h;

Ultimately and for those interested, I'm trying to build some different digital cameras with the Arduino as a base. I have been disuaded in other parts of the forum, but for very low-resolution it now seems quite do-able. I'm using grayscale because I'd like to build my own sensor with 35mm SLR lenses and LDRs (so far that part works great) and those don't read color...

Thanks for the (very speedy) help!

I was lazy so I just hard coded 24.

Each row must be a multiple of four bytes so I think the correct expressions for rowSize and fileSize are

  int rowSize = 4*((3*w +3)/4);
  int fileSize = 54 + h*rowSize;

This depends on truncation of integer divide.

The number of padding bytes at the end of a row would be:

  int nPad = rowSize - 3*w;

In your case rowSize is 12 bytes and fileSize is 78 = (54 + 2*12).

Padding at the end of a row is three bytes.

I have not really tested this very well, just ran this sketch:

void setup() {
  Serial.begin(9600);
  int h = 2;
  for (int w = 0; w < 10; w++) {
    Serial.print(w);
    Serial.write(' ');
    int rowSize = 4*((3*w +3)/4);
    int fileSize = 54 + h*rowSize;
    int nPad = rowSize - 3*w;
    Serial.print(rowSize);
    Serial.write(' ');
    Serial.print(nPad);
    Serial.write(' ');
    Serial.println(fileSize);
  }
}
void loop() {}

and got this result:

0 0 0 54
1 4 1 62
2 8 2 70
3 12 3 78
4 12 0 78
5 16 1 86
6 20 2 94
7 24 3 102
8 24 0 102
9 28 1 110

Success! The final code below will save a BMP of any size to an SD card (in my case using a Mega). Thanks for all the help, fat16lib!

#include <SdFat.h>
#include <SdFatUtil.h>

/*
 WRITE BMP TO SD CARD
 Jeff Thompson
 Summer 2012
 
 TO USE MEGA:
 The SdFat library must be edited slightly to use a Mega - in line 87
 of SdFatConfig.h, change to:
 
   #define MEGA_SOFT_SPI 1
 
 (this uses pins 10-13 for writing to the card)
 
 Writes pixel data to an SD card, saved as a BMP file.  Lots of code
 via the following...
 
 BMP header and pixel format:
   http://stackoverflow.com/a/2654860
 
 SD save:
   http://arduino.cc/forum/index.php?topic=112733 (lots of thanks!)
 ... and the SdFat example files too
 
 www.jeffreythompson.org
 */

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

const int imgSize = w*h;
int px[w*h];                        // actual pixel data (grayscale - added programatically below)

SdFat sd;
SdFile file;
const uint8_t cardPin = 8;          // pin that the SD is connected to (d8 for SparkFun MicroSD shield)

void setup() {
  
  // iteratively create pixel data
  int increment = 256/(w*h);        // divide color range (0-255) by total # of px
  for (int i=0; i<imgSize; i++) {
    px[i] = i * increment;          // creates a gradient across pixels for testing
  }

  // SD setup
  Serial.begin(9600);
  if (!sd.init(SPI_FULL_SPEED, cardPin)) {
    sd.initErrorHalt();
    Serial.println("---");
  }

  // 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
    if (file.open(name, O_CREAT | O_EXCL | O_WRITE)) {
      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...
    }
  }

  // print px and img data for debugging
  if (debugPrint) {
    Serial.print("\nWriting \"");
    Serial.print(name);
    Serial.print("\" to file...\n");
    for (int i=0; i<imgSize; i++) {
      Serial.print(px[i]);
      Serial.print("  ");
    }
  }

  // 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
  file.write(bmpInfoHeader, sizeof(bmpInfoHeader));    // " info header

  for (int i=0; i<h; i++) {                            // iterate image array
    file.write(img+(w*(h-i-1)*3), 3*w);                // write px data
    file.write(bmpPad, (4-(w*3)%4)%4);                 // and padding as needed
  }
  file.close();                                        // close file when done writing

  if (debugPrint) {
    Serial.print("\n\n---\n");
  }
}

void loop() { }

Hi all, I would like to ask, how I make a "printscreen" from the TFT display? And next write it to the SD card, than BMP file. Thank you in advance.

1 Like