Arducam-Mini working with UNO and LCD

This is a carry-over from another thread.
http://forum.arduino.cc/index.php?topic=376790.0

Using my UNO board, I now have the Arducam-Mini writing directly to the ST7735 160x128 LCD, and displaying nice images [well, relatively nice, as they are a bit dim compared to jpeg images captured to SD card].

I adapted the sketch posted on the other thread for writing camera-to-SD card - there appear to be 2 fatal bugs in that code when it comes to writing camera-to-LCD.

The Arducam library implements only a single non-JPEG mode, which is BMP format QVGA mode at 320x240 pixels, so I am decimating it by 4:1 to fit into the 160x128 display. To produce anything else like CIF would take some playing around with the OV2640 camera registers. But it works.

I'll clean up the code, and post it, if anyone is interested.

EDIT:
Added 2 pictures, one is the direct jpeg capture at 320x240, the other shows the 160x128 4:1 decimated LCD image. It shows a little dimmer here than on the actual LCD display.

c1.JPG

Carrying over from the last thread, it seems like you've sorted out the code compiling issues -- mostly it is just commenting out the debugging serial print lines. Could you please post your code? Your decimation process sounds like it would solve my problem. Shame it happens after the initial image processing stage, but a good workaround is better than a bad problem :slight_smile:

Also, I'd be interested in what you identified as the fatal bugs -- the script was running well on my setup aside from the image size issue?

If you're interested in changing the brightness of the image (this was next on my list of improvements), The key is probably in the RGB565 - RGB888 conversion. This was a tricky thing to get working for me -- the final shift of each R, G, B line seems to affect the brightness with which the channel is recorded. My next step is to do some trial-and-error here to try and display a brighter image. I have a much older thread discussing the RGB conversion here:

http://forum.arduino.cc/index.php?topic=336943.0

Can you post a sample of your BMP images over on the other thread, so I can see what they look like. I find the quality significantly worse than the JPEGs.

If you're interested in changing the brightness of the image (this was next on my list of improvements), The key is probably in the RGB565 - RGB888 conversion.

No, I'm writing straight from the Arducam-Mini to the LCD, and the SD card stuff is totally gone from my sketch. When in BMP mode, the camera outputs RGB565 directly, and that goes straight to the LCD, so no RBG888 to deal with.

Also, I'd be interested in what you identified as the fatal bugs -- the script was running well on my setup aside from the image size issue?

Again, this pertains to camera-to-LCD only, and what I needed to do to get it to draw correctly. You could see what effect these changes have in your writeBMP() function.

First, change this "char VH, VL;" to this "unsigned char VH, VL;" in my capture function, as it affects the following statement "color = (VH<< 8 ) | VL;". Secondly I removed the following line, as I got garbage otherwise.

//Read the first dummy byte from FIFO
temp = myCAM1.read_fifo();

I'll post the code a little later, still working to clean it up. I'm currently using tft.drawPixel(), so it's running extremely slowly, takes 40-sec to fill the LCD screen using the UNO. Duh. But it works.

The process of saving to SD card and then loading to the screen is taking roughly 10 secs on my setup. I'm not sure if we're using different screens, as mine (the 1.8" microSD breakout from Adafruit) will only load 24-bit saved images, though it does seem to push them to the screen as 565. The fidelity of my images is not good -- as I discussed in the RGB conversion thread, there's some weirdness around object boundaries. I'll post an image for you, but it won't be until tomorrow when I have all the bits and pieces I need in the same place.

feathersinthread:
The process of saving to SD card and then loading to the screen is taking roughly 10 secs on my setup. I'm not sure if we're using different screens, as mine (the 1.8" microSD breakout from Adafruit) will only load 24-bit saved images, though it does seem to push them to the screen as 565. The fidelity of my images is not good -- as I discussed in the RGB conversion thread, there's some weirdness around object boundaries. I'll post an image for you, but it won't be until tomorrow when I have all the bits and pieces I need in the same place.

I believe the 24-bit you're referring to is the common BMP image format being stored to the SD card, but the LCD uses RGB565 format for display. The following code in your BMPdraw() function is doing the conversion.

        // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r,g,b));

This method is probably more efficient than drawPixel() as I'm currently using, as pushColor() apparently just writes into the next available RAM location on the LCD.

Here is a sketch to test - now draws to the LCD in 2.0-sec, I had a silly delay in there before.

/*
file: arducam_lcd1_0.ino
revised: 02/07/16 (orig 02/06/16)
author: oric_dan
cpus: UNO(tested ok)
-------------------------------

Program to test Arducam-Mini capture directly to LCD display.
Uses Arducam-Mini BMP mode, QVGA 320x240 (this is the only
non-JPEG mode built into the Arducam library).

Special Notes:
1. best to run Adafruit graphicstest.ino example first to 
   verify LCD shield working properly.

Bug Notes:
1. in modified sketch, when writing direct to LCD in
   capture2Lcd(), the first (dummy) FIFO read needs 
   to be removed or colors are hosed, also s/b 
   "unsigned char VH,VL;" rather than "char VH,VL;".
2. DISP_ROTATE value may need to be changed when 1.8" LCD 
   "module" is used.

Notes: 
1. ArduCam-Mini I/O pins are specified as 5V tolerant.
2. sketch adapted from "progress_camera_screen_crop.ino" here:
   http://forum.arduino.cc/index.php?topic=376790.0
3. sketch changes: removed SD store, and modified for direct 
   image display on 160x128 ST7735 LCD.
4. capture and draw takes about 2.0-sec using pushColor()
   method, and 3.8-sec using drawPixel() method.
-------------------------------------------------------

Screen Orientation

- for rotation(r=0), coordinates shown as (x,y):
      +(0,159)-------------(0,0)+   
      |        |                |   width  height
      |      Hline  --Vline--   |    |
      |        |                |    w      -h-
      +(127,159)---------(127,0)+    |

- origin (x,y)=(0,0) shown for screen rotations (r=0,1,2,3):
      +(r=3)-----(r=0)+
      |               |
      |               |
      +(r=2)-----(r=1)+
      
//////////////////////////////////////////////////////////
Original links:
// http://forum.arduino.cc/index.php?topic=285778.0
// inspired by 
http://sauerwine.blogspot.fr/2013/07/an-arduino-time-lapse-camera-using.html
http://www.arducam.com/how-arducam-use-a-external-trigger-from-a-sensor/
*****************************************************/

#include <Adafruit_GFX.h>      //Core graphics library
#include <Adafruit_ST7735.h>   //Hardware-specific library
#include <SPI.h>
#include <Wire.h>
//#include <SD.h>             //SD support removed.
#include <ArduCAM.h>
#include <memorysaver.h>
#include <avr/pgmspace.h>

char *progname="\nArducam-Mini Capture-to-Lcd1_0...(02/07/16)";

#define dbg   Serial 

///////////////////////////////////////////////
//// select setup for LCD module or shield ////
///////////////////////////////////////////////
#if false
// camera setup - from original sketch/
// uses Adafruit 1.8" LCD "module", p/n 358.
#define SD_CS    9      //SD card chip-select
#define TFT_CS  10      //Chip select line for TFT display
#define TFT_RST  3      //Reset line for TFT (or see below...)
#define TFT_DC   8      //Data/command line for TFT
const int screenlite=A2;
#define DISP_ROTATE  3  // MAY NEED TO BE CHANGED.
const int CAM_CS=7;     //Arducam CS pin.

#else
// setup for Adafruit 1.8" TFT "shield".
//#define SD_CS   4      //SD card chip-select
#define TFT_CS   10      //Chip select line for TFT display
#define TFT_DC    8      //Data/command line for TFT
#define TFT_RST   0      //Reset line for TFT
#define DISP_ROTATE   3  //draws right side up.  
//#define DISP_ROTATE 1  //draws up side down. 
const int CAM_CS=7;      //Arducam CS pin.
#endif

// instantiate LCD and ArduCAM.
Adafruit_ST7735 tft=Adafruit_ST7735(TFT_CS,TFT_DC,TFT_RST);
ArduCAM myCAM1(OV2640,CAM_CS);

// control macros - go inline //
#define cam_assert()   digitalWrite(CAM_CS,LOW)
#define cam_desert()   digitalWrite(CAM_CS,HIGH)
#define lcd_assert()   digitalWrite(TFT_CS,LOW)
#define lcd_desert()   digitalWrite(TFT_CS,HIGH)


/*************************************************/
void setup()
{
  uint8_t vid, pid, temp; 

  delay(1000);
  dbg.begin(115200);
  dbg.println(progname);
  disp_freeram();

//pinMode(screenlite, OUTPUT);
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH); 
  pinMode(CAM_CS,OUTPUT);
  digitalWrite(CAM_CS,HIGH);

  SPI.begin();
  Wire.begin(); 

  // initializer for 1.8" TFT.
  tft.initR(INITR_BLACKTAB);
  rotate_test(500);              //write test lines.
  delay(2000);
  tft.setRotation(DISP_ROTATE);  //set rotation for images.
  tft.fillScreen(ST7735_BLACK);

  // check if the ArduCAM SPI bus is ok.
  myCAM1.write_reg(ARDUCHIP_TEST1, 0x55);
  temp=myCAM1.read_reg(ARDUCHIP_TEST1);
  dbg.print("Camera SPI comms..");
  if(temp != 0x55) {
    dbg.println("Error, halt!");  while(1);
  }
  else dbg.println("OK");

  // check if the Arducam module type is OV2640.
  myCAM1.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
  myCAM1.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
  dbg.print("Camera I2C comms..");
  if( (vid != 0x26) || (pid != 0x42) ) {
    dbg.println("Error, halt!");  while(1);  
  }
  else  dbg.println("OK");

  // change MCU mode (?)
  myCAM1.write_reg(ARDUCHIP_MODE,0x00);
  myCAM1.InitCAM();
}

/************************************************/
void loop()
{ 
  if( dbg.available() ) {  //hold picture on keypress.
    while(1);
  }
  startCapture();
  capture2Lcd();

  // this is the slower method.
//  startCapture();
//  capture2Lcd2();  

  //delay(5000);
}

/* captures QVGA 320x240 image from camera, and writes 
*  directly to 160x128 LCD using 4:1 decimation.
*  - uses pushColor().
******************************************************/
void capture2Lcd( void )
{
  unsigned char VH,VL;  //must be unsigned.
  uint8_t temp;
  int row, col;
  int xo=0, yo=0;       //start position of displayed image.
  uint16_t color;
  unsigned long prevtm;

  dbg.print("Writing picture 1...");
  prevtm=millis();

  // set TFT address window bounds to entire screen.
  int w=160, h=120;    //width,height of displayed image.
  tft.setAddrWindow(xo, yo, xo+w-1, yo+h-1);

  // read dummy byte from FIFO.
//temp=myCAM1.read_fifo(); //remove,screws up LCD color scheme.

  // read 320x240x2 byte from FIFO, but draw only every
  // other row, and every other pixel on each row drawn.
  for( row=0; row<240; row++) {

    // process every other col pixel.
    for( col=0; col<(320/2); col++) {
      cam_assert();
      VH=myCAM1.read_fifo();    //get every other col pixel.
      VL=myCAM1.read_fifo();
      temp=myCAM1.read_fifo();  //toss next pixel.
      temp=myCAM1.read_fifo();
      cam_desert();

      if( (row%2) == 0) {  //draw only every other row.
        lcd_assert();
        color = (VH << 8 ) | VL;  //compute RGB565 color.
        tft.pushColor(color);  
        lcd_desert();
      }
    }
  }
  dbg.println("finished");
  dbg.print( millis()-prevtm );
  dbg.println(" msec");
}

/* captures QVGA 320x240 image from camera, and writes 
*  directly to 160x128 LCD using 4:1 decimation.
*  - uses drawPixel().
******************************************************/
void capture2Lcd2()
{
  unsigned char VH,VL;  //must be unsigned.
  uint8_t temp;
  int row, col;
  int xo=0, yo=0;       //start position of displayed image.
  int w=160, h=128;     //width, height  "      "       "  .
  uint16_t color;
  unsigned long prevtm;

  dbg.print("Writing picture 2...");
  prevtm=millis();

  // read dummy byte from FIFO.
//temp=myCAM1.read_fifo(); //remove,screws up LCD color scheme.

  // read 320x240x2 byte from FIFO, but draw only every
  // other row, and every other pixel on each row.
  for( row=0; row<240; row++) {

    // process every other col pixel.
    for( col=0; col<(320/2); col++) {
      cam_assert();
      VH=myCAM1.read_fifo();    //get pixel.
      VL=myCAM1.read_fifo();
      temp=myCAM1.read_fifo();  //toss next pixel.
      temp=myCAM1.read_fifo();
      cam_desert();

      if( (row%2) == 0) {   //draw only every other row.
        lcd_assert();
        color = (VH << 8 ) | VL;
        tft.drawPixel( col, (row/2), color);    
        lcd_desert();
      }
    }
  }
  dbg.println("finished");
  dbg.print( millis()-prevtm );
  dbg.println(" msec");
}

/* simple to use function, which takes a picture either in 
*  JPEG or BMP format in function of the 'mode' constant.
********************************************************/
void startCapture( void )
{
  dbg.print("Taking picture...");

  myCAM1.write_reg(ARDUCHIP_MODE, 0x00);
  myCAM1.set_format(BMP);
  myCAM1.InitCAM();
  
  myCAM1.flush_fifo();         //clear ArduCAM buffer.
  myCAM1.clear_fifo_flag();    //start capture.
  myCAM1.start_capture();
  while( !(myCAM1.read_reg(ARDUCHIP_TRIG) & CAP_DONE_MASK) ) {
    delay(10); 
  }
  dbg.println("finished");
  myCAM1.clear_fifo_flag();
  myCAM1.InitCAM();
}
[/code

Remainder of sketch is here, see also attached.

/* rotation test for screen orientations.
*********************************************/
void rotate_test( int dly )
{
  for( int i=0; i<4; i++) {
    tft.setRotation(i);
    draw_bits();
    delay(dly);
  }
}

/* draw a bit on screen for reference.
***********************************************/
void draw_bits( void )
{
  tft.fillScreen(ST7735_BLACK);
  tft.fillRect(64,80,10,20,ST7735_YELLOW);        //(x,y,w,h,color).
//tft.drawFastVLine(0,0,155,ST7735_RED);         //(x,y,h,color).
//tft.drawFastHLine(0,0,123,ST7735_YELLOW);      //(x,y,w,color).
  tft.drawLine(0,0,(127-8),(159-8),ST7735_WHITE); //(x0,y0,x1,y1,color).
}

//// Utility to display free ram available ////

/*********************************************/
void disp_freeram()
{
  dbg.print("-SRAM left: ");
  dbg.println(freeRam());
}

/*************************************************/
int freeRam() 
{
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v - (__brkval == 0  
    ? (int)&__heap_start : (int) __brkval);  
}

arducam_lcd1_0.zip (3.33 KB)

I modified the LCD draw sketch. Due to using the silly jumper wires to connect the camera, it's natural resting position was upside down, and I was drawing a mirror-image to the screen, and didn't realize it. This is now fixed. I'm now capturing a row of pixels, and drawing it to the LCD in reverse-order, and the new scheme draws to the LCD in 1.5-sec down from 2.0-sec. Also, note the sketch is near the ragged limit for RAM usage on an Arduino UNO.

New draw function:

uint16_t PixHld[160];  //holds 1 row of pixel colors.

/* captures QVGA 320x240 image from camera, and writes 
*  directly to 160x128 LCD using 4:1 decimation.
*  - uses pushColor().
*  - modified for mirror-image correction, saves 1 row of 
*    pixels, and draws in reverse-order.
***********************************************************/
void capture2Lcd( void )
{
  uint8_t VH,VL;      //must be unsigned.
  uint8_t temp;
  int row, col;
  int xo=0, yo=0;     //start position of displayed image.
  uint16_t color;
  unsigned long prevtm;

  dbg.print(F("Writing picture 1..."));
  prevtm=millis();

  // set TFT address window bounds to entire screen.
  int w=160, h=120;     //width,height of displayed image.
  tft.setAddrWindow(xo, yo, xo+w-1, yo+h-1);

  // read dummy byte from FIFO.
//temp=myCAM1.read_fifo(); //remove,screws up LCD color scheme.

  // read 320x240x2 byte from FIFO, but draw only every
  // other row, and every other pixel on each row drawn.
  for( row=0; row<240; row++) {

    // process every other col pixel.
    cam_assert();
    for( col=0; col<(320/2); col++) {
      VH=myCAM1.read_fifo();    //get every other col pixel.
      VL=myCAM1.read_fifo();
      temp=myCAM1.read_fifo();  //toss next pixel.
      temp=myCAM1.read_fifo();
      PixHld[col] = (VH << 8) | VL;  //save RGB565 color.
    }
    cam_desert();

    if( (row%2) == 0) {  //draw only every other row.
      // display data in reverse-order.
      lcd_assert();
      for( col=0; col<(320/2); col++) {
        tft.pushColor( PixHld[160-1-col] );  
      }
      lcd_desert();
    }
  }
  dbg.print(F("done "));
  dbg.print( millis()-prevtm );
  dbg.println(F(" msec"));
}

arducam_lcd1_1.zip (3.54 KB)