Funpack第11期-LPC55S69 基于SD卡的BMP图像LCD显示
Funpack第11期活动,任务二将预先存储在SD卡中的图片显示在LCD当中
标签
MPU
显示
ZeBiner
更新2021-11-02
722

一.自我介绍和项目介绍

hello,大家好,我是一名大三学生,这次我参加了 硬禾学堂的FunPack第十一期活动-基于LPC55S69-EVK。事实上呢,我自己也是一个板卡收集爱好者,LPC系列的板卡我自己手头也有非常多,比如这两块逐飞的LPC55S69开发板。虽然我也有这些板子,但是我实际使用LPC单片机的次数并不多,所以完成这次的任务也可以说是从0开始,学习了很多相关的知识。

这次的硬件平台基于LPC55S69-EVK开发板,这是由NXP官方设计的开发版,整体版型采用四层PCB设计,板子上也集成了包括下载器、SD卡、加速度计、音频等众多的外设,同时兼容Arduino接口禾MicroBUS接口,可玩性也很高。其中最吸引我有两点,1是M33的双核架构,2是可用于多路配置的FC。这次的项目当中,我主要使用了FC外设用于配置屏幕实现数据刷新,利用SD卡配合FATFS文件系统进行bmp文件格式的图像存储。LPC的FC8外设,也就是高速SPI可以说是专用于屏幕刷新的SPI,但是由于在配合DMA使用当中遇到一些问题,一直未能解决,因此在该项目中我使用了FC2外设,配置SPI接口进行屏幕刷新。虽然没有使用DMA功能,但是20M的SPI主频对本项目的屏幕显示需求依然游刃有余。

 

二.任务完成思路和实现过程

在本次活动中,我选取的是任务二,读取SD卡中预先存入的图像,显示在屏幕上(OLED或LCD)。

SD卡进行图像存储,一般有两种思路,一种是不适用文件系统,直接将图像数据存储于扇区当中,另一种则是将文件系统写入SD卡当中,用户需要按照一定的格式将文件导入SD卡当中。之前由于智能车、电子设计竞赛等大赛对于性能的要求,在之前使用SD卡存图的经验,都是采用SPI读取SD卡后,直接对扇区进行读写操作。这种需要用户自己完全掌握SD的扇区分配情况,同时使用较为复杂,适合将SD卡作为类似FLASH的用途,即单纯的用于数据的存储。优点则是效率较高。而文件系统的方式进行存储,优点则在于使用灵活,可以将SD卡作为介质,在多个设备中起到文件传输的作用。由于我个人对第二种采用文件系统的方案并不熟悉,所以我采用了FATFS文件系统的方式进行。

软件方案采用MDK+MCUXpresso Config Tools配置工具的方式进行联合开发,在MCUXpresso Config Tools中有SD卡FATFS的例程,明显降低了开发难度。屏幕采用了中景园2.4寸TFT屏幕,IC为il9341 SPI接口。

软件部分简单分为几个框架

1. 屏幕驱动移植
2. SD卡调试
3. 利用FATFS读取BMP文件格式,并显示

屏幕驱动方面,直接移植了中景园屏幕例程,较为简单,值得注意的一点是,当使用寄存器配置屏幕的DC、CS等引脚时需要采用推挽+上拉的模式,否则系统中将会存在大量的噪声,导致屏幕配置出现问题。SPI初始化以及SPI读写相关代码如下

#define BoardSPI_SCLK_PORT			1
#define BoardSPI_SCLK_PIN				23U

#define WS2812_PIXELS           24u                                   // How many WS2812 LEDs on the trip

#define WS2812_DATA_PORT         0u
#define WS2812_DATA_PIN         26u
#define WS2812_DATA_FUNC        IOCON_FUNC1
#define WS2812_DATA_PINCFG      IOCON_MODE_INACT | IOCON_DIGITAL_EN

#define WS2812_SPI              SPI2                                  // WS2812 SPI Handler   
#define WS2812_SPI_RST          kFC2_RST_SHIFT_RSTn                   // WS2812 SPI Reset
#define WS2812_SPI_CLKATTACH    kMAIN_CLK_to_FLEXCOMM2                // WS2812 SPI Clock Source
#define WS2812_SPI_CLKFREQ      CLOCK_GetFlexCommClkFreq(2U)          // WS2812 SPI Clock Frequency
#define WS2812_SPI_CLKSRC       kCLOCK_Flexcomm2     

void BoardSPI_Init(void)
{
	    spi_master_config_t masterConfig;
    
    /* Enables the clock for the I/O controller.: Enable Clock. */
    CLOCK_EnableClock(kCLOCK_Iocon);
    /* LED strip WS2812 Data Pin */
    IOCON->PIO[WS2812_DATA_PORT][WS2812_DATA_PIN] = (WS2812_DATA_FUNC  | WS2812_DATA_PINCFG );
    IOCON->PIO[BoardSPI_SCLK_PORT][BoardSPI_SCLK_PIN] = (WS2812_DATA_FUNC  | WS2812_DATA_PINCFG );
		/* Disables the clock for the I/O controller.: Disable Clock. To Save Power */
    CLOCK_DisableClock(kCLOCK_Iocon);

    /* attach 12 MHz clock to SPI3 */
    CLOCK_AttachClk(WS2812_SPI_CLKATTACH);
    /* reset FLEXCOMM2 for SPI */
    RESET_PeripheralReset(WS2812_SPI_RST);
    
    /* Initialize SPI master with configuration. */
    /* SPI init */

    SPI_MasterGetDefaultConfig(&masterConfig);
    
    /* (0.4+0.85uS) or (0.8+0.45uS) = 1.25uS/8bit = 0.15625uS ---> Use 6.4MHz  */
    masterConfig.baudRate_Bps = 15000000;
    masterConfig.dataWidth = kSPI_Data16Bits;
   
    masterConfig.sselNum = (spi_ssel_t)0;
    masterConfig.sselPol = (spi_spol_t)kSPI_SpolActiveAllLow;
    SPI_MasterInit(SPI2, &masterConfig, WS2812_SPI_CLKFREQ);
    
}

uint8_t SPI_ReadWrite(uint8_t TxData)
{
    volatile uint32_t i;
    volatile uint32_t temp;
   
        /* clear tx/rx errors and empty FIFOs */
        WS2812_SPI->FIFOCFG  |= SPI_FIFOCFG_EMPTYTX_MASK | SPI_FIFOCFG_EMPTYRX_MASK;  // 清空 RX FIFO内容 和 TX FIFO的内容
        WS2812_SPI->FIFOSTAT |= SPI_FIFOSTAT_TXERR_MASK | SPI_FIFOSTAT_RXERR_MASK;   // 清空错误标志位
        WS2812_SPI->FIFOWR    = TxData| 0x07300000;
        /* wait if TX FIFO of previous transfer is not empty */
        while ((WS2812_SPI->FIFOSTAT & SPI_FIFOSTAT_RXNOTEMPTY_MASK) == 0);

        temp = (uint8_t)((WS2812_SPI->FIFORD)&0x000000FF);  // 
 return temp;
}

LCD屏幕初始化程序

void LCD_Init(void)
{
	BoradLcdGPIO_Init();
	BoardSPI_Init();
LCD_RES_Clr();//复位
	systick_delay_ms(100);
	LCD_RES_Set();
	systick_delay_ms(100);
	
	LCD_BLK_Set();//打开背光
  systick_delay_ms(100);
	
	//************* Start Initial Sequence **********//
	LCD_WR_REG(0x11); //Sleep out 
	systick_delay_ms(120);              //Delay 120ms 
	//************* Start Initial Sequence **********// 
	LCD_WR_REG(0xCF);
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0xC1);
	LCD_WR_DATA8(0X30);
	LCD_WR_REG(0xED);
	LCD_WR_DATA8(0x64);
	LCD_WR_DATA8(0x03);
	LCD_WR_DATA8(0X12);
	LCD_WR_DATA8(0X81);
	LCD_WR_REG(0xE8);
	LCD_WR_DATA8(0x85);
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0x79);
	LCD_WR_REG(0xCB);
	LCD_WR_DATA8(0x39);
	LCD_WR_DATA8(0x2C);
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0x34);
	LCD_WR_DATA8(0x02);
	LCD_WR_REG(0xF7);
	LCD_WR_DATA8(0x20);
	LCD_WR_REG(0xEA);
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0x00);
	LCD_WR_REG(0xC0); //Power control
	LCD_WR_DATA8(0x1D); //VRH[5:0]
	LCD_WR_REG(0xC1); //Power control
	LCD_WR_DATA8(0x12); //SAP[2:0];BT[3:0]
	LCD_WR_REG(0xC5); //VCM control
	LCD_WR_DATA8(0x33);
	LCD_WR_DATA8(0x3F);
	LCD_WR_REG(0xC7); //VCM control
	LCD_WR_DATA8(0x92);
	LCD_WR_REG(0x3A); // Memory Access Control
	LCD_WR_DATA8(0x55);
	LCD_WR_REG(0x36); // Memory Access Control
	if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x48);
	else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0X88);
	else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x28);
	else LCD_WR_DATA8(0xE8);
	LCD_WR_REG(0xB1);
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0x12);
	LCD_WR_REG(0xB6); // Display Function Control
	LCD_WR_DATA8(0x0A);
	LCD_WR_DATA8(0xA2);
	
	LCD_WR_REG(0x21);
	LCD_WR_DATA8(0x00);

	LCD_WR_REG(0x44);
	LCD_WR_DATA8(0x02);

	LCD_WR_REG(0xF2); // 3Gamma Function Disable
	LCD_WR_DATA8(0x00);
	LCD_WR_REG(0x26); //Gamma curve selected
	LCD_WR_DATA8(0x01);
	LCD_WR_REG(0xE0); //Set Gamma
	LCD_WR_DATA8(0x0F);
	LCD_WR_DATA8(0x22);
	LCD_WR_DATA8(0x1C);
	LCD_WR_DATA8(0x1B);
	LCD_WR_DATA8(0x08);
	LCD_WR_DATA8(0x0F);
	LCD_WR_DATA8(0x48);
	LCD_WR_DATA8(0xB8);
	LCD_WR_DATA8(0x34);
	LCD_WR_DATA8(0x05);
	LCD_WR_DATA8(0x0C);
	LCD_WR_DATA8(0x09);
	LCD_WR_DATA8(0x0F);
	LCD_WR_DATA8(0x07);
	LCD_WR_DATA8(0x00);
	LCD_WR_REG(0XE1); //Set Gamma
	LCD_WR_DATA8(0x00);
	LCD_WR_DATA8(0x23);
	LCD_WR_DATA8(0x24);
	LCD_WR_DATA8(0x07);
	LCD_WR_DATA8(0x10);
	LCD_WR_DATA8(0x07);
	LCD_WR_DATA8(0x38);
	LCD_WR_DATA8(0x47);
	LCD_WR_DATA8(0x4B);
	LCD_WR_DATA8(0x0A);
	LCD_WR_DATA8(0x13);
	LCD_WR_DATA8(0x06);
	LCD_WR_DATA8(0x30);
	LCD_WR_DATA8(0x38);
	LCD_WR_DATA8(0x0F);
	
	LCD_WR_REG(0x29); //Display on
	
} 

SD卡驱动以及FATFS直接使用MCUXpresso Config Tools 生成即可,这里不做展示。

关键的步骤在于解析BMP文件格式,并显示在屏幕上,编写如下函数

void FATFS_BmpRead( char *filename,uint16_t x,uint16_t y,uint8_t ShowType)
{
		FIL  file;   
	FRESULT res;
	unsigned char buff[64];     
  unsigned int i,j;
	unsigned int px,py;
	unsigned int  temp,temp1,temp2;  

	unsigned int length,width;
	
  unsigned long filelen,alen,colorlist;
	unsigned long color;
	unsigned int  rb;
	unsigned char *work,*c_ptr,*pan_ptr;

	uint16 RowSize = 0,bit = 0;
	
	filelen=0;
	alen=0;
	
	if (f_open(&file,(char*)filename, FA_READ|FA_OPEN_ALWAYS) != FR_OK )  //打开图片  
	{
	    PRINTF("BMPFile %s cannot open!\n",filename);
	}
	else
	{
		// 从第54个数据开始是位图信息
		f_read(&file, buff, 54, &rb);//读取文件头信息54Byte   
		if(buff[0]==0x42 && buff[1]==0x4D)        //文件格式标记为BM
		{
			py=0;
			px=0;
			temp  = buff[18]+(buff[19]<<8);  //X
			temp1 = buff[22]+(buff[23]<<8);  //Y
			temp2 = buff[28];   // 文件类型
			
			length = temp1;
			width = temp;
			
			if(temp%2 != 0)
			{
				temp+=1;
			}
			PRINTF("BMP File Width=%u,Height=%u.\n",temp,temp1); 
			
			if(temp2 == 24 )
			{
				PRINTF("RGB888 color file \n");  
				
				filelen = buff[2]+(buff[3]<<8)+(buff[4]<<16)+(buff[5]<<24);  //读取文件长度
				
				colorlist =  buff[10]+(buff[11]<<8)+(buff[12]<<16)+(buff[13]<<24);		
					
					
				PRINTF(" filelen = %d , colorlist = %d  ",filelen, colorlist);
					
					
					
				// bmp文件解析思路 :
				// 如果每行的像素数量不为4,则需要自动将数据补位4的倍数 该值可以通过 (原					数据+3)/4后*4得到
				// 利用新数据-原数据即可得到补位数量,f_open函数每次读取一行的数据(新数					据)
				// 每次只刷新原数据的宽度,补位部分跳过
					
				RowSize = (((width + 3)>>2)<<2);
				bit = RowSize - width;
				
				PRINTF("RowSize = %d , bit = %d ",RowSize , bit );	
					
				// 利用sd卡读取数据显示到屏幕上
				PRINTF("lcd start");	
				// 首先设置LCD显示位置
				
					
				// bmp文件数据每行的数据必须是4的整数 存在用0进行填充的情况
	
				// 正向显示
				if	(ShowType == 0){
					
					for(i=0;i<length;i++)
					{
					
						for(j=0;j<width;j++) // 实际数据部分
						{
							
							uint16_t u16data;
							
							f_read(&file, buff,3, &rb);//读取当前像素位的24位数据	
							
							u16data = RGB888toRGB565(buff[0],buff[1],buff[2]);
											
							LCD_DrawPoint(x+j,y+i,u16data);
						}
						
						for(j=0;j<bit;j++) // 补位数据部分
						{
							uint16_t u16data;					
							f_read(&file, buff,3, &rb);//读取当前像素位的24位数据	
							
						}
					}		
			
			}
			
		}
	}
}

该程序 基于FATFS驱动以及LCD屏幕底层函数编写,主要实现的功能为,通过形参中写入文件地址以及文件名称,自动解析BMP文件格式的数据并显示在屏幕上。BMP文件是一种未经压缩的文件格式,相比于JPEG要简单,主要参考了CSDN上和BMP相关的博客完成开发,值得一提的是,由于windiwos系统的限制,BMP文件搁置会自动的将每行的像素数量定义为4的整数倍,虽然程序进行了一些技术处理,但是效果还没有到完美的状态。在写入时每一行的像素必须为4的整数倍方可正确显示。

直接调用即可。

FATFS_BmpRead("2:/456.bmp",24,80,1);
		systick_delay_ms(3000);
		FATFS_BmpRead("2:/789.bmp",48,60,2);
		systick_delay_ms(3000);

三.效果演示

FroMz7mYMomYXPJ0TyWqUOP08rfwFqR7Pq9CLMaRjgE-LdaLaMVUSpHF

四.感想以及收获

很感谢硬禾学堂的这次活动,在这次活动之前,我一直受限于配置工具、文件系统等的学习难度,没有进行相关的研究,更多使用传统的方案进行学习开发,使得开发效率较低,也很难学习到一些新的知识。这次也可以说突破了我的舒适区,进行了一些额外的尝试。同时也熟悉了LPC系列单片机的开发。总体来说 有很大的收获

附件下载
lpc55s69_sd_fatfs_lcd.zip
团队介绍
大学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号