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.