◎ SD 카드 BMP 파일 출력
이전 글에서 SDIO를 이용한 SD 카드 포트 초기화와 file 시스템 컨트롤을 위한 FATFS 시스템을 적용하는 방법을 설명 했습니다. 이제 모든 장치는 준비가 완료 되었고 파일을 읽고 출력 하는 코드를 정리해 보겠습니다.
result = f_mount(&fs, "0:", 1);
f_mount 함수는 물리적인 드라이브와 FatFs 모듈을 연결 하거나 등록 해제 하는 역할을 합니다. 파일 시스템에 접근하기 전에 반드시 이 함수를 호출하여 작업 영역 (FATFS 객체)을 등록해야 합니다.
<함수 형태>
FRESULT f_mount (
FATFS* fs, // 파일 시스템 객체에 대한 포인터
const TCHAR* path, // 논리 드라이브 번호
BYTE opt // 옵션 플래그
);
● fs : 등록할 파일 시스템 객체에 대한 포인터 입니다. NULL을 전달하면 드라이브가 등록 해제 됩니다.
● path : 대상 논리 드라이브를 지정 합니다. 예를들어 "0:" , "1:" 이런식으로 지정합니다.
● opt : 마운트 옵션으로 , 0이면 지연 마운트 (필요할 때 마운트), 1이면 즉시 마운트를 의미 합니다.
※ ff.c 파일에 등록되어 있습니다.
if(result != FR_OK){
printf("Mount failed (%d)\r\n",result);
return 0;
}
반환된 결과값에 FR_OK((0) Succeeded) 가 맞다면 등록이 성공이 된겁니다.
result = f_open(&file, filename, FA_READ);
f_open 함수는 파일을 여는 함수 입니다. FA_READ로 매개변수값을 준경우는 읽기 전용 모드를 의미 합니다.
FRESULT f_open (
FIL* fp, /* Pointer to the blank file object */
const TCHAR* path, /* Pointer to the file name */
BYTE mode /* Access mode and file open mode flags */
)
● fp : FIL 타입의 파일 객채에 대한 포인터를 매개 변수로 전달 합니다. 이 객체는 파일이 성공적으로 열리면 파일 관련 정보를 저장합니다.
● path : 열고자 하는 파일의 이름(경로)을 담고 있는 문자열 변수 입니다.
● mode : 파일 접근 모드를 설정 합니다.
f_read(&file, &bmpHeader.bfType, 2, &br);
f_open 함수는 파일에서 데이터를 읽어오는 함수 입니다.
FRESULT f_read (
FIL* fp, /* Pointer to the file object */
void* buff, /* Pointer to data buffer */
UINT btr, /* Number of bytes to read */
UINT* br /* Pointer to number of bytes read */
)
● fp : f_open 함수에서 실행되어 저장된 파일 정보를 f_read 매개 변수로 전달 합니다.
● buff : 읽어온 데이터를 저장할 버퍼의 주소를 저장 합니다. 이 코드 같은 경우 SD 카드안에 저장된 bmp 파일을 읽어 드릴겁니다. bmpHeader 라는 구조체 안에 bfType 변수 안에 저장을 합니다.
● btr: 읽어올 데이터의 바이트 단위를 지정합니다. BMP 파일 같은 경우는 2바이트 크기 입니다.
● br : UINT타입의 변수에 대한 포인터로 , 실제로 읽는 바이트 수가 이 변수에 저장됩니다. 이를 통해 요청한 크기만큼 데이터를 읽었는지 확인할수 있습니다.
f_read(&file, &bmpHeader.bfSize, 4, &br);
f_read(&file, &bmpHeader.bfReserved1, 2, &br);
f_read(&file, &bmpHeader.bfReserved2, 2, &br);
f_read(&file, &bmpHeader.bfOffBits, 4, &br);
f_read(&file, &bmpHeader.biSize, 4, &br);
f_read(&file, &bmpHeader.biWidth, 4, &br);
f_read(&file, &bmpHeader.biHeight, 4, &br);
f_read(&file, &bmpHeader.biPlanes, 2, &br);
f_read(&file, &bmpHeader.biBitCount, 2, &br);
f_read(&file, &bmpHeader.biCompression, 4, &br);
f_read(&file, &bmpHeader.biSizeImage, 4, &br);
f_read(&file, &bmpHeader.biXPelsPerMeter, 4, &br);
f_read(&file, &bmpHeader.biYPelsPerMeter, 4, &br);
f_read(&file, &bmpHeader.biClrUsed, 4, &br);
f_read(&file, &bmpHeader.biClrImportant, 4, &br);
SD 카드 안에 있는 bmp 파일 정보를 모두 읽어 들여 bmpHeader 구조체 변수들에 모두 저장 합니다.
if(bmpHeader.biBitCount != 24 || bmpHeader.biCompression != 0){
printf("Only uncompressed 24-bit BMP supported\r\n");
f_close(&file);
f_mount(NULL, "0:", 0);
return 0;
}
현재 저장된 BMP 파일은 24비트 컬러로 저장 했습니다. 설정된 조건이 아닐경우 파일을 닫고 FATFS 시스템 연결을 해제 합니다.
width = bmpHeader.biWidth;
height = bmpHeader.biHeight;
bmp 파일에 넓이와 높이를 width, height 변수에 저장됩니다.
lineBytes = width * 3;
각 라인의 바이트 단위수를 계산 합니다. 24비트 컬러 이미지 이기 때문에 바이트로 하면 3바이트 입니다. 넓이값을 곱하면 BMP 이미지를 읽을 경우 한라인의 바이트수를 계산 할수 있습니다.
예를들어 가로가 480 일 경우 계산을 하면 480 x 3 = 1440 바이트 입니다.
paddingBytes = (4 - (lineBytes % 4)) % 4;
BMP 이미지는 형식상 각 이미지 행이 4바이트의 배수로 정렬되어야 하는 규칙이 있습니다. 만약에 4의 배수가 아닌경우 맞추기 위하여 위의 코드 같은 나머지 값을 이용한 패딩 과정을 진행 해야 합니다.
paddingBytes = (4 - (1440 % 4)) % 4 = (4 - 0) % 4 = 0 이기 때문에 별도의 계산 없이 추가 패딩 작업이 필요 없습니다.
totalLineBytes = lineBytes + paddingBytes;
패딩 과정에서 4의 배수가 아닌경우 위의 공식에서 나온 나머지 값을 더해서 4의 배수로 맞추는 작업을 진행 합니다.
pLineBuffer = (uint8_t *)malloc(totalLineBytes);
4의 배수로 맞춰진 라인 버퍼 값을 molloc 을 이용하여 메모리 사이즈를 pLineBuffer 에 할당 하여 저장 합니다.
f_lseek(&file, bmpHeader.bfOffBits);
f_lseek 함수는 파일 내의 읽기/쓰기 위치를 이동시키는 함수 입니다.
FRESULT f_lseek (
FIL* fp, /* Pointer to the file object */
FSIZE_t ofs /* File pointer from top of file */
)
● fp : 현재 열려 있는 BMP 파일의 파일 핸들(포인터) 입니다.
● ofs : BMP 파일 헤더의 읽어온 중요한 값으로 , 파일의 시작부터 실제 비트멥 이미지 데이터가 시작되는 위치 까지의 오프셋(바이트 단위)을 나타 냅니다.
※ f_lseek() 함수를 사용하면 바로 이미지 데이터가 시작 되는 위치로 이동할수 있어 효율적입니다.
RA8875_SetDispWin(0, 0, 272, 480);
RA8875_SetCursor(x,y);
RA8875_WriteRAM_Prepare();
for (i = 0; i < height; i++){
// 현재 라인 인덱스 계산 (BMP는 기본적으로 아래에서 위로)
uint16_t lineIndex = topDown ? i : (height - 1 - i);
// 한 라인 데이터 읽기
result = f_read(&file, pLineBuffer, totalLineBytes, &br);
if (result != FR_OK || br != totalLineBytes) {
printf("Line read failed at line %d (%d, %d)\r\n", i, result, br);
break;
}
RA8875_SetCursor(x,y+lineIndex);
RA8875_WriteRAM_Prepare();
// RA8875에 연속적으로 데이터 전송
for (j = 0; j < width; j++){
// BMP는 BGR 형식으로 저장됨
b = pLineBuffer[j * 3 + 0];
g = pLineBuffer[j * 3 + 1];
r = pLineBuffer[j * 3 + 2];
// RGB888 -> RGB565 변환
pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
LCD_DataWrite(pixel);
}
}
RA8875_SetDispWin(0, 0, 272, 480);
RA8875_SetCursor(x,y);
RA8875_WriteRAM_Prepare();
이 코드는 이미 TFT LCD 를 정리하는 글에서 언급한적이 있기 때문에 별도의 설명은 생략 하겠습니다.
for (i = 0; i < height; i++) {
이미지의 높이 만큼 반복하여 각 라인(행)을 처리 합니다.
uint16_t lineIndex = topDown ? i : (height - 1 - i);
이 코드는 SD 카드에서 나오는 이미지가 위에서 아래 또는 아래에서 위로 파일이 읽어질 경우를 판단하여 반복문이 결정 됩니다.
result = f_read(&file, pLineBuffer, totalLineBytes, &br);
FatFS 파일 시스템을 사용해 파일에서 한 라인의 데이터를 읽어 pLineBuffer 에 저장 합니다.
totalLineBytes 는 한 라인의 총 바이트 수(픽셀 데이터 + 패딩) 입니다.
for (i = 0; i < height; i++){
// 현재 라인 인덱스 계산 (BMP는 기본적으로 아래에서 위로)
uint16_t lineIndex = topDown ? i : (height - 1 - i);
// 한 라인 데이터 읽기
result = f_read(&file, pLineBuffer, totalLineBytes, &br);
if (result != FR_OK || br != totalLineBytes) {
printf("Line read failed at line %d (%d, %d)\r\n", i, result, br);
break;
}
RA8875_SetCursor(x,y+lineIndex);
RA8875_WriteRAM_Prepare();
// RA8875에 연속적으로 데이터 전송
for (j = 0; j < width; j++){
// BMP는 BGR 형식으로 저장됨
b = pLineBuffer[j * 3 + 0];
g = pLineBuffer[j * 3 + 1];
r = pLineBuffer[j * 3 + 2];
// RGB888 -> RGB565 변환
pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
LCD_DataWrite(pixel);
}
}
이 코드에서 부터는 실제 f_read 에서 함수에 저장된 pLineBuffer 값을 LCD에 출력 하는 코드입니다. 방향은 아래에서 위 또는 위에서 아래로 한줄씩 읽기 때문에 높이부터 for 문을 이용합니다.
uint16_t lineIndex = topDown ? i : (height - 1 - i);
bmp 에서 아래 에서 위인지 위에서 아래인진 방향을 판단해서 높이를 계산하여 동작 합니다.
result = f_read(&file, pLineBuffer, totalLineBytes, &br);
totalLineBytes 에 할당된 메모리 사이즈 만큼 pLineBuffer 에 파일 데이터를 저장 합니다.
for (j = 0; j < width; j++)
이 코드 구간 부터는 실제 한줄씩 bmp 데이터를 그리는 구간 입니다.
b = pLineBuffer[j * 3 + 0];
g = pLineBuffer[j * 3 + 1];
r = pLineBuffer[j * 3 + 2];
현재 sd 카드에 저장되어 있는 이미지는 24비트 bmp 파일 입니다. 그러므로 각 픽셀은 3바이트로 구성되어 있습니다. BMP 파일은 BGR 로 구성이 됩니다.
j * 3 구간은 현재 픽셀의 시작 인덱스를 계산합니다.
pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
24바트 RGB 값을 (RGB 888) 을 16비트 RGB 값 (RGB565) 로 변환 합니다.
r : 8비트에서 5비트로 변환하기 위해서는 3비트 우측 시프트후 11비트 좌측 시프트 합니다.
g : 8비트에서 6비트로 변환하기 위해 2비트 우측 시프트후 5비트 좌측 시프트b : 8비트에서 5비트로 변환하기 위해 3비트 좌측 시프트※ g 같은 경우는 전체 16비트 값에서 중간을 차지 하기 때문에 6으로 고정해야 합니다.
f_close(&file);
f_mount(NULL, "0:", 0);
free(pLineBuffer);
bmp 파일을 LCD에 출력이 완료되면 파일을 클로즈 하고 fatfs 시스템 연결도 해제 합니다.
전체코드
#pragma pack(push, 1)
typedef struct
{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
} BMP_HEADER;
#pragma pack(pop)
uint8_t RA8875_LoadBmpToScreen(const char* filename, uint16_t x, uint16_t y)
{
BMP_HEADER bmpHeader;
uint16_t width, height;
uint32_t lineBytes, paddingBytes, totalLineBytes;
uint8_t *pLineBuffer = NULL;
FRESULT result;
FATFS fs;
FIL file;
UINT br;
uint16_t i, j;
uint16_t pixel;
uint8_t r, g, b;
bsp_LedOn(1);
result = f_mount(&fs, "0:", 1);
if (result != FR_OK) {
printf("Mount failed (%d)\r\n", result);
return 0;
}
// 파일 열기
result = f_open(&file, filename, FA_READ);
if (result != FR_OK) {
printf("File open failed (%d)\r\n", result);
f_mount(NULL, "0:", 0);
return 0;
}
// BMP 헤더 읽기
f_read(&file, &bmpHeader.bfType, 2, &br);
// BMP 포맷 확인 (BM 시그니처)
if (bmpHeader.bfType != 0x4D42) {
printf("Not a valid BMP file\r\n");
f_close(&file);
f_mount(NULL, "0:", 0);
return 0;
}
// 나머지 헤더 읽기
f_read(&file, &bmpHeader.bfSize, 4, &br);
f_read(&file, &bmpHeader.bfReserved1, 2, &br);
f_read(&file, &bmpHeader.bfReserved2, 2, &br);
f_read(&file, &bmpHeader.bfOffBits, 4, &br);
f_read(&file, &bmpHeader.biSize, 4, &br);
f_read(&file, &bmpHeader.biWidth, 4, &br);
f_read(&file, &bmpHeader.biHeight, 4, &br);
f_read(&file, &bmpHeader.biPlanes, 2, &br);
f_read(&file, &bmpHeader.biBitCount, 2, &br);
f_read(&file, &bmpHeader.biCompression, 4, &br);
f_read(&file, &bmpHeader.biSizeImage, 4, &br);
f_read(&file, &bmpHeader.biXPelsPerMeter, 4, &br);
f_read(&file, &bmpHeader.biYPelsPerMeter, 4, &br);
f_read(&file, &bmpHeader.biClrUsed, 4, &br);
f_read(&file, &bmpHeader.biClrImportant, 4, &br);
// 헤더 검증
if (bmpHeader.biBitCount != 24 || bmpHeader.biCompression != 0) {
printf("Only uncompressed 24-bit BMP supported\r\n");
f_close(&file);
f_mount(NULL, "0:", 0);
return 0;
}
// BMP 정보 확인
width = bmpHeader.biWidth;
height = bmpHeader.biHeight;
// 높이가 음수인 경우 (상단에서 하단으로 저장된 BMP)
uint8_t topDown = 0;
if (height < 0) {
height = -height;
topDown = 1;
}
// 각 라인의 바이트 수 계산 (RGB888 = 3바이트/픽셀)
lineBytes = width * 3;
// BMP 라인은 4바이트 경계에 맞춰야 함
paddingBytes = (4 - (lineBytes % 4)) % 4;
totalLineBytes = lineBytes + paddingBytes;
// 한 라인을 저장할 버퍼 할당
pLineBuffer = (uint8_t *)malloc(totalLineBytes);
if (pLineBuffer == NULL) {
printf("Memory allocation failed\r\n");
f_close(&file);
f_mount(NULL, "0:", 0);
return 0;
}
// 파일 포인터를 이미지 데이터의 시작 위치로 이동
f_lseek(&file, bmpHeader.bfOffBits);
RA8875_SetDispWin(0, 0, 272, 480);
RA8875_SetCursor(x,y);
RA8875_WriteRAM_Prepare();
for (i = 0; i < height; i++){
// 현재 라인 인덱스 계산 (BMP는 기본적으로 아래에서 위로)
uint16_t lineIndex = topDown ? i : (height - 1 - i);
// 한 라인 데이터 읽기
result = f_read(&file, pLineBuffer, totalLineBytes, &br);
if (result != FR_OK || br != totalLineBytes) {
printf("Line read failed at line %d (%d, %d)\r\n", i, result, br);
break;
}
RA8875_SetCursor(x,y+lineIndex);
RA8875_WriteRAM_Prepare();
// RA8875에 연속적으로 데이터 전송
for (j = 0; j < width; j++){
// BMP는 BGR 형식으로 저장됨
b = pLineBuffer[j * 3 + 0]; // 8비트 변수에
g = pLineBuffer[j * 3 + 1];
r = pLineBuffer[j * 3 + 2];
// RGB888 -> RGB565 변환
pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
LCD_DataWrite(pixel);
}
}
RA8875_SetDispWin(0, 0, 272, 480);
// 파일 닫기
f_close(&file);
f_mount(NULL, "0:", 0);
free(pLineBuffer);
bsp_LedOff(1);
printf("BMP file loaded: %s\r\n", filename);
return 1;
}
테스트 영상
'STM32 (Cortex-M4)' 카테고리의 다른 글
STM32 media 보드 - FATFS (0) | 2025.04.23 |
---|---|
STM32 media 보드 - SD CARD (0) | 2025.04.23 |
STM32 media 보드 - TFT LCD 제어 Part 4 (0) | 2025.04.02 |
STM32 media 보드 - TFT LCD 제어 Part 3 (0) | 2025.03.31 |
STM32 media 보드 - TFT LCD 제어 PART2 (0) | 2025.03.31 |