OV7670, Generic STM32F103RC and ILI9341 display

What are you developing?
User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Sun Sep 24, 2017 9:55 am

BTW.

I have it wired like this
lcd_sd_camera_blue_pill.png
lcd_sd_camera_blue_pill.png (31.55 KiB) Viewed 225 times
(Right click and view the image to get it in higher quality, as PHPBB seems to generate render views that are lower quality JPEG and I uploaded PNG)

I'll double check that the SD_CS line and LCD_CS line are not somehow connected together.

I'll also check what happens if I write a file to SD, and then try to write to LCD, in case its the SD code which is leaving the SPI in an incorrect mode.

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Sun Sep 24, 2017 10:08 am

Found 1 problem.


Looks like SD leave SPI in 8 bit mode, but the ILI9341 lib needs 16 bit mode

So I added a call to set into 16 bit before writing the text to the LCD, and it now saves the LCD screen multiple times

Code: Select all

#include <SdFat.h>
#include "Adafruit_ILI9341_STM.h"

#define TFT_CS         PB0
#define TFT_DC         PA2
#define TFT_RST        PB1
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST); // Use hardware SPI

const uint8_t SD_CS = PC15;
SdFat sd;

unsigned long testText()
{
  SPI.setDataSize(DATA_SIZE_16BIT);
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_RED);  tft.setTextSize(3);
  tft.println("LCD Screen reader\n");
  tft.setTextColor(ILI9341_GREEN); tft.setTextSize(3);
  tft.println("rogerclark.net\n");
  tft.setTextColor(ILI9341_BLUE); tft.setTextSize(3);
  tft.println(__TIME__);
  tft.println(random(100000));

  return 0;
}

void LCD2SD()
{
  const int w = 320;
  const int h = 240;
  SdFile file;
#define NUM_LINES_BUFFERED 12
  uint8_t lineBufSD[w * 3 * NUM_LINES_BUFFERED];
  boolean doFileActions = true;

  unsigned char bmpFileHeader[14] = {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0 };
  unsigned char bmpInfoHeader[40] = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0 };
  unsigned long m;
  char name[] = "LCD_00.bmp";

  if (doFileActions)
  {
    // if name exists, create new filename
    for (int i = 0; i < 100; i++)
    {
      name[4] = i / 10 + '0';
      name[5] = i % 10 + '0';
      if (file.open(name, O_CREAT | O_EXCL | O_WRITE))
      {
        break;
      }
    }
  }
  // create image data
  int filesize = 54 + 4 * w * h;      //  w is image width, h is image height
  bmpFileHeader[ 2] = (unsigned char)(filesize    );
  bmpFileHeader[ 3] = (unsigned char)(filesize >> 8);
  bmpFileHeader[ 4] = (unsigned char)(filesize >> 16);
  bmpFileHeader[ 5] = (unsigned char)(filesize >> 24);

  bmpInfoHeader[ 4] = (unsigned char)(       w    );
  bmpInfoHeader[ 5] = (unsigned char)(       w >> 8);
  bmpInfoHeader[ 6] = (unsigned char)(       w >> 16);
  bmpInfoHeader[ 7] = (unsigned char)(       w >> 24);
  bmpInfoHeader[ 8] = (unsigned char)(       h    );
  bmpInfoHeader[ 9] = (unsigned char)(       h >> 8);
  bmpInfoHeader[10] = (unsigned char)(       h >> 16);
  bmpInfoHeader[11] = (unsigned char)(       h >> 24);
  if (doFileActions)
  {
    file.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
    file.write(bmpInfoHeader, sizeof(bmpInfoHeader));    // " info header
  }
  m = millis();
  uint8_t t;
  int lineOffset;
  for (int y = 0 ; y < h ; y++)
  {

    lineOffset = y % NUM_LINES_BUFFERED * 3 * w;
    tft.readPixels24(0, (h - 1) - y, 320, (h - 1) - y, lineBufSD + lineOffset);
    if ((y + 1) % NUM_LINES_BUFFERED == 0)
    {
      // swap colour channels from  RGB to BGR
      for (int x = 0; x < w * 3 * NUM_LINES_BUFFERED; x += 3)
      {
        t = lineBufSD[x + 2];
        lineBufSD[x + 2] = lineBufSD[x];
        lineBufSD[x] = t;
      }
      if (doFileActions)
      {
        file.write(lineBufSD, 3 * w * NUM_LINES_BUFFERED);
      }
    }
  }
  Serial.print("Saved in "); Serial.print(millis() - m); Serial.println(" mS ");
  if (doFileActions)
  {
    file.close();
  }
}

void setup()
{
  Serial.begin(9600);
  delay(500);
  Serial.println("Starting");

  if (!sd.begin(SD_CS, SD_SCK_MHZ(50)))
  {
    sd.initErrorHalt();
  }

  tft.begin();
  uint16_t screen_w = ILI9341_TFTHEIGHT;
  uint16_t screen_h = ILI9341_TFTWIDTH;

  tft.setRotation(1);

}

void loop()
{
  testText();// Draw something
  LCD2SD();
  delay(1000);
}

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Sun Sep 24, 2017 10:24 am

BTW.

To get SD working, I removed the resistors and also shorted the link beside the 3.3V regulator on the LCD board, so that the SD card receives 3.3V instead of 3V

I'm not sure if I needed to do either of these but the SD works for me.

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Sun Sep 24, 2017 10:42 am

Steve

I've kinda got it working

Code: Select all

//
// Created by indrek on 4.12.2016.
//
// Adapted for ILI9341 by stevestrong on 29.07.2017
//
#include <LiveOV7670_stm32_OK.h>
#include <Adafruit_ILI9341_STM.h>
#include <SdFat.h>

const uint8_t SD_CS = PC15;
SdFat sd;


// TFT connection:
// PB1 - TFT reset
// PA2 - TFT D/C (data/command)
// PB0 - TFT chip select
// PA5 - TFT SPI CLK
// PA6 - TFT SPI MISO
// PA7 - TFT SPI MOSI

// Camera connection:
// PA8 - camera clock
// PB3 - HREF
// PA1 - pixel clock PCLK
// PB5 - VSYNC
// PB6 - I2C Clock
// PB7 - I2C data
// PB8..PB15 - pixel data

// CPU@72MHZ -> 11fps, CPU@80MHZ-> 12.5fps
BufferedCameraOV7670_QVGA camera(CameraOV7670::PIXEL_RGB565, BufferedCameraOV7670_QVGA::FPS_11p_Hz);

#define LED_PIN PC13

#define TFT_CS         PB0                  
#define TFT_DC         PA2               
#define TFT_RST        PB1 
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST); // Use hardware SPI

#define SD_CS PC15

// Normally it is a portrait screen. Use it as landscape
uint16_t screen_w = ILI9341_TFTHEIGHT;
uint16_t screen_h = ILI9341_TFTWIDTH;
uint16_t screenLineIndex;
uint8_t bufIndex;

#define PIXELS_PER_LINE ILI9341_TFTHEIGHT

// The GPIO.IDR register can be only accessed in word (32 bit) mode.
// Therefore a 4 times larger buffer should be allocated to one line.
// But because for each pixel we need 2 reads, the buffer should be doubled again.
// The useful bytes are packed in the same buffer after reading is finished.
// This is a lot of wasted memory, but it runs with DMA, which is a huge benefit!
#define PIXEL_BUFFER_LENGTH (2*PIXELS_PER_LINE+1)

uint8_t pixBuf[2][PIXEL_BUFFER_LENGTH];

//-----------------------------------------------------------------------------
void TIMER_Setup(void)
{
	gpio_set_mode(GPIOA, 1, GPIO_INPUT_FLOATING);

// Slave mode: Reset mode (see RM0008, chap. 15.3.14, page 394)
// ------------------------
// The counter and its prescaler can be reinitialized in response to an event on a trigger input.
// Moreover, if the URS bit from the TIMx_CR1 register is low, an update event UEV is generated.
// Then all the preloaded registers (TIMx_ARR, TIMx_CCRx) are updated.
//
// In the following example, the upcounter is cleared in response to a rising edge on TI2 input:
// • Configure the channel 2 to detect rising edges on TI2.
// - Configure the input filter duration (in this example, we don’t need any filter, so we keep IC1F=0000).
// - The capture prescaler is not used for triggering, so you don’t need to configure it.
// - The CC2S bits select the input capture source only, CC2S = 01 in the TIMx_CCMR1 register.
// - Write CC2P=0 in TIMx_CCER register to validate the polarity (and detect rising edges only).
// • Configure the timer in reset mode by writing SMS=100 in TIMx_SMCR register.
// - Select TI2 as the input source by writing TS=101 in TIMx_SMCR register.
// • Start the counter by writing CEN=1 in the TIMx_CR1 register.
//
// The counter starts counting on the internal clock, then behaves normally until TI2 rising edge.
// When TI2 rises, the counter is cleared and restarts from 0.
// In the meantime, the trigger flag is set (TIF bit in the TIMx_SR register) and an interrupt request,
// or a DMA request can be sent if enabled (depending on the TIE and TDE bits in TIMx_DIER register).
//
// This event will trigger the DMA to save the content of GPIOB.IDR[1] to memory.

	timer_pause(TIMER2); // stop timer
	timer_init(TIMER2);  // turn timer RCC on
	// configure PA2 = timer 2 channel 2 == input TI2

 


#define TIMER_RELOAD_VALUE 2 // must be adapted according to the results
	// as this mode is not supported by the core lib, we have to set up the registers manually.
	//(TIMER2->regs).gen->CR1 = TIMER_CR1_CEN;
	(TIMER2->regs).gen->CR2 = 0;
	(TIMER2->regs).gen->SMCR = (TIMER_SMCR_TS_TI2FP2 | TIMER_SMCR_SMS_RESET);//TIMER_SMCR_SMS_TRIGGER);


 
	(TIMER2->regs).gen->DIER = (TIMER_DIER_UDE); // enable DMA request on TIM2 update


 
	(TIMER2->regs).gen->SR = 0;

 
	(TIMER2->regs).gen->EGR = 0;
	(TIMER2->regs).gen->CCMR1 = TIMER_CCMR1_CC2S_INPUT_TI2; // IC2F='0000', IC2PSC='0', CC2S='01' 
	(TIMER2->regs).gen->CCMR2 = 0;
	(TIMER2->regs).gen->CCER = (TIMER_CCER_CC2P); // inverse polarity, active low


 
	(TIMER2->regs).gen->CNT = 0;//TIMER_RELOAD_VALUE;	// set it only in down-counting more
	(TIMER2->regs).gen->PSC = 0;
	(TIMER2->regs).gen->ARR = 0;//TIMER_RELOAD_VALUE;
	(TIMER2->regs).gen->CCR1 = 0;
	(TIMER2->regs).gen->CCR2 = 0;
	(TIMER2->regs).gen->CCR3 = 0;
	(TIMER2->regs).gen->CCR4 = 0;
	(TIMER2->regs).gen->DCR = 0;			// don't need DMA for timer
	(TIMER2->regs).gen->DMAR = 0;
	// don't forget to set the DMA trigger source to TIM2-UP
	//timer_resume(TIMER2); // start timer
}
//-----------------------------------------------------------------------------
void DMA_Setup(void)
{
	dma_init(DMA1);
	uint32_t pmem = ((uint32_t)(&(GPIOB->regs->IDR)) + 1); // use GPIOB high byte as source
    dma_setup_transfer(DMA1, DMA_CH2,
	    (uint8_t *)pmem, DMA_SIZE_8BITS,
        (uint8_t *)&pixBuf, DMA_SIZE_8BITS,
		(DMA_MINC_MODE));//| DMA_CIRC_MODE));
    dma_set_priority(DMA1, DMA_CH2, DMA_PRIORITY_VERY_HIGH);
    //dma_set_num_transfers(DMA1, DMA_CH2, 2*PIXELS_PER_LINE); // 2 readings for each pixel
    //dma_enable(DMA1, DMA_CH2);
}



void setup()
{
  //while(!Serial); delay(1000);
  pinMode(SD_CS,OUTPUT);
  digitalWrite(SD_CS,HIGH);

  // initialise the LCD
  tft.begin(); // use standard SPI port
  
  if (!sd.begin(SD_CS, SD_SCK_MHZ(50)))
  {
    sd.initErrorHalt();
  }

  
  bufIndex = 0;

  tft.setRotation(1); // rotate 90° for landscape
  tft.setAddrWindow(0, 0, screen_h, screen_w);
  tft.fillScreen(ILI9341_BLUE);
  tft.setTextSize(2);
  tft.print("\n\nScreen size: "); tft.print(screen_w); tft.write('x'); tft.print(screen_h);
  tft.println("\n\nSetting up the camera...");
  Serial.print("Setting up the camera...");

    // initialise the camera
  if ( !camera.init() ) {
    tft.fillScreen(ILI9341_RED);
    tft.print("\n   Camera init failure!");
  Serial.println("failed!");
  blink(0);
  } else {
   // tft.print("done.");
  Serial.println("done.\n");
  }
  
  initTimerAndDMA();
}
//-----------------------------------------------------------------------------

void initTimerAndDMA()
{
  tft.setRotation(1); // rotate 90° for landscape
  tft.setAddrWindow(0, 0, screen_w, screen_h); 

  camera.waitForVsync();  camera.waitForVsyncEnd();
  camera.waitForVsync();  camera.waitForVsyncEnd();
  // enable the timer and corresponding DMA request
  DMA_Setup();  
  TIMER_Setup();

  SPI.setDataSize(DATA_SIZE_8BIT); // set to 8 bit mode
}

void blink(uint8_t br)
{
	pinMode(LED_PIN, OUTPUT);
	while(1)
	{
		digitalWrite(LED_PIN, LOW);
		delay(125);
		digitalWrite(LED_PIN, HIGH);
		delay(125);
		if (br) break;
	}
}
uint32_t loop_counter, line_counter;
//-----------------------------------------------------------------------------


void LCD2SD()
{
const int w = 320;
const int h = 240;
SdFile file;  
#define NUM_LINES_BUFFERED 12
uint8_t lineBufSD[w*3*NUM_LINES_BUFFERED];

  unsigned char bmpFileHeader[14] = {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0 };
  unsigned char bmpInfoHeader[40] = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0 };  
  unsigned long m;
  char name[] = "LCD_00.bmp";


  // if name exists, create new filename
  for (int i = 0; i < 100; i++)
  {
    name[4] = i / 10 + '0';
    name[5] = i % 10 + '0';
    if (file.open(name, O_CREAT | O_EXCL | O_WRITE))
    {
      break;
    }
  }

  // create image data
  int filesize = 54 + 4 * w * h;      //  w is image width, h is image height
  bmpFileHeader[ 2] = (unsigned char)(filesize    );
  bmpFileHeader[ 3] = (unsigned char)(filesize >> 8);
  bmpFileHeader[ 4] = (unsigned char)(filesize >> 16);
  bmpFileHeader[ 5] = (unsigned char)(filesize >> 24);

  bmpInfoHeader[ 4] = (unsigned char)(       w    );
  bmpInfoHeader[ 5] = (unsigned char)(       w >> 8);
  bmpInfoHeader[ 6] = (unsigned char)(       w >> 16);
  bmpInfoHeader[ 7] = (unsigned char)(       w >> 24);
  bmpInfoHeader[ 8] = (unsigned char)(       h    );
  bmpInfoHeader[ 9] = (unsigned char)(       h >> 8);
  bmpInfoHeader[10] = (unsigned char)(       h >> 16);
  bmpInfoHeader[11] = (unsigned char)(       h >> 24);

  file.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
  file.write(bmpInfoHeader, sizeof(bmpInfoHeader));    // " info header

  m=millis();
  uint8_t t;
  int lineOffset;
  for (int y = 0 ; y < h ; y++) 
  {

    lineOffset = y%NUM_LINES_BUFFERED *3 * w;
    tft.readPixels24(0,(h-1)-y,320,(h-1)-y, lineBufSD + lineOffset);
    if ((y+1)%NUM_LINES_BUFFERED==0)
    {
      // swap colour channels from  RGB to BGR
      for (int x = 0; x < w * 3 * NUM_LINES_BUFFERED; x+=3) 
      {
        t=lineBufSD[x+2];
        lineBufSD[x+2]=lineBufSD[x];
        lineBufSD[x]=t;
      }
       file.write(lineBufSD, 3 * w * NUM_LINES_BUFFERED);
    }
  }
  Serial.print("Saved in ");Serial.print(millis()-m);Serial.println(" mS ");
  file.close();  

}
int totalFrames=0;
void loop()
{
  uint8_t * ptr = (uint8_t*)&pixBuf;

  // start of frame
  camera.waitForVsync();
	digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  camera.waitForVsyncEnd();

  if ( (loop_counter&(BIT2-1))==0 ) {
	  if  ( (loop_counter&(BIT7-1))==0 ) {
		  Serial.write('\n');
	  }
	  Serial.write('.');
  }
  loop_counter ++;

  line_counter = screen_h;

//noInterrupts();
  // receive lines
  while ( (line_counter--) )
  {
    // activate here the DMA and the timer
    dma_set_num_transfers(DMA1, DMA_CH2, 2*PIXELS_PER_LINE+1); // 2 readings for each pixel
	dma_clear_isr_bits(DMA1, DMA_CH2);
    dma_enable(DMA1, DMA_CH2);
    timer_resume(TIMER2); // start timer
    // one byte will be read instantly - TODO: find out why ?
	
	camera.waitForHref(); // wait for line start

    uint32_t t0 = millis();
    // monitor the DMA channel transfer end, just loop waiting for the flag to be set.
    while ( !(dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TCIF) ) {
	  //Serial.print("DMA status: "); Serial.println(status, HEX);
      if ((millis() - t0) > 1000) { Serial.println("DMA timeout!"); blink(0); }
  	  if ( dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TEIF ) {
  	  	Serial.println("DMA error!"); blink(0); // error
  	  }
	  if ( !camera.isHrefOn() ) break; // stop if over end of line
    }


  timer_pause(TIMER2); // stop timer
  dma_disable(DMA1, DMA_CH2);
	dma_clear_isr_bits(DMA1, DMA_CH2);

	if ( dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TCIF ) 
	{
	//	Serial<< "2. DMA ISR: " << _HEX(DMA1->regs->ISR) << ", CNT: " << (DMA1CH2_BASE->CNDTR) << endl;
		Serial.println("DMA ISR bits reset error!"); 
		blink(0);
	}
	// send pixel data to LCD
	//SPI.setDataSize(DATA_SIZE_8BIT); // set to 8 bit mode
    tft.pushColors((uint16_t*)ptr, 2*PIXELS_PER_LINE, 0); // 3rd parameter: send async
	//SPI.setDataSize(DATA_SIZE_16BIT); // set back to 16 bit mode
	if ( dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TCIF )
	{
	//	Serial<< "3. DMA ISR: " << _HEX(DMA1->regs->ISR) << ", CNT: " << (DMA1CH2_BASE->CNDTR) << endl;
		Serial.println("DMA ISR bits reset error!"); 
		blink(0);
	}

  }

  #define SAVE_EVERY_X_FRAMES 50
  if ((totalFrames++)%SAVE_EVERY_X_FRAMES == (SAVE_EVERY_X_FRAMES-1))
  {
  //  timer_pause(TIMER2);
    (TIMER2->regs).gen->DIER = (0); // disable DMA request on TIM2 update
    LCD2SD();
    initTimerAndDMA();
  }
//interrupts();
}

unsigned long testText()
{
  SPI.setDataSize(DATA_SIZE_16BIT);
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_RED);  tft.setTextSize(3);
  tft.println("LCD Screen reader\n");
  tft.setTextColor(ILI9341_GREEN); tft.setTextSize(3);
  tft.println("rogerclark.net\n");
  tft.setTextColor(ILI9341_BLUE); tft.setTextSize(3);
  tft.println(__TIME__);
  tft.println(random(100000));

  return 0;
}

But I'm currently re-initing both the timer and the DMA each time after saving the LCD to SD

I probably don't need to completely re-init them.

BTW. It seems to be very important to set the orientation of the display, otherwise the camera code does not work after saving to SD

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Mon Sep 25, 2017 8:09 am

I had a bug in the code I posted

Code: Select all

//
// Created by indrek on 4.12.2016.
//
// Adapted for ILI9341 by stevestrong on 29.07.2017
//
#include <LiveOV7670_stm32_OK.h>
#include <Adafruit_ILI9341_STM.h>
#include <SdFat.h>

const uint8_t SD_CS = PC15;
SdFat sd;


// TFT connection:
// PB1 - TFT reset
// PA2 - TFT D/C (data/command)
// PB0 - TFT chip select
// PA5 - TFT SPI CLK
// PA6 - TFT SPI MISO
// PA7 - TFT SPI MOSI

// Camera connection:
// PA8 - camera clock
// PB3 - HREF
// PA1 - pixel clock PCLK
// PB5 - VSYNC
// PB6 - I2C Clock
// PB7 - I2C data
// PB8..PB15 - pixel data

// CPU@72MHZ -> 11fps, CPU@80MHZ-> 12.5fps
BufferedCameraOV7670_QVGA camera(CameraOV7670::PIXEL_RGB565, BufferedCameraOV7670_QVGA::FPS_11p_Hz);

#define LED_PIN PC13

#define TFT_CS         PB0                  
#define TFT_DC         PA2               
#define TFT_RST        PB1 
Adafruit_ILI9341_STM tft = Adafruit_ILI9341_STM(TFT_CS, TFT_DC, TFT_RST); // Use hardware SPI

#define SD_CS PC15

// Normally it is a portrait screen. Use it as landscape
uint16_t screen_w = ILI9341_TFTHEIGHT;
uint16_t screen_h = ILI9341_TFTWIDTH;
uint16_t screenLineIndex;
uint8_t bufIndex;

#define PIXELS_PER_LINE ILI9341_TFTHEIGHT

// The GPIO.IDR register can be only accessed in word (32 bit) mode.
// Therefore a 4 times larger buffer should be allocated to one line.
// But because for each pixel we need 2 reads, the buffer should be doubled again.
// The useful bytes are packed in the same buffer after reading is finished.
// This is a lot of wasted memory, but it runs with DMA, which is a huge benefit!
#define PIXEL_BUFFER_LENGTH (2*PIXELS_PER_LINE+1)

uint8_t pixBuf[2][PIXEL_BUFFER_LENGTH];

//-----------------------------------------------------------------------------
void TIMER_Setup(void)
{
	gpio_set_mode(GPIOA, 1, GPIO_INPUT_FLOATING);

// Slave mode: Reset mode (see RM0008, chap. 15.3.14, page 394)
// ------------------------
// The counter and its prescaler can be reinitialized in response to an event on a trigger input.
// Moreover, if the URS bit from the TIMx_CR1 register is low, an update event UEV is generated.
// Then all the preloaded registers (TIMx_ARR, TIMx_CCRx) are updated.
//
// In the following example, the upcounter is cleared in response to a rising edge on TI2 input:
// • Configure the channel 2 to detect rising edges on TI2.
// - Configure the input filter duration (in this example, we don’t need any filter, so we keep IC1F=0000).
// - The capture prescaler is not used for triggering, so you don’t need to configure it.
// - The CC2S bits select the input capture source only, CC2S = 01 in the TIMx_CCMR1 register.
// - Write CC2P=0 in TIMx_CCER register to validate the polarity (and detect rising edges only).
// • Configure the timer in reset mode by writing SMS=100 in TIMx_SMCR register.
// - Select TI2 as the input source by writing TS=101 in TIMx_SMCR register.
// • Start the counter by writing CEN=1 in the TIMx_CR1 register.
//
// The counter starts counting on the internal clock, then behaves normally until TI2 rising edge.
// When TI2 rises, the counter is cleared and restarts from 0.
// In the meantime, the trigger flag is set (TIF bit in the TIMx_SR register) and an interrupt request,
// or a DMA request can be sent if enabled (depending on the TIE and TDE bits in TIMx_DIER register).
//
// This event will trigger the DMA to save the content of GPIOB.IDR[1] to memory.

	timer_pause(TIMER2); // stop timer
	timer_init(TIMER2);  // turn timer RCC on
	// configure PA2 = timer 2 channel 2 == input TI2

 


#define TIMER_RELOAD_VALUE 2 // must be adapted according to the results
	// as this mode is not supported by the core lib, we have to set up the registers manually.
	//(TIMER2->regs).gen->CR1 = TIMER_CR1_CEN;
	(TIMER2->regs).gen->CR2 = 0;
	(TIMER2->regs).gen->SMCR = (TIMER_SMCR_TS_TI2FP2 | TIMER_SMCR_SMS_RESET);//TIMER_SMCR_SMS_TRIGGER);


 
	(TIMER2->regs).gen->DIER = (TIMER_DIER_UDE); // enable DMA request on TIM2 update


 
	(TIMER2->regs).gen->SR = 0;

 
	(TIMER2->regs).gen->EGR = 0;
	(TIMER2->regs).gen->CCMR1 = TIMER_CCMR1_CC2S_INPUT_TI2; // IC2F='0000', IC2PSC='0', CC2S='01' 
	(TIMER2->regs).gen->CCMR2 = 0;
	(TIMER2->regs).gen->CCER = (TIMER_CCER_CC2P); // inverse polarity, active low


 
	(TIMER2->regs).gen->CNT = 0;//TIMER_RELOAD_VALUE;	// set it only in down-counting more
	(TIMER2->regs).gen->PSC = 0;
	(TIMER2->regs).gen->ARR = 0;//TIMER_RELOAD_VALUE;
	(TIMER2->regs).gen->CCR1 = 0;
	(TIMER2->regs).gen->CCR2 = 0;
	(TIMER2->regs).gen->CCR3 = 0;
	(TIMER2->regs).gen->CCR4 = 0;
	(TIMER2->regs).gen->DCR = 0;			// don't need DMA for timer
	(TIMER2->regs).gen->DMAR = 0;
	// don't forget to set the DMA trigger source to TIM2-UP
	//timer_resume(TIMER2); // start timer
}
//-----------------------------------------------------------------------------
void DMA_Setup(void)
{
	dma_init(DMA1);
	uint32_t pmem = ((uint32_t)(&(GPIOB->regs->IDR)) + 1); // use GPIOB high byte as source
    dma_setup_transfer(DMA1, DMA_CH2,
	    (uint8_t *)pmem, DMA_SIZE_8BITS,
        (uint8_t *)&pixBuf, DMA_SIZE_8BITS,
		(DMA_MINC_MODE));//| DMA_CIRC_MODE));
    dma_set_priority(DMA1, DMA_CH2, DMA_PRIORITY_VERY_HIGH);
    //dma_set_num_transfers(DMA1, DMA_CH2, 2*PIXELS_PER_LINE); // 2 readings for each pixel
    //dma_enable(DMA1, DMA_CH2);
}

boolean hasSD = true;

void setup()
{
  //while(!Serial); delay(1000);
  pinMode(SD_CS,OUTPUT);
  digitalWrite(SD_CS,HIGH);

  if (!sd.begin(SD_CS, SD_SCK_MHZ(50)))
  {
    hasSD=false;
   // sd.initErrorHalt();
  }

  // initialise the LCD
  tft.begin(); // use standard SPI port
  


  
  bufIndex = 0;

  tft.setRotation(1); // rotate 90° for landscape
  tft.setAddrWindow(0, 0, screen_h, screen_w);
  tft.fillScreen(ILI9341_BLUE);
  tft.setTextSize(2);
  tft.print("\n\nScreen size: "); tft.print(screen_w); tft.write('x'); tft.print(screen_h);
  tft.println("\n\nSetting up the camera...");
  Serial.print("Setting up the camera...");

    // initialise the camera
  if ( !camera.init() ) {
    tft.fillScreen(ILI9341_RED);
    tft.print("\n   Camera init failure!");
  Serial.println("failed!");
  blink(0);
  } else {
   // tft.print("done.");
  Serial.println("done.\n");
  }
  
  initTimerAndDMA();
}
//-----------------------------------------------------------------------------

void initTimerAndDMA()
{
  tft.setRotation(1); // rotate 90° for landscape
  tft.setAddrWindow(0, 0, screen_w, screen_h); 

  camera.waitForVsync();  camera.waitForVsyncEnd();
  camera.waitForVsync();  camera.waitForVsyncEnd();
  // enable the timer and corresponding DMA request
  DMA_Setup();  
  TIMER_Setup();
  SPI.setDataSize(DATA_SIZE_8BIT); // set to 8 bit mode
}

void blink(uint8_t br)
{
	pinMode(LED_PIN, OUTPUT);
	while(1)
	{
		digitalWrite(LED_PIN, LOW);
		delay(125);
		digitalWrite(LED_PIN, HIGH);
		delay(125);
		if (br) break;
	}
}
uint32_t loop_counter, line_counter;
//-----------------------------------------------------------------------------

 


void LCD2SD()
{
unsigned char bmpFileHeader[14] ;//= {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0 };
unsigned char bmpInfoHeader[40] ;//= {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };   

static int lastSavedFileNumber = -1;
char name[12];// = "LCD_0000.bmp";
const int w = 320;
const int h = 240;
SdFile file;  
#define NUM_LINES_BUFFERED 12
// WARNING  NUM_LINES_BUFFERED MUST BE A FACTOR OF 240

uint8_t lineBufSD[w*3*NUM_LINES_BUFFERED];
unsigned long m;


  
  
  

  // if name exists, create new filename
  if ((lastSavedFileNumber+1)>511)
  {
    return;
  }
  
  for (int i = (lastSavedFileNumber+1); i < 512; i++)
  {
    sprintf(name,"LCD_%04d.bmp",i);
    Serial.print("\nTrying name ");Serial.println(name);
    if (file.open(name, O_CREAT | O_EXCL | O_WRITE))
    {
      lastSavedFileNumber=i;
      break;
      
    }
  }

  // create image data
  int filesize = 54 + 4 * w * h;      //  w is image width, h is image height

  memset(bmpFileHeader,0,sizeof(bmpFileHeader));
  memset(bmpInfoHeader,0,sizeof(bmpInfoHeader));
  bmpFileHeader[0]='B';
  bmpFileHeader[1]='M';
  bmpFileHeader[ 2] = (unsigned char)(filesize    );
  bmpFileHeader[ 3] = (unsigned char)(filesize >> 8);
  bmpFileHeader[ 4] = (unsigned char)(filesize >> 16);
  bmpFileHeader[ 5] = (unsigned char)(filesize >> 24);
  bmpFileHeader[10]=54;  
  
  bmpInfoHeader[0]=40;
  bmpInfoHeader[ 4] = (unsigned char)(       w    );
  bmpInfoHeader[ 5] = (unsigned char)(       w >> 8);
  bmpInfoHeader[ 6] = (unsigned char)(       w >> 16);
  bmpInfoHeader[ 7] = (unsigned char)(       w >> 24);
  bmpInfoHeader[ 8] = (unsigned char)(       h    );
  bmpInfoHeader[ 9] = (unsigned char)(       h >> 8);
  bmpInfoHeader[10] = (unsigned char)(       h >> 16);
  bmpInfoHeader[11] = (unsigned char)(       h >> 24);
  bmpInfoHeader[12]=1;
  bmpInfoHeader[14]=24;  

  file.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
  file.write(bmpInfoHeader, sizeof(bmpInfoHeader));    // " info header

  m=millis();
  uint8_t t;
  int lineOffset;
  for (int y = 0 ; y < h ; y++) 
  {

    lineOffset = y%NUM_LINES_BUFFERED *3 * w;
    tft.readPixels24(0,(h-1)-y,320,(h-1)-y, lineBufSD + lineOffset);
    if ((y+1)%NUM_LINES_BUFFERED==0)
    {
      // swap colour channels from  RGB to BGR
      for (int x = 0; x < w * 3 * NUM_LINES_BUFFERED; x+=3) 
      {
        t=lineBufSD[x+2];
        lineBufSD[x+2]=lineBufSD[x];
        lineBufSD[x]=t;
      }
       file.write(lineBufSD, 3 * w * NUM_LINES_BUFFERED);
    }
  }
  Serial.print("Saved in ");Serial.print(millis()-m);Serial.println(" mS ");
  file.close();  

}
int totalFrames=0;
void loop()
{
  uint8_t * ptr = (uint8_t*)&pixBuf;

  // start of frame
  camera.waitForVsync();
	digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  camera.waitForVsyncEnd();

  if ( (loop_counter&(BIT2-1))==0 ) {
	  if  ( (loop_counter&(BIT7-1))==0 ) {
		  Serial.write('\n');
	  }
	  Serial.write('.');
  }
  loop_counter ++;

  line_counter = screen_h;

//noInterrupts();
  // receive lines
  while ( (line_counter--) )
  {
    // activate here the DMA and the timer
    dma_set_num_transfers(DMA1, DMA_CH2, 2*PIXELS_PER_LINE+1); // 2 readings for each pixel
	dma_clear_isr_bits(DMA1, DMA_CH2);
    dma_enable(DMA1, DMA_CH2);
    timer_resume(TIMER2); // start timer
    // one byte will be read instantly - TODO: find out why ?
	
	camera.waitForHref(); // wait for line start

    uint32_t t0 = millis();
    // monitor the DMA channel transfer end, just loop waiting for the flag to be set.
    while ( !(dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TCIF) ) {
	  //Serial.print("DMA status: "); Serial.println(status, HEX);
      if ((millis() - t0) > 1000) { Serial.println("DMA timeout!"); blink(0); }
  	  if ( dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TEIF ) {
  	  	Serial.println("DMA error!"); blink(0); // error
  	  }
	  if ( !camera.isHrefOn() ) break; // stop if over end of line
    }


  timer_pause(TIMER2); // stop timer
  dma_disable(DMA1, DMA_CH2);
	dma_clear_isr_bits(DMA1, DMA_CH2);

	if ( dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TCIF ) 
	{
	//	Serial<< "2. DMA ISR: " << _HEX(DMA1->regs->ISR) << ", CNT: " << (DMA1CH2_BASE->CNDTR) << endl;
		Serial.println("DMA ISR bits reset error!"); 
		blink(0);
	}
	// send pixel data to LCD
	//SPI.setDataSize(DATA_SIZE_8BIT); // set to 8 bit mode
    tft.pushColors((uint16_t*)ptr, 2*PIXELS_PER_LINE, 0); // 3rd parameter: send async
	//SPI.setDataSize(DATA_SIZE_16BIT); // set back to 16 bit mode
	if ( dma_get_isr_bits(DMA1, DMA_CH2) & DMA_ISR_TCIF )
	{
	//	Serial<< "3. DMA ISR: " << _HEX(DMA1->regs->ISR) << ", CNT: " << (DMA1CH2_BASE->CNDTR) << endl;
		Serial.println("DMA ISR bits reset error!"); 
		blink(0);
	}

  }

  #define SAVE_EVERY_X_FRAMES 11
  if (hasSD && (totalFrames++)%SAVE_EVERY_X_FRAMES == (SAVE_EVERY_X_FRAMES-1))
  {
  //  timer_pause(TIMER2);
    (TIMER2->regs).gen->DIER = (0); // disable DMA request on TIM2 update
    LCD2SD();
    initTimerAndDMA();
  }
//interrupts();
}




The bitmap image headers were getting messed up.

I think its because of global vs local array initialisation, but I'll need to confirm this

Anyway moving the file header arrays to global scope has fixed this, and I'll work out why.. later

Also, this code takes timelapse photos about 1 per 1.5 secs, timing is dependent on the speed of the SD which seems quite variable

It can be as low as 300mS to store to SD, but also be as long as 1 second

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Mon Sep 25, 2017 10:20 am

Just for a bit of fun, I left the camera running, point out of the window, it took 512 images which I've used ffmpeg to convert to a short video

https://drive.google.com/open?id=0B4Adv ... FBoczh5MEU

BTW.

The colours look strange, but the sun was setting behind the camera, so the colour was more accurate than it looks.

User avatar
zoomx
Posts: 540
Joined: Mon Apr 27, 2015 2:28 pm
Location: Mt.Etna, Italy

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by zoomx » Mon Sep 25, 2017 2:10 pm

Unfortunately my OV7670 module seems lost somewhere :(

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Tue Sep 26, 2017 10:44 am

I was wondering if there were any better sets of camera register data, so I tried the set from ArduCam

https://github.com/ArduCAM/Arduino/tree/master/ArduCAM

For the OV7670.

The colour correction was the same as the ones in indrek's code, however the image seems to be inverted / rotated.

I had to rotate the display by 180 degrees to get the same picture.

Unfortunately its not easy to compare register settings, because s indrek's code uses lots of defines but the ArduCam version is just raw numbers.

I did intially think that perhaps I could change the paramaters so that I'd not need to read from the LCD line by line to vertically mirror, but I managed to find a setting that would do that, the image on the LCD would look strange

stevestrong
Posts: 1813
Joined: Mon Oct 19, 2015 12:06 am
Location: Munich, Germany

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by stevestrong » Tue Sep 26, 2017 11:31 am

Best register settings for OV7670? That is kind of voodoo science...
This is what I have found here:

Code: Select all

   			when x"06" => sreg <= x"8C00"; -- RGB444 Set RGB format
   			when x"07" => sreg <= x"0400"; -- COM1   no CCIR601
 				when x"08" => sreg <= x"4010"; -- COM15  Full 0-255 output, RGB 565
				when x"09" => sreg <= x"3a04"; -- TSLB   Set UV ordering,  do not auto-reset window
				when x"0A" => sreg <= x"1438"; -- COM9  - AGC Celling
				when x"0B" => sreg <= x"4fb3"; -- MTX1  - colour conversion matrix
				when x"0C" => sreg <= x"50b3"; -- MTX2  - colour conversion matrix
				when x"0D" => sreg <= x"5100"; -- MTX3  - colour conversion matrix
				when x"0E" => sreg <= x"523d"; -- MTX4  - colour conversion matrix
				when x"0F" => sreg <= x"53a7"; -- MTX5  - colour conversion matrix
				when x"10" => sreg <= x"54e4"; -- MTX6  - colour conversion matrix
				when x"11" => sreg <= x"589e"; -- MTXS  - Matrix sign and auto contrast
				when x"12" => sreg <= x"3dc0"; -- COM13 - Turn on GAMMA and UV Auto adjust
				when x"13" => sreg <= x"1100"; -- CLKRC  Prescaler - Fin/(1+1)
Or here the default settings from the linux driver: http://elixir.free-electrons.com/linux/ ... 670.c#L271

Or another here: https://gist.github.com/studiobytestorm ... 0-ino-L328

Or from previous post: http://stm32duino.com/viewtopic.php?f=1 ... ngs#p32261

User avatar
RogerClark
Posts: 7437
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: OV7670, Generic STM32F103RC and ILI9341 display

Post by RogerClark » Tue Sep 26, 2017 9:25 pm

Thanks Steve

I thought perhaps the ArduCam settings may be better, as they have been working on these cameras for several years, but I replaced the default settings, with the ArduCam ones, and as far as I could tell the colour looked the same.

The way the registers code is split up into the Default settings and settings for each resolution is good, as I could just replace the default and the camera still worked even though I'm sure the ArduCam uses 640x480 resolution.

I think ArduCam have some sort of PC Gui program, which allows the colour ( register ) settings to be changed on the fly and for the resultant image to be viewed on the PC, but their hardware is a FPGA and uses a Image Capture USB device and possibly also a control device. ( or perhaps the control is via the attached AVR Arduino device)

I think it would be possible to do roughly the same thing, except we'd need to send the image via serial, so it would only be for Still Image capture

I guess I could implement some simplistic control via serial, e.g send REGISTER,VALUE and get the code to write that to the camera at the end of a frame ( though I dont know if there would be time to do this without interrupting the flow of data from the camera

Post Reply