BMP image creator and viewer

Post your cool example code here.
Post Reply
User avatar
RogerClark
Posts: 7494
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

BMP image creator and viewer

Post by RogerClark » Sat Sep 30, 2017 2:20 am

I've been testing a OV7670 camera connected to a BluePill and ILI9341 SPI based LCD display for several weeks, and to help debugging, I've written a couple of bits of code that people may find handy

The first one is a Python based image viewer, which accepts a BMP image via Serial and displays, as well as having the option to save (only in BMP format) https://gist.github.com/rogerclarkmelbo ... 30e522be08

As I know most people don't have a camera attached, I've also created a test sketch which generates a rainbow coloured image which is sent via serial
https://gist.github.com/rogerclarkmelbo ... 0f01163553

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

Re: BMP image creator and viewer

Post by stevestrong » Sat Sep 30, 2017 10:14 am

Roger, thanks for sharing.

Regarding the BMP generation, would it make sense to make some variables global constants?
And the BMP header also seems to be constant, so it could be called once at setup time, like this:

Code: Select all

//-----------------------------------------------------------------------------
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 };  
char name[12];
//-----------------------------------------------------------------------------
const int w = 320;
const int h = 240;
const int filesize = 54 + 4 * w * h;      //  w is image width, h is image height
//-----------------------------------------------------------------------------
void BMP_Setup(void)
{
  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;
}

setup()
{
  ...
  BMP_Setup();
  ...
}

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

Re: BMP image creator and viewer

Post by RogerClark » Sat Sep 30, 2017 11:12 am

Steve

Yes. That would work.

If they are global's I also don't need to call memset, and you can uncomment the global initialisation code.

e.g.

Code: Select all

// = {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0 };
and

Code: Select all

// = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0 };
But if they are local variables the elements that are not listed in the initialisation declaration, don't get set to zero
( I know this is normal behaviour for C)

The strange thing is even if

I put zero in all the remaining fields e.g.

Code: Select all

 {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 };  


It didnt seem to work. (and I've no idea why)

So I changed the code to use memset and just set the B M 54 40 1 and 24 in code
(not very efficient)


The reason I made the buffers local was because originally I wrote most the code for a different sketch, and I wanted to keep as much global heap free as possible.

But in hindsight, 54 bytes is not much, so I may as well have made them global.

Also, they could just be 1 array, I think the original code I borrowed this from, just does it in this way to make a logical distinction between different sections in the BMP header.

PS. I think the original code (on the Arduino.cc forum, has a mistake where it miss calculates the total file size, as it write a RGB image, but calculates for RGBA (4 bytes per pixel).

This does not seem to be a problem with Python, or WIndows image viewer or Photoshop, but I've now corrected it in my code

PS.

I did consider producing a more interesting test pattern, e.g. perhaps a colour wheel (circle).
But I didn't have time to work out how to output that sort of image, on a line by line basis

PPS.

I'm not sure of which is the bottom or the top of the image, I think there is possibility that the first data in the file is the bottom of the image not the top. (but this would the way BMP images are handled, rather than a bug in the code)

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

Re: BMP image creator and viewer

Post by RogerClark » Sat Sep 30, 2017 10:06 pm

Steve

As you have the OV7670 camera hooked up to your BluePill, you can use this function

Its basically the same function that I use in the "test image" generator, except it reads from the LCD, and

Code: Select all

void LCD2Serial()
{
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;


  
  
  
  // create image data
  int filesize = 54 + 3 * 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;  

  Serial.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
  Serial.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;
      }
       Serial.write(lineBufSD, 3 * w * NUM_LINES_BUFFERED);
      // delay(1);
    }
  }
}

Here is my modified version of your camera code

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 + 3 * 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();  

}

void LCD2Serial()
{
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;


  
  
  
  // create image data
  int filesize = 54 + 3 * 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;  

  Serial.write(bmpFileHeader, sizeof(bmpFileHeader));    // write file header
  Serial.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;
      }
       Serial.write(lineBufSD, 3 * w * NUM_LINES_BUFFERED);
      // delay(1);
    }
  }
}

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();
  }
  if (Serial.available())
  {
    if (Serial.read()=='0')
    {    
      (TIMER2->regs).gen->DIER = (0); // disable DMA request on TIM2 update
      LCD2Serial();
      initTimerAndDMA();
    }
  }
//interrupts();
}


Its a bit of a hack as the only way I could get the camera to start again after stopping it to upload the image (or save to SD), was to totally re-initialise the DMA and timer each time.

I suspect that a full re-initialisation isn't required, but as the code to init the time and the DMA is quite small, and also would run in a few microseconds, I didn't see much point wasting loads of time, trying to determine precisely which things need to be re-initialised, so I just made a function which does init calls to both, and call that function in the initial setup and just after a frame grab is taken.

Post Reply