摘要
本教程详细介绍基于 STM32F103RC 微控制器和 TFT-LCD 显示屏的电子相册系统开发,涵盖硬件连接、文件系统移植、BMP/JPEG 解码算法实现及显示优化。通过 FATFS 文件系统读取 SD 卡图片,采用专用解码算法处理图像数据,最终在 320×480 分辨率屏幕上流畅显示。
本文介绍基于 STM32F103RC 和 TFT-LCD 的电子相册系统开发。涵盖硬件连接、FATFS 文件系统移植、BMP/JPEG 解码算法实现及显示优化。通过 SPI 读取 SD 卡图片,利用 FSMC 驱动屏幕,提供源码与问题解决方案,支持幻灯片播放模式。

本教程详细介绍基于 STM32F103RC 微控制器和 TFT-LCD 显示屏的电子相册系统开发,涵盖硬件连接、文件系统移植、BMP/JPEG 解码算法实现及显示优化。通过 FATFS 文件系统读取 SD 卡图片,采用专用解码算法处理图像数据,最终在 320×480 分辨率屏幕上流畅显示。
| 组件 | 型号 | 数量 |
|---|---|---|
| 主控 | STM32F103RCT6 | 1 |
| 显示屏 | ILI9486 3.5 寸 TFT | 1 |
| 存储 | MicroSD 卡 (≥4GB) | 1 |
| 接口 | SPI 转 SD 模块 | 1 |
/* 引脚定义 - stm32f10x_pin.h */
#define LCD_CS GPIO_Pin_12 // PB12
#define LCD_DC GPIO_Pin_11 // PB11
#define SD_CS GPIO_Pin_8 // PA8
添加中间件:
Middleware/
├─ FATFS/
├─ LibJPEG/
└─ STemWin/
// lcd_init.c
void LCD_Init(void)
{
LCD_Reset(); // 硬件复位
delay_ms(50);
LCD_WriteCmd(0x11); // 退出睡眠模式
delay_ms(120);
LCD_WriteCmd(0x3A); // 设置颜色模式
LCD_WriteData(0x55); // 16 位 RGB565
LCD_WriteCmd(0x36); // 设置扫描方向
LCD_WriteData(0x48); // 竖屏模式
LCD_WriteCmd(0x29); // 开启显示
}
// fsmc.c
void FSMC_LCD_Config(void)
{
FSMC_NORSRAMInitTypeDef init;
FSMC_NORSRAMTimingInitTypeDef timing;
timing.FSMC_AddressSetupTime = 2;
timing.FSMC_AddressHoldTime = 1;
timing.FSMC_DataSetupTime = 5;
timing.FSMC_BusTurnAroundDuration = 1;
init.FSMC_Bank = FSMC_Bank1_NORSRAM1;
init.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
init.FSMC_MemoryType = FSMC_MemoryType_SRAM;
init.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
init.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
init.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInit(&init);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);
}
// fatfs_impl.c
DSTATUS SD_disk_initialize(void)
{
SPI_InitTypeDef spi;
spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi.SPI_Mode = SPI_Mode_Master;
spi.SPI_DataSize = SPI_DataSize_8b;
spi.SPI_CPOL = SPI_CPOL_High;
spi.SPI_CPHA = SPI_CPHA_2Edge;
spi.SPI_NSS = SPI_NSS_Soft;
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_Init(SPI1, &spi);
SPI_Cmd(SPI1, ENABLE);
return RES_OK;
}
// file_scan.c
FRESULT scan_files(char* path)
{
DIR dir;
FILINFO fno;
FRESULT res = f_opendir(&dir, path);
while(1)
{
res = f_readdir(&dir, &fno);
if(res != FR_OK || fno.fname[0] == 0) break;
if(fno.fattrib & AM_DIR)
{
// 目录处理
}
else
{
if(strstr(fno.fname, ".bmp") || strstr(fno.fname, ".jpg"))
{
// 添加到图片列表
}
}
}
f_closedir(&dir);
return res;
}
// bmp_decoder.c
uint8_t BMP_Show(const char* filename)
{
FIL file;
UINT bytesread;
BMP_Header header;
f_open(&file, filename, FA_READ);
f_read(&file, &header, sizeof(BMP_Header), &bytesread);
// 校验文件头
if(header.signature != 0x4D42) return 1;
// 设置显示窗口
LCD_SetWindow(0, 0, header.width, header.height);
// 读取像素数据
uint32_t offset = header.data_offset;
f_lseek(&file, offset);
for(int y = header.height - 1; y >= 0; y--)
{
f_read(&file, line_buffer, header.width * 3, &bytesread);
for(int x = 0; x < header.width; x++)
{
uint16_t color = RGB888toRGB565(line_buffer[x*3+2], line_buffer[x*3+1], line_buffer[x*3]);
LCD_WriteData(color);
}
}
f_close(&file);
return 0;
}
// jpeg_decoder.c
void JPEG_Decode(const char* filename)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FIL file;
f_open(&file, filename, FA_READ);
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
// 设置文件源
jpeg_stdio_src(&cinfo, &file);
jpeg_read_header(&cinfo, TRUE);
cinfo.out_color_space = JCS_RGB;
jpeg_start_decompress(&cinfo);
// 逐行解码
uint8_t* line = malloc(cinfo.output_width * 3);
while(cinfo.output_scanline < cinfo.output_height)
{
jpeg_read_scanlines(&cinfo, &line, 1);
RGB888_To_LCD(line, cinfo.output_width);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
f_close(&file);
free(line);
}
// main.c
int main(void)
{
HAL_Init();
SystemClock_Config();
LCD_Init();
SD_Init();
while(1)
{
switch(current_mode)
{
case MODE_BROWSE:
show_image_list();
break;
case MODE_DISPLAY:
display_current_image();
break;
case MODE_SLIDESHOW:
auto_play_images();
break;
}
key_scan();
}
}
uint16_t buffer1[320*50]; // 50 行缓冲区
uint16_t buffer2[320*50];
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 花屏 | 时序配置错误 | 调整 FSMC 时序参数 |
| 图片显示错位 | 字节对齐问题 | 添加 __align(4) 修饰缓冲区 |
| JPEG 解码慢 | 未启用硬件加速 | 使用 CRC 加速 DCT 计算 |
| 文件读取失败 | SD 卡格式不兼容 | 格式化为 FAT32 分配单元 32KB |
| 解码方式 | 320×240 | 640×480 | 1024×768 |
|---|---|---|---|
| BMP 直读 | 120ms | 480ms | 1900ms |
| JPEG 软解 | 280ms | 620ms | 2400ms |
| JPEG 硬解 | 85ms | 210ms | 780ms |

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online