prettyprint

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









2021年1月13日 星期三

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

    本實驗開發版使用STM32F4VE,軟體使用STM32CubeIDE與HAL library為開發環境,透過FatFS API同時管理三個SD-4bits,SD-SPI,SPI-FLASH等Devices,SD使用SDIO 4bits與SPI介面兩種方式,另外使用SPI介面存取開發版上的W25Q16JV Flash。

參考資料:

  1. http://elm-chan.org/fsw/ff/00index_e.html
  2. https://github.com/nimaltd/w25qxx
  3. https://controllerstech.com/sd-card-using-spi-in-stm32/
  4. SDIO匯流排(一) - IT閱讀
 

硬體環境: 

開發版上的MicroSD接口使用SDIO SD 4 bits Wide Bus模式,另外使用SPI 2連接外接TFT卡的SD卡,使用SPI1連接W25Q16JV Flash。

實驗內容:

    同時連接三個設備,開發版內接MicroSD為Volume 0,TFT Module上的SD為Volume 1,開發版上的Flash為Volume 2。
    同時mount三個Volumes分別建立檔案,寫入資料,再從Volume0複製image檔到Volume1。




上圖說明Application 透過FatFs Module做檔案操作,FatFs Module透過FatFs MAI(Media Access Interface)與Storage Device Driver對實體設備存取。本實驗著重在MAI實作。SPI SD與Flash Device driver使用上述參考網址2與3。

實驗步驟:

步驟一

    設定SDIO, SPI1, SPI2 Connectivity
  • Volume 0:SDIO使用SD 4 bits Wide bus connectivity,開啟相關DMA、NVIC。
使用4bits Wide bus
開啟DMA
NVIC enable

  • Volume 1: SPI2(連接TFT卡上的SD,使用SPI mode)
DMA enable


GPIO Pin
PC3為SD_CS
  • Volume2: SPI1(連接開發版上W25Q17JV Flash)
GPIO pins
PB0為FLASH_CS
未啟用DMA


步驟二

設定FATFS

  • SD Card連接SDIO 4 bits Widw Bus為Volume 0
  • User-defined連接SPI 2 SD為Voulme 1
  • SPI1 Flash透過手動更改FATF/APP/fatfs.c加入volume2
  • Locale and Namespace Parameter:
    • USE_LFN:2(使用長檔名)
    • LFN_UNICODE:ANSI/OEM(暫時不用中文檔名)
    • STRF_ENCODE:UTF-8(可存中文內容)
  • Physical Driver Parameter:
    • VOLUMES:3(手動改為3,因為要實際連接3個Volumes)
    • MAX_SS:4096
修改FATFS/Target/ffconf.h:
#define _FS_LOCK    3  //改為3,因為要同時開啟3個檔案

加入程式碼:


程式堆疊為Application->Fatfs Module->FatFs MAI->Storage Device Controller(Hardware Driver)
  • Hardware Driver:
    • SD 4 bits Wide bus: bsp_driver_sd.c,bsp_driver_sd.h,STM32CubeIDE已自動產生。
    • SD SPI bus: fatfs_sd.c, fatfs_sd.h (參考網址3)
    • Flash SPI bus: w25qxx.c w25qxx.h, w25qxxConf.h((參考網址2)
  • FatFs MAI:
    • 要實作initialize, status, read, write, ioctl
    • 透過上面五個functions 呼叫 Hardware Driver實際讀寫資料到device。
    • 實作get_fattime取得檔案時間。
    • SD 4 bits Wide bus: sd_diskio.c, sd_diskio.h,STM32CubeIDE已自動產生並實作上述functions。
    • SD SPI bus:  user_diskio.c, user_diskio.h ,STM32CubeIDE已自動產生,但並未連結。呼叫Hardware Driver參閱下列程式碼一。
    • Flash SPI bus: flash_diskio.c, flash_diskio.h參閱下列程式碼二、三。
    • 定義每個device的Diskio_drvTypeDef以便呼叫各自的initialize, status, read, write, ioctl functions。SD_Driver(SD 4bits Wide Bus)與USER_Driver(SD SPI)已自動產生,FLASH_Driver參閱程式碼三。
    • FATFS/APP/fatfs.c: void MX_FATFS_Init(void)將volume path與 hardware driver連結,修改fatfs.c部分請參閱程式碼四。
  • 實驗應用程式main.c請參閱程式碼五。
程式碼一(user_diskio.c)
DSTATUS USER_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { /* USER CODE BEGIN INIT */ Stat = STA_NOINIT; return SD_disk_initialize(pdrv); /* USER CODE END INIT */ } /** * @brief Gets Disk Status * @param pdrv: Physical drive number (0..) * @retval DSTATUS: Operation status */ DSTATUS USER_status ( BYTE pdrv /* Physical drive number to identify the drive */ ) { /* USER CODE BEGIN STATUS */ Stat = STA_NOINIT; return SD_disk_status(pdrv); /* USER CODE END STATUS */ } /** * @brief Reads Sector(s) * @param pdrv: Physical drive number (0..) * @param *buff: Data buffer to store read data * @param sector: Sector address (LBA) * @param count: Number of sectors to read (1..128) * @retval DRESULT: Operation result */ DRESULT USER_read ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to read */ ) { /* USER CODE BEGIN READ */ return SD_disk_read(pdrv, buff, sector, count); /* USER CODE END READ */ } /** * @brief Writes Sector(s) * @param pdrv: Physical drive number (0..) * @param *buff: Data to be written * @param sector: Sector address (LBA) * @param count: Number of sectors to write (1..128) * @retval DRESULT: Operation result */ #if _USE_WRITE == 1 DRESULT USER_write ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to write */ ) { /* USER CODE BEGIN WRITE */ /* USER CODE HERE */ return SD_disk_write(pdrv, buff, sector, count); /* USER CODE END WRITE */ } #endif /* _USE_WRITE == 1 */ /** * @brief I/O control operation * @param pdrv: Physical drive number (0..) * @param cmd: Control code * @param *buff: Buffer to send/receive control data * @retval DRESULT: Operation result */ #if _USE_IOCTL == 1 DRESULT USER_ioctl ( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { /* USER CODE BEGIN IOCTL */ DRESULT res = RES_ERROR; return SD_disk_ioctl(pdrv, cmd, buff); /* USER CODE END IOCTL */ } #endif /* _USE_IOCTL == 1 */

程式碼二:  flash_diskio.h
#ifndef __FLASH_DISKIO_H
#define __FLASH_DISKIO_H

#ifdef __cplusplus
 extern "C" {
#endif

extern Diskio_drvTypeDef  FLASH_Driver;



#ifdef __cplusplus
}
#endif

#endif
程式碼三: flash_diskio.c
#include <string.h>
#include "ff_gen_drv.h"
#include "w25qxx.h"

static volatile DSTATUS Stat = STA_NOINIT;

DSTATUS FLASH_initialize (BYTE pdrv);
DSTATUS FLASH_status (BYTE pdrv);
DRESULT FLASH_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT FLASH_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT FLASH_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  FLASH_Driver =
{
		FLASH_initialize,
		FLASH_status,
		FLASH_read,
#if  _USE_WRITE
		FLASH_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
		FLASH_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS FLASH_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
    Stat = STA_NOINIT;
    if (W25qxx_Init()) Stat = RES_OK;
    return Stat;
  /* USER CODE END INIT */
}

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS FLASH_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */

    return Stat;

  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT FLASH_read (
	BYTE pdrv,      /* Physical drive nmuber to identify the drive */
	BYTE *buff,     /* Data buffer to store read data */
	DWORD sector,   /* Sector address in LBA */
	UINT count      /* Number of sectors to read */
)
{
/* pdrv should be 0 */
	if (pdrv || !count) return RES_PARERR;

	/* no disk */
	if (Stat & STA_NOINIT) return RES_NOTRDY;

	while(count>0)
	{
		W25qxx_ReadSector(buff, sector, 0, 0);
		buff+=w25qxx.SectorSize;
		sector++;
		count--;
	}

	return count ? RES_ERROR : RES_OK;
}

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT FLASH_write (
	BYTE pdrv,          /* Physical drive nmuber to identify the drive */
	const BYTE *buff,   /* Data to be written */
	DWORD sector,       /* Sector address in LBA */
	UINT count          /* Number of sectors to write */
)
{
  /* USER CODE BEGIN WRITE */
  /* USER CODE HERE */

	while(count > 1)
	{
		W25qxx_EraseSector(sector);
		W25qxx_WriteSector(buff, sector, 0, 0);
		count--;
		buff += w25qxx.SectorSize;
		sector++;
	}
	if (count == 1)
	{
		W25qxx_EraseSector(sector);
		W25qxx_WriteSector(buff, sector, 0, 0);
		count--;
	}

	return count ? RES_ERROR : RES_OK;
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT FLASH_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res = RES_ERROR;

    switch(cmd) {
    case CTRL_SYNC:
    	res = RES_OK;
    	break;
    case GET_SECTOR_SIZE:
    	*(DWORD*)buff = w25qxx.SectorSize;
    	res = RES_OK;
    	break;
    case GET_BLOCK_SIZE:
    	*(DWORD*)buff = w25qxx.BlockSize;
    	res = RES_OK;
    	break;
    case GET_SECTOR_COUNT:
    	*(DWORD*)buff = w25qxx.SectorCount;
    	res = RES_OK;
    	break;
    default:
    	res = RES_PARERR;
    	break;
    }

    return res;

  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */


程式碼四:fatfs.c
/**
  ******************************************************************************
  * @file   fatfs.c
  * @brief  Code for fatfs applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

#include "fatfs.h"

uint8_t retSD;    /* Return value for SD */
char SDPath[4];   /* SD logical drive path */
FATFS SDFatFS;    /* File system object for SD logical drive */
FIL SDFile;       /* File object for SD */
uint8_t retUSER;    /* Return value for USER */
char USERPath[4];   /* USER logical drive path */
FATFS USERFatFS;    /* File system object for USER logical drive */
FIL USERFile;       /* File object for USER */

/* USER CODE BEGIN Variables */
extern RTC_HandleTypeDef hrtc;
RTC_DateTypeDef sDate;
RTC_TimeTypeDef sTime;

uint8_t retFLASH;    /* Return value for USER */
char FLASHPath[4];   /* USER logical drive path */
FATFS FLASHFatFS;    /* File system object for USER logical drive */
FIL FLASHFile;       /* File object for USER */
/* USER CODE END Variables */

void MX_FATFS_Init(void)
{
  /*## FatFS: Link the SD driver ###########################*/
  retSD = FATFS_LinkDriver(&SD_Driver, SDPath); //SDPath: 0:/
  /*## FatFS: Link the USER driver ###########################*/
  retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);//USERPath: 1:/
/* USER CODE BEGIN Init */ /*## FatFS: Link the FLASH driver ###########################*/ retFLASH = FATFS_LinkDriver(&FLASH_Driver, FLASHPath);//FLASHPath: 2:/
/* additional user code for init */ /* USER CODE END Init */ } /** * @brief Gets Time from RTC * @param None * @retval Time in DWORD */ DWORD get_fattime(void) { /* USER CODE BEGIN get_fattime */         HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN); DWORD tm = (sDate.Year+20)<<25|sDate.Month<<21|sDate.Date<<16|sTime.Hours<<11|sTime.Minutes<<5|sTime.Seconds;//year from 1980, second/2 //return 0; return tm; /* USER CODE END get_fattime */ } /* USER CODE BEGIN Application */ /* USER CODE END Application */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

程式碼五: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"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "fatfs_sd.h"
#include "ff.h"
#include "w25qxx.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;
SD_HandleTypeDef hsd;
DMA_HandleTypeDef hdma_sdio_rx;
DMA_HandleTypeDef hdma_sdio_tx;

SPI_HandleTypeDef hspi1;
SPI_HandleTypeDef hspi2;
DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi2_tx;

/* USER CODE BEGIN PV */
FATFS fs_sd, fs_spi_sd, fs_spi_flash;
FIL file_sd, file_spi_sd, file_spi_flash;
FRESULT res;
char fname[256];
/* 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_SDIO_SD_Init(void);
static void MX_SPI2_Init(void);
static void MX_SPI1_Init(void);
static void MX_RTC_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

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

/* 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_SDIO_SD_Init();
  MX_FATFS_Init();
  MX_SPI2_Init();
  MX_SPI1_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */



  UINT bw,br;
  BYTE work[_MAX_SS];
  //對FLASH產生檔案系統
  //W25qxx_EraseChip();
  //res = f_mkfs(FLASHPath, FM_ANY, 0, work, _MAX_SS);
  char buff[256];
 // mount 3 volumes
  res=f_mount(&fs_sd, SDPath, 0);
  res=f_mount(&fs_spi_sd, USERPath, 0);
  res=f_mount(&fs_spi_flash, FLASHPath, 0);
  //create a file in volume0
  memset(fname,0,256);
  strcpy(fname,SDPath);
  strcat(fname, "sd_file.txt");
  res = f_open(&file_sd, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  //create a file in volume1
  memset(fname,0,256);
  strcpy(fname,USERPath);
  strcat(fname, "sd_spi_file.txt");
  res = f_open(&file_spi_sd, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  //create a file in volume2
  memset(fname,0,256);
  strcpy(fname,FLASHPath);
  strcat(fname, "flash_spi_file.txt");
  res = f_open(&file_spi_flash, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  sprintf(buff, "write test to sd 寫入SD\n");
  res=f_write(&file_sd, buff, strlen(buff), &bw);
  sprintf(buff, "write test to spi sd 寫入SPI SD\n");
  res=f_write(&file_spi_sd, buff, strlen(buff), &bw);
  sprintf(buff, "write test to flash 寫入FLASH\n");
  res=f_write(&file_spi_flash, buff, strlen(buff), &bw);
  res=f_close(&file_sd);
  res=f_close(&file_spi_sd);
  res=f_close(&file_spi_flash);
//copy a image file from volume0 to volume1
  memset(fname,0,256);
  strcpy(fname,SDPath);
  strcat(fname, "test16.bmp");
  res = f_open(&file_sd, fname, FA_READ);
  memset(fname,0,256);
  strcpy(fname,USERPath);
  strcat(fname, "test16.bmp");
  res = f_open(&file_spi_sd, fname, FA_CREATE_NEW|FA_WRITE|FA_READ);
  do
  {
	  memset(buff, 0, 256);
	  res = f_read(&file_sd, buff, 256, &br);
	  if (res == FR_OK)
	  {
		  res= f_write(&file_spi_sd, buff, br, &bw);
	  }

  }while (res==FR_OK && br > 0);
  res=f_close(&file_sd);
  res=f_close(&file_spi_sd);

  res=f_mount(&fs_sd, "", 0);
  res=f_mount(&fs_spi_sd, "", 0);
  res=f_mount(&fs_spi_flash, "", 0);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* 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 = 0;
  sTime.Minutes = 0;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_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 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 SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

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

  /* USER CODE END SPI1_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_2;
  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_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

}

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

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

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_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 : SD_CS_Pin */
  GPIO_InitStruct.Pin = SD_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(SD_CS_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PA6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  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);

  /*Configure GPIO pin : FLASH_CS_Pin */
  GPIO_InitStruct.Pin = FLASH_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(FLASH_CS_GPIO_Port, &GPIO_InitStruct);

}

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

執行結果:





2020年12月3日 星期四

STM32F103C8T6 Blue Pill and Black Pill開發板RTC模組精準度與校正探討

 本實驗主要探討STM32F103C8T6 RTC模組在使用不同Clock source精準度探討,並比較兩種常用開發版(Blue Pill 與 Black Pill)的差異。

參考資料AN2604 Application note: STM32F101xx and STM32F103xx RTC calibration

使用元件:

  1. STM32F103C8T6 Blue Pill x1
  2. STM32F103C8T6 Black Pill x1
測量模組:
  1. 使用STM32F103C8T6 TIMER Input Capture 偵測輸入方波的頻率與寬度(https://rfwumcu.blogspot.com/2020/11/stm32f103c8t6-timer-input-capture.html)
實作影片:




實驗步驟:
  • 測量兩種開發板不同RTC Clock Source(LSE, HSE, LSI)的頻率。
        RTC可使用三種時鐘來源:外部震盪器RTC_HSE(8M Hz/128=62.5K Hz)、LSE(32.768K Hz)與內部RC震盪器LSI(40K Hz)。



STM32CubeIDE RCC設定畫面要開啟HSE與LSE



RTC開啟RTC OUT,把PC13接到測量模組。此時測得的頻率為除64的值。

因為Blue Pill開發板,PC13接到板子內建的LED,因此當選用LSE clock source測量數值一直大幅飄動,當把板子上的LED移除掉,則可測到穩定值。

實驗數值影片
Blue Pill
HSE clock source



LSE clock source
未移除PC13 LED


移除PC13 LED


LSI  clock source




Black Pill
HSE clock source


LSE clock source





LSI  clock source



實驗結果
Blue Pill:
  • HSE clock source:平均為976.63525,HSE clock frequency = 976.63525x64=62504.656,推算每天約快6.4秒。
  • LSE clock source: 如影片平均值為511.9952,所以LSE clock 頻率為5111.9952x64=32767.6928,推算每天約慢0.81秒。
  • LSI clock source:平均為598.9092,LSI clock frequency=598.9092x64=38330.1888,推算每天約慢3606秒。

Black Pill:
  • HSE clock source:平均為976.62185,HSE clock frequency = 976.62185x64=62503.7984,推算每天約快5.2秒。
  • LSE clock source: 如影片平均值為512.0005,所以LSE clock 頻率為512.0005x64=32768.032,推算每天約慢0.08秒。
  • LSI clock source:平均為624.6267,LSI clock frequency=624.6267x64=40104.1088,推算每天約快224秒。
由以上結果RTC Clock Source以LSE最精確(獨立32.768KHz震盪器),其次HSE,再其次LSI。本次實驗用的開發板Black Pill也比Blue Pill較精確。分別以兩塊開發板在「使用STM32F103C8T6 RTC 實作時鐘、鬧鐘」文章中實作也驗證以上結果。但特別聲明並不代表所有black pill開發版RTC clock就比Blue pill 精確,只是針對本次實驗用的這兩塊板子的比較

時間校正
STM32F10xxx使用數位校正線路,在每2^20 clock cycles移除0~127cycles,以達到正確的clock cycles,因此只能調慢,像本實驗的Blue pill clock(32,767.6928)就比較慢(32,768),因此必須使用prescale將時間變快後,再移除一些clock cycles。

Blue Pill調整參數,選用LSE clock source,


prescale為32767,因此
hrtc.Init.AsynchPrediv = 32767-1;
計算每30天快幾秒:
((32767.6928/32767)-1)*86400*30=54.79
或假設需移除的cycles為C,每秒需移除32767.6928-32767=0.6928 cycles。
所以(C/2^20)*32767=0.6928,所以C=22.17


因此Calibration value為22,在RTC_Init()加入HAL_RTCEx_SetSmoothCalib(&hrtc, 0, 0, 22);指令。

Black Pill調整參數,選用LSE clock source,
hrtc.Init.AsynchPrediv =RTC_AUTO_1_SECOND
((32768.032/32768)-1)*86400*30=2.5,Calibration value為1,在RTC_Init()加入HAL_RTCEx_SetSmoothCalib(&hrtc, 0, 0, 1);指令。

簡易時間校正規則:
前述方法測量RTC OUT的頻率,可能因為所使用自製測量模組本身震盪器精確度或所使用的杜邦線線材品質可能會有誤差。簡易的方法以觀察一段時間記錄總時間誤差,再設定hrtc.Init.AsynchPrediv與查AN2604 Application note table 1設定Calibration value來做校正。
例如,30天38秒,查表最接近為37秒,Calibration value=15。
若30天60秒,推算LSE頻率f`, ((f/32768)-1)*86400*30=-60, f=32767.234,
設定hrtc.Init.AsynchPrediv=32767-1,則30天就會快((32767.234/32767)-1)*86400*30=18.50,查表Calibration value=7。