TTL/UART camera can't read full data stream

Hi everyone, I recently purchased this (rather obscure) camera and after some lengthy troubleshooting and back-and-forth with the manufacturer, I'm happy to say I've successfully managed to get a data stream flowing from the camera to the Arduino (and ultimately, the SD card)

Here is an excerpt of that data:


B4 01 48 06 F7 A5 ED 4B 8A 5A 60 37 14 B8 E2 97 BF 34 01 40 09 8A 31 EB 4B 47 7C 50 21 31 C8 C5 

08 65 19 F4 A5 A4 A6 20 19 FA D0 07 34 63 D2 97 14 80 31 4E 14 01 91 4E 03 9A A4 26 5E D2 B0 B3 

AE E1 81 C6 05 1B 69 C0 71 4B 8A D4 C1 8C 03 D0 D0 14 E3 9E 69 F8 A5 23 8C 62 81 11 E3 03 34 63 

3F 1A 6E 3A E6 A6 22 98 46 0E 29 0C 84 F5 A6 F5 CE 2A 46 1C D3 71 81 8A 2C 32 3C 03 83 C5 35 97 

9C D2 B0 C8 8A F0 31 DE A3 23 15 38 E2 98 C3 92 71 F8 51 60 B9 11 18 C7 E7 4D 2B C7 15 2E DE 07 

00 2D 25 2D 25 00 14 51 45 02 0A 28 A4 A0 62 D1 49 45 02 0A 28 A4 A0 05 A2 8A 28 00 A2 8A 28 00 

Problem is, this data never seems to end. I tried setting the camera to the lowest resolution and highest baud rate (not sure if those commands took effect) but the data stream just keeps going and going with no end in sight. If I interrupt the saving to the SD card, all I get is a malformed JPG that can't be opened.

Here is my code:

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

byte ZERO = 0x00;
byte incomingbyte;
long int j=0,k=0,count=0,i=0x0000;
uint8_t MH,ML;
boolean EndFlag=0;
File myFile;
void SendResetCmd()
{
Serial3.write(0x56);
Serial3.write(ZERO);
Serial3.write(0x26);
Serial3.write(ZERO);
}
/*************************************/
/* Set ImageSize :
/* <1> 0x22 : 160*120
/* <2> 0x11 : 320*240
/* <3> 0x00 : 640*480
/* <4> 0x1D : 800*600
/* <5> 0x1C : 1024*768
/* <6> 0x1B : 1280*960
/* <7> 0x21 : 1600*1200
/************************************/
void SetImageSizeCmd()
{
Serial3.write(0x56);
Serial3.write(ZERO);
Serial3.write(0x31);
Serial3.write(0x05);
Serial3.write(0x04);
Serial3.write(0x01);
Serial3.write(ZERO);
Serial3.write(0x19);
Serial3.write(0x11);
}
/*************************************/
/* Set BaudRate :
/* <1>¡¡0xAE : 9600
/* <2>¡¡0x2A : 38400
/* <3>¡¡0x1C : 57600
/* <4>¡¡0x0D : 115200
/* <5>¡¡0xAE : 128000
/* <6>¡¡0x56 : 256000
/*************************************/
void SetBaudRateCmd()
{
Serial3.write(0x56);
Serial3.write(ZERO);
Serial3.write(0x31);
Serial3.write(0x06);
Serial3.write(0x05);
Serial3.write(0x02);
Serial3.write(ZERO);
Serial3.write(0x08);
Serial3.write(0xEE);
Serial3.write(0xA1);
Serial3.write(0xEE);
Serial3.write(0xA3);
}
void SendTakePhotoCmd()
{
Serial3.write(0x56);
Serial3.write(ZERO);
Serial3.write(0x36);
Serial3.write(0x01);
Serial3.write(ZERO);
}
void SendReadDataCmd()
{
MH=i/0x100;
ML=i%0x100;
Serial3.write(0x56);
Serial3.write(ZERO);
Serial3.write(0x32);
Serial3.write(0x0c);
Serial3.write(ZERO);
Serial3.write(0x0a);
Serial3.write(ZERO);
Serial3.write(ZERO);
Serial3.write(MH);
Serial3.write(ML);
Serial3.write(ZERO);
Serial3.write(ZERO);
Serial3.write(ZERO);
Serial3.write(0x20);
Serial3.write(ZERO);
Serial3.write(0x0a);
i+=0x20;
}
void StopTakePhotoCmd()
{
Serial3.write(0x56);
Serial3.write(ZERO);
Serial3.write(0x36);
Serial3.write(0x01);
Serial3.write(0x03);
}
void setup()
{
Serial.begin(115200);

Serial.print("Initializing SD card...");
// On the Ethernet Shield, CS is pin 4. It's set as an output by default.
// Note that even if it's not used as the CS pin, the hardware SS pin
// (10 on most Arduino boards, 53 on the Mega) must be left as an output
// or the SD library functions will not work.
pinMode(10, OUTPUT);
if (!SD.begin(4))
{
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
Serial.println("please waiting ....");
Serial3.begin(115200);
delay(100);
SendResetCmd();
//SetImageSizeCmd();
//SetBaudRateCmd();
delay(2000);
}
void loop()
{
byte a[32];
int ii;
SendResetCmd();
delay(1000);
Serial.println("PHOTO STREAM...");
delay(2000); //Wait 2-3 second to send take picture command
SendTakePhotoCmd();
delay(1000);
while(Serial3.available()>0)
{
incomingbyte=Serial3.read();
}
myFile = SD.open("pic.jpg", FILE_WRITE); //The file name should not be too long
while(!EndFlag)
{
j=0;
k=0;
count=0;
//Serial3.flush();
SendReadDataCmd();
delay(20);
while(Serial3.available()>0)
{
incomingbyte=Serial3.read();
k++;
delay(1); //250 for regular
if((k>5)&&(j<32)&&(!EndFlag))
{
a[j]=incomingbyte;
if((a[j-1]==0xFF)&&(a[j]==0xD9)) //tell if the picture is finished
{
EndFlag=1;
}
j++;
count++;
}
}
for(j=0;j<count;j++)
{
if(a[j]<0x10) Serial.print("0");
Serial.print(a[j],HEX); // observe the image through serial port
Serial.print(" ");
}
for(ii=0; ii<count; ii++)
myFile.write(a[ii]);
Serial.println();
}
myFile.close();
Serial.print("Finished writing data to file");
while(1);
}

I've waited for several minutes (far longer than is practical for my application) and data just keeps pouring through.

Note that despite the manufacturer claiming it's compatible, I never got this to work with the OV7670 arduino library. This doesn't suprise me, I think that while the module might have the same base component (the OV7670 camera) the comm protocol with respect to the Arduino is completely difference. Hence the tedious byte-by-byte hand-coding.

TLDR: Anyone know why the data stream from this camera never ends, as if an end code is never sent? Thanks!

Cameras are designed to operate that way! The data you posted indicates what, exactly? In what format is the data structured? I assume it’s not from a Kodak Brownie, but beyond that, I have no additional context to work with.

Quote from your link
"Users can send out a snapshot
command from the host in order to capture a full resolution single-frame still picture. The Picture is then compressed by the DSP and transferred to the host."

Did you try the stop capture or reset commands ?

delay(1); //250 for regular

Why the delay?
In 1 ms at 115200 bps about 11 bytes are received, so by the time a few bytes are read from the buffer it overflows and data loss begins.
That must be the problem.

Ok, I've done some more detailed analysis, and the whole problem is that I can't read the full data stream of the camera. Or, more specifically, I can't get to the end of file marker. It's as if the camera just randomly stops transmitting data.

OUTPUT:

Total bytes read so far: 5824
Reading from address: 5824
Last few bytes of chunk: E8 16 AA C3 D7 15 7A 11 4C 76 
Total bytes read so far: 5888
Reading from address: 5888
Last few bytes of chunk: 4A 65 85 35 2A 9A 81 4D 4A 76 
Total bytes read so far: 5952
Reading from address: 5952
Last few bytes of chunk: 26 6 8C D4 79 A0 B5 16 10 76 
Total bytes read so far: 6016
Reading from address: 6016
Last few bytes of chunk: 47 81 4E 53 C1 A6 89 65 84 76 
Total bytes read so far: 6080
Reached reported data length without finding EOI marker. Finalizing file.
Appending missing EOI marker.
Image data saved successfully to SD card.
Image saved to SD card as image.jpg.

MY CURRENT CODE:

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

// Command Definitions
const uint8_t CMD_GET_VERSION[] = {0x56, 0x00, 0x11, 0x00};
const uint8_t CMD_CAPTURE[] = {0x56, 0x00, 0x36, 0x01, 0x00};
const uint8_t CMD_READ_DATA_PREFIX[] = {0x56, 0x00, 0x32, 0x0C, 0x00, 0x0A};
const uint8_t CMD_STOP_CAPTURE[] = {0x56, 0x00, 0x36, 0x01, 0x03};
const uint8_t CMD_READ_LENGTH[] = {0x56, 0x00, 0x34, 0x01, 0x00};

// Pin Configuration
const int chipSelect = 4;

// Function to send a command
void sendCommand(const uint8_t *command, size_t length) {
  Serial3.write(command, length); // Send the command to the camera
}

// Function to read bytes from Serial3
bool readBytes(uint8_t *buffer, size_t length, unsigned long timeout = 1000) {
  size_t index = 0;
  unsigned long startTime = millis();
  while (index < length && (millis() - startTime) < timeout) {
    if (Serial3.available()) {
      buffer[index++] = Serial3.read();
      startTime = millis(); // Reset timeout after each received byte
    }
  }
  return index == length;
}

// Debugging function to print raw response in HEX
void debugPrintResponse(const uint8_t *response, size_t length) {
  Serial.print("Response (HEX): ");
  for (size_t i = 0; i < length; i++) {
    Serial.print("0x");
    if (response[i] < 0x10) Serial.print("0");
    Serial.print(response[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

// Function to get the image data length
uint32_t getImageDataLength() {
  sendCommand(CMD_READ_LENGTH, sizeof(CMD_READ_LENGTH));
  uint8_t response[9];
  
  if (readBytes(response, sizeof(response), 1000)) {
    debugPrintResponse(response, sizeof(response)); // Debug: Print full response

    if (response[0] == 0x76 && response[1] == 0x00 && response[2] == 0x34) {
      uint32_t imageLength = ((uint32_t)response[5] << 24) |
                             ((uint32_t)response[6] << 16) |
                             ((uint32_t)response[7] << 8) |
                             (uint32_t)response[8];

      Serial.print("Parsed Image Data Length: ");
      Serial.println(imageLength);

      return imageLength;
    } else {
      Serial.println("Invalid response format for image length.");
    }
  } else {
    Serial.println("No response received for image length.");
  }
  return 0; // Return 0 if the length cannot be determined
}

// Function to capture an image
bool captureImage() {
  sendCommand(CMD_CAPTURE, sizeof(CMD_CAPTURE));
  uint8_t response[5];

  if (readBytes(response, sizeof(response), 1000)) {
    if (response[0] == 0x76 && response[1] == 0x00 && response[2] == 0x36 && response[3] == 0x00) {
      return true;
    } else {
      Serial.println("Invalid response to capture command.");
    }
  } else {
    Serial.println("No response received for capture command.");
  }
  return false;
}

// Function to save image to SD card
bool saveImageToSD(uint32_t dataLength) {
  if (dataLength == 0 || dataLength > 500000) { // Validate data length
    Serial.println("Invalid image data length. Aborting read.");
    return false;
  }

  File imgFile = SD.open("image.jpg", FILE_WRITE);
  if (!imgFile) {
    Serial.println("Failed to open image file on SD card.");
    return false;
  }

  uint32_t startAddress = 0;
  const uint16_t chunkSize = 64; // Moderate chunk size for efficiency
  uint8_t buffer[chunkSize];
  size_t totalBytesRead = 0;
  bool endOfImage = false;

  while (totalBytesRead < dataLength + 128 && !endOfImage) { // Allow small buffer beyond reported length
    uint8_t addressBytes[4] = {
      (uint8_t)((startAddress >> 24) & 0xFF),
      (uint8_t)((startAddress >> 16) & 0xFF),
      (uint8_t)((startAddress >> 8) & 0xFF),
      (uint8_t)(startAddress & 0xFF)
    };

    uint8_t lengthBytes[4] = {
      (uint8_t)((chunkSize >> 24) & 0xFF),
      (uint8_t)((chunkSize >> 16) & 0xFF),
      (uint8_t)((chunkSize >> 8) & 0xFF),
      (uint8_t)(chunkSize & 0xFF)
    };

    // Send the READ IMAGE DATA command for the current chunk
    sendCommand(CMD_READ_DATA_PREFIX, sizeof(CMD_READ_DATA_PREFIX));
    Serial3.write(addressBytes, sizeof(addressBytes)); // Start address
    Serial3.write(lengthBytes, sizeof(lengthBytes));   // Chunk size
    Serial3.write(0x00);                               // Reserved byte
    Serial3.write(0xFF);                               // Delimiter byte

    // Log the start address
    Serial.print("Reading from address: ");
    Serial.println(startAddress);

    // Read data chunk
    if (readBytes(buffer, chunkSize, 2000)) { // Extended timeout
      for (size_t i = 0; i < chunkSize; i++) {
        if (i > 0 && buffer[i - 1] == 0xFF && buffer[i] == 0xD9) {
          Serial.println("JPEG EOI marker detected.");
          endOfImage = true; // End of image marker found
          break;
        }
      }

      // Log the last few bytes of the chunk for debugging
      Serial.print("Last few bytes of chunk: ");
      for (size_t i = max(0, chunkSize - 10); i < chunkSize; i++) {
        Serial.print(buffer[i], HEX);
        Serial.print(" ");
      }
      Serial.println();

      // Write the chunk to the SD card
      imgFile.write(buffer, chunkSize);
      totalBytesRead += chunkSize;
      startAddress += chunkSize; // Update start address

      // Debug progress
      Serial.print("Total bytes read so far: ");
      Serial.println(totalBytesRead);
    } else {
      Serial.println("Timeout or error during image read.");
      break;
    }

    // Stop if we reach the reported data length without detecting EOI
    if (totalBytesRead >= dataLength) {
      Serial.println("Reached reported data length without finding EOI marker. Finalizing file.");
      break;
    }

    delay(20); // Short delay to avoid overwhelming the camera
  }

  // Append EOI marker if not found
  if (!endOfImage) {
    Serial.println("Appending missing EOI marker.");
    uint8_t eoiMarker[2] = {0xFF, 0xD9};
    imgFile.write(eoiMarker, sizeof(eoiMarker));
  }

  imgFile.close();

  if (endOfImage || totalBytesRead >= dataLength) {
    Serial.println("Image data saved successfully to SD card.");
    return true;
  } else {
    Serial.println("Data read incomplete. File finalized with appended EOI marker.");
    return false;
  }
}



void setup() {
  Serial.begin(115200);  // Start serial for monitoring
  Serial3.begin(115200); // Start serial for camera communication

  // Initialize SD card
  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("SD card initialization failed!");
    while (true);
  }
  Serial.println("SD card initialized.");

  // Wait for camera to initialize
  Serial.println("Initializing camera...");
  delay(3000);

  // Capture an image
  if (captureImage()) {
    Serial.println("Image captured successfully.");

    // Get image data length
    uint32_t imageDataLength = getImageDataLength();
    if (imageDataLength > 0) {
      Serial.print("Image Data Length: ");
      Serial.println(imageDataLength);

      // Save image data to SD card
      if (saveImageToSD(imageDataLength)) {
        Serial.println("Image saved to SD card as image.jpg.");
      } else {
        Serial.println("Failed to save image to SD card.");
      }
    } else {
      Serial.println("Failed to get image data length.");
    }
  } else {
    Serial.println("Failed to capture image.");
  }
}

void loop() {
  // Nothing to do in the loop
}

For reference, here is some code the manufacturer kindly provided me. It's for an older version of the camera, so not all of the specifics of the camera are the same. (like the same commands) But I think the important thing to look at is how the data is buffered and continuously read all the way to the end. AFAIK this code is confirmed to work on an older version of the camera.

#include <SoftwareSerial.h>
#include <SPI.h>
#include <SD.h>
SoftwareSerial mySerial(5,6); // Set Arduino pin 4 and 5
as softserial
byte ZERO = 0x00;
byte incomingbyte;
long int j=0,k=0,count=0,i=0x0000;
uint8_t MH,ML;
boolean EndFlag=0;
File myFile;
void SendResetCmd()
{
mySerial.write(0x56);
mySerial.write(ZERO);
mySerial.write(0x26);
mySerial.write(ZERO);
}
/*************************************/
/* Set ImageSize :
/* <1> 0x22 : 160*120
/* <2> 0x11 : 320*240
/* <3> 0x00 : 640*480
/* <4> 0x1D : 800*600
/* <5> 0x1C : 1024*768
/* <6> 0x1B : 1280*960
/* <7> 0x21 : 1600*1200
/************************************/
void SetImageSizeCmd(byte Size)
{
mySerial.write(0x56);
mySerial.write(ZERO);
mySerial.write(0x54);
mySerial.write(0x01);
mySerial.write(Size);
}
/*************************************/
/* Set BaudRate :
/* <1>¡¡0xAE : 9600
/* <2>¡¡0x2A : 38400
/* <3>¡¡0x1C : 57600
/* <4>¡¡0x0D : 115200
/* <5>¡¡0xAE : 128000
/* <6>¡¡0x56 : 256000
/*************************************/
void SetBaudRateCmd(byte baudrate)
{
mySerial.write(0x56);
mySerial.write(ZERO);
mySerial.write(0x24);
mySerial.write(0x03);
mySerial.write(0x01);
mySerial.write(baudrate);
}
void SendTakePhotoCmd()
{
mySerial.write(0x56);
mySerial.write(ZERO);
mySerial.write(0x36);
mySerial.write(0x01);
mySerial.write(ZERO);
}
void SendReadDataCmd()
{
MH=i/0x100;
ML=i%0x100;
mySerial.write(0x56);
mySerial.write(ZERO);
mySerial.write(0x32);
mySerial.write(0x0c);
mySerial.write(ZERO);
mySerial.write(0x0a);
mySerial.write(ZERO);
mySerial.write(ZERO);
mySerial.write(MH);
mySerial.write(ML);
mySerial.write(ZERO);
mySerial.write(ZERO);
mySerial.write(ZERO);
mySerial.write(0x20);
mySerial.write(ZERO);
mySerial.write(0x0a);
i+=0x20;
}
void StopTakePhotoCmd()
{
mySerial.write(0x56);
mySerial.write(ZERO);
mySerial.write(0x36);
mySerial.write(0x01);
mySerial.write(0x03);
}
void setup()
{
Serial.begin(38400);
while (!Serial)
{
; // wait for serial port to connect. Needed for Leonardo
only
}
Serial.print("Initializing SD card...");
// On the Ethernet Shield, CS is pin 4. It's set as an output
by default.
// Note that even if it's not used as the CS pin, the
hardware SS pin
// (10 on most Arduino boards, 53 on the Mega) must be left
as an output
// or the SD library functions will not work.
pinMode(10, OUTPUT);
if (!SD.begin(4))
{
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
Serial.println("please waiting ....");
mySerial.begin(115200);
delay(100);
SendResetCmd();
delay(2000);
SetBaudRateCmd(0x2A);
delay(500);
mySerial.begin(38400);
delay(100);
}
void loop()
{
byte a[32];
int ii;
SendResetCmd();
delay(2000); //Wait 2-3 second to
send take picture command
SendTakePhotoCmd();
delay(1000);
while(mySerial.available()>0)
{
incomingbyte=mySerial.read();
}
myFile = SD.open("pic.jpg", FILE_WRITE); //The file name
should not be too long
while(!EndFlag)
{
j=0;
k=0;
count=0;
//mySerial.flush();
SendReadDataCmd();
delay(20);
while(mySerial.available()>0)
{
incomingbyte=mySerial.read();
k++;
delay(1); //250 for regular
if((k>5)&&(j<32)&&(!EndFlag))
{
a[j]=incomingbyte;
if((a[j-1]==0xFF)&&(a[j]==0xD9)) //tell if
the picture is finished
{
EndFlag=1;
}
j++;
count++;
}
}
for(j=0;j<count;j++)
{
if(a[j]<0x10) Serial.print("0");
Serial.print(a[j],HEX); // observe the
image through serial port
Serial.print(" ");
}
for(ii=0; ii<count; ii++)
myFile.write(a[ii]);
Serial.println();
}
myFile.close();
Serial.print("Finished writing data to file");
while(1);
}

@MaximoEsfuerzo and @noweare Thanks for your questions, this is what got me to dig deeper in the code.

@gilshultz I've posted my output so hopefully that answers your question / provides the much-needed context.

Do they have a utility program that allows interfacing with the camera so that you can get 1 frame on your pc ? That way you can open use that file to test your code for incoming end of file marker.

You may want to look for just 0xff and then check either side of it for the other value if for some reason its transposed.

They do, but unfortunately it's only available for Windows and I only have a Linux laptop and a Mac laptop. :frowning:

What does this mean? Am I not looking for that byte as the end of file anyways?

1 Like

Your code says your looking for 0xff and 0xd9 as end of file marker. Your looking for 0xff , then 0xd9 in that order. I am saying what if they are transposed then you would not find that sequence.

1 Like

So you're saying I should just check for 0xd9? What do you mean by "check either side of it for the other value"?

Sorry, I'm not familiar with what transposing (or truncating) means in this context.

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