一.自我介绍和项目介绍
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);
三.效果演示
四.感想以及收获
很感谢硬禾学堂的这次活动,在这次活动之前,我一直受限于配置工具、文件系统等的学习难度,没有进行相关的研究,更多使用传统的方案进行学习开发,使得开发效率较低,也很难学习到一些新的知识。这次也可以说突破了我的舒适区,进行了一些额外的尝试。同时也熟悉了LPC系列单片机的开发。总体来说 有很大的收获