ArduCAM / Arduino

This is ArduCAM library for Arduino boards
MIT License
473 stars 348 forks source link

writing multiple frames from the buffer to jpg files on an SD card #87

Open JoeMcArdle opened 8 years ago

JoeMcArdle commented 8 years ago

Hello, I've been unable to resolve a coding problem I've been having with the Arducam Mini 2MP and I was hoping someone might be willing to give me some pointers or clear up any misconceptions I'm having regarding the cameras capabilities.

I have my loop setup so the camera captures an image and following this, a lux measurement is taken. If lux exceeds a specified threshold, the image is transferred to an SD card. If not, then fifo is flushed and start_capture is returned to zero.

What I'd like to have is a series of say 3 images, leading up to the point in time when lux exceeds the threshold.

According to the Arducam software manual (my interpretation at least) I can buffer more than one frame by writing to the Arduchip registry (myCAM.write_reg(ARDUCHIP_FRAMES. 0x01);

What I haven't been able to resolve is how to write more than one frame to an SD. The section below, from the example sketch is where I assume the alterations should be made. I've added comments indicating my best guess at how the code works. I don't really understand why we write the image to file in two buffered parts (first one 256 bytes) and the second, containing the remainder.

` i = 0; myCAM.CS_LOW(); myCAM.set_fifo_burst(); temp = SPI.transfer(0x00);

/* Read JPEG data from FIFO. */ while ( (temp != 0xD9) | (temp_last != 0xFF)) //write data to buffer until final address reached (FFD9) { temp_last = temp; temp = SPI.transfer(0x00);

      /* Write image data to buffer if not full. */
          if (i < 256)
            buf[i++] = temp;           //increment temp into the vector buf
          else
          {
      /* Write 256 bytes image data to file. */
            myCAM.CS_HIGH();
            outFile.write(buf, 256);   // now we write the vector buf, to SD
            i = 0;                             // reset the buffer pointer to the beginning
            buf[i++] = temp;           // again, increment temp into the buffer
            myCAM.CS_LOW();
            myCAM.set_fifo_burst();
          }
        }
      /* Write the remain bytes in the buffer */
        if (i > 0)
        {
          myCAM.CS_HIGH();
          outFile.write(buf, i);
        }
      /* Close the file. */
        outFile.close();
        total_time = millis() - total_time;
        Serial.print("Total time used:");
        Serial.print(total_time, DEC);
        Serial.println(" ms");
      /* Clear the capture done flag */
        myCAM.clear_fifo_flag();
      /* Clear the start capture flag. */
        start_capture = 0;

` Could anyone clarify how this code works and how it could be modified in order to write more than one image to the SD card? Thanks in advance,, Joe

gutcZHAW commented 7 years ago

Hello @JoeMcArdle

Actually i nearly want to do the same. I'm trying to capture up to four frames which should be buffered (384kB should reach for 4 compressed frames 1600x1200). In a next step i want to read out the frames from the buffer an store them on the SD-Card. I guess we nearly have the same problem.

According to the Arducam software manual (my interpretation at least) I can buffer more than one frame by writing to the Arduchip registry (myCAM.write_reg(ARDUCHIP_FRAMES. 0x01);

Since I read the ArduCAM library I'm not sure anymore if it's possible to write more than one frame in the frame buffer of the ArduCAM mini 2mp. See the fallowing code fragment of the ArduCAM.h file:

#if !(defined OV2640_MINI_2MP)
  #define ARDUCHIP_FRAMES  0x01  //FRAME control register, Bit[2:0] = Number of frames to be captured
                             //On 5MP_Plus platforms bit[2:0] = 7 means continuous capture until frame buffer is full
#endif

Maybe @ArduCAM could say if this feature is only available on the ArduCAM mini 5mp or also on the 2 mp version.

I don't really understand why we write the image to file in two buffered parts (first one 256 bytes) and the second, containing the remainder.

The frame is stored on the frame buffer on the ArduCAM mini board. To store the Data on the SD-Card, we have to do two steps:

  1. Read the data via SPI from the frame buffer to the arduino board
  2. Write the data via SPI from the arduino board to the SD-Card controller To avoid to store the whole image on the arduino board, we don't read the whole image but rather do it in 256 Bytes blocks.

First of all I'll show you how I try to write several frames to the SD-Card:

     if(frame_cnt)  // at least one picture captured
     { 
              uint8_t frame_idx;                  
              sequence_nr++;

              // one loop for each frame
              for(frame_idx = 0; frame_idx < frame_cnt; frame_idx++){
                  char str[22] ="Seq";
                  char num[5];
                  File file;

                  // Time measurement (not used)
                  StartTime = millis();

                  // generate filename(SeqXImgY.jpg)
                  itoa(sequence_nr, num, 10);
                  strcat(str, num);
                  strcat(str, "Img");
                  itoa(frame_idx, num, 10);
                  strcat(str, num);
                  strcat(str, ".jpg");

                  //Open the new file
                  file = SD.open(str, O_WRITE | O_CREAT | O_TRUNC);
                  if(file){
                      uint16_t i = 0;
                      uint8_t temp = 0, temp_last = 0;
                      byte buf[256];

                      //Read JPEG data from FIFO
                      myCAM1.CS_LOW();
                      myCAM1.set_fifo_burst();

                      // ignore first byte
                      SPI.transfer(0xFF);

                      // get Data
                      temp  = 0;
                      i     = 0;
                      while ( (temp !=0xD9) | (temp_last !=0xFF)){
                          temp_last = temp;
                          temp = SPI.transfer(0x00);

                          //Write image data to buffer if not full
                          if( i < 256)
                              buf[i++] = temp;
                          else{
                              //Write 256 bytes image data to file
                              myCAM1.CS_HIGH();
                              file.write(buf ,256);
                              i = 0;
                              buf[i++] = temp;
                              myCAM1.CS_LOW();
                              myCAM1.set_fifo_burst();
                          }
                      }  

                      //Write the remain bytes in the buffer
                      if(i > 0){
                          myCAM1.CS_HIGH();
                          file.write(buf,i);
                      }

                      //Close the file
                      file.close(); 

                      CurrentTime = millis();
                      Serial.print("StorageTime: ");
                      Serial.print(CurrentTime-StartTime,DEC);
                      Serial.println(" ms");
                  }
                  else {
                      Serial.println("create file failed");
                  }
              }
          }

Unfortunately i got the problem that only one frame will be stored in the framebuffer (see #108). I hope i could help you if you still stuck on the same problem. Otherwise I would be grateful if you could give me some advice.

ArduCAM commented 7 years ago

@gutcZHAW, Sorry to let you know that the current ArduCAM-Mini-2MP doesn't support multiple frames capture. It is only available for 5MP camera.

gutcZHAW commented 7 years ago

@ArduCAM Thank you a lot for your fast reply :) Maybe it wouldn't be a bad idea to omit the part 7.2 "Multiple capture mode" int the datasheet of the ArduCAM mini 2MP.

gutcZHAW commented 7 years ago

Hello @ArduCAM Since I use the camera module in a battery device the ArduCAM-mini 5MP is not in line for my application. As I suppose the multi capture mode could be implemented by modifying the CPLD program. Is the CPLD program opensource or is such an implementation planned in the near future?

thank you for the information beforehead.

ArduCAM commented 7 years ago

@gutcZHAW, yes you are right, I should update the document much eariler.

ArduCAM commented 7 years ago

@gutcZHAW, actually we have run out of the resources in the CPLD, so I have no room for another register and control logic for multiple capture function. If you need customized implementation of CPLD firmware, please contact us for detail.

JoeMcArdle commented 7 years ago

Hello @gutcZHAW Good News! I have figured out how to write multiple buffered frames to SD using the Arducam mini 2MP. The issue for the both of us was that we did not understand the coding behind the multi-stage transfer transfer. We needed an example showing how to adapt the transfer to multiple frames. When Lee published the example code for the Arducam Mini Plus, we finally had that example. The script below, buffers 4 frames, takes a LUX reading, and then, if the reading exceeds a given threshold, transfers the frames to the SD (if the level is not exceeded, and frames are cleared then loops). For some reason, I only get 3 frames (eventually I will trouble shoot this, but since I had already spent months working on this project, I just made due). ` // Logs a lux reading and time stamp at approximately _ms intervals // 1. three image frames are aquired // 2. A lux reading and corresponding time of reading is written to the log // 3. If lux exceeds threshold, the log is closed and the three buffered images are written to the SD card, // 4. If lux does not exceed threshold, the buffer is wiped // Image file names indicates the time at which the log was closed and order of image aquisition // lux readings resume when images are saved (within s of last lux reading) // Note: flush command have been removed to speed up sampling rate. lux readings are written to SD when the buffer is full (some data at end will be lost) //

include

include

include

include

include "memorysaver.h"

include "RTClib.h"

include

include "Adafruit_TSL2591.h"

//#define FIFO_SIZE 0x07FFFFF

define FRAMES_NUM 0x04

const int CS = 23;

define SD_CS 10

bool is_header = false; int total_time = 0;

ArduCAM myCAM(OV2640, CS); uint8_t read_fifo_burst(ArduCAM myCAM); /----------------------------------------------------------------------/ float LUX; char charFileName; int q; int k; unsigned long T; volatile uint32_t length;

RTC_DS1307 RTC; // define Real Time Clock File logfile; // logging file Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591); // pass in a number for the sensor identifier

define ECHO_TO_SERIAL 0 // zero shuts off serial output

define WAIT_TO_START 0 // Wait for serial input in setup()

void configureSensor(void) { / Change the gain to adapt to brighter/dimmer light situations. / tsl.setGain(TSL2591_GAIN_LOW); // 1x gain (bright light) // tsl.setGain(TSL2591_GAIN_MED); // 25x gain // tsl.setGain(TSL2591_GAIN_HIGH); // 428x gain

/ longer is slower, but good in low light / tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS); // shortest integration time (bright light) //tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS); //tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS); //tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS); //tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS); //tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS); // longest integration time (dim light) }

void stamp() { if (! logfile) { logfile = SD.open("test.csv", FILE_WRITE); } DateTime now; now = RTC.now(); // fetch the time logfile.print(now.month(), DEC); logfile.print("-"); logfile.print(now.day(), DEC); logfile.print("-"); logfile.print(now.year(), DEC); logfile.print("__"); logfile.print(now.hour(), DEC); logfile.print(":"); logfile.print(now.minute(), DEC); logfile.print(":"); logfile.println(now.second(), DEC); } /----------------------------------------------------------------------/

void setup() { uint8_t vid, pid; uint8_t temp; Wire.begin(); Serial.begin(115200);

if WAIT_TO_START

Serial.println("Type any character to start"); while (!Serial.available());

endif //WAIT_TO_START

pinMode(CS, OUTPUT); // set the CS as an output: SPI.begin(); // initialize SPI: myCAM.write_reg(ARDUCHIP_TEST1, 0x55); //Check if the ArduCAM SPI bus is OK temp = myCAM.read_reg(ARDUCHIP_TEST1); // Serial.println(temp,HEX); if (temp != 0x55) { Serial.println("SPI interface Error!"); while (1); }

myCAM.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid); //Check if the camera module type is OV2640 myCAM.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid); if ((vid != 0x26) || (pid != 0x42)) Serial.println("Can't find OV2640 module!"); else Serial.println("OV2640 detected. ArduCAM Start!");

//Change to JPEG capture mode and initialize the OV2640 module myCAM.set_format(JPEG); myCAM.InitCAM(); //myCAM.OV2640_set_JPEG_size(OV2640_640x480); //myCAM.OV2640_set_JPEG_size(OV2640_1600x1200); myCAM.set_bit(ARDUCHIP_TIM, VSYNC_LEVEL_MASK); myCAM.clear_fifo_flag(); myCAM.write_reg(ARDUCHIP_FRAMES, FRAMES_NUM);

//Initialize SD Card if (!SD.begin(SD_CS, 11, 12, 13)) { Serial.println("SD Card Error!"); while (1); //If failed, stop here } else Serial.println("SD Card detected.");

if (tsl.begin()) { Serial.println("Found a TSL2591 sensor"); } else { Serial.println("No sensor found ... check your wiring?"); while (1); } configureSensor(); stamp(); }

void loop() {

acquire(); k = 0; }

void acquire() { uint8_t temp, temp_last; bool is_header = false; myCAM.flush_fifo(); myCAM.clear_fifo_flag(); //myCAM.OV5640_set_JPEG_size(OV5640_640x480); //Start capture myCAM.start_capture(); // Serial.println("start capture!"); total_time = millis(); // Serial.print (total_time, DEC); Serial.print(" start capture"); while ( !myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK)); // Serial.println("CAM Capture Done!"); total_time = millis() - total_time; // Serial.print(" capture total_time used (in miliseconds):"); // Serial.println(total_time, DEC);

uint32_t lum = tsl.getFullLuminosity(); uint16_t ir, full; ir = lum >> 16; full = lum & 0xFFFF; LUX = tsl.calculateLux(full, ir); T = (millis()); if (! logfile) { logfile = SD.open("test.csv", FILE_WRITE); }

if ECHO_TO_SERIAL

Serial.print (T); Serial.print(" "); Serial.print(LUX); Serial.println(" ");

endif //ECHO_TO_SERIAL

logfile.print(" "); logfile.print(T); logfile.print(" "); logfile.print(" "); logfile.print(LUX); logfile.println(" "); if (LUX > 10000) { logfile.print ("thresholdexceeded"); logfile.print (T); logfile.println ("_ms"); Serial.print ("thresholdexceeded"); Serial.print (T); Serial.println ("_ms"); logfile.close(); // Serial.print("fifo length is "); Serial.println(myCAM.read_fifo_length(), DEC); // Serial.print("fifo length is "); Serial.println(myCAM.read_fifo_length(), HEX); length = myCAM.read_fifo_length(); // Serial.print("The fifo length is :"); // Serial.println(length, DEC);

// Serial.print("capture total_time used (in miliseconds):"); // Serial.println(total_time, DEC); //the time it took sensor to capture images

total_time = millis();                // starting time for transfer of frames to the SD-card
read_fifo_burst(myCAM);               // run the transfer function
total_time = millis() - total_time;   // time use to transfer

// Serial.print("save capture total_time used:"); // Serial.println(total_time, DEC); //Clear the capture done flag myCAM.clear_fifo_flag(); // delay(5000); } else { myCAM.clear_fifo_flag(); return; } // }

}

uint8_t read_fifo_burst(ArduCAM myCAM) { uint8_t temp, temp_last; uint32_t length = 0; static int i = 0; // static int k = 0; unsigned long position = 0; // uint16_t frame_cnt = 0; // uint8_t remnant = 0; // char quad_buf[4] = {}; char str[8]; File outFile; byte buf[256]; length = myCAM.read_fifo_length(); // Serial.print("The fifo length is :"); // Serial.println(length, DEC); if (length >= 0x07fffff) //8M { Serial.println("Over size."); return 0; } if (length == 0 ) //0 kb { Serial.println("Size is 0."); return 0; } myCAM.CS_LOW(); myCAM.set_fifo_burst();//Set fifo burst mode SPI.transfer(0x00);//First byte is 0x00 ,not 0xff length--; i = 0; while ( length-- ) { temp_last = temp; temp = SPI.transfer(0x00); //Read JPEG data from FIFO if ( (temp == 0xD9) && (temp_last == 0xFF) ) //If find the end ,break while, { buf[i++] = temp; //save the last 0XD9 //Write the remain bytes in the buffer myCAM.CS_HIGH(); outFile.write(buf, i); //Close the file outFile.close(); // Serial.println("OK"); is_header = false; myCAM.CS_LOW(); myCAM.set_fifo_burst(); i = 0; } if (is_header == true) { //Write image data to buffer if not full if (i < 256) buf[i++] = temp; else { //Write 256 bytes image data to file myCAM.CS_HIGH(); outFile.write(buf, 256); i = 0; buf[i++] = temp; myCAM.CS_LOW(); myCAM.set_fifo_burst(); } } else if ((temp == 0xD8) & (temp_last == 0xFF)) { is_header = true; myCAM.CS_HIGH(); // //Create a avi file // k = k + 1; // itoa(k, str, 10); // strcat(str, ".jpg"); // //Open the new file // outFile = SD.open(str, O_WRITE | O_CREAT | OTRUNC); //Construct a file name k = k + 1; String fileName = String(); fileName = T; fileName += ""; fileName += k; fileName += ".jpg"; char charFileName[fileName.length() +1]; fileName.toCharArray(charFileName, sizeof(charFileName)); outFile = SD.open(charFileName, O_WRITE | O_CREAT | O_TRUNC); if (! outFile) { Serial.println("open file failed"); while (1); } Serial.print("Writing to: "); Serial.println(charFileName);

    myCAM.CS_LOW();
    myCAM.set_fifo_burst();
    buf[i++] = temp_last;
    buf[i++] = temp;
  }

}
myCAM.CS_HIGH();

} `

gutcZHAW commented 7 years ago

@JoeMcArdle Thanks for your reply! I'm a little confused because you report it's possible to capture multiple frames and @ArduCAM wrote it's not implemented in the ArduCAM mini 2MP. I briefly tried your code yesterday but it still store only one frame on the sd. But i'll try it again today. Which ArduCAM library do you use for your project? The library I use at the moment doesn't define the VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES if the ArduCAM mini 2MP is selected.

@ArduCAM Did you received my email to admin@arducam.com?

JoeMcArdle commented 7 years ago

Hi @gutcZHAW,

Yes, that is indeed confusing. I think I would have given the whole thing up immediately if @ArduCAM had told me there was no way to get the 2MP mini to save multiple frames. I checked the library. I guess the best way to indicate the version is to list the last revision date from the comments. This is the version I'm using:

Arducam.cpp last revision on 2016/06/07 V3.5.0 Arducam.h last revision on 2015/09/20 V3.4.8

I also text searched and found both VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES defined in Arducam.h. But what you are saying is that those definitions do not come up if you select the mini 2MP. Shouldn't you then get some sort of error message when the script compiles? (if you are calling up VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES in your script and they are not defined, I would think that the script would not compile).

You might have already thought of this, but have you checked the fifo length to see if the camera has more than one frame in the buffer? In my script, there are some disabled commands for this (I don't remember why I wrote two versions of the command. Here is both, just in case one does not work): version 1: // Serial.print("fifo length is "); Serial.println(myCAM.read_fifo_length(), DEC);

Version 2: // length = myCAM.read_fifo_length(); // Serial.print("The fifo length is :"); // Serial.println(length, DEC);

Compare fifo length between calls for a single frame and multiple frames (I'm a novice, so I apologise if this is an obvious test).

The secret sauce (for recording the multiple frames to the SD card) is in the read_fifo_burst function. I copied that right out of the example for the mini 5MP Plus. If Arducam had not released that, I would have never figured out how it works.

Hope you get it to work. Please let me know if you do, or if you have any questions. I'm happy to help. Best Regards,, Joe

On Wed, Nov 30, 2016 at 1:48 AM, gutcZHAW notifications@github.com wrote:

@JoeMcArdle https://github.com/JoeMcArdle Thanks for your reply! I'm a little confused because you report it's possible to capture multiple frames and @ArduCAM https://github.com/ArduCAM wrote it's not implemented in the ArduCAM mini 2MP. I briefly tried your code yesterday but it still store only one frame on the sd. But i'll try it again today. Which ArduCAM library do you use for your project? The library I use at the moment doesn't define the VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES if the ArduCAM mini 2MP is selected.

@ArduCAM https://github.com/ArduCAM Did you received my email to admin@arducam.com?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ArduCAM/Arduino/issues/87#issuecomment-263797108, or mute the thread https://github.com/notifications/unsubscribe-auth/AVVyhiwtZDmo7jxEgf-dCImHOJRpajENks5rDRxGgaJpZM4KEOE0 .

gutcZHAW commented 7 years ago

Hello @JoeMcArdle

Thank you for your help!

The defines of the VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES will be defined in the ArduCAM.h V3.4.8 independent of the used hardware. That's why your script compiles correctly. I use the following versions: Arducam.cpp last revision on 2016/06/07 V4.0.1 Arducam.h last revision on 2015/09/20 V4.0.1 When I define the VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES manually, your code compiles but it still stores only one frame. I also compared the two versions of the library but I' cant figure out any differences which affect the number of frames.

I logged the frame length and the capture duration but the number of frames doesn't influence these values. So I guess only one frame will be captured.

Which hardware elements do you use? Actually I use the ArduCAM mini 2MP with the Arduino Zero Pro as MCU. But because of different reasons I'll switch to the ArduCAM-F shield, OV2640 module and the Arduino Zero Pro. So at the moment it's not that important anymore to solve this issue. But I would be interested in a solution anyway.

Best regards :-)

JoeMcArdle commented 7 years ago

Hi @gutcZHAW I'm in the same boat. I spent way too much time getting this to work and now, the need for this application has long passed. But still it would be nice to know what is going on. I agree, if the capture duration and fifo length are remain fairly constant, despite calls for one or multiple frames, it seems clear that the camera is only capturing a single frame. I'm using the Arducam mini module camera shield with OV2640 2MP (description on Amazon: "Arducam-M-2MP is optimized version of Arducam shield Rev.C,") with an Arduino Mega.

By the way, do you have the camera plugged directly into your controller or do you have a cable feeding between the two? I'm sure the issue you are having is not cable related (since fifo length doesn't vary between single and multiple frame requests), but I have noticed that depending on the cable I use, I will sometimes loose frames in transfer to SD. I've made several cables of various lengths and types of wire. At first I assumed the problem was related to wire length and made progressively shorter cables. It didn't seem to make a difference and I started to think that I was just not very good at crimping DuPont connectors. But then I found some single strand ribbon cable (solid wire as opposed to multi-stand). This new single stand cable appears to work much better, even at a little over 2 feet in length. All the frames transfer and I never get an errant corrupt image. I was just really surprised what a difference this made.

Best Regards,, Joe

On Mon, Dec 5, 2016 at 2:33 AM, gutcZHAW notifications@github.com wrote:

Hello @JoeMcArdle https://github.com/JoeMcArdle

Thank you for your help!

The defines of the VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES will be defined in the ArduCAM.h V3.4.8 independent of the used hardware. That's why your script compiles correctly. I use the following versions: Arducam.cpp last revision on 2016/06/07 V4.0.1 Arducam.h last revision on 2015/09/20 V4.0.1 When I define the VSYNC_LEVEL_MASK and ARDUCHIP_FRAMES manually, your code compiles but it still stores only one frame. I also compared the two versions of the library but I' cant figure out any differences which affect the number of frames.

I logged the frame length and the capture duration but the number of frames doesn't influence these values. So I guess only one frame will be captured.

Which hardware elements do you use? Actually I use the ArduCAM mini 2MP with the Arduino Zero Pro as MCU. But because of different reasons I'll switch to the ArduCAM-F shield, OV2640 module and the Arduino Zero Pro. So at the moment it's not that important anymore to solve this issue. But I would be interested in a solution anyway.

Best regards :-)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ArduCAM/Arduino/issues/87#issuecomment-264785530, or mute the thread https://github.com/notifications/unsubscribe-auth/AVVyhmYQ_ydJwcN927La5tbmeuvr_6msks5rE75ggaJpZM4KEOE0 .