prettyprint

顯示具有 STM32CubeIDE 標籤的文章。 顯示所有文章
顯示具有 STM32CubeIDE 標籤的文章。 顯示所有文章

2022年3月23日 星期三

STM32 使用HAL程式庫實作I2S介面語音錄音儲存與播放(STM32 I2S Audio record/store/play using HAL library)

 本實驗使用STM32F407VE I2S語音介面,以INMP441 MEMS microphone與MAX98357A DAC & Amplifier來實做一組錄音/儲存/播放語音設備。

一、使用元件

  1. STM32F407VE開發版
  2. INMP441
  3. MAX98357A
  4. 3W4Ω Speaker
  5. Micro SD card
  6. LM386

二、INMP441基本說明(摘錄自INMP441 datasheet)

  1. Digital I²S Interface with High-Precision 24-Bit Data
  2. 6 pins:
    SCK: Serial-Data Clock for I²S Interface
    SD: Serial-Data Output for I²S Interface
    WS: Serial Data-Word Select for I²S Interface
    L/R: Left/Right Channel Select. 本實驗只使用一個INMP441 Microphone, default is left channel
    VDD: Power, 1.8 V to 3.3 V.
    GND: Ground
  3. MSB-first
  4. Data Word Length: The output data word length is 24 bits per channel. The INMP441 must always have 64 clock cycles for every stereo data-word 。
  5. Data-Word Format:
    The default data format is I²S (two’s complement), MSB-first. In this format, the MSB of each word is delayed by one SCK cycle from the start of each half-frame.

  6. 根據以上INMP441規格,本實驗ST32F407VE I2S2設為 Master Receive Mode, I2S Philips。雖然Data Word Length只有24Bits,但是補滿32bits傳送,所以STM32F407VE data and frame format設定為32 Bits Data in 32 Bit Frame。

三、MAX98357A基本說明(摘錄自MAX98357A datasheet)

  1. 16 or 32 bits Data length, MSB First
  2. No MCLK Required
  3. supporting 8kHz–96kHz sampling rates with 16/24/32-bit resolution for I2S/left justified data
  4. The MAX98357A accepts standard I2S data through DIN, BCLK, and LRCLK while the MAX98357B accepts left-justified data through the same inputs
  5. Pins:
    Vin: 3.3 or 5V
    GND: ground
    SD: 選左右聲道,內定為(left/2+right/2)
    Gain: 設定增益(9dB)
    DIN: Serial Data(即為STM32F407VE I2S的SD pin)
    BCLK: Serial-Data Clock for I²S Interface(同SCK)
    LRC: Serial Data-Word Select for I²S Interface(同WS)
  6. Input data format (Standard I2S 32 bit)


    STM32F407VE I2S3 Transmission Mode: Master Transmission Mode, Communication Standard: I2S Philips。

四、STM32F4xx I2S (摘錄RM0090 Reference manual)

  1. The I2S shares three common pins with the SPI
  2. I2S Philips standard: For this standard, the WS signal is used to indicate which channel is being transmitted. It is activated one CK clock cycle before the first bit (MSB) is available.
  3. MSB justified standard:  For this standard, the WS signal is generated at the same time as the first data bit, which is the MSB first.
  4.  LSB justified standard: This standard is similar to the MSB justified standard (no difference for the 16-bit and 32-bit full-accuracy frame formats).
  5. For all data formats and communication standards, the most significant bit is always sent first (MSB first).
  6. data width is 16bits
選擇I2S2連接INMP441, Master Receive Mode; Communication Standard: I2S Philips; data and frame format: 32 Bits Data in 32 Bit Frame。
選擇I2S3連接MAX93857A, Master Transmission Mode; Communication Standard: I2S Philips; data and frame format: 32 Bits Data in 32 Bit Frame。

五、MAX98357A輸出聲音過小問題:

    以上列方式實驗,錄音能取得正常音量,但由MAX98357A輸出時音量太小(如成果影片展示),將SD接HIGH(只輸出LEFT Channel),Gain 100KΩ接地(15dB)以獲得最大輸出。但音量仍不理想。在上篇實驗[ESP-IDF: ESP32 I2S介面語音錄音機(ESP32 I2S Audio Recorder using ESP-ADF)]使用相同的MAX98357A但能輸出較大的音量。
  1. 使用硬體放大:再將MAX98357A輸出接入LM386音頻放大器,再一次放大,可以獲得較大的音量(如成果影片展示)。
  2. 使用簡易軟體放大:((out_word)&0x80000000) |( ((out_word)<< 3)&0x7FFFFFFF );out_word為欲輸出的digital data先運算放大再輸出,可以獲得較大的音量(如成果影片展示)。

六、啟動與停止錄音按鈕

    實驗流程,按一次EXTI GPIO按鈕啟動錄音,儲存WAVE檔案至SD Card,再按一次停止錄音,接者播放儲存的WAVE檔案。按鈕採用硬體RC Debounce 線路。

七、WAVE Format

        實驗以WAVE format儲存在SD card上(有關STM32F4xx storage 可參閱另一篇文章[STM32微控制器(STM32F407VET6) SD-4bits、SD-SPI,FLASH等儲存設備管理]。
wave 檔案的header如下所示:
(圖片來源: Win38383838 - 自己的作品, CC BY-SA 4.0, 
https://commons.wikimedia.org/w/index.php?curid=46442654)

先寫入44bytes header,資依序寫入由INMP441 microphone擷取資料,最後修改總長度與data長度。INMP441為MSB格式(Big Endian),由於MAX98357A播放的資料亦為MSB,因此本實驗儲存在SD card上的檔案暫不轉換成Wave檔案little Endian格式,下列程式另提供轉換函式。
亦可存成AIFF檔案,AIFF為未壓縮PCM big endian格式,換成AIFF header直接輸出big endian資料,本實驗暫時未實作。

八、STM32CubeMX設定

  1. SDIO: 4 bits, SDIO_RX and SDIO_TX DMA enable


  2. EXTI GPIO button & and Red LED, Green LED: all pull down, enable EXTI line1 interrupt
  3. FatFS: 
  4. I2S2 for INMP441 MEMS Microphone:


    因為I2S 介面為16bits,  所以DMA data width只能選half word
  5. I2S3 for MAX98357A:

  6. RCC clock: High Speed Clock(HSE)-Crystal/Ceramic Resonate

九、成果展示



十、程式碼

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define MAX_DMA_ACCESS_COUNT	(40960)

#define DMA_READ_SIZE	(128)

#define BUFFER_COUNT	(4)

#define STATE_STOP				1
#define STATE_RECORDING			2
#define STATE_START_RECORDING	3
#define STATE_PLAYING			4
typedef union {
	uint32_t w;
	char b[4];
} _WORD;
typedef union  {
	uint16_t hw;
	char b[2];
} _HALF_WORD;

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
 I2S_HandleTypeDef hi2s2;
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi3_tx;

RTC_HandleTypeDef hrtc;

SD_HandleTypeDef hsd;
DMA_HandleTypeDef hdma_sdio_rx;
DMA_HandleTypeDef hdma_sdio_tx;

/* USER CODE BEGIN PV */
uint16_t DMA_TxRx_SIZE = DMA_READ_SIZE*2;
static uint16_t rcvBuf[DMA_READ_SIZE*2*BUFFER_COUNT];
static uint32_t rCount=0, wCount=0;
static uint8_t audio_state = STATE_STOP;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SDIO_SD_Init(void);
static void MX_DMA_Init(void);
static void MX_I2S2_Init(void);
static void MX_RTC_Init(void);
static void MX_I2S3_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
typedef struct _WaveHeader{
	char riff[4];
	uint32_t size;
	char wave[4];
	char fmt[4];
	uint32_t fmt_size;
	uint16_t format; //1:PCM
	uint16_t channels; // channels
	uint32_t sampleRate;  // sample rate
	uint32_t rbc;//sampleRate*bitsPerSample*channels/8
	uint16_t bc; //bitsPerSample*channels/8
	uint16_t bitsPerSample; //bitsPerSample
	char data[4];
	uint32_t data_size;
} WAVE_HEADER;

void convertEndian(char* sd_path, char *file_in, char *file_out) {
	WAVE_HEADER wave_header;
	FRESULT res;
	FIL fin, fout;
	char fn[256];
	UINT bw, br;
	uint16_t bitsSample;
	uint8_t readBytes;
	_WORD *w_data;
	_HALF_WORD *h_data;

	  //res = f_mount(&SDFatFS, SDPath, 0);
	sprintf(fn, "%s%s", sd_path, file_in);
	res = f_open(&fin, fn, FA_OPEN_EXISTING|FA_READ);
	sprintf(fn, "%s%s", sd_path, file_out);
	res = f_open(&fout, fn, FA_CREATE_ALWAYS|FA_WRITE);
	f_read(&fin, (uint8_t*)&wave_header, sizeof(wave_header), &br);

	  bitsSample= wave_header.bitsPerSample;
	  if (bitsSample == 32) {
		  w_data = (_WORD*)malloc(512);
	  } else if (bitsSample == 16){
		  h_data = (_HALF_WORD*)malloc(512);
	  } else {
		  return;
	  }


	  f_write(&fout, (uint8_t*)&wave_header, sizeof(wave_header), &bw);
	  for (int i=0; i < wave_header.data_size; i+=512) {
		  if (bitsSample == 32) {
			  f_read(&fin, (uint8_t*)w_data, 512, &br);
			  for (int i = 0; i < br/4; i++) {
				  w_data[i].w = w_data[i].b[0] << 24 | w_data[i].b[1] << 16 | w_data[i].b[2] << 8 | w_data[i].b[3];
			  }
			  f_write(&fout, (uint8_t*)(w_data), br, &bw);
		  }
		  else {
			  f_read(&fin, (uint8_t*)h_data, 512, &br);
			  for (int i = 0; i < br/2; i++) {
				  h_data[i].hw = h_data[i].b[0] << 8 | h_data[i].b[1];
			  }
			  f_write(&fout, (uint8_t*)(h_data), br, &bw);
		  }
	  }
	  f_close(&fout);
	  f_close(&fin);
}


uint8_t txCplt = 0;

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
	txCplt=1;
}
uint8_t rcvCplt = 0;
uint16_t* rpt, *wpt, *temppt;
void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s){
	rCount++;
	rpt = (rcvBuf)+(rCount%BUFFER_COUNT)*DMA_TxRx_SIZE;
	HAL_I2S_Receive_DMA(hi2s, rpt, DMA_READ_SIZE);
}

FRESULT fwrite_wav_header(FIL* file, uint16_t sampleRate, uint8_t bitsPerSample, uint8_t channels) {
	UINT bw;
	WAVE_HEADER wave_header;
	wave_header.riff[0] = 'R';wave_header.riff[1] = 'I';
	wave_header.riff[2] = 'F';wave_header.riff[3] = 'F';
	wave_header.size = (uint32_t)0;
	wave_header.wave[0] = 'W';wave_header.wave[1] = 'A';
	wave_header.wave[2] = 'V';wave_header.wave[3] = 'E';
	wave_header.fmt[0] = 'f';wave_header.fmt[1] = 'm';
	wave_header.fmt[2] = 't';wave_header.fmt[3] = ' ';
	wave_header.fmt_size = 16;
	wave_header.format = 1; // PCM
	wave_header.channels = channels; // channels
	wave_header.sampleRate=sampleRate;  // sample rate
	wave_header.rbc = sampleRate*bitsPerSample*2/8;
	wave_header.bc =  bitsPerSample*2/8;
	wave_header.bitsPerSample = bitsPerSample; //bitsPerSample
	wave_header.data[0] = 'd'; wave_header.data[1] = 'a';
	wave_header.data[2] = 't'; wave_header.data[3] = 'a';
	wave_header.data_size = 0;
	return f_write(file, (uint8_t*)&wave_header, sizeof(wave_header), &bw);
}

void startRecord(char *filename) {
	UINT bw;
	UINT writeBytes;
	UINT skipCount=125;// skip 0.5 second
	FIL fp;
	FRESULT res;

	writeBytes = DMA_TxRx_SIZE*2;
	res = f_open(&fp, filename, FA_CREATE_ALWAYS|FA_WRITE);
	res = fwrite_wav_header(&fp, 16000, 32, 2);

	HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
	audio_state = STATE_RECORDING;
	rpt = rcvBuf;
	wpt = rpt;
	rCount=0; wCount=0;
	HAL_I2S_Receive_DMA(&hi2s2, rpt, DMA_READ_SIZE);

	while (1)
	{
		if (wCount < rCount ) {
			if (rCount > skipCount)
			{
				res = f_write(&fp, wpt, writeBytes, &bw);
			}
			 wCount++;
			 wpt = (rcvBuf)+(wCount%BUFFER_COUNT)*DMA_TxRx_SIZE;
		  }

		  if (audio_state == STATE_STOP || rCount > MAX_DMA_ACCESS_COUNT)
		  {
			  HAL_I2S_DMAStop(&hi2s2);
			  break;
		  }
	}
	uint32_t data_len = (wCount-1) * writeBytes;
	uint32_t total_len = data_len+36;
	f_lseek(&fp, 4);
	f_write(&fp, (uint8_t*)&total_len, 4, &bw);
	f_lseek(&fp, 40);
	f_write(&fp, (uint8_t*)&data_len, 4, &bw);
	f_close(&fp);
	HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
	audio_state = STATE_STOP;
}
void startPlay(char *filename) {

	FIL fp;
	WAVE_HEADER wave_header;
	UINT br;

	uint16_t *readpt, *writept, *temppt;
	FRESULT res;

	res = f_open(&fp, filename, FA_OPEN_EXISTING|FA_READ);

	res = f_read(&fp, (uint8_t*)&wave_header, sizeof(wave_header), &br);
	if (br != sizeof(wave_header)) {f_close(&fp);return; }

	audio_state = STATE_PLAYING;
	HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);

	readpt = rcvBuf;
	writept = (rcvBuf)+DMA_TxRx_SIZE;
	  txCplt=0;
	  f_read(&fp, (uint8_t*)readpt, DMA_TxRx_SIZE*2, &br);
	  temppt=writept;
	  writept=readpt;
	  readpt=temppt;
	  HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t*)writept, DMA_READ_SIZE);
	  uint32_t *tu;
	  while (1) {
		  res = f_read(&fp, (uint8_t*)readpt, DMA_TxRx_SIZE*2, &br);
		  for (int i = 0; i < DMA_TxRx_SIZE; i+=2) {
			tu = (uint32_t*)&readpt[i];
			*tu = ((*tu)&0x80000000) | (((*tu) << 3)&0x7FFFFFFF);
		}

		  while(!txCplt) ;
		  txCplt=0;
		  temppt=writept;
		  writept=readpt;
		  readpt=temppt;

		  HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t*)writept, DMA_READ_SIZE);
		  if (br < DMA_TxRx_SIZE*2) break;
	  }


	  HAL_I2S_DMAStop(&hi2s3);

	  f_close(&fp);
	  HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
	  audio_state = STATE_STOP;
}


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if (GPIO_Pin == BUTTON_Pin) {
		switch (audio_state) {
		case STATE_STOP:
			audio_state = STATE_START_RECORDING;
			break;
		case STATE_RECORDING:
			audio_state = STATE_STOP;
			break;
		case STATE_PLAYING:
			break;
		case STATE_START_RECORDING:
			break;
		default:
			audio_state = STATE_STOP;
			break;
		}

	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

/* Configure the peripherals common clocks */
  PeriphCommonClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SDIO_SD_Init();
  MX_DMA_Init();
  MX_FATFS_Init();
  MX_I2S2_Init();
  MX_RTC_Init();
  MX_I2S3_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  FRESULT res;
  char filename[256];
  res = f_mount(&SDFatFS, SDPath, 0);

  uint16_t count;
   while (1)
  {
	    if (audio_state == STATE_START_RECORDING) {
	  		  HAL_Delay(1);
	  		  sprintf(filename, "%sr_%05d.wav", SDPath, count++);
	  		  startRecord(filename);

	  		  HAL_Delay(1000);
	  		  startPlay(filename);

	  	  }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

  f_mount(&SDFatFS, "", 0);

  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief Peripherals Common Clock Configuration
  * @retval None
  */
void PeriphCommonClock_Config(void)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Initializes the peripherals clock
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
  PeriphClkInitStruct.PLLI2S.PLLI2SN = 50;
  PeriphClkInitStruct.PLLI2S.PLLI2SR = 2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief I2S2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2S2_Init(void)
{

  /* USER CODE BEGIN I2S2_Init 0 */

  /* USER CODE END I2S2_Init 0 */

  /* USER CODE BEGIN I2S2_Init 1 */

  /* USER CODE END I2S2_Init 1 */
  hi2s2.Instance = SPI2;
  hi2s2.Init.Mode = I2S_MODE_MASTER_RX;
  hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
  hi2s2.Init.DataFormat = I2S_DATAFORMAT_32B;
  hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
  hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_16K;
  hi2s2.Init.CPOL = I2S_CPOL_LOW;
  hi2s2.Init.ClockSource = I2S_CLOCK_PLL;
  hi2s2.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
  if (HAL_I2S_Init(&hi2s2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2S2_Init 2 */
  //hi2s2.Instance->CR1 = hi2s2.Instance->CR1 | SPI_CR1_LSBFIRST;
  /* USER CODE END I2S2_Init 2 */

}

/**
  * @brief I2S3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2S3_Init(void)
{

  /* USER CODE BEGIN I2S3_Init 0 */

  /* USER CODE END I2S3_Init 0 */

  /* USER CODE BEGIN I2S3_Init 1 */

  /* USER CODE END I2S3_Init 1 */
  hi2s3.Instance = SPI3;
  hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
  hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
  hi2s3.Init.DataFormat = I2S_DATAFORMAT_32B;
  hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
  hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_16K;
  hi2s3.Init.CPOL = I2S_CPOL_LOW;
  hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
  hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
  if (HAL_I2S_Init(&hi2s3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2S3_Init 2 */
  //hi2s3.Instance->CR1 = hi2s3.Instance->CR1 | SPI_CR1_LSBFIRST;
  /* USER CODE END I2S3_Init 2 */

}

/**
  * @brief RTC Initialization Function
  * @param None
  * @retval None
  */
static void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */

  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */
if (0) {
  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0;
  sTime.Minutes = 0;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_MARCH;
  sDate.Date = 6;
  sDate.Year = 22;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
}
  /* USER CODE END RTC_Init 2 */

}

/**
  * @brief SDIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_SDIO_SD_Init(void)
{

  /* USER CODE BEGIN SDIO_Init 0 */

  /* USER CODE END SDIO_Init 0 */

  /* USER CODE BEGIN SDIO_Init 1 */

  /* USER CODE END SDIO_Init 1 */
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
  /* USER CODE BEGIN SDIO_Init 2 */

  /* USER CODE END SDIO_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);
  /* DMA1_Stream5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, LED_RED_Pin|LED_GREEN_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : BUTTON_Pin */
  GPIO_InitStruct.Pin = BUTTON_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(BUTTON_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LED_RED_Pin LED_GREEN_Pin */
  GPIO_InitStruct.Pin = LED_RED_Pin|LED_GREEN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI1_IRQn);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

2021年3月27日 星期六

Digital Camera with STM32 and OV2640 || STM32CubeIDE || OV2640 || ILI9341 16 bits parallel

本實驗使用STM32F407VE、OV2640與ILI9341 16 bits parallel(含XPT2046 touch)製作一組數位相機。

主要功能:

  1. Still photo capture: JPEG format(1280x960)
  2. Movie record: RIFF-AVI Motion JPEG(320x240 at least 3fps)
  3. Playback: 在TFT 上顯示 JPEG與AVI
  4. Media: 影像儲存在SD card
  5. Button: Touch

主要組件:

  1. STM32F407VE: STM32_F4VE開發版
  2. OV2640鏡頭
  3. SD Card
  4. ILI9341 TFT: 16bit parallel mode and XPT2046 touch
  5. 轉接版

開發轉體環境:
STM32CubeIDE and HAL library。

STM32F407VET6使用介面

  1. SDIO
  2. FSMC
  3. DCMI

Middleware:

  1. FATFS
  2. LIBJPEG
本篇著重在DCMI介面的應用,其他有關FSCM(TFT display)與SD card等Device應用請參閱前兩篇文章的詳細就紹:
OV2640 鏡頭:
    本實驗使用的OV2640模組為DVP 8bit(D0~D7)輸出,含VSYNC, HREF, RST, PWDN, SCCB(I2C,SDA,SCL), VCC與GND等接腳。最大輸出為2M,格式可為RGB565或JPEG格式。詳細內容請參考Datasheet。
OV2640 的 Device control registers分為DSP與Sensor兩種模式(0xFF為0 or 1),幾個主要設定resister說明:
  1. 0xDA(when 0xFF=00):Image Mode; 本實驗分別設定為RGB565(0x09)輸出給TFT,Live顯示;  JPEG(0x10)輸出,儲存照片與影片。
  2. 0x5A(when 0xFF=00):Output width; 實際寬度除以4,輸出320給TFT,值為0x50。1280給JPEG照片ˊ值為0x40。
  3. 0x5B(when 0xFF=00):Output Height; 實際寬度除以4,輸出240給TFT,值為0x3C。1280給JPEG照片ˊ值為0xf0。
  4. 0x5C(when 0xFF=00):Bit[1:0]  OUTW, Bit[2] OUTH。輸出1280x960JPEG,值為0x01

STM32F407VE DCMI參數設定:




  • OV2640 0x15 register default value is 00,DCMI mode如上圖設定。
  • DMA: mode: circular peripheral to memory, increment Address(memory)
  • NVIC: DCMI global interrupt
  • GPIO: maximum speed: very height

使用HAL指令

  • HAL_DCMI_Start_DMA(hdcmi, DCMI_MODE_CONTINUOUS, pData, Length); or
  • HAL_DCMI_Start_DMA(hdcmi, DCMI_MODE_SNAPSHOT, pData, Length);
將OV2640 data output透過DMA2 Stream1 輸入至相對應的memory(pData)。
當沒有拍照或錄影時,設備處於Live Mode。影像以RGB565直接輸出至TFT Display,先將OV2640 control register 設成320x240 RGB565 output mode,再透過HAL_DCMI_Start_DMA指定直接寫到TFT GRAM即可。
如以下程式碼即可完成。

HAL_DCMI_Stop(&hdcmi);
ov2640_Init(0x60, CAMERA_Monitor); //set control register: 320x240, RGB565
lcdSetWindow(0, 0, lcdGetWidth()-1, lcdGetHeight()-13); // ILI9341 display window
status=HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS, ((uint32_t)(LCD_BASE1)), lcdGetWidth()*(lcdGetHeight()-12)/2);
LCD_BASE1為 ILI9341 data address  ((uint32_t)0x60080000)

STILL PHOTO Capture:
    STM32F407VET6 RAM分為CCMRAM 64K, RAM 128K。程式部分只能使用到128K,因此當我們擷取靜態照片時設定OV2640 output 為JPEG格式,因此1280x960解析度的照片只要使用90K的RAM即夠用。

DCMI CR

HAL_DCMI_Stop(&hdcmi);
HAL_DCMI_Start_DMA(hdcmi, DCMI_MODE_SNAPSHOT, ((uint32_t)dmabuff), MAX_AVI_BUFF/4);
因為DMA transfer為4 bytes,所以上述指令為MAX_AVI_BUFF/4。因為擷取JPEG照片每張大小未知,因此檢查DCMI_CR 的CM 與Capture bit決定是否已擷取完畢。
timeout=0;
while((hdcmi->Instance->CR & 0x03) == 3)
{
    if (timeout > 5000)
    {
	break; //max timeout: 5 seconds
    }
    HAL_Delay(1);
    timeout++;
}
而JPEG stream 以0xFFD8為開始,0xFFD9為結束。因此我們檢查
for (idx = 0; idx<MAX_AVI_BUFF;idx++)
{
    if (headerFinder == 0 && dmabuff[idx] == 0xff && dmabuff[idx+1]==0xD8)
    {
	headerFinder=1;
	f_begin=idx;
    }
    if (headerFinder==1 && dmabuff[idx]== 0xFF && dmabuff[idx+1] == 0xD9 )
    {
	frame_ok=1;
	headerFinder=0;
	f_end = idx+1;
    .
    .
    .

將dmabuff[f_begin]~dmabuff[f_end]寫入SD card即可儲存成JPEG file。

Movie Record:
    輸出大小為320x240,目前實驗的硬體環境只能做到3 fps。採用格式RIFF-AVI Header Motion JPEG格式。有關RIFF請參閱
RIFF(resource interchange file format),下圖為本實驗輸出AVI檔RIFF header。
本實驗輸出的檔案。

Playback:

本實驗利用STM32CubeIDE環境下整合提供的LIBJPEG,作為解碼JPEG到RGB565格式,以便展示靜態照片;
動態影片: 根據RIFF header 的dwMicroSecPerFrame 取得影片中每個影格輸出到TFT 影像的速度,而每個影格由LIST/move/00db取得JPEG影像大小與內容,以正確速度decode JPEG到TFT,完成播放avi 檔。

成果影片:



程式碼:
main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "libjpeg.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ili9341.h"
#include "avi.h"
#include "ov2640.h"
#include "stdio.h"
#include "string.h"
#include "XPT2046_touch.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
DCMI_HandleTypeDef hdcmi;
DMA_HandleTypeDef hdma_dcmi;

I2C_HandleTypeDef hi2c1;

RTC_HandleTypeDef hrtc;

SD_HandleTypeDef hsd;
DMA_HandleTypeDef hdma_sdio_rx;
DMA_HandleTypeDef hdma_sdio_tx;

SPI_HandleTypeDef hspi2;
DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi2_tx;

SRAM_HandleTypeDef hsram1;

/* USER CODE BEGIN PV */
FATFS fs;
FRESULT res;
FIL file;
HAL_StatusTypeDef status;
uint8_t menuSelected=0;
uint8_t CAMERA_STATE=0;  // 0: live View, 1: Take a picture, 2: movie recording, 3: playback, 4: No SD Card
DIR dir;
long totalFiles=0;
long playingFileIndex=0;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_FSMC_Init(void);
static void MX_RTC_Init(void);
static void MX_DCMI_Init(void);
static void MX_SPI2_Init(void);
static void MX_I2C1_Init(void);
static void MX_SDIO_SD_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */


void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
{
	 if (GPIO_Pin == T_IRQ_Pin)
	 {
		  uint16_t cx=0,cy=0;
		  if(XPT2046_TouchPressed())
		  {
			  XPT2046_TouchGetCoordinates(&cx, &cy);
			  if (cy >= 180)
			  {
				  if (cx <= 90 ) menuSelected=1;
				  if (cx >  120 && cx < 200 )
				  {
					  menuSelected=2;
					  if (read_avi_output_status() == 1) set_avi_output_status(2); // stop recording
				  }
				  if (cx > 230  ) menuSelected=3;
			  }

		  }

	 }
}
void takeAPicture()
{
		take_A_Picture(&hdcmi);

}
void movie()
{
	char fn[64];
	memset(fn, 0, 64);

	sprintf(fn, "%smov_%05ld.avi",SDPath, get_fattime()&0xFFFF);
	res = f_open(&file, fn, FA_CREATE_ALWAYS|FA_WRITE);
	if (res==FR_OK)
	{
	  start_output_mjpeg_avi(&file, &hdcmi, (uint8_t)3, (uint8_t)1);
	}
}
void camera_monitor_state()
{
	HAL_DCMI_Stop(&hdcmi);
	ov2640_Init(0x60, CAMERA_Monitor);
	lcdFillCircle(lcdGetWidth()/3, lcdGetHeight()-11, 5, COLOR_BLACK);
	lcdSetWindow(0, 0, lcdGetWidth()-1, lcdGetHeight()-13);
	status=HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS, ((uint32_t)(LCD_BASE1)), lcdGetWidth()*(lcdGetHeight()-12)/2);
	CAMERA_STATE=0;
}


void setPlaybackButton()
{
	lcdSetCursor(15, lcdGetHeight()-11);
	lcdPrintf("<");

	lcdSetCursor(lcdGetWidth()/3+14, lcdGetHeight()-11);
	lcdPrintf(">");

	if (playingFileIndex == 2)
	{
		lcdSetCursor(15, lcdGetHeight()-11);
		lcdPrintf(" ");
	}
	if (playingFileIndex == totalFiles)
	{
		lcdSetCursor(lcdGetWidth()/3+14, lcdGetHeight()-11);
		lcdPrintf(" ");
	}

}


void playPrev()
{
	FILINFO fno;
	FRESULT res;

	long i;

	if (playingFileIndex == 1) return;
	playingFileIndex--;
	if (playingFileIndex > 0)
	{
		res = f_opendir(&dir, "0:/");
		if (res == FR_OK)
		{
			for (i = 0; i < playingFileIndex; i++)
			{
				res = f_readdir(&dir, &fno);
				if (res != FR_OK || fno.fname[0] == 0) break;
			}
			if (i == playingFileIndex)
			{
				if (strstr(fno.fname, ".avi"))
				{
					lcdFillTriangle(280, lcdGetHeight()-11, 280, lcdGetHeight()-3, 290, lcdGetHeight()-7, COLOR_GREEN);
					play_avi_file(fno.fname);
					lcdFillTriangle(280, lcdGetHeight()-11, 280, lcdGetHeight()-3, 290, lcdGetHeight()-7, COLOR_BLACK);
				}
				if (strstr(fno.fname, ".jpg"))
					decode_jpeg_file_to_ftf(fno.fname);
			}
			f_closedir(&dir);
		}
	}

	setPlaybackButton();

}

void playNext()
{
	FILINFO fno;
	FRESULT res;
	long i;

	if (playingFileIndex == totalFiles) return;
	playingFileIndex++;

	if (playingFileIndex <= totalFiles)
	{
		res = f_opendir(&dir, "0:/");
		if (res == FR_OK)
		{
			for (i = 0; i < playingFileIndex; i++)
			{
				res = f_readdir(&dir, &fno);
				if (res != FR_OK || fno.fname[0] == 0) break;
			}
			if (i == playingFileIndex)
			{
				if (strstr(fno.fname, ".avi"))
				{
					lcdFillTriangle(280, lcdGetHeight()-11, 280, lcdGetHeight()-3, 290, lcdGetHeight()-7, COLOR_GREEN);
					play_avi_file(fno.fname);
					lcdFillTriangle(280, lcdGetHeight()-11, 280, lcdGetHeight()-3, 290, lcdGetHeight()-7, COLOR_BLACK);
				}
				if (strstr(fno.fname, ".jpg"))
					decode_jpeg_file_to_ftf(fno.fname);
			}
			f_closedir(&dir);

		}
	}

	setPlaybackButton();
}

void play_First_file()
{
	static FILINFO fno;
	FRESULT res;

	res = f_opendir(&dir, "0:/");
	if (res == FR_OK) {
		totalFiles=0;
		for (;;) {
			res = f_readdir(&dir, &fno);
			if (res != FR_OK || fno.fname[0] == 0) break;
			totalFiles++;
		}
		f_closedir(&dir);
		playingFileIndex=1;
		playNext();

	}

}



void setPlaybackMenu()
{
	lcdSetCursor(15, lcdGetHeight()-11);
	lcdPrintf("<      ");
	lcdSetCursor(lcdGetWidth()/3+14, lcdGetHeight()-11);
	lcdPrintf(">     ");
	lcdSetCursor(lcdGetWidth()*2/3+14, lcdGetHeight()-11);
	lcdPrintf("RETURN");
}

void setMainMenu()
{
	lcdSetCursor(15, lcdGetHeight()-11);
	lcdPrintf("PICTURE");
	lcdSetCursor(lcdGetWidth()/3+14, lcdGetHeight()-11);
	lcdPrintf("MOVIE");
	lcdSetCursor(lcdGetWidth()*2/3+14, lcdGetHeight()-11);
	lcdPrintf("PLAY   ");
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_FSMC_Init();
  MX_RTC_Init();
  MX_DCMI_Init();
  MX_SPI2_Init();
  MX_I2C1_Init();
  MX_FATFS_Init();
  MX_LIBJPEG_Init();
  MX_SDIO_SD_Init();
  /* USER CODE BEGIN 2 */


  lcdBacklightOn();
  lcdInit();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */


  lcdSetOrientation(LCD_ORIENTATION_LANDSCAPE);
  lcdFillRGB(COLOR_BLACK);
  lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
  setMainMenu();

  res = f_mount(&fs, SDPath, 1);
  if (res != FR_OK)
  {
	  lcdSetCursor(lcdGetWidth()*2/-30, lcdGetHeight()/2);
	  lcdPrintf("NO SD Card!");
	  CAMERA_STATE=4;
  }
  camera_monitor_state();

  while (1)
  {
	  if (CAMERA_STATE < 3)
	  {
		  switch (menuSelected)
		  {
		  case 1:
			  HAL_DCMI_Suspend(&hdcmi);
			  lcdFillRect(0, 0, 319, 227, COLOR_BLACK);
			  takeAPicture();
			  menuSelected=0;
			  CAMERA_STATE=1;
			  camera_monitor_state();
			  break;
		  case 2:
			  if (read_avi_output_status() == 0)
			  {
				  HAL_DCMI_Stop(&hdcmi);
				  lcdSetCursor(lcdGetWidth()/3+14, lcdGetHeight()-11);
				  lcdFillCircle(lcdGetWidth()/3, lcdGetHeight()-13, 5, COLOR_RED);
				  movie();
			  }
			  if (read_avi_output_status() == 3) //output finished
			  {
				  //HAL_Delay(1000); // wait for final capture to finish
				  camera_monitor_state();
				  set_avi_output_status(0);
			  }
			  CAMERA_STATE=2;
			  menuSelected=0;
			  break;
		  case 3:
			  menuSelected=0;
			  CAMERA_STATE=3;
			  HAL_DCMI_Stop(&hdcmi);
			  setPlaybackMenu();
			  play_First_file();
			  break;
		  }
	  }
	  else
	  {
		  if (CAMERA_STATE == 3)
		  {
			  int sel =menuSelected;
			  menuSelected=0;
			  switch(sel)
			  {
			  case 1:
				  playPrev();
				  break;
			  case 2:
				  playNext();
				  break;
			  case 3:
				  setMainMenu();
				  camera_monitor_state();
				  break;
			  }
		  }
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief DCMI Initialization Function
  * @param None
  * @retval None
  */
static void MX_DCMI_Init(void)
{

  /* USER CODE BEGIN DCMI_Init 0 */

  /* USER CODE END DCMI_Init 0 */

  /* USER CODE BEGIN DCMI_Init 1 */

  /* USER CODE END DCMI_Init 1 */
  hdcmi.Instance = DCMI;
  hdcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE;
  hdcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING;
  hdcmi.Init.VSPolarity = DCMI_VSPOLARITY_LOW;
  hdcmi.Init.HSPolarity = DCMI_HSPOLARITY_LOW;
  hdcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME;
  hdcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B;
  hdcmi.Init.JPEGMode = DCMI_JPEG_ENABLE;
  if (HAL_DCMI_Init(&hdcmi) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN DCMI_Init 2 */

  /* USER CODE END DCMI_Init 2 */

}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

/**
  * @brief RTC Initialization Function
  * @param None
  * @retval None
  */
static void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */
  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */
int set =1;
if (set)
{
  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 11;
  sTime.Minutes = 37;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY;
  sDate.Month = RTC_MONTH_MARCH;
  sDate.Date = 24;
  sDate.Year = 21;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
}
  /* USER CODE END RTC_Init 2 */

}

/**
  * @brief SDIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_SDIO_SD_Init(void)
{

  /* USER CODE BEGIN SDIO_Init 0 */

  /* USER CODE END SDIO_Init 0 */

  /* USER CODE BEGIN SDIO_Init 1 */

  /* USER CODE END SDIO_Init 1 */
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
  /* USER CODE BEGIN SDIO_Init 2 */

  /* USER CODE END SDIO_Init 2 */

}

/**
  * @brief SPI2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI2_Init(void)
{

  /* USER CODE BEGIN SPI2_Init 0 */

  /* USER CODE END SPI2_Init 0 */

  /* USER CODE BEGIN SPI2_Init 1 */

  /* USER CODE END SPI2_Init 1 */
  /* SPI2 parameter configuration*/
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI2_Init 2 */

  /* USER CODE END SPI2_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);
  /* DMA1_Stream4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);
  /* DMA2_Stream1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, LCD_BL_Pin|T_CS_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, DC_RST_Pin|DC_PDWN_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : T_IRQ_Pin */
  GPIO_InitStruct.Pin = T_IRQ_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(T_IRQ_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LCD_BL_Pin T_CS_Pin */
  GPIO_InitStruct.Pin = LCD_BL_Pin|T_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pins : DC_RST_Pin DC_PDWN_Pin */
  GPIO_InitStruct.Pin = DC_RST_Pin|DC_PDWN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

}

/* FSMC initialization function */
static void MX_FSMC_Init(void)
{

  /* USER CODE BEGIN FSMC_Init 0 */

  /* USER CODE END FSMC_Init 0 */

  FSMC_NORSRAM_TimingTypeDef Timing = {0};

  /* USER CODE BEGIN FSMC_Init 1 */

  /* USER CODE END FSMC_Init 1 */

  /** Perform the SRAM1 memory initialization sequence
  */
  hsram1.Instance = FSMC_NORSRAM_DEVICE;
  hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
  /* hsram1.Init */
  hsram1.Init.NSBank = FSMC_NORSRAM_BANK1;
  hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
  hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
  hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
  hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
  hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
  hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
  hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
  hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
  hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
  hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
  hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
  hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
  hsram1.Init.PageSize = FSMC_PAGE_SIZE_NONE;
  /* Timing */
  Timing.AddressSetupTime = 1;
  Timing.AddressHoldTime = 15;
  Timing.DataSetupTime = 5;
  Timing.BusTurnAroundDuration = 0;
  Timing.CLKDivision = 16;
  Timing.DataLatency = 17;
  Timing.AccessMode = FSMC_ACCESS_MODE_A;
  /* ExtTiming */

  if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
  {
    Error_Handler( );
  }

  /* USER CODE BEGIN FSMC_Init 2 */

  /* USER CODE END FSMC_Init 2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

ov2640,c

#include "ov2640.h"
#include "main.h"

extern I2C_HandleTypeDef hi2c1;

/* Initialization sequence for QVGA resolution (320x240, RGB565) */
const unsigned char OV2640_QVGA[][2]=
{
  {0xff, 0x00},  {0x2c, 0xff},  {0x2e, 0xdf},  {0xff, 0x01},  {0x3c, 0x32},  {0x11, 0x00},
  {0x09, 0x02},  {0x04, 0xF8}, //mirror
  {0x13, 0xe5},  {0x14, 0x48},  {0x2c, 0x0c},  {0x33, 0x78},  {0x3a, 0x33},
  {0x3b, 0xfB},  {0x3e, 0x00},  {0x43, 0x11},  {0x16, 0x10},  {0x4a, 0x81},
  {0x21, 0x99},  {0x24, 0x40},  {0x25, 0x38},  {0x26, 0x82},  {0x5c, 0x00},
  {0x63, 0x00},  {0x46, 0x3f},  {0x0c, 0x38},  {0x61, 0x70},  {0x62, 0x80},
  {0x7c, 0x05},  {0x20, 0x80},  {0x28, 0x30},  {0x6c, 0x00},  {0x6d, 0x80},
  {0x6e, 0x00},  {0x70, 0x02},  {0x71, 0x94},  {0x73, 0xc1},  {0x3d, 0x34},
  {0x5a, 0x57},  {0x11, 0x00},  {0x17, 0x11},  {0x18, 0x75},  {0x19, 0x01},
  {0x1a, 0x97},  {0x32, 0x36},  {0x03, 0x0f},  {0x37, 0x40},  {0x4f, 0xbb},
  {0x50, 0x9c},  {0x5a, 0x57},  {0x6d, 0x80},  {0x6d, 0x38},  {0x39, 0x02},
  {0x35, 0x88},  {0x22, 0x0a},  {0x37, 0x40},  {0x23, 0x00},  {0x34, 0xa0},
  {0x36, 0x1a},  {0x06, 0x02},  {0x07, 0xc0},  {0x0d, 0xb7},  {0x0e, 0x01},
  {0x4c, 0x00},  {0xff, 0x00},  {0xe5, 0x7f},  {0xf9, 0xc0},  {0x41, 0x24},
  {0xe0, 0x14},  {0x76, 0xff},  {0x33, 0xa0},  {0x42, 0x20},  {0x43, 0x18},
  {0x4c, 0x00},  {0x87, 0xd0},  {0x88, 0x3f},  {0xd7, 0x03},  {0xd9, 0x10},
  {0xd3, 0x82},  {0xc8, 0x08},  {0xc9, 0x80},  {0x7d, 0x00},  {0x7c, 0x03},
  {0x7d, 0x48},  {0x7c, 0x08},  {0x7d, 0x20},  {0x7d, 0x10},  {0x7d, 0x0e},
  {0x90, 0x00},  {0x91, 0x0e},  {0x91, 0x1a},  {0x91, 0x31},  {0x91, 0x5a},
  {0x91, 0x69},  {0x91, 0x75},  {0x91, 0x7e},  {0x91, 0x88},  {0x91, 0x8f},
  {0x91, 0x96},  {0x91, 0xa3},  {0x91, 0xaf},  {0x91, 0xc4},  {0x91, 0xd7},
  {0x91, 0xe8},  {0x91, 0x20},  {0x92, 0x00},  {0x93, 0x06},  {0x93, 0xe3},
  {0x93, 0x02},  {0x93, 0x02},  {0x93, 0x00},  {0x93, 0x04},  {0x93, 0x00},
  {0x93, 0x03},  {0x93, 0x00},  {0x93, 0x00},  {0x93, 0x00},  {0x93, 0x00},
  {0x93, 0x00},  {0x93, 0x00},  {0x93, 0x00},  {0x96, 0x00},  {0x97, 0x08},
  {0x97, 0x19},  {0x97, 0x02},  {0x97, 0x0c},  {0x97, 0x24},  {0x97, 0x30},
  {0x97, 0x28},  {0x97, 0x26},  {0x97, 0x02},  {0x97, 0x98},  {0x97, 0x80},
  {0x97, 0x00},  {0x97, 0x00},  {0xc3, 0xef},  {0xff, 0x00},  {0xba, 0xdc},
  {0xbb, 0x08},  {0xb6, 0x24},  {0xb8, 0x33},  {0xb7, 0x20},  {0xb9, 0x30},
  {0xb3, 0xb4},  {0xb4, 0xca},  {0xb5, 0x43},  {0xb0, 0x5c},  {0xb1, 0x4f},
  {0xb2, 0x06},  {0xc7, 0x00},  {0xc6, 0x51},  {0xc5, 0x11},  {0xc4, 0x9c},
  {0xbf, 0x00},  {0xbc, 0x64},  {0xa6, 0x00},  {0xa7, 0x1e},  {0xa7, 0x6b},
  {0xa7, 0x47},  {0xa7, 0x33},  {0xa7, 0x00},  {0xa7, 0x23},  {0xa7, 0x2e},
  {0xa7, 0x85},  {0xa7, 0x42},  {0xa7, 0x33},  {0xa7, 0x00},  {0xa7, 0x23},
  {0xa7, 0x1b},  {0xa7, 0x74},  {0xa7, 0x42},  {0xa7, 0x33},  {0xa7, 0x00},
  {0xa7, 0x23},  {0xc0, 0xc8},  {0xc1, 0x96},  {0x8c, 0x00},  {0x86, 0x3d},
  {0x50, 0x92},  {0x51, 0x90},  {0x52, 0x2c},  {0x53, 0x00},  {0x54, 0x00},
  {0x55, 0x88},  {0x5a, 0x50},  {0x5b, 0x3c},  {0x5c, 0x00},  {0xd3, 0x04},
  {0x7f, 0x00},  {0xda, 0x09},  {0xe5, 0x1f},  {0xe1, 0x67},  {0xe0, 0x00},
  {0xdd, 0x7f},  {0x05, 0x00},  {0xff, 0x00},  {0xe0, 0x04},  {0xc0, 0xc8},
  {0xc1, 0x96},  {0x86, 0x3d},  {0x50, 0x92},  {0x51, 0x90},  {0x52, 0x2c},
  {0x53, 0x00},  {0x54, 0x00},  {0x55, 0x88},  {0x57, 0x00},  {0x5a, 0x50},
  {0x5b, 0x39},  {0x5c, 0x00},  {0xd3, 0x08},  {0xe0, 0x00},  {0xFF, 0x00},
  {0x05, 0x00},  {0xDA, 0x08},  {0xda, 0x09},  {0x98, 0x00},  {0x99, 0x00},
  {0x00, 0x00},
};


const unsigned char OV2640_JPEG_INIT[][2] = { { 0xff, 0x00 }, { 0x2c, 0xff }, {
		0x2e, 0xdf }, { 0xff, 0x01 }, { 0x3c, 0x32 }, { 0x11, 0x00 }, { 0x09,
		0x02 }, { 0x04, 0x28 }, { 0x13, 0xe5 }, { 0x14, 0x48 }, { 0x2c, 0x0c },
		{ 0x33, 0x78 }, { 0x3a, 0x33 }, { 0x3b, 0xfB }, { 0x3e, 0x00 }, { 0x43,
				0x11 }, { 0x16, 0x10 }, { 0x39, 0x92 }, { 0x35, 0xda }, { 0x22,
				0x1a }, { 0x37, 0xc3 }, { 0x23, 0x00 }, { 0x34, 0xc0 }, { 0x36,
				0x1a }, { 0x06, 0x88 }, { 0x07, 0xc0 }, { 0x0d, 0x87 }, { 0x0e,
				0x41 }, { 0x4c, 0x00 }, { 0x48, 0x00 }, { 0x5B, 0x00 }, { 0x42,
				0x03 }, { 0x4a, 0x81 }, { 0x21, 0x99 }, { 0x24, 0x40 }, { 0x25,
				0x38 }, { 0x26, 0x82 }, { 0x5c, 0x00 }, { 0x63, 0x00 }, { 0x61,
				0x70 }, { 0x62, 0x80 }, { 0x7c, 0x05 }, { 0x20, 0x80 }, { 0x28,
				0x30 }, { 0x6c, 0x00 }, { 0x6d, 0x80 }, { 0x6e, 0x00 }, { 0x70,
				0x02 }, { 0x71, 0x94 }, { 0x73, 0xc1 }, { 0x12, 0x40 }, { 0x17,
				0x11 }, { 0x18, 0x43 }, { 0x19, 0x00 }, { 0x1a, 0x4b }, { 0x32,
				0x09 }, { 0x37, 0xc0 }, { 0x4f, 0x60 }, { 0x50, 0xa8 }, { 0x6d,
				0x00 }, { 0x3d, 0x38 }, { 0x46, 0x3f }, { 0x4f, 0x60 }, { 0x0c,
				0x3c }, { 0xff, 0x00 }, { 0xe5, 0x7f }, { 0xf9, 0xc0 }, { 0x41,
				0x24 }, { 0xe0, 0x14 }, { 0x76, 0xff }, { 0x33, 0xa0 }, { 0x42,
				0x20 }, { 0x43, 0x18 }, { 0x4c, 0x00 }, { 0x87, 0xd5 }, { 0x88,
				0x3f }, { 0xd7, 0x03 }, { 0xd9, 0x10 }, { 0xd3, 0x82 }, { 0xc8,
				0x08 }, { 0xc9, 0x80 }, { 0x7c, 0x00 }, { 0x7d, 0x00 }, { 0x7c,
				0x03 }, { 0x7d, 0x48 }, { 0x7d, 0x48 }, { 0x7c, 0x08 }, { 0x7d,
				0x20 }, { 0x7d, 0x10 }, { 0x7d, 0x0e }, { 0x90, 0x00 }, { 0x91,
				0x0e }, { 0x91, 0x1a }, { 0x91, 0x31 }, { 0x91, 0x5a }, { 0x91,
				0x69 }, { 0x91, 0x75 }, { 0x91, 0x7e }, { 0x91, 0x88 }, { 0x91,
				0x8f }, { 0x91, 0x96 }, { 0x91, 0xa3 }, { 0x91, 0xaf }, { 0x91,
				0xc4 }, { 0x91, 0xd7 }, { 0x91, 0xe8 }, { 0x91, 0x20 }, { 0x92,
				0x00 }, { 0x93, 0x06 }, { 0x93, 0xe3 }, { 0x93, 0x05 }, { 0x93,
				0x05 }, { 0x93, 0x00 }, { 0x93, 0x04 }, { 0x93, 0x00 }, { 0x93,
				0x00 }, { 0x93, 0x00 }, { 0x93, 0x00 }, { 0x93, 0x00 }, { 0x93,
				0x00 }, { 0x93, 0x00 }, { 0x96, 0x00 }, { 0x97, 0x08 }, { 0x97,
				0x19 }, { 0x97, 0x02 }, { 0x97, 0x0c }, { 0x97, 0x24 }, { 0x97,
				0x30 }, { 0x97, 0x28 }, { 0x97, 0x26 }, { 0x97, 0x02 }, { 0x97,
				0x98 }, { 0x97, 0x80 }, { 0x97, 0x00 }, { 0x97, 0x00 }, { 0xc3,
				0xed }, { 0xa4, 0x00 }, { 0xa8, 0x00 }, { 0xc5, 0x11 }, { 0xc6,
				0x51 }, { 0xbf, 0x80 }, { 0xc7, 0x10 }, { 0xb6, 0x66 }, { 0xb8,
				0xA5 }, { 0xb7, 0x64 }, { 0xb9, 0x7C }, { 0xb3, 0xaf }, { 0xb4,
				0x97 }, { 0xb5, 0xFF }, { 0xb0, 0xC5 }, { 0xb1, 0x94 }, { 0xb2,
				0x0f }, { 0xc4, 0x5c }, { 0xc0, 0x64 }, { 0xc1, 0x4B }, { 0x8c,
				0x00 }, { 0x86, 0x3D }, { 0x50, 0x00 }, { 0x51, 0xC8 }, { 0x52,
				0x96 }, { 0x53, 0x00 }, { 0x54, 0x00 }, { 0x55, 0x00 }, { 0x5a,
				0xC8 }, { 0x5b, 0x96 }, { 0x5c, 0x00 }, { 0xd3, 0x00 }, { 0xc3,
				0xed }, { 0x7f, 0x00 }, { 0xda, 0x00 }, { 0xe5, 0x1f }, { 0xe1,
				0x67 }, { 0xe0, 0x00 }, { 0xdd, 0x7f }, { 0x05, 0x00 }, { 0x12,
				0x40 }, { 0xd3, 0x04 }, { 0xc0, 0x16 }, { 0xC1, 0x12 }, { 0x8c,
				0x00 }, { 0x86, 0x3d }, { 0x50, 0x00 }, { 0x51, 0x2C }, { 0x52,
				0x24 }, { 0x53, 0x00 }, { 0x54, 0x00 }, { 0x55, 0x00 }, { 0x5A,
				0x2c }, { 0x5b, 0x24 }, { 0x5c, 0x00 },  };

const unsigned char OV2640_YUV422[][2] = { { 0xFF, 0x00 }, { 0x05, 0x00 }, {
		0xDA, 0x10 }, { 0xD7, 0x03 }, { 0xDF, 0x00 }, { 0x33, 0x80 }, { 0x3C,
		0x40 }, { 0xe1, 0x77 }, { 0x00, 0x00 },  };

const unsigned char OV2640_JPEG[][2] = { { 0xe0, 0x14 }, { 0xe1, 0x77 }, { 0xe5,
		0x1f }, { 0xd7, 0x03 }, { 0xda, 0x10 }, { 0xe0, 0x00 }, { 0xFF, 0x01 },
		//{ 0x04, 0x08 }, { 0xff, 0xff }, };
		{ 0x04, 0xF8 },  };

const unsigned char OV2640_1280x960_JPEG[][2] = { { 0xFF, 0x01 },
		{ 0x11, 0x01 }, { 0x12, 0x00 }, { 0x17, 0x11 }, { 0x18, 0x75 }, { 0x32,
				0x36 }, { 0x19, 0x01 }, { 0x1a, 0x97 }, { 0x03, 0x0f }, { 0x37,
				0x40 }, { 0x4f, 0xbb }, { 0x50, 0x9c }, { 0x5a, 0x57 }, { 0x6d,
				0x80 }, { 0x3d, 0x34 }, { 0x39, 0x02 }, { 0x35, 0x88 }, { 0x22,
				0x0a }, { 0x37, 0x40 }, { 0x34, 0xa0 }, { 0x06, 0x02 }, { 0x0d,
				0xb7 }, { 0x0e, 0x01 }, { 0xFF, 0x00 }, { 0xe0, 0x04 }, { 0xc0,
				0xc8 }, { 0xc1, 0x96 }, { 0x86, 0x3d }, { 0x50, 0x00 }, { 0x51,
				0x90 }, { 0x52, 0x2c }, { 0x53, 0x00 }, { 0x54, 0x00 }, { 0x55,
				0x88 }, { 0x57, 0x00 }, { 0x5a, 0x40 }, { 0x5b, 0xf0 }, { 0x5c,
				0x01 }, { 0xd3, 0x02 }, { 0xe0, 0x00 },  };

void CAMERA_IO_Write(uint8_t deviceAddr, uint8_t reg, uint8_t value)
{
	HAL_I2C_Mem_Write(&hi2c1, deviceAddr, reg, I2C_MEMADD_SIZE_8BIT, &value, 1, 100);
}
void CAMERA_Delay(uint32_t Delay)
{
	HAL_Delay(Delay);
}

void ov2640_Init(uint16_t DeviceAddr, uint8_t action)
{
  uint32_t index;
  
//hardware and software init
  HAL_GPIO_WritePin(DC_PDWN_GPIO_Port, DC_PDWN_Pin, GPIO_PIN_RESET); //power on
  HAL_Delay(1);
  HAL_GPIO_WritePin(DC_RST_GPIO_Port, DC_RST_Pin, GPIO_PIN_RESET);  //hardware reset
  HAL_Delay(1);
  HAL_GPIO_WritePin(DC_RST_GPIO_Port, DC_RST_Pin, GPIO_PIN_SET);
  HAL_Delay(1);
  
  /* Prepare the camera to be configured */
  CAMERA_IO_Write(DeviceAddr, OV2640_DSP_RA_DLMT, 0x01);
  CAMERA_IO_Write(DeviceAddr, OV2640_SENSOR_COM7, 0x80);
  CAMERA_Delay(20);
  
  /* Initialize OV2640 */
  switch (action)
  {
  case CAMERA_Movie:
	  for(index=0; index<(sizeof(OV2640_JPEG_INIT)/2); index++)
	  {
		  CAMERA_IO_Write(DeviceAddr, OV2640_JPEG_INIT[index][0], OV2640_JPEG_INIT[index][1]);
		  CAMERA_Delay(1);
	  }

	for(index=0; index<(sizeof(OV2640_YUV422)/2); index++)
	{
		CAMERA_IO_Write(DeviceAddr, OV2640_YUV422[index][0], OV2640_YUV422[index][1]);
	  	CAMERA_Delay(1);
	}

	for(index=0; index<(sizeof(OV2640_JPEG)/2); index++)
	{
		CAMERA_IO_Write(DeviceAddr, OV2640_JPEG[index][0], OV2640_JPEG[index][1]);
		CAMERA_Delay(1);
	}

	for(index=0; index<(sizeof(OV2640_320x240_JPEG)/2); index++)
	{
		CAMERA_IO_Write(DeviceAddr, OV2640_320x240_JPEG[index][0], OV2640_320x240_JPEG[index][1]);
		CAMERA_Delay(1);
	}
	break;
  case CAMERA_Monitor:
    {
      for(index=0; index<(sizeof(OV2640_QVGA)/2); index++)
      {
        CAMERA_IO_Write(DeviceAddr, OV2640_QVGA[index][0], OV2640_QVGA[index][1]);
        CAMERA_Delay(1);
      } 
      break;
    }
  case CAMERA_Picture:
  	  for(index=0; index<(sizeof(OV2640_JPEG_INIT)/2); index++)
  	  {
  		  CAMERA_IO_Write(DeviceAddr, OV2640_JPEG_INIT[index][0], OV2640_JPEG_INIT[index][1]);
  		  CAMERA_Delay(1);
  	  }

  	for(index=0; index<(sizeof(OV2640_YUV422)/2); index++)
  	{
  		CAMERA_IO_Write(DeviceAddr, OV2640_YUV422[index][0], OV2640_YUV422[index][1]);
  	  	CAMERA_Delay(1);
  	}

  	for(index=0; index<(sizeof(OV2640_JPEG)/2); index++)
  	{
  		CAMERA_IO_Write(DeviceAddr, OV2640_JPEG[index][0], OV2640_JPEG[index][1]);
  		CAMERA_Delay(1);
  	}

  	for(index=0; index<(sizeof(OV2640_1280x960_JPEG)/2); index++)
  	{
  		CAMERA_IO_Write(DeviceAddr, OV2640_1280x960_JPEG[index][0], OV2640_1280x960_JPEG[index][1]);
  		CAMERA_Delay(1);
  	}
	  break;
  default:
    {
      break;
    }
  }
}

ov2640.h

#ifndef __OV2640_H
#define __OV2640_H

#ifdef __cplusplus
 extern "C" {
#endif 

#include "stm32f4xx_hal.h"
#define CAMERA_Monitor	1
#define CAMERA_Movie	2
#define CAMERA_Picture	3
#define CAMERA_Play		4

void     ov2640_Init(uint16_t DeviceAddr, uint8_t action);
void     ov2640_Config(uint16_t DeviceAddr, uint32_t feature, uint32_t value, uint32_t BR_value);
uint16_t ov2640_ReadID(uint16_t DeviceAddr);

void     CAMERA_IO_Init(void);
void     CAMERA_IO_Write(uint8_t addr, uint8_t reg, uint8_t value);
uint8_t  CAMERA_IO_Read(uint8_t addr, uint8_t reg);
void     CAMERA_Delay(uint32_t delay);
#ifdef __cplusplus
}
#endif
#endif /* __OV2640_H */

avi.c


#define JPEG_INTERNALS
#include "jinclude.h"
#include "jpeglib.h"
#include "ili9341.h"

#include "avi.h"
#include "stdlib.h"
#include "ov2640.h"
#include "string.h"
#include "unistd.h"

#define MAX_AVI_BUFF		20480//20k for motion jpeg
#define MAX_PICTURE_BUFF	92160 //90k for stikk jepg

extern I2C_HandleTypeDef hi2c1;

struct jpeg_decompress_struct cinfo;
JSAMPROW buffer[2] = {0};
uint8_t rowBuff[1024];

typedef struct RGB
{
  uint8_t B;
  uint8_t G;
  uint8_t R;
}RGB_typedef;
struct jpeg_error_mgr jerr;

RGB_typedef *RGB_matrix;

uint16_t RGB16PixelColor;

uint8_t bAviStartRecording=0; // 1: start, 2: pending, 3: closed output file , 0: ready for recording

DWORD totalLen=0;
DWORD frames=0;

DWORD LSBtoDWORD(char* lsb)
{
	return lsb[0] | lsb[1]<<8 | lsb[2]<<16 | lsb[3] << 24;
}
FRESULT fwrite_DWORD(FIL * file, DWORD word)
{
	unsigned char * p;
	UINT bw;

	p = (unsigned char *)&word;

	return (f_write(file, p, 4, &bw));

}

FRESULT fwrite_WORD(FIL * file, WORD word)
{
	unsigned char * p;
	UINT bw;

	p = (unsigned char*)&word;
	return (f_write(file, p, 2, &bw));

}
void set_avi_output_status(uint8_t stat)
{
	bAviStartRecording = stat;
}
uint8_t read_avi_output_status()
{
	return bAviStartRecording;
}

void play_avi_file(char* fname)
{
	DWORD dwMilliPerFrame; 	// offset=32
	DWORD dwTotalFramess;		//offset=48
	DWORD dwWidth, dwHeight;	//offset=64,68
	FRESULT res;
	FIL file;
	UINT br;
	uint32_t msdelay;
	uint32_t offset;
	uint32_t imgSize;
	uint8_t imgBuff[MAX_AVI_BUFF];
	uint16_t blocks, remain;
	uint8_t *ptrBuff;

	char dwBuff[5];
	memset(dwBuff,0,5);


	res = f_open(&file, fname, FA_READ);
	if (res != FR_OK) { f_close(&file); return; }
	if (f_size(&file)==0)
	{
		lcdSetCursor(10, 80);
		lcdPrintf("%s: file size = 0", fname);
		return;
	}
	f_lseek(&file, 32);
	res = f_read(&file, dwBuff, 4, &br);
	if (res != FR_OK) { f_close(&file); return; }
	dwMilliPerFrame = LSBtoDWORD(dwBuff)/1000;

	f_lseek(&file, 48);
	res = f_read(&file, dwBuff, 4, &br);
	if (res != FR_OK) { f_close(&file); return; }
	dwTotalFramess = LSBtoDWORD(dwBuff);

	f_lseek(&file, 64);
	res = f_read(&file, dwBuff, 4, &br);
	if (res != FR_OK) { f_close(&file); return; }
	dwWidth = LSBtoDWORD(dwBuff);

	f_lseek(&file, 68);
	res = f_read(&file, dwBuff, 4, &br);
	if (res != FR_OK) { f_close(&file); return; }
	dwHeight = LSBtoDWORD(dwBuff);



	offset=240; //movi start here
	for (DWORD i = 0; i < dwTotalFramess; i++)
	{
		msdelay=HAL_GetTick();
		memset(imgBuff,0,MAX_AVI_BUFF);
		ptrBuff = imgBuff;
		offset+=4;  // jpeg image size;
		f_lseek(&file, offset);
		res=f_read(&file, dwBuff, 4, &br);
		if (res!=FR_OK) { f_close(&file); return; }
		imgSize = LSBtoDWORD(dwBuff);
		offset += 4; // image data start here
		blocks = imgSize/512;
		remain = imgSize%512;
		for (int b = 0; b < blocks; b++)
		{
			f_lseek(&file, offset);
			f_read(&file, ptrBuff, 512, &br);
			offset += 512;
			ptrBuff += 512;
		}
		if (remain)
		{
			f_lseek(&file, offset);
			f_read(&file, ptrBuff, remain, &br);
			offset += remain;
		}
		if (imgSize%2) offset++;
		decode_jpeg_to_tft(imgBuff, imgSize);
		msdelay = HAL_GetTick() - msdelay;
		if (msdelay < dwMilliPerFrame) HAL_Delay(dwMilliPerFrame-msdelay);

	}
	f_close(&file);



}

void decode_jpeg_file_to_ftf(char* fname)
{
	FIL file;
	FRESULT res;
	uint32_t line_counter = 0;
	uint32_t i = 0;
	res = f_open(&file, fname, FA_READ);
	if (res != FR_OK)
		return;
	if (f_size(&file)==0)
	{
		lcdSetCursor(10, 80);
		lcdPrintf("%s: file size = 0", fname);
		return;
	}


	buffer[0] = rowBuff;

	cinfo.err = jpeg_std_error(&jerr);

	jpeg_create_decompress(&cinfo);

	jpeg_stdio_src(&cinfo, &file);
	jpeg_read_header(&cinfo, TRUE);
	cinfo.scale_num=1;
	cinfo.scale_denom=4;
	cinfo.dct_method = JDCT_IFAST;

	jpeg_start_decompress(&cinfo);
	//while (cinfo.output_scanline < cinfo.output_height && line_counter < 240)
	lcdSetWindow(0, 0, 319, 227);
	while (cinfo.output_scanline < cinfo.output_height && line_counter < 228) //240-12 (12 for menu)
	{
		(void)jpeg_read_scanlines(&cinfo, buffer, 1);

		RGB_matrix=(RGB_typedef*)buffer[0];
		for(i = 0; i < cinfo.output_width ; i++)
		{
			RGB16PixelColor = (uint16_t)
			(
				((RGB_matrix[i].R & 0x00F8) >> 3)|
				((RGB_matrix[i].G & 0x00FC) << 3)|
				((RGB_matrix[i].B & 0x00F8) << 8)
			);
			LCD_DataWrite(RGB16PixelColor);

		}

		line_counter++;

	}

	jpeg_finish_decompress(&cinfo);

	jpeg_destroy_decompress(&cinfo);
	f_close(&file);
}

void decode_jpeg_to_tft(uint8_t *buff, uint32_t len)
{
	uint32_t line_counter = 0;
	uint32_t i = 0;

	buffer[0] = rowBuff;

	if (!(buff[0] == 0xFF && buff[1] == 0xD8 && buff[len-2]==0xFF && buff[len-1] == 0xD9))
		return; //jpeg must begin 0xffd8 end oxffd9
	cinfo.err = jpeg_std_error(&jerr);

	jpeg_create_decompress(&cinfo);

	jpeg_mem_src(&cinfo, buff, len);
	jpeg_read_header(&cinfo, TRUE);
	//cinfo.scale_num=1;
	//cinfo.scale_denom=2;
	cinfo.dct_method = JDCT_IFAST;

	jpeg_start_decompress(&cinfo);
	//while (cinfo.output_scanline < cinfo.output_height && line_counter < 240)
	lcdSetWindow(0, 0, 319, 227);
	while (cinfo.output_scanline < cinfo.output_height && line_counter < 228) //240-12 (12 for menu)
	{
		(void)jpeg_read_scanlines(&cinfo, buffer, 1);

		RGB_matrix=(RGB_typedef*)buffer[0];
		for(i = 0; i < cinfo.output_width ; i++)
		{
			RGB16PixelColor = (uint16_t)
			(
				((RGB_matrix[i].R & 0x00F8) >> 3)|
				((RGB_matrix[i].G & 0x00FC) << 3)|
				((RGB_matrix[i].B & 0x00F8) << 8)
			);
			LCD_DataWrite(RGB16PixelColor);

		}

	  	line_counter++;

	}

	jpeg_finish_decompress(&cinfo);

	jpeg_destroy_decompress(&cinfo);
}

FRESULT output_avi_header(FIL *file, uint8_t fps, uint16_t width, uint16_t height)
{
	FRESULT res=FR_OK;
	UINT bw;
	RIFF RIFF_LIST;


	RIFF_LIST.dwRIFF = 'RIFF';
	res = f_write(file, "RIFF", 4, &bw); //offset=0

	//RIFF_LIST.dwSize =  150 + 12 + len + 8*frames + 8 + 4*4*frames;
	RIFF_LIST.dwSize =  0;
	// must rewrite when stop_output at file offset  4;
	res = fwrite_DWORD(file, RIFF_LIST.dwSize); //offset+4=4

	RIFF_LIST.dwFourCC = 'AVI ';
	res = f_write(file, "AVI ", 4, &bw); //offset+4=8
	// 	RIFF_LIST.data = WAIT WITH THIS

	LIST hdrl;
	hdrl.dwList = 'LIST';
	res = f_write(file, "LIST", 4, &bw); //offset+4=12

	hdrl.dwSize = 208;
	res = fwrite_DWORD(file, hdrl.dwSize);//offset+4=16

	hdrl.dwFourCC = 'hdrl';
	res = f_write(file, "hdrl", 4, &bw);//offset+4=20

	MainAVIHeader avih;

	avih.dwFourCC = 'avih';
	res = f_write(file, "avih", 4, &bw); //offset+4=24

	avih.dwSize = 56;
	res = fwrite_DWORD(file, avih.dwSize);//offset+4=28

	avih.dwMicroSecPerFrame = 1000000/fps;
	res = fwrite_DWORD(file, avih.dwMicroSecPerFrame);//offset+4=32

	avih.dwMaxBytesPerSec = 7000;
	res = fwrite_DWORD(file, avih.dwMaxBytesPerSec);//offset+4=36

	avih.dwPaddingGranularity = 0;
	res = fwrite_DWORD(file, avih.dwPaddingGranularity);//offset+4=40

	// dwFlags set to 16, do not know why!
	avih.dwFlags = 16;
	res = fwrite_DWORD(file, avih.dwFlags);//offset+4=44
///////////////////////////////
	avih.dwTotalFrames = 0;  // frames, offset ?, must rewrite

	res = fwrite_DWORD(file, avih.dwTotalFrames);//offset+4=48

	avih.dwInitialFrames = 0;
	res = fwrite_DWORD(file, avih.dwInitialFrames);//offset+4=52

	avih.dwStreams = 1;
	res = fwrite_DWORD(file, avih.dwStreams);//offset+4=56

	avih.dwSuggestedBufferSize = 0;
	res = fwrite_DWORD(file, avih.dwSuggestedBufferSize);//offset+4=60

	avih.dwWidth = width;
	res = fwrite_DWORD(file, avih.dwWidth);//offset+4=64

	avih.dwHeight = height;
	res = fwrite_DWORD(file, avih.dwHeight);//offset+4=68

	avih.dwReserved[0] = 0;
	res = fwrite_DWORD(file, avih.dwReserved[0]);//offset+4=72
	avih.dwReserved[1] = 0;
	res = fwrite_DWORD(file, avih.dwReserved[1]);//offset+4=76
	avih.dwReserved[2] = 0;
	res = fwrite_DWORD(file, avih.dwReserved[2]);//offset+4=80
	avih.dwReserved[3] = 0;
	res = fwrite_DWORD(file, avih.dwReserved[3]);//offset+4=84

	LIST strl;
	strl.dwList = 'LIST';
	res = f_write(file, "LIST", 4, &bw);//offset+4=88

	strl.dwSize = 132;
	res = fwrite_DWORD(file, strl.dwSize);//offset+4=92

	strl.dwFourCC = 'strl';
	res = f_write(file, "strl", 4, &bw);//offset+4=96

	AVIStreamHeader strh;
	strh.dwFourCC = 'strh';
	res = f_write(file, "strh", 4, &bw);//offset+4=100

	strh.dwSize = 48;
	res = fwrite_DWORD(file, strh.dwSize);//offset+4=104

	strh.fccType = 'vids';
	res = f_write(file, "vids", 4, &bw);//offset+4=108

	strh.fccHandler = 'MJPG';
	res = f_write(file, "MJPG", 4,&bw);//offset+4=112

	strh.dwFlags = 0;
	res = fwrite_DWORD(file, strh.dwFlags);//offset+4=116

	strh.wPriority = 0; // +2 = 14
	res = fwrite_WORD(file, strh.wPriority);//offset+4=120

	strh.wLanguage = 0; // +2 = 16
	res = fwrite_WORD(file, strh.wLanguage);//offset+2=122

	strh.dwInitialFrames = 0; // +4 = 20
	res = fwrite_DWORD(file, strh.dwInitialFrames);//offset+2=124

	strh.dwScale = 1; // +4 = 24
	res = fwrite_DWORD(file, strh.dwScale);//offset+4=128

	// insert FPS
	strh.dwRate = fps; // +4 = 28
	res = fwrite_DWORD(file, strh.dwRate);//offset+4=132

	strh.dwStart = 0; // +4 = 32
	res = fwrite_DWORD(file, strh.dwStart);//offset+4=136
	//////////////////////
	// insert nbr of jpegs, must rewrite, offset ?
	strh.dwLength = 0; //nbr_of_jpgs;  +4 = 36
	res = fwrite_DWORD(file, strh.dwLength);//offset+4=140

	strh.dwSuggestedBufferSize = 0; // +4 = 40
	res = fwrite_DWORD(file, strh.dwSuggestedBufferSize);//offset+4=144

	strh.dwQuality = 0; // +4 = 44
	res = fwrite_DWORD(file, strh.dwQuality);//offset+4=148
	// Specifies the size of a single sample of data.
	// This is set to zero if the samples can vary in size.
	// If this number is nonzero, then multiple samples of data
	// can be grouped into a single chunk within the file.
	// If it is zero, each sample of data (such as a video frame) must be in a separate chunk.
	// For video streams, this number is typically zero, although
	// it can be nonzero if all video frames are the same size.
	//
	strh.dwSampleSize = 0; // +4 = 48
	res = fwrite_DWORD(file, strh.dwSampleSize);//offset+4=152

	EXBMINFOHEADER strf;

	strf.dwFourCC = 'strf';
	res = f_write(file, "strf", 4, &bw);//offset+4=156

	strf.dwSize = 40;
	res = fwrite_DWORD(file, strf.dwSize);//offset+4=160

	strf.biSize = 40;
	res = fwrite_DWORD(file, strf.biSize);//offset+4=164

	strf.biWidth = width;
	res = fwrite_DWORD(file, strf.biWidth);//offset+4=168
	strf.biHeight = height;
	res = fwrite_DWORD(file, strf.biHeight);//offset+4=172
	strf.biPlanes = 1;
	res = fwrite_WORD(file, strf.biPlanes);//offset+4=176
	strf.biBitCount = 24;
	res = fwrite_WORD(file, strf.biBitCount);//offset+2=178
	strf.biCompression = 'MJPG';
	res = f_write(file, "MJPG", 4, &bw);//offset+2=180

	strf.biSizeImage = ((strf.biWidth*strf.biBitCount/8 + 3)&0xFFFFFFFC)*strf.biHeight;
	res = fwrite_DWORD(file, strf.biSizeImage);//offset+4=184
	strf.biXPelsPerMeter = 0;
	res = fwrite_DWORD(file, strf.biXPelsPerMeter);//offset+4=188
	strf.biYPelsPerMeter = 0;
	res = fwrite_DWORD(file, strf.biYPelsPerMeter);//offset+4=192
	strf.biClrUsed = 0;
	res = fwrite_DWORD(file, strf.biClrUsed);//offset+4=196
	strf.biClrImportant = 0;
	res = fwrite_DWORD(file, strf.biClrImportant);//offset+4=200

	res = f_write(file, "LIST", 4, &bw);//offset+4=204

	DWORD ddww = 16;
	res = fwrite_DWORD(file, ddww);//offset+4=208
	res = f_write(file, "odml", 4, &bw);//offset+4=212
	res = f_write(file, "dmlh", 4, &bw);//offset+4=216

	DWORD szs = 4;
	res = fwrite_DWORD(file, szs);//offset+4=220
////////////////////////////////////////
	// nbr of jpgs
	DWORD totalframes = 0; //nbr_of_jpgs; must rewrite, offset ?;
	res = fwrite_DWORD(file, totalframes);//offset+4=224

	LIST movi;
	movi.dwList = 'LIST';
	res = f_write(file, "LIST", 4, &bw);//offset+4=228

	//////////////////////////////
	//movi.dwSize = len + 4 + 8*nbr_of_jpgs;   must rewrite, offset?, len, nbr_of_jpgs;
	movi.dwSize = 0;
	res = fwrite_DWORD(file, movi.dwSize);//offset+4=232
	movi.dwFourCC = 'movi';
	res = f_write(file, "movi", 4, &bw);//offset+4=236
	return res;
}

FRESULT start_output_mjpeg_avi(FIL *file, DCMI_HandleTypeDef *hdcmi, uint8_t fps, uint8_t resolution)
{
	FRESULT res=FR_OK;
	FIL temp_idx1;
	uint32_t next_idx1_offset;
	uint8_t dmabuff[MAX_AVI_BUFF];
	uint16_t width, height;
	uint16_t f_begin = 0;
	uint32_t f_end = 0;
	uint32_t idx = 0;

	uint32_t sublen=0;
	uint8_t headerFinder=0;
	UINT bw;
	uint16_t f,r;
	CHUNK data;
	uint8_t *buff;
	uint8_t frame_ok=0;

	uint32_t lastSuccessTick;
	uint16_t timeout=0;
	uint8_t adjTime=0;

	unsigned long beginTick;
	long time_diff=0;


	bAviStartRecording=1;
	HAL_DCMI_Stop(hdcmi);
	ov2640_Init(0x60, CAMERA_Movie);

	width=320;
	height=240;

	 res = f_open(&temp_idx1, "0:/temp_idx1", FA_CREATE_ALWAYS|FA_WRITE|FA_READ);
	 if (res != FR_OK) return res;

	//fps=3; // test
	 if (output_avi_header(file, fps, width, height) != FR_OK) //default fps = 15
		return res;

	totalLen=0;
	frames=0;

	next_idx1_offset=4;
	lcdSetWindow(0, 0, 319, 227);
	lastSuccessTick=HAL_GetTick();
	while(bAviStartRecording==1)
	{
		memset(dmabuff,0, MAX_AVI_BUFF);
		headerFinder=0;
		//if (frame_ok 0)
		if (frame_ok && adjTime  == 0)
		{
			time_diff = HAL_GetTick()-lastSuccessTick;

			//if (1000/fps > time_diff && time_diff > 0)
			if (1000 > time_diff)
				HAL_Delay(1000 - time_diff);
		}

		beginTick = HAL_GetTick();

		HAL_DCMI_Start_DMA(hdcmi, DCMI_MODE_SNAPSHOT, ((uint32_t)dmabuff), MAX_AVI_BUFF/4);

		timeout=0;
		while((hdcmi->Instance->CR & 0x03) == 3)
		{
			if (timeout > 1000)
			{
				break; //max timeout: 1 seconds
			}
			HAL_Delay(1);
			timeout++;
		}
		if(timeout <= 1000)
		{
			frame_ok=0;
			for (idx = 0; idx<MAX_AVI_BUFF-4;idx++)
			{
				if (headerFinder == 0 && dmabuff[idx] == 0xff && dmabuff[idx+1]==0xD8)
				{
					headerFinder=1;
					f_begin=idx;
				}
				if (headerFinder==1 && dmabuff[idx]== 0xFF && dmabuff[idx+1] == 0xD9 )
				{
					if (adjTime == 0) // 1 sec
						lastSuccessTick=beginTick;

					adjTime = (adjTime+1)%fps; // 1 sec

					frame_ok=1;
					headerFinder=0;
					f_end = idx+1;

					buff = dmabuff + f_begin;

					data.dwFourCC = '00db';
					f_write(file, "00db", 4, &bw);
					sublen = f_end-f_begin+1;

					data.dwSize = sublen;
					fwrite_DWORD(file, data.dwSize);
					f = sublen / 512;
					r = sublen % 512;
					for (int i = 0; i < f; i++)
					{
						res=f_write(file, buff, 512, &bw);
						buff += 512;
					}

					if (r > 0)
					{
						res=f_write(file, buff, r, &bw);

					}

					buff = dmabuff + f_begin;

					decode_jpeg_to_tft(buff, sublen);

					if (sublen%2)
					{
						sublen++;
						res=f_write(file, '\0', 1, &bw);
					}
					frames++;
					totalLen += (sublen);
					f_write(&temp_idx1, "00db", 4, &bw);
					fwrite_DWORD(&temp_idx1, 16);//AVI_KEYFRAME=16
					fwrite_DWORD(&temp_idx1, next_idx1_offset);
					fwrite_DWORD(&temp_idx1, sublen);
					next_idx1_offset += (8+sublen);

					break;
				}

			}

		}
	}
	if (bAviStartRecording==2)
	{
		stop_output_mjpeg_avi(file, &temp_idx1);
	}
	//bAviStartRecording=2; //wait for rewriting some parameters and to close file;

	return res;
}
void stop_output_mjpeg_avi(FIL *file, FIL* temp_idx1)
{
	UINT br;
	UINT bw;
	unsigned char buf[512];
	if (bAviStartRecording != 2) return;

	f_write(file, "idx1", 4, &br);
	fwrite_DWORD(file, 4*4*frames);
	f_lseek(temp_idx1, 0);
	do {
		f_read(temp_idx1, buf, 512, &br);
		f_write(file, buf, br, &bw);
	} while (br == 512);
	f_close(temp_idx1);
	f_unlink("0:/temp_idx1");
	// rewrite file size, frames
	// file size offset 4
	DWORD size;

	//RIFF_LIST.dwSize =  150 + 12 + len + 8*frames + 8 + 4*4*frames;

	//size = 150 + 12 + totalLen + 8*frames + 8 + 4*4*frames(idx1);
	size = 150 + 12 + totalLen + 8*frames + 8 + 4*4*frames;
	f_lseek(file, 4);
	fwrite_DWORD(file, size);

	//DWORD avih  totalframes = 0; nbr_of_jpgs;;
	size = frames;
	f_lseek(file, 48);
	fwrite_DWORD(file, size);//offset+4=48

	//strh.dwLength = nbr_of_jpgs;  +4 = 36
	size=frames;
	f_lseek(file, 140); //140
	fwrite_DWORD(file, size);


	//totalFrames(dmlh)
	size=frames;
	f_lseek(file, 224); //140
	fwrite_DWORD(file, size);


	//movi.dwSize = len + 4 + 8*nbr_of_jpgs;
	size = totalLen + 4 + 8*frames;
	f_lseek(file, 232);
	fwrite_DWORD(file, size);//offset+4=232
	f_close(temp_idx1);
	f_close(file);
	bAviStartRecording=3;
}

uint8_t take_A_Picture(DCMI_HandleTypeDef *hdcmi)
{
	FIL file;
	uint8_t pBuff[MAX_PICTURE_BUFF];
	uint8_t *buff;
	uint8_t picture_ok=0;
	uint8_t headerFinder;
	uint32_t f_begin, f_end, sublen;
	uint16_t f_count, r;
	UINT bw;
	FRESULT res;
	uint32_t timeout=0;
	uint32_t testTime=0;

	char fn[64];
	memset(fn, 0, 64);
	sprintf(fn, "%simg_%05ld.jpg",SDPath, get_fattime()&0xFFFF);
	res = f_open(&file, fn, FA_CREATE_ALWAYS|FA_WRITE);
	if (res != FR_OK) return picture_ok;

	ov2640_Init(0x60, CAMERA_Picture);
	memset(pBuff, 0, MAX_PICTURE_BUFF);
	HAL_DCMI_Stop(hdcmi);
	HAL_Delay(10);
	HAL_DCMI_Start_DMA(hdcmi, DCMI_MODE_SNAPSHOT, ((uint32_t)pBuff), MAX_PICTURE_BUFF/4);

	while((hdcmi->Instance->CR & 0x03) == 3)
	{
		if (timeout > 1000)
		{
			break; //max timeout: 10 seconds
		}
		HAL_Delay(10);
		timeout++;
	}
	if(timeout <= 1000)
	{
		for (int idx = 0; idx<MAX_PICTURE_BUFF;idx++)
		{
			if (headerFinder == 0 && pBuff[idx] == 0xff && pBuff[idx+1]==0xD8)
			{
				headerFinder=1;
				f_begin=idx;
			}

			if (headerFinder==1 && pBuff[idx]== 0xFF && pBuff[idx+1] == 0xD9 )
			{
				picture_ok=1;
				headerFinder=0;
				f_end = idx+1;
				buff = pBuff + f_begin;
				sublen = f_end-f_begin+1;
				f_count = sublen / 512;
				r = sublen % 512;
				for (int i = 0; i < f_count; i++)
				{

					f_write(&file, buff, 512, &bw);
					buff += 512;

				}

				if (r > 0)
				{
					f_write(&file, buff, r, &bw);

				}
			break;
			}
		}

	}

	f_close(&file);

	return picture_ok;
}

avi.h


#ifndef INC_AVI_H_
#define INC_AVI_H_
#include "main.h"
#include "fatfs.h"

enum avi_resolution
{
	RES_320X240 = 1,
	RES_640X480	= 2,
	RES_800X600	= 3,
};


typedef unsigned long DWORD;
typedef long LONG;
typedef unsigned short WORD;
typedef unsigned char BYTE;

// AVI atoms
typedef struct {
	DWORD dwRIFF;
	DWORD dwSize;
	DWORD dwFourCC;
} RIFF;

typedef struct {
	DWORD dwFourCC;
	DWORD dwSize;
	//	BYTE* data; // dwSize in length
} CHUNK;

typedef struct {
	DWORD dwList;
	DWORD dwSize;
	DWORD dwFourCC;
	//	BYTE* data; // dwSize - 4 in length
} LIST;

typedef struct {
	DWORD dwFourCC;
	DWORD dwSize;

	DWORD dwMicroSecPerFrame;
	DWORD dwMaxBytesPerSec;
	DWORD dwPaddingGranularity;

	DWORD dwFlags;
	DWORD dwTotalFrames;
	DWORD dwInitialFrames;
	DWORD dwStreams;
	DWORD dwSuggestedBufferSize;

	DWORD dwWidth;
	DWORD dwHeight;

	DWORD dwReserved[4];
} MainAVIHeader;

typedef struct _RECT {
	LONG left;
	LONG top;
	LONG right;
	LONG bottom;
} RECT;

typedef struct {
	DWORD dwFourCC;
	DWORD dwSize;

	DWORD fccType;
	DWORD fccHandler;
	DWORD dwFlags;
	WORD  wPriority;
	WORD  wLanguage;
	DWORD dwInitialFrames;
	DWORD dwScale;
	DWORD dwRate;
	DWORD dwStart;
	DWORD dwLength;
	DWORD dwSuggestedBufferSize;
	DWORD dwQuality;
	DWORD dwSampleSize;

} AVIStreamHeader;

typedef struct {
	DWORD dwFourCC;
	DWORD dwSize;

	DWORD biSize;
	DWORD  biWidth;
	DWORD  biHeight;
	WORD  biPlanes;
	WORD  biBitCount;
	DWORD biCompression;
	DWORD biSizeImage;
	DWORD biXPelsPerMeter;
	DWORD  biYPelsPerMeter;
	DWORD biClrUsed;
	DWORD biClrImportant;
} AVI_BITMAPINFOHEADER;

typedef struct tagEXBMINFOHEADER {
	DWORD dwFourCC;
	DWORD dwSize;

	DWORD biSize;
	LONG  biWidth;
	LONG  biHeight;
	WORD  biPlanes;
	WORD  biBitCount;
	DWORD biCompression;
	DWORD biSizeImage;
	LONG  biXPelsPerMeter;
	LONG  biYPelsPerMeter;
	DWORD biClrUsed;
	DWORD biClrImportant;

} EXBMINFOHEADER;

typedef struct {
	DWORD ckid;
	DWORD dwFlags;
	DWORD dwChunkOffset;
	DWORD dwChunkLength;
} AVIINDEXENTRY;

typedef struct {
	DWORD fcc;
	DWORD cd;
	WORD wLongsPerEntry;
	char bIndexSubType;
	char bIndexType;
	DWORD nEntriesInUse;
	DWORD dwChunkId;
	DWORD dwReserved[3];
	AVIINDEXENTRY axiindex_entry;
} AVIINDEXCHUNK;

typedef struct {
	DWORD name;
	DWORD dwSize;
	DWORD dwTotalFrames;
} ODMLExtendedAVIheader;

typedef struct {
	RIFF riff_AVI;
	LIST hdrl;
	MainAVIHeader avih;
	LIST strl;
	AVIStreamHeader strh;
	EXBMINFOHEADER strf;
	LIST odml;
	ODMLExtendedAVIheader dmlh;
	LIST movi;
	CHUNK movi_data;
} avi_file;

FRESULT output_avi_header(FIL *file, uint8_t fps, uint16_t width, uint16_t height);
FRESULT start_output_mjpeg_avi(FIL *file, DCMI_HandleTypeDef *hdcmi, uint8_t fps, uint8_t resolution);
void stop_output_mjpeg_avi(FIL *file, FIL* temp_idx1);
void set_avi_output_status(uint8_t stat);
void decode_jpeg_file_to_ftf(char* fname);
void decode_jpeg_to_tft(uint8_t *buff, uint32_t len);
void play_avi_file(char* fname);
uint8_t read_avi_output_status();
uint8_t take_A_Picture(DCMI_HandleTypeDef *hdcmi);
#endif /* INC_AVI_H_ */