prettyprint

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

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_ */



2021年2月4日 星期四

Interface TFT display with STM32 in 16 bits parallel mode -- Display, Touch and Screenshot || STM32CubeIDE || HAL

 本實驗使用STM32F407VE(開發版STM32_F4VE)連接2.8inch 16BIT parallel-port Module(含 ILI9341、XPT2046與SPI SD card)。開發環境使用STM32CubeIDE與HAL library。

實驗目標:

  1. 顯示儲存在SD卡上JPEG image file, image width 無須小於TFT顯示寬度。
  2. Screenshot: 將TFT顯示內容儲存在SD卡上(RGB565 bitmap format)。
  3. TFT顯示方向改變時,修正Touch screen座標與TFT座標一致。
  4. pop-up message window。
  5. XPT2046與SPI SD Card使用同一個SPI介面,run time改變baud rate以適應慢速XPT2046與高速需求SD read/write。

模組圖片

  • 2.8 ' LCD Module:

  • 開發版STM32_F4VE

  • 線路連接:

使用FSMC、SPI2介面與LCD module連接。開發版TFT pin腳位如下:

TFT LCD header pins

#NameFunctionConnected to
1-GNDGround plane
2-RSTReset button
3-FSMC D15PD10
4-FSMC D14PD9
5-FSMC D13PD8
6-FSMC D12PE15
7-FSMC D11PE14
8-FSMC D10PE13
9-FSMC D9PE12
10-FSMC D8PE11
11-FSMC D7PE10
12-FSMC D6PE9
13-FSMC D5PE8
14-FSMC D4PE7
15-FSMC D3PD1
16-FSMC D2PD0
17-FSMC D1PD15
18-FSMC D0PD14
19-FSMC NOEPD4
20-FSMC NWEPD5
21-FSMC A18PD13
22-FSMC NE1PD7
23-Touch CLKPB13
24-Touch CSPB12
25-Touch MOSIPB15
26-Touch MISOPB14
27-Touch PENPC5
28-LCD BacklightPB1
29-VBATN.C.
30-GNDGround plane
31-3V3+3.3V rail
32-GNDGround plane

接線方式

STM32_F4VE Pin            LCD Module pin

31 3V3                            VDD
32 GND                          GND
19 NOE                           RD
20 NWE                          WR
21 A18                            RS
22 NE1                           CS
23 Touch CLK               CLK
24 Touch CS                  T_CS
25 Touch MOSI             MOSI
26 Touch MSIO             MISO
27 Touch PEN              PEN
28 LCD Backlight        BL
2 RST                            RST
D0~D15                        D0~D15

PC3                            SDCS

完成圖:



介面設定

1.FSMC: LCD Interface 16bit


2. SPI2介面:
  • Prescaler :128配合慢速Touch XPT2046,當要做SD  IO時再改變baud rate。
  • DMA,SPI2 pin與interrupt如下圖選擇。    

Middleware:使用FATFS與LIBJPEG。

FATFS與SPI SD存取檔案詳細運作,請參閱前篇

STM32微控制器(STM32F407VET6) SD-4bits、SD-SPI,FLASH等儲存設備管理


使用LIBJPEG library 處理jpeg image,但對於大的影像檔 LIBJPEG僅能縮小的倍數為1/1,1/2,1/4,1/8,所以當超過libjpeg最大縮小倍數時仍可能超過TFT最大寬度,因此需再二次處理。本實驗處理的照片檔如下:

程式碼說明:

  • 儲存TFT影像檔於SD卡(Screenshot):
因為LCD module 顯示為16bit RGB565模式,因此選擇儲存檔案的格式為Bitmap,RGB565 header設定,File Information header的bitCount:16 RGB565,Compression:BI_BITFIELDS=3,
RGB565 mask 為0x00f80000e00700001f000000。height為正數,因此從左下至右上依序讀取每個16bits pixel color value轉成low byte first寫入SD中。詳細程式碼請參閱bitmap.h, bitmap.c。
因為SPI SD讀寫需較高baud rate,更改baud rate prescale:
HAL_SPI_DeInit(&hspi2);
oldBraudRate = hspi2.Init.BaudRatePrescaler;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
HAL_SPI_Init(&hspi2);

.
.
.

HAL_SPI_DeInit(&hspi2);
hspi2.Init.BaudRatePrescaler = oldBraudRate;
HAL_SPI_Init(&hspi2);


  • XPT2046 touch module
參考網址:https://github.com/taburyak/STM32-touchscreen-XPT2046-HAL-SPI。
改寫原來程式碼加入calibration與TFT螢幕轉向時touch point相對座標轉換。詳細程式碼請參閱XPT2046_touch.c, XPT2046_touch.h。
  • ILI9341 FSMC driver
參考網址:https://stm32withoutfear.blogspot.com/2019/09/stm32-ili9341-fsmc.html。
將ili9341, fontxx複製到src資料夾。

  • Display JPEG image files:
使用Middleware LIBJPEG,因開發板的SRAM只有192KiB,因此對於image width大於TFT display width的檔案先設定 
cinfo.scale_num 與cinfo.scale_denom調整縮小倍數,呼叫jpeg_read_scanlines才能使用較小量的memory,因為最大的縮小量為8倍,像本實驗05.jpg為4656X2620,縮小後仍大於TFT display width,為了能整個顯示,需再一次處理調整顯示再TFT display上的pixel。詳細程式碼請參閱jpeg_view.c,jpeg_view.h。
本實驗使用LCD module上的SPI介面SD,讀取速度較慢,因此對於7MB的05.jpg IO時間過長,較不具實用價值,因此在實用上須將SD改為開發版上SDIO介面。

  • screenshot demo
  • pop-up message window
浮動訊息視窗,設定顯示秒數,關閉視窗後,回存被視窗覆蓋的影像。詳細程式碼參閱showmessage.h,showmessage.c


完成測試影片





程式碼

bitmap.h
#ifndef INC_BITMAP_H_
#define INC_BITMAP_H_
enum compressionMethod {
	BI_RGB=0,
	BI_RLE8=1,
	BI_RLE4=2,
	BI_BITFIELDS=3,
	BI_JPEG=4,
	BI_PNG=5,
	BI_ALPHABITFIELDS=6
};
typedef struct tagBITMAPFILEHEADER
{
	uint16_t 	bfType;
	DWORD 		bfSize;	// file size
	uint16_t 	bfReserved1;
	uint16_t 	bfReserved2;
	DWORD 		bfOffBits;  //bitmap data offset
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER
{
	DWORD biSize;    // info header size
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes;
	WORD biBitCount;	//1, 4, 8, 16, 24 or 32
	DWORD biCompression; //0:BI_RGB, 1:BI_RLE8, 2: BI_RLE4, 3:BI_BITFIELDS, 4:BI_JPEG, 5:BI_PNG
	DWORD biSizeImage;   // if BI_RGB, can set be 0
	LONG biXPelsPerMeter;
	LONG biYPelsPerMeter;
	DWORD biClrUsed;
	DWORD biClrImportant;
} BITMAPINFOHEADER;
FRESULT saveScreenBigmap(FIL *fp);
uint8_t saveScreen(TCHAR* path);
FRESULT bitmapHeader(FIL* fp, BITMAPFILEHEADER *fh, BITMAPINFOHEADER *ih);



#endif /* INC_BITMAP_H_ */
bitmap.c
#include "fatfs.h"
#include "string.h"
#include "bitmap.h"
#include "ili9341.h"
#include "string.h"
#include "stdio.h"
extern SPI_HandleTypeDef hspi2;

FRESULT LSBFirstWrite(FIL *fp,  DWORD value, uint8_t len)
{
	UINT bw;
	char lsb[len];
	for (int i = 0; i < len; i++)
	{
		lsb[i] = (value>>(i*8))&0xFF;
	}

	return f_write(fp, lsb, len, &bw);

}

FRESULT saveScreenBigmap(FIL *fp)
{
	FRESULT res;

	uint8_t RGBMask[12] = {0x00,0xf8,0x00,0x00,0xe0,0x07,0x00,0x00,0x1f,0x00,0x00,0x00};
	UINT bw;
	DWORD tempValue;
	uint16_t w,h;
	w=lcdGetWidth();
	h=lcdGetHeight();
	uint16_t pixvalue;
	char buff[w*2];

	//write file header
	// type: BM
	res = f_write(fp, "BM", 2, &bw); if (res!=FR_OK) return res;
	// size: width*height*2(bytes,16bits)+14(file header)+40(info header)+12(RGB mask)
	tempValue = lcdGetWidth()*lcdGetHeight()*2+66;
	res = LSBFirstWrite(fp, tempValue, 4); if (res!=FR_OK) return res;
	// Reserved1, Reserved2: 0,0
	memset(buff,0,4);
	res = f_write(fp, buff, 4, &bw); if (res!=FR_OK) return res;
	// bitmap offset: 14+40+12=66
	res = LSBFirstWrite(fp, 66, 4); if (res!=FR_OK) return res;
	// File Information Header
	// info header size:40
	res = LSBFirstWrite(fp, 40, 4); if (res!=FR_OK) return res;
	//width: lcd width
	res = LSBFirstWrite(fp, (DWORD)w, 4); if (res!=FR_OK) return res;
	//height: lcd height
	res = LSBFirstWrite(fp, (DWORD)h, 4); if (res!=FR_OK) return res;
	//Planes:1
	res = LSBFirstWrite(fp, 1, 2); if (res!=FR_OK) return res;
	//bitCount:16 RGB565
	res = LSBFirstWrite(fp, 16, 2); if (res!=FR_OK) return res;
	//Compression:BI_BITFIELDS=3,
	res = LSBFirstWrite(fp, 3, 4); if (res!=FR_OK) return res;
	//sizeImage:0, do not care
	res = LSBFirstWrite(fp, tempValue, 4); if (res!=FR_OK) return res;
	//x: 72 dpi=2835 PelsPerMeter
	res = LSBFirstWrite(fp, 2835, 4); if (res!=FR_OK) return res;
	//y: 72 dpi=2835 PelsPerMeter
	res = LSBFirstWrite(fp, 2835, 4); if (res!=FR_OK) return res;
	//color use:0
	res = LSBFirstWrite(fp, 0, 4); if (res!=FR_OK) return res;
	//color important:0
	res = LSBFirstWrite(fp, 0, 4); if (res!=FR_OK) return res;
	// RGB565 mask
	res = f_write(fp, RGBMask, 12, &bw); if (res!=FR_OK) return res;

	//write image pixel from LB to RT
	for (int y = h-1; y >= 0; y--)
	{
		memset(buff,0, w*2);
		for (int x = 0; x <=w-1; x++)
		{
			pixvalue=0;
			pixvalue = lcdReadPixel((uint16_t)x, (uint16_t)y);
			buff[x*2+1] = ((pixvalue>>8) & 0xFF);
			buff[x*2] = (pixvalue & 0xFF);

		}
		res = f_write(fp, buff,w*2,&bw); if (res!=FR_OK) return res;
	}


	return res;


}

uint8_t saveScreen(char* path)
{
	FATFS fs;
	FIL file;
	FRESULT res;
	uint32_t oldBraudRate;
	char fn[256];

	HAL_SPI_DeInit(&hspi2);
	oldBraudRate = hspi2.Init.BaudRatePrescaler;
	hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
	HAL_SPI_Init(&hspi2);

	res = f_mount(&fs, path, 1);
	if (res==FR_OK)
	{
		sprintf(fn,"%sScreen%ld.bmp",path,get_fattime()&0xFFFF);
		res = f_open(&file, fn, FA_CREATE_ALWAYS|FA_WRITE);
		if (res==FR_OK)
		{
			lcdSetWindow(0, 0, lcdGetWidth()-1, lcdGetHeight()-1);
			res=saveScreenBigmap(&file);
			f_close(&file);
		}
		f_mount(&fs, "", 0);
	}



	HAL_SPI_DeInit(&hspi2);
	hspi2.Init.BaudRatePrescaler = oldBraudRate;
	HAL_SPI_Init(&hspi2);
	return res;

}

FRESULT bitmapHeader(FIL* fp, BITMAPFILEHEADER *fh, BITMAPINFOHEADER *ih)
{
	FRESULT r;
	uint8_t fbuff[14], ibuff[40],maskbuf[12];
	UINT br;
	if ((r=f_read(fp, fbuff, 14, &br)) != FR_OK) return r;
	fh->bfType=fbuff[0] << 8 | fbuff[1];
	fh->bfSize = fbuff[5] << 24 | fbuff[4] << 16 |fbuff[3] << 8 | fbuff[2];
	fh->bfReserved1 =fbuff[7] << 8 |fbuff[6];
	fh->bfReserved2 =fbuff[9] << 8 |fbuff[8];
	fh->bfOffBits = fbuff[13] << 24 | fbuff[12] << 16 |fbuff[11] << 8 | fbuff[10];

	if ((r=f_read(fp, ibuff, 40, &br)) != FR_OK) return r;
	ih->biSize = ibuff[3] << 24 | ibuff[2] << 16 |ibuff[1] << 8 | ibuff[0];
	ih->biWidth = ibuff[7] << 24 | ibuff[6] << 16 |ibuff[5] << 8 | ibuff[4];
	ih->biHeight = ibuff[11] << 24 | ibuff[10] << 16 |ibuff[9] << 8 | ibuff[8];
	ih->biPlanes = ibuff[13] << 8 | ibuff[12];
	ih->biBitCount = ibuff[15] << 8 | ibuff[14];
	ih->biCompression = ibuff[19] << 24 | ibuff[18] << 16 |ibuff[17] << 8 | ibuff[16];
	ih->biSizeImage =ibuff[23] << 24 | ibuff[22] << 16 |ibuff[21] << 8 | ibuff[20];
	ih->biXPelsPerMeter = ibuff[27] << 24 | ibuff[26] << 16 |ibuff[25] << 8 | ibuff[24];
	ih->biYPelsPerMeter =ibuff[31] << 24 | ibuff[30] << 16 |ibuff[29] << 8 | ibuff[28];
	ih->biClrUsed = ibuff[35] << 24 | ibuff[34] << 16 |ibuff[33] << 8 | ibuff[32];
	ih->biClrImportant = ibuff[39] << 24 | ibuff[38] << 16 |ibuff[37] << 8 | ibuff[36];

	if ((r=f_read(fp, maskbuf, 12, &br)) != FR_OK) return r;

	f_close(fp);

	return FR_OK;
}

showmessage.h
#ifndef INC_SHOWMESSAGE_H_
#define INC_SHOWMESSAGE_H_
#include "stm32f4xx_hal.h"
#include "integer.h"

void showMessage(char* msg, uint8_t sec);

#endif /* INC_SHOWMESSAGE_H_ */

showmessage.c
#include "showmessage.h"
#include "ili9341.h"
#include "string.h"
#include "stdlib.h"

void showMessage(char* msg, uint8_t sec)
{
	UINT px,py;
	UINT x,y;
	uint8_t fw,fh;
	uint16_t *msgBuff;

	px = (lcdGetWidth()-210)/2;
	py = (lcdGetHeight()-110)/2;
	msgBuff = malloc(sizeof(uint16_t)*(210*110));
	lcdSetWindow(0, 0, lcdGetWidth(), lcdGetHeight());
	for (x=px; x < 210+px; x++)
	{
		for (y=py; y < 110+py;y++)
		{
		 msgBuff[(x-px)*110+y-py] = lcdReadPixel(x, y);
		}
	}
	lcdFillRect(px, py, 209, 109, COLOR_BLUE);
	lcdFillRect(px+5, py+5, 199, 99, COLOR_WHITE);
	lcdSetTextColor(COLOR_BLUE, COLOR_WHITE);
	lcdSetTextFont(&Font16);
	fw=Font16.Width;
	fh = Font16.Height;
	for (int posx=0, posy=0, i=0; i < strlen(msg);i++,posx++)
	{
		if (msg[i] == '\n' || (posx+1)*(fw) > 200-fw)
		{
			posy++;
			posx=0;
		}
		if (msg[i] == '\n') i++;
		lcdDrawChar(px+12+posx*fw, py+12+posy*(fh+2), msg[i], COLOR_BLUE, COLOR_WHITE);
	}

	HAL_Delay(sec*1000);

	lcdSetWindow(0, 0, lcdGetWidth(), lcdGetHeight());
	for (x=px; x < 210+px; x++)
	{
		for (y=py; y < 110+py; y++)
		{
		 lcdDrawPixel(x, y, msgBuff[(x-px)*110+y-py]);
		}
	}
	free(msgBuff);
	HAL_Delay(10);
}

XPT2046_touch,h
#ifndef XPT2046_TOUCH_H_
#define XPT2046_TOUCH_H_

#include "main.h"
#include <stdbool.h>

/*** Redefine if necessary ***/

// Warning! Use SPI bus with < 2.5 Mbit speed, better ~650 Kbit to be save.
#define XPT2046_SPI_PORT hspi2
extern SPI_HandleTypeDef XPT2046_SPI_PORT;

#define XPT2046_IRQ_Pin       T_IRQ_Pin
#define XPT2046_IRQ_GPIO_Port T_IRQ_GPIO_Port
#define XPT2046_CS_Pin        T_CS_Pin
#define XPT2046_CS_GPIO_Port  T_CS_GPIO_Port

// change depending on screen orientation
#define XPT2046_SCALE_X 240
#define XPT2046_SCALE_Y 320

// to calibrate uncomment UART_Printf line in ili9341_touch.c
//#define XPT2046_MIN_RAW_X 3400
//#define XPT2046_MAX_RAW_X 29000
//#define XPT2046_MIN_RAW_Y 3300
//#define XPT2046_MAX_RAW_Y 30000

#define XPT2046_MIN_RAW_X 2000
#define XPT2046_MAX_RAW_X 30000
#define XPT2046_MIN_RAW_Y 1500
#define XPT2046_MAX_RAW_Y 29000

#define XTP2046_CALI_DIFF	600

// call before initializing any SPI devices
void XPT2046_TouchUnselect(void);
bool XPT2046_TouchPressed(void);
bool XPT2046_TouchGetCoordinates(uint16_t* x, uint16_t* y);
bool XPT2046_TouchGetRawCoordinates(uint32_t* raw_, uint32_t* raw_y);
bool XPT2046_TouchCalibration(void);

#endif /* XPT2046_TOUCH_H_ */
XPT2046_touch.c
#include <stdio.h>
#include <stdlib.h>
#include <XPT2046_touch.h>
#include "ili9341.h"

#define READ_X 0xD0
#define READ_Y 0x90


uint16_t cRawX_min = XPT2046_MIN_RAW_X;
uint16_t cRawX_max = XPT2046_MAX_RAW_X;
uint16_t cRawY_min = XPT2046_MIN_RAW_Y;
uint16_t cRawY_max = XPT2046_MAX_RAW_Y;


static void XPT2046_TouchSelect()
{
    HAL_GPIO_WritePin(XPT2046_CS_GPIO_Port, XPT2046_CS_Pin, GPIO_PIN_RESET);
}

void XPT2046_TouchUnselect()
{
    HAL_GPIO_WritePin(XPT2046_CS_GPIO_Port, XPT2046_CS_Pin, GPIO_PIN_SET);
}

bool XPT2046_TouchPressed()
{
    return HAL_GPIO_ReadPin(XPT2046_IRQ_GPIO_Port, XPT2046_IRQ_Pin) == GPIO_PIN_RESET;
}

bool XPT2046_TouchGetCoordinates(uint16_t* x, uint16_t* y)
{
	bool ret_value=false;
	uint16_t tx,ty;
	uint32_t raw_x;
	uint32_t raw_y;
	if (XPT2046_TouchGetRawCoordinates(&raw_x, &raw_y))
	{
		if(raw_x < cRawX_min) raw_x = cRawX_min;
		if(raw_x > cRawX_max) raw_x = cRawX_max;

		if(raw_y < cRawY_min) raw_y = cRawY_min;
		if(raw_y > cRawY_max) raw_y = cRawY_max;

		tx = (raw_x - cRawX_min) * XPT2046_SCALE_X / (cRawX_max - cRawX_min);
		ty = (raw_y - cRawY_min) * XPT2046_SCALE_Y / (cRawY_max - cRawY_min);

		lcdOrientationTypeDef lot = lcdGetOrientation();
		switch (lot)
		{
		case LCD_ORIENTATION_PORTRAIT:
			*x=tx;
			*y=ty;
			break;
		case LCD_ORIENTATION_LANDSCAPE:
			*x=ty;
			*y=ILI9341_PIXEL_WIDTH-tx;
					break;
		case LCD_ORIENTATION_PORTRAIT_MIRROR:
			*x=ILI9341_PIXEL_WIDTH-tx;
			*y=ILI9341_PIXEL_HEIGHT-ty;
					break;
		case LCD_ORIENTATION_LANDSCAPE_MIRROR:
			*x=ILI9341_PIXEL_HEIGHT-ty;
			*y=tx;
			break;
		}
		ret_value =true;
    }
	return ret_value;

}
bool XPT2046_TouchGetRawCoordinates(uint32_t* raw_x, uint32_t* raw_y)
{
    static const uint8_t cmd_read_x[] = { READ_X };
    static const uint8_t cmd_read_y[] = { READ_Y };
    static const uint8_t zeroes_tx[] = { 0x00, 0x00 };
    static const uint8_t SAMPLES=16;

    XPT2046_TouchSelect();

    uint32_t avg_x = 0;
    uint32_t avg_y = 0;
    uint8_t nsamples = 0;

    for(uint8_t i = 0; i < SAMPLES; i++)
    {
        if(!XPT2046_TouchPressed())
            break;

        nsamples++;

        HAL_SPI_Transmit(&XPT2046_SPI_PORT, (uint8_t*)cmd_read_y, sizeof(cmd_read_y), HAL_MAX_DELAY);
        uint8_t y_raw[2];
        HAL_SPI_TransmitReceive(&XPT2046_SPI_PORT, (uint8_t*)zeroes_tx, y_raw, sizeof(y_raw), HAL_MAX_DELAY);

        HAL_SPI_Transmit(&XPT2046_SPI_PORT, (uint8_t*)cmd_read_x, sizeof(cmd_read_x), HAL_MAX_DELAY);
        uint8_t x_raw[2];
        HAL_SPI_TransmitReceive(&XPT2046_SPI_PORT, (uint8_t*)zeroes_tx, x_raw, sizeof(x_raw), HAL_MAX_DELAY);

        avg_x += (((uint16_t)x_raw[0]) << 8) | ((uint16_t)x_raw[1]);
        avg_y += (((uint16_t)y_raw[0]) << 8) | ((uint16_t)y_raw[1]);
    }

    XPT2046_TouchUnselect();

    if(nsamples < SAMPLES)
        return false;

    *raw_x = (avg_x / SAMPLES);
    *raw_y = (avg_y / SAMPLES);
    return true;

}

bool XPT2046_TouchCalibration()
{
	uint32_t x0=0,y0=0,x1=0,y1=0,x2=0,y2=0,x3=0,y3=0;
	bool correct=true;
	lcdOrientationTypeDef lot = lcdGetOrientation();
	lcdSetOrientation(LCD_ORIENTATION_PORTRAIT);

	lcdFillRGB(COLOR_BLACK);
	lcdDrawRect(0, 0, 6, 6, COLOR_YELLOW);
	//lcdSetCursor(20, 100);
	while(!XPT2046_TouchPressed()) ;
	if (!XPT2046_TouchGetRawCoordinates(&x0, &y0))
	{
		//lcdPrintf("Calibration Error!");
		lcdSetOrientation(lot);
		return false;
	}
	//lcdPrintf("Release Pen");
	lcdFillRGB(COLOR_BLACK);
	lcdDrawRect(0, lcdGetHeight()-6, 6, 6, COLOR_YELLOW);
	while(XPT2046_TouchPressed());
	HAL_Delay(1);

	//lcdSetCursor(20, 100);
	while(!XPT2046_TouchPressed());
	if(!XPT2046_TouchGetRawCoordinates(&x1, &y1))
	{
		//lcdPrintf("Calibration Error!");
		lcdSetOrientation(lot);
		return false;
	}
	//lcdPrintf("Release Pen");
	lcdFillRGB(COLOR_BLACK);
	lcdDrawRect(lcdGetWidth()-6, lcdGetHeight()-6, 6, 6, COLOR_YELLOW);
	while(XPT2046_TouchPressed());
	HAL_Delay(1);


	//lcdSetCursor(20, 100);
	while(!XPT2046_TouchPressed());
	if (!XPT2046_TouchGetRawCoordinates(&x2, &y2))
	{
		//lcdPrintf("Calibration Error!");
		lcdSetOrientation(lot);
		return false;
	}
	//lcdPrintf("Release Pen");
	lcdFillRGB(COLOR_BLACK);
	lcdDrawRect(lcdGetWidth()-6, 0, 6, 6, COLOR_YELLOW);
	while(XPT2046_TouchPressed());
	HAL_Delay(1);

	//lcdSetCursor(20, 100);
	while(!XPT2046_TouchPressed());
	if (!XPT2046_TouchGetRawCoordinates(&x3, &y3))
	{
		//lcdPrintf("Calibration Error!");
		lcdSetOrientation(lot);
		return false;
	}
	//lcdPrintf("Release Pen");
	while(XPT2046_TouchPressed());

	if ( (int)(x0-x1) < -1*XTP2046_CALI_DIFF  || (int)(x0-x1) > XTP2046_CALI_DIFF) correct=false;
	if ( (int)(y1-y2) < -1*XTP2046_CALI_DIFF  || (int)(y1-y2) > XTP2046_CALI_DIFF) correct=false;
	if ( (int)(x2-x3) < -1*XTP2046_CALI_DIFF  || (int)(x2-x3) > XTP2046_CALI_DIFF) correct=false;
	if ( (int)(y0-y3) < -1*XTP2046_CALI_DIFF  || (int)(y0-y3) > XTP2046_CALI_DIFF) correct=false;

	if (correct) {
		cRawX_min = (x0+x1)/2;
		cRawX_max = (x2+x3)/2;
		cRawY_min = (y0+y3)/2;
		cRawY_max = (y1+y2)/2;
	}

	lcdFillRGB(COLOR_BLACK);
	//lcdSetCursor(20, 100);
	//lcdPrintf("Calibration OK:\r\nx_min=%04x,\r\ny_min=%04x,\r\nx_max=%04x,\r\ny_max=%04x",cRawX_min,cRawY_min,cRawX_max,cRawY_max);
	lcdSetOrientation(lot);
	return correct;
}


jpeg_view.h
#ifndef INC_JPEG_VIEW_H_
#define INC_JPEG_VIEW_H_

void jpeg_screen_view(char* path, char* fn, int px, int py, UINT *iw, UINT *ih);


#endif /* INC_JPEG_VIEW_H_ */

jpeg_view.c
#define JPEG_INTERNALS
#include "jinclude.h"
#include "jpeglib.h"
#include "ili9341.h"
#include "showmessage.h"

extern SPI_HandleTypeDef hspi2;

struct jpeg_decompress_struct cinfo;
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;
static uint8_t *rowBuff;

static uint8_t jpeg_decode(JFILE *file, uint8_t *rowBuff, int posx, int posy, UINT *iw, UINT *ih)
{
	uint32_t line_counter = 0;
	uint32_t i = 0, xc=0, ratio;
	uint8_t offset=1;
	JSAMPROW buffer[2] = {0};

  UINT lcdWidth, lcdHeight;


  buffer[0] = rowBuff;
  lcdWidth = lcdGetWidth();
  lcdHeight = lcdGetHeight();

  cinfo.err = jpeg_std_error(&jerr);

  jpeg_create_decompress(&cinfo);

  jpeg_stdio_src (&cinfo, file);


  jpeg_read_header(&cinfo, TRUE);
  if (cinfo.image_width > 6000)
  {
	  showMessage("Image width exceeds 6000!!!", 5);
	  return 0;
  }

  if (cinfo.image_width > lcdWidth)
    {
  	  ratio=cinfo.image_width/lcdWidth;
  	  cinfo.scale_num=1;
  	  if (ratio <= 8) {
		  cinfo.scale_denom=1;
		  for(int s = 0x8; s > 0x01; s /=2)
		  {
			  if (ratio & s)
			  {
				  cinfo.scale_denom=s;
				  break;
			  }
		  }
  	  }
  	  else
  	  {
  		cinfo.scale_denom=8;
  	  }
    }

  cinfo.dct_method = JDCT_IFAST;

    jpeg_start_decompress(&cinfo);
    if (cinfo.output_width > lcdWidth)
    {
    	offset = cinfo.output_width / lcdWidth;
    	if (cinfo.output_width % lcdWidth > lcdWidth/4) offset++;
    }

    if (posx <0 || posy < 0)
    {
    	posx=(lcdWidth-(cinfo.output_width*(offset-1)/offset))/2;
    	posy=(lcdHeight-(cinfo.output_height*(offset-1)/offset))/2;
    }
    *iw = cinfo.image_width;
    *ih = cinfo.image_height;

    lcdFillRGB(COLOR_BLACK);

    if (posx > 0  && cinfo.output_width/offset < lcdWidth)
	{
    	lcdDrawRect(posx-1, posy-1, cinfo.output_width/offset+2, cinfo.output_height/offset+2, COLOR_WHITE);
	}

    while (cinfo.output_scanline < cinfo.output_height && line_counter < lcdHeight-posy)
    {
    	(void) jpeg_read_scanlines(&cinfo, buffer, 1);
      RGB_matrix=(RGB_typedef*)buffer[0];
      for(i = 0, xc=0; i < cinfo.output_width && xc < (lcdWidth -posx); i+=offset, xc++)
	  {
    	RGB16PixelColor = (uint16_t)
	    (
	     ((RGB_matrix[i].R & 0x00F8) >> 3)|
	     ((RGB_matrix[i].G & 0x00FC) << 3)|
	     ((RGB_matrix[i].B & 0x00F8) << 8)
	    );
	    lcdDrawPixel(xc+posx, line_counter+posy, RGB16PixelColor);

	  }
      for(i=0; i < offset-1 && cinfo.output_scanline < cinfo.output_height; i++)
    	  (void) jpeg_read_scanlines(&cinfo, buffer, 1);


      line_counter++;

    }

    jpeg_finish_decompress(&cinfo);

  jpeg_destroy_decompress(&cinfo);

  return 1;
}

void jpeg_screen_view(char* path, char* fn, int px, int py, UINT *iw, UINT *ih)
{
  FIL file;
  FATFS fs;

  uint32_t oldBaudRate;

  char sf[256];

  	HAL_SPI_DeInit(&hspi2);
  	oldBaudRate = hspi2.Init.BaudRatePrescaler;
  	hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  	HAL_SPI_Init(&hspi2);

  	rowBuff = JMALLOC(2048);

  sprintf(sf, "%s%s", path, fn);
  if (f_mount(&fs, path, 0) != FR_OK) {
	  JFREE(rowBuff);
	  return;
  }
  if(f_open(&file, sf, FA_READ) == FR_OK)
  {
    jpeg_decode(&file,rowBuff,px,py, iw, ih);
    f_close(&file);
  }
  else
  {
	  sprintf(sf, "%s\nFile open Error!!", sf);
	  showMessage(sf, 5);
  }
  f_mount(&fs, "", 0);
  JFREE(rowBuff);
  HAL_SPI_DeInit(&hspi2);
	hspi2.Init.BaudRatePrescaler = oldBaudRate;
	HAL_SPI_Init(&hspi2);

}



main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2020 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 "XPT2046_touch.h"
#include "lcdDemo.h"
#include "menu.h"
#include "string.h"
#include "bitmap.h"
#include "jpeg_view.h"
#include "showmessage.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 ---------------------------------------------------------*/
RTC_HandleTypeDef hrtc;

SPI_HandleTypeDef hspi2;
DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi2_tx;

SRAM_HandleTypeDef hsram1;

/* USER CODE BEGIN PV */

uint8_t isDraw=0;

uint8_t penWidth=1;

uint8_t tempIgnoralEXTI=0;

RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
uint16_t cx = 0, cy = 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_SPI2_Init(void);
static void MX_RTC_Init(void);
/* USER CODE BEGIN PFP */


/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t isCalbriating,demo;

void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
{
 if (GPIO_Pin == T_IRQ_Pin)
 {
	 if (tempIgnoralEXTI || isCalbriating) return;
	 if (activedMenuItem == SAVEITEM || activedMenuItem == JPEGITEM) return;

	  if(XPT2046_TouchPressed())
	  {
		  tempIgnoralEXTI=1;
		  XPT2046_TouchGetCoordinates(&cx, &cy);

		  if (menuActived)
		  {
			  uint8_t i=getTouchMenItem(cx, cy);
			  if (i > 0)
			  {
				 showMenu(i);
				  switch (i)
				  {
				   case DRAWINGITEM:
					  lcdFillRect(1, 26, lcdGetWidth()-2, lcdGetHeight()-2, COLOR_BLACK);
					  lcdDrawRect(1, 26, lcdGetWidth()-2, lcdGetHeight()-2, COLOR_WHITE);
					  break;
				  case LCDORIITEM:
					  menuActived=0;
					  break;
				  }
			  }
		  }
		  if (activedMenuItem == DRAWINGITEM)
		  {
			  if (cx > 1 && cx < lcdGetWidth()-1 && cy > 26 && cy < lcdGetHeight()-1 )
								  lcdFillCircle(cx, cy, penWidth, COLOR_GREENYELLOW);
		  }
		  tempIgnoralEXTI=0;
	  }

 }
}



void loadJpegFileTest()
{
	uint32_t stick,etick;

	lcdOrientationTypeDef lcdori;
	UINT iw, ih;

	lcdori=lcdGetOrientation();

	lcdSetOrientation(LCD_ORIENTATION_LANDSCAPE);
	lcdSetTextFont(&Font16);
	lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);


	stick = HAL_GetTick();
	jpeg_screen_view("0:/", "jpg/01.jpg", 0, 0, &iw, &ih);
	etick = HAL_GetTick();

	lcdSetCursor(0, lcdGetHeight() - 2*lcdGetTextFont()->Height - 1);
	lcdPrintf("Resolution:%dX%d, \nTime: %4lu ms", iw, ih, etick-stick);
	HAL_Delay(5000);


	jpeg_screen_view("0:/", "jpg/nofile.jpg", 0, 0, &iw, &ih);
	HAL_Delay(1000);

	lcdSetTextFont(&Font16);
	lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);

	stick = HAL_GetTick();
	jpeg_screen_view("0:/", "jpg/02.jpg", 0, 0, &iw, &ih);
	etick = HAL_GetTick();

	lcdSetCursor(0, lcdGetHeight() - 2*lcdGetTextFont()->Height - 1);
	lcdPrintf("Resolution:%dX%d, \nTime: %4lu ms", iw, ih, etick-stick);
	HAL_Delay(5000);

	stick = HAL_GetTick();
	jpeg_screen_view("0:/", "jpg/03.jpg", -1, 0, &iw, &ih);
	etick = HAL_GetTick();

	lcdSetCursor(0, lcdGetHeight() - 2*lcdGetTextFont()->Height - 1);
	lcdPrintf("Resolution:%dX%d, \nTime: %4lu ms", iw, ih, etick-stick);
	HAL_Delay(5000);

	stick = HAL_GetTick();
	jpeg_screen_view("0:/", "jpg/04.jpg", -1, 0, &iw, &ih);
	etick = HAL_GetTick();

	lcdSetCursor(0, lcdGetHeight() - 2*lcdGetTextFont()->Height - 1);
	lcdPrintf("Resolution:%dX%d, \nTime: %4lu ms", iw, ih, etick-stick);
	HAL_Delay(5000);

	stick = HAL_GetTick();
	jpeg_screen_view("0:/", "jpg/05.jpg", -1, 0, &iw, &ih);
	etick = HAL_GetTick();

	lcdSetCursor(0, lcdGetHeight() - 2*lcdGetTextFont()->Height - 1);
	lcdPrintf("Resolution:%dX%d, \nTime: %4lu ms", iw, ih, etick-stick);
	HAL_Delay(5000);

	lcdSetOrientation(lcdori);

}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	activedMenuItem=0;
	 menuActived=0;
	 FATFS fs;
  /* 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_SPI2_Init();
  MX_FATFS_Init();
  MX_RTC_Init();
  MX_LIBJPEG_Init();
  /* USER CODE BEGIN 2 */

  lcdBacklightOn();
  lcdInit();
  lcdFillRGB(COLOR_BLACK);
  //lcdSetOrientation(LCD_ORIENTATION_LANDSCAPE);
  demo=false;

  lcdSetOrientation(LCD_ORIENTATION_PORTRAIT);
  //start Calibrating
  isCalbriating=true;
  showMessage("Start Calibration",2);
  //HAL_Delay(2000);
  while(!XPT2046_TouchCalibration())
  {
	showMessage("Calibration Error!!!\n Try again", 2);
   }
    isCalbriating=false;
    showMessage("Calibrate\n successfully",2);
    //HAL_Delay(2000);
  // end Calibrating
    menuActived=1;

    showMenu(0);

  /* USER CODE END 2 */

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

  while (1)
  {
	  switch(activedMenuItem)
	  {
	  case SAVEITEM:
		  saveScreen(USERPath);
		  showMenu(0);
		  break;
	  case JPEGITEM:
		  loadJpegFileTest();
		  showMenu(0);
		  break;
	  case DEMOITEM:
		  demoLCD();
		  showMenu(0);
		  break;
	  case DRAWINGITEM:

		  break;

	  case LCDORIITEM:
		  tempIgnoralEXTI=1;
		  lcdFillRGB(COLOR_BLACK);
		  lcdSetOrientation((lcdGetOrientation()+1)%4);
		  showMenu(0);
		  tempIgnoralEXTI=0;
		  menuActived=1;
		  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_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();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @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 */

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 12;
  sTime.Minutes = 10;
  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_JANUARY;
  sDate.Date = 1;
  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 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_128;
  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_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);

}

/**
  * @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_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_SET);

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

  /*Configure GPIO pin : SD_CS_Pin */
  GPIO_InitStruct.Pin = SD_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(SD_CS_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : T_IRQ_Pin */
  GPIO_InitStruct.Pin = T_IRQ_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  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_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOB, &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****/


menu.h
#include "stm32f4xx_hal.h"
#include "ili9341.h"

#define MENU_ITEMS	5
#define JPEGITEM 	1
#define DEMOITEM 	2
#define DRAWINGITEM	3
#define SAVEITEM	5
#define LCDORIITEM	4

uint8_t activedMenuItem;
uint8_t menuActived;

void showMenu(uint8_t selectedIndex);
void hideMenu(void);
uint8_t getTouchMenItem(uint16_t x, uint16_t y);

menu.c
#include "menu.h"

uint16_t menuColordinate[][4]={{5,5,44,20},{52, 5, 44, 20},{99, 5, 44, 20},{146, 5, 25, 20}, {174,5,44,20}};
char *menuText[]={"JPEG", "DEMO","DRAW", "R", "SAVE"};
uint16_t menuBGColor[]={COLOR_BLUE,COLOR_BLUE,COLOR_BLUE,COLOR_BLUE,COLOR_BLUE};
uint16_t selmenuBGColor=COLOR_GREEN;



void hideMenu()
{
	lcdFillRect(0, 0,lcdGetWidth(), 25, COLOR_BLACK);
}

void showMenu(uint8_t selectedIndex)
{
	lcdSetTextFont(&Font12);
	if (selectedIndex > 0 && selectedIndex <= MENU_ITEMS)
	{
		if (activedMenuItem > 0 && activedMenuItem <= MENU_ITEMS)
		{
			int ti=activedMenuItem-1;
			lcdSetTextColor(COLOR_WHITE, menuBGColor[ti]);
			lcdFillRect(menuColordinate[ti][0], menuColordinate[ti][1], menuColordinate[ti][2], menuColordinate[ti][3], menuBGColor[ti]);
			lcdSetCursor(menuColordinate[ti][0]+6, menuColordinate[ti][1]+4);
			lcdPrintf(menuText[ti]);
		}
		activedMenuItem=selectedIndex;
		selectedIndex--;
		lcdSetTextColor(COLOR_WHITE, selmenuBGColor);
		lcdFillRect(menuColordinate[selectedIndex][0], menuColordinate[selectedIndex][1], menuColordinate[selectedIndex][2], menuColordinate[selectedIndex][3], selmenuBGColor);
		lcdSetCursor(menuColordinate[selectedIndex][0]+6, menuColordinate[selectedIndex][1]+4);
		lcdPrintf(menuText[selectedIndex]);
	}
	else
	{
		hideMenu();
		activedMenuItem=0;
		for (int i=0;i<MENU_ITEMS;i++)
		{
			lcdSetTextColor(COLOR_WHITE, menuBGColor[i]);
			lcdFillRect(menuColordinate[i][0], menuColordinate[i][1], menuColordinate[i][2], menuColordinate[i][3], menuBGColor[i]);
			lcdSetCursor(menuColordinate[i][0]+6, menuColordinate[i][1]+4);
			lcdPrintf(menuText[i]);
		}
	}
}
uint8_t getTouchMenItem(uint16_t x, uint16_t y)
{
	uint8_t ret = 0;

	for (int i=0;i<MENU_ITEMS;i++)
	{
		if (x > menuColordinate[i][0] && x < menuColordinate[i][0]+menuColordinate[i][2] &&
				y > menuColordinate[i][1] && y < menuColordinate[i][1]+menuColordinate[i][3])
		{
			ret = i+1;
			break;
		}
	}
	return ret;
}