Difficulties Saving BMP Images to SD Card from ArduCam

I've recently set-up a circuit with an Arduino Uno R3, an ArduCam Mini OV2640, and an SD Card reader. They are all bussed together through SPI.

I am able to successfully capture, acquire and save images in JPEG format with this setup to the SD Card from the camera. I have used the ArduCam library examples for my camera to branch my own set of functions.

I want to be able to save images in BMP format as well. I have attempted to look at the example code from the ArduCam library, however it does not appear to be as straightforward compared to saving JPEG images.

Here's my current function responsible for acquiring & saving the BMP image data:

void ReadBMPBurst()
{
  Serial.println(F("ACK CMD Reading BMP burst... END"));

  uint32_t length = Camera.read_fifo_length();

  if (length >= MAX_FIFO_SIZE)
  {
    Serial.println(F("ACK CMD ERROR: Over size! END"));

    Camera.clear_fifo_flag();

    return;
  }
  if (length == 0)
  {
    Serial.println(F("ACK CMD ERROR: Size is 0! END"));

    Camera.clear_fifo_flag();

    return;
  }

  Camera.CS_LOW();
  Camera.set_fifo_burst();

  File outputFile;

  if (PIN_SD_CS >= 0)
  {
    Camera.CS_HIGH();

    outputFile = SD.open("IMG_" + String(FilesCreated++) + ".bmp", O_WRITE | O_CREAT | O_TRUNC);

    Camera.CS_LOW();
    Camera.set_fifo_burst();

    if (!outputFile)
    {
      Serial.println(F("ACK CMD ERROR: Unable to create BMP on SD! END"));

      Camera.clear_fifo_flag();

      return;
    }
  }

  if (PIN_SD_CS >= 0)
  {
    byte send1 = 0xFF;
    byte send2 = 0xAA;

    Camera.CS_HIGH();

    outputFile.write(&send1, 1);
    outputFile.write(&send2, 1);

    Camera.CS_LOW();
    Camera.set_fifo_burst();
  }
  else
  {
    Serial.write(0xFF);
    Serial.write(0xAA);
  }

  for (int i = 0; i < BMPImageOffset; i++)
  {
    if (PIN_SD_CS >= 0)
    {
      byte send = pgm_read_byte(&BMPHeader[i]);

      Camera.CS_HIGH();

      outputFile.write(&send, 1);

      Camera.CS_LOW();
      Camera.set_fifo_burst();
    }
    else
    {
      Serial.write(pgm_read_byte(&BMPHeader[i]));
    }
  }

  //SPI.transfer(0x00);

  for (int i = 0; i < 240; i++)
  {
    for (int j = 0; j < 320; j++)
    {
      char VH = SPI.transfer(0x00);
      char VL = SPI.transfer(0x00);

      if (PIN_SD_CS >= 0)
      {
        Camera.CS_HIGH();

        outputFile.write(&VL, 1);
        delayMicroseconds(12);
        outputFile.write(&VH, 1);
        delayMicroseconds(12);

        Camera.CS_LOW();
        Camera.set_fifo_burst();
      }
      else
      {
        Serial.write(VL);
        delayMicroseconds(12);
        Serial.write(VH);
        delayMicroseconds(12);
      }
    }
  }

  if (PIN_SD_CS >= 0)
  {
    byte send1 = 0xBB;
    byte send2 = 0xCC;

    Camera.CS_HIGH();

    outputFile.write(&send1, 1);
    outputFile.write(&send2, 1);

    Camera.CS_LOW();
    Camera.set_fifo_burst();
  }
  else
  {
    Serial.write(0xBB);
    Serial.write(0xCC);
  }

  Camera.CS_HIGH();

  if (PIN_SD_CS >= 0)
    outputFile.close();

  Serial.println(F("ACK CMD BMP burst read! END"));

  return;
}

I'm running the code above assuming that PIN_SD_CS will always be set to the appropriate SD Card Reader SPI CS pin. The global variable Camera is the variable of type ArduCam which includes all the class functions responsible for running the camera.

This function also uses the BMP header global variable BMPHeader, declared as such:

#define BMPImageOffset 66
const char BMPHeader[BMPImageOffset] PROGMEM =
{
  0x42, 0x4D, 0x36, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00,
  0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x03, 0x00,
  0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x1F, 0x00,
  0x00, 0x00
};

All of the code above was adapted from the official ArduCam library examples for my specific camera.

With the current setup, all BMP files saved to the SD card are corrupted and not readable by any image viewer I have. The most common error message being that the header is corrupted.

I was under the impression that a BMP image simply requires a header that describes it's size (hence why the size in this specific example is hardcoded to 320x240), however it clearly appears to be more complicated than that, as there may be some other things I'm missing in order to successfully save my BMP files.

Thanks for reading my post, any guidance is appreciated!

1 Like

I don't see where you command the camera output the image in BMP format.
Could you please show the code that capture JPEG images?

So I'm actually simply just making this a loop, I don't want this to use any external app such as the host app that's used with the original ArduCam library example.

Here's the code responsible for acquiring & saving the JPEG files:

void ReadJPEGBurst()
{
  Serial.println(F("ACK CMD Reading JPEG burst... END"));

  uint32_t length = Camera.read_fifo_length();

  if (length >= MAX_FIFO_SIZE)
  {
    Serial.println(F("ACK CMD ERROR: Over size! END"));

    Camera.clear_fifo_flag();

    return;
  }
  if (length == 0)
  {
    Serial.println(F("ACK CMD ERROR: Size is 0! END"));

    Camera.clear_fifo_flag();

    return;
  }

  Camera.CS_LOW();
  Camera.set_fifo_burst();

  uint8_t temp = SPI.transfer(0x00);
  uint8_t temp_last = 0;
  
  File outputFile;

  if (PIN_SD_CS >= 0)
  {
    Camera.CS_HIGH();

    outputFile = SD.open("IMG_" + String(FilesCreated++) + ".jpg", O_WRITE | O_CREAT | O_TRUNC);

    Camera.CS_LOW();
    Camera.set_fifo_burst();

    if (!outputFile)
    {
      Serial.println(F("ACK CMD ERROR: Unable to create JPEG on SD! END"));

      Camera.clear_fifo_flag();

      return;
    }
  }

  length--;

  while (length--)
  {
    temp_last = temp;
    temp =  SPI.transfer(0x00);

    if (IsHeader == true)
    {
      if (PIN_SD_CS >= 0)
      {
        Camera.CS_HIGH();
        
        outputFile.write(&temp, 1);

        Camera.CS_LOW();
        Camera.set_fifo_burst();
      }
      else
      {
        Serial.write(temp);
      }
    }
    else if ((temp == 0xD8) & (temp_last == 0xFF))
    {
      IsHeader = true;

      if (PIN_SD_CS >= 0)
      {
        Camera.CS_HIGH();

        outputFile.write(&temp_last, 1);
        outputFile.write(&temp, 1);

        Camera.CS_LOW();
        Camera.set_fifo_burst();
      }
      else
      {
        Serial.write(temp_last);
        Serial.write(temp);
      }
    }

    if ((temp == 0xD9) && (temp_last == 0xFF))
      break;

    delayMicroseconds(15);
  }

  Camera.CS_HIGH();

  if (PIN_SD_CS >= 0)
    outputFile.close();

  Serial.println(F("ACK CMD JPEG burst read! END"));

  IsHeader = false;

  return;
}

And here's the code in my main loop:

void loop() 
{
  Camera.flush_fifo();
  Camera.clear_fifo_flag();

  if (SavingJPEG)
    Camera.OV2640_set_JPEG_size(OV2640_320x240);

  int currTime, captureTime, processingTime;

  Serial.println(F("ACK CMD Starting image capture... END"));

  Camera.start_capture();

  currTime = millis();

  while (!Camera.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK));

  captureTime = millis() - currTime;
  delay(50);

  Serial.println(F("ACK CMD Image capture finished! END")); //TODO: Timing
  Serial.println(F("ACK CMD Starting image processing... END"));

  currTime = millis();

  if (SavingJPEG)
    ReadJPEGBurst();
  else
    ReadBMPBurst();

  processingTime = millis() - currTime;
  
  Serial.println(F("ACK CMD Image processing finished! END")); //TODO: Timing

  Camera.clear_fifo_flag();

  delay(ImageDelay * 1000);
}

Here SavingJPEG would be turned to false if I wanted to save in BMP format, true otherwise.

1 Like

Just turning a flag is not enough.
As far as I can see, your code for getting a picture from the camera is the same for BMP and JPG, except the image size.
So how does the camera know what type of picture it should send you?
It looks like when you request a BMP you still get a JPG, which is why it's not readable

I'm not sure what you're reffering to, because my code for reading the JPEG and BMP images is definitely not the same.

Both of these were adapted from the official ArduCam library examples, this is how they had it.

The SavingJPEG flag was created by me, and the only thing it does is to differentiate the function call for reading the burst image, thanks to this section from my main program loop:

  if (SavingJPEG)
    ReadJPEGBurst();
  else
    ReadBMPBurst();

As you can see, I have a function for reading & saving the JPEG images, and a function for reading & saving the BMP images. The code inside these two functions is not the same, and saving the JPEG format works, while the BMP format doesn't.

Hi Svarun123,
I got it running. I found out that the header was not written (at least in my copy of your program). This is the header:

#define BMPIMAGEOFFSET 66
const char bmp_header[BMPIMAGEOFFSET] PROGMEM =
{
  0x42, 0x4D, 0x36, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00,
  0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x03, 0x00,
  0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x1F, 0x00,
  0x00, 0x00
};

This is the code for writing to SD:

void ReadBMPBurst()
{
//  Serial.println(F("ACK CMD Reading BMP burst... END"));
  Serial.println("Reading BMP burst... ");
  uint32_t length = myCAM.read_fifo_length();
  if (length >= MAX_FIFO_SIZE)
  {
    Serial.println(F("ACK CMD ERROR: Over size! END"));
    myCAM.clear_fifo_flag();
    return;
  }
  if (length == 0)
  {
    Serial.println(F("ACK CMD ERROR: Size is 0! END"));
    myCAM.clear_fifo_flag();
    return;
  }

  myCAM.CS_LOW();
  myCAM.set_fifo_burst();
  File outputFile;

  if (SD_CS >= 0)
  {
    myCAM.CS_HIGH();
    outputFile = SD.open("IMG_" + String(FilesCreated++) + ".bmp", O_WRITE | O_CREAT | O_TRUNC);
    myCAM.CS_LOW();
    myCAM.set_fifo_burst();
    if (!outputFile)
    {
      Serial.println(F("ACK CMD ERROR: Unable to create BMP on SD! END"));
      myCAM.clear_fifo_flag();
      return;
    }
  }

  Serial.println(SD_CS);
  if (SD_CS >= 0)
  {
    byte send1 = 0xFF;
    byte send2 = 0xAA;
    myCAM.CS_HIGH();
//    outputFile.write(&send1, 1);
//    outputFile.write(&send2, 1);
    myCAM.CS_LOW();
    myCAM.set_fifo_burst();
  }
  else
  {
    Serial.write(0xFF);
    Serial.write(0xAA);
  }

  Serial.print("BMPIMAGEOFFSET: ");Serial.println(BMPIMAGEOFFSET);
  for (int i = 0; i < BMPIMAGEOFFSET; i++)
  {
    if (SD_CS >= 0)
    {
      byte send = pgm_read_byte(&bmp_header[i]);
      Serial.print("bmpHeader");Serial.print(i);Serial.print(": ");Serial.println(send);
      myCAM.CS_HIGH();
      outputFile.write(&send, 1);
      myCAM.CS_LOW();
      myCAM.set_fifo_burst();
    }
    else
    {
      Serial.write(pgm_read_byte(&bmp_header[i]));
    }
  }

  //SPI.transfer(0x00);

  for (int i = 0; i < 240; i++)
  {
    for (int j = 0; j < 320; j++)
    {
      char VH = SPI.transfer(0x00);
      char VL = SPI.transfer(0x00);
      if (SD_CS >= 0)
      {
        myCAM.CS_HIGH();
        outputFile.write(&VL, 1);
        delayMicroseconds(12);
        outputFile.write(&VH, 1);
        delayMicroseconds(12);
        myCAM.CS_LOW();
        myCAM.set_fifo_burst();
      }
      else
      {
//        Serial.write(VL);
//        delayMicroseconds(12);
//        Serial.write(VH);
//        delayMicroseconds(12);
      }
    }
  }

  if (SD_CS >= 0)
  {
    byte send1 = 0xBB;
    byte send2 = 0xCC;
    myCAM.CS_HIGH();
    outputFile.write(&send1, 1);
    outputFile.write(&send2, 1);
    myCAM.CS_LOW();
    myCAM.set_fifo_burst();
  }
  else
  {
    Serial.write(0xBB);
    Serial.write(0xCC);
  }

  myCAM.CS_HIGH();
  if (SD_CS >= 0)
    outputFile.close();
  Serial.println(F("ACK CMD BMP burst read! END"));
  return;
}

Kind regards
Günter

1 Like

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