一、概述
本次设计使用LPC55S69-EVK开发板,实现了读取SD卡上BMP格式的图片,并显示到OLED单色屏。开发板依次读取3张图片,即NXP、DigiKey、硬禾学堂等3个logo,进行循环显示。
系统硬件使用了硬禾学堂赠送的mikroBUS扩展模块,模块自带1块0.96寸OLED显示屏。显示屏通过I2C接口与开发板通讯。
程序中主要是对BMP图像进行解码,调用u8g2库完成显示功能。
二、具体设计
2.1硬件准备
LPC55S69-EVK开发板 1块 NXP公司
MikroBUS模块(带OLED) 1块 硬禾学堂
SD卡(2G容量) 1张
Micro USB下载线 1根
LPC55S69-EVK开发板是NXP公司推出的,LPC55S6x MCU家族是全球首款基于通用Cortex-M33的微控制器,控制器为双核结构,运行频率高达100MHz。
开发板具有USB、SD卡插槽、音频输入输出、I2C、SPI等接口,并支持mikroBUS接口扩展。
MikroBUS模块(带OLED)是硬禾学堂在Funpack第7期赠送的,此次主要用到模块上的OLED显示屏,显示屏大小为0.96寸,分辨率128*64,色彩为单色。
由于MikroBUS是标准接口,只需将模块插到开发板即可使用。
下载时,使用开发板的USB端口下载,端口号为P6。
2.2软件开发
2.2.1 软件工具
MCUXpresso IDE v11.4.1_6260:NXP官方IDE。
SDK_2_10_1_LPCXpresso55S69
U8g2库:用于驱动OLED显示屏。
PCtoLCD2002:用于取字模,该软件主要用于早期开发时,测试u8g2显示字模数组用。
WinHex:用于打开BMP文件,查看二进制数据。
FlashMagic:开发板异常时,刷Hex文件用。
2.2.2 实现过程
本次任务完成的思路和过程
本人先测试u8g2显示部分,使用OLED屏幕显示字符或汉字,确保屏幕驱动及硬件正常。
然后,使用了PCtoLCD2002.exe生成了一个字模数组,测试u8g2的 BMP函数功能正常。
最后,开发完成操作SD卡的BMP文件并显示的功能。
下面重点介绍操作BMP文件并显示的程序的具体操作步骤。
其中,最关键的步骤是BMP解码及U8g2显示。
为操作简便,此次操作的图片都提前进行了处理。因采用的OLED分辨率只有128*64,所以将3张图片大小都编辑成128*64,类型都是单色BMP格式。
为正确解码,需要将图像、字模数组和BMP文件内容三者进行对比,找出规律。因程序可以识别字模数组并显示,只需将BMP文件解码成字模数组相同的格式即可。
以NXP图片为例
字模数组每行16个byte,共64行,上图主要显示中间的非零行。
BMP文件内容(部分)
根据BMP文件格式信息可知,BMP文件前面62个byte都是文件头、信息头、调色板等信息,主要包含了图像类型、大小、高度、宽度、偏移量等信息,见参考文献3。只有第63个byte开始才是图像内容。上图中,第一个红框表示图片内容的起始行,第二个红框标出了第一个非空行。图像内容也有64行,上图主要截取了前半部分。
将字模数组和BMP文件比较后可知:
相同点:对于单色BMP位图,二者都是由16*64的Byte矩阵表示像素颜色。1个byte表示8个像素,因此,恰好每行128个像素,共64行。
不同点:
a)总体来看,行号不一样。
字模数组,与图像对应关系是从第一行开始,从左到右,从上到下。
BMP文件,与图像对应关系是从最后一行开始,从左到右,从下到上。
所以,对应行号时,BMP文件的第1行,对应的是数组的最后1行;数组的第1行,对应的是BMP文件的最后1行。
b)具体到每个byte来看,最高位的位置不一样。
字模数组,8个像素1组,靠左的是低位。
BMP文件,8个像素1组,靠左的是高位。
比较BMP文件非空第1行和字模数组非空最后1行,二者对应关系如下。
字模数组里面,0x03对应BMP文件里是0xC0,即二进制0000 0011和1100 0000。
因此,需要将读到BMP文件的内容进行转换和对应,转换成和字模数组相同的格式,才能用u8g2正确显示。
编程开始前,先将SD卡插到读卡器,格式化,创建一个MEDIA文件夹,并放置3张预先处理过的BMP文件,图像大小128*64,类型都是单色BMP格式。
2.2.3主要代码
bmp_decode.c 文件主要代码
unsigned char byte_change(unsigned char data)
{
data = ((data & 0xAA) >> 1) | ((data & 0x55) << 1);
data = ((data & 0xCC) >> 2) | ((data & 0x33) << 2);
data = (data >> 4) | (data << 4);
return data;
}
void BMPShow(char *filename,uint16_t x,uint16_t y)
{
FIL file;
unsigned char buff[64];
unsigned int i,j;
unsigned int m,n;
unsigned int px,py;
unsigned int iWidth,iHeigth,iBitCount;//iWidth,iHeigth,iBitCount; //图像的宽度, 高度 ,每个像素所需色彩位数
unsigned int a,b;
unsigned long filelen,iOffbits,colorlist;//filelen文件大小 iOffbits偏移量
unsigned long color;
unsigned int rb;
unsigned int rb1;
unsigned char *work,*c_ptr,*pan_ptr;
filelen=0;
iOffbits=0;
if (f_open(&file,(char*)filename, FA_READ|FA_OPEN_ALWAYS) != FR_OK ) //打开图片
{
PRINTF("BMPFile %s cannot open!\n",filename);
}
else
{
f_read(&file, buff, 54, &rb);//读取文件头信息 54Byte
if(buff[0]==0x42 && buff[1]==0x4D) //文件格式标记为BM
{
py=0;
px=0;
iWidth = buff[18]+(buff[19]<<8); //X
iHeigth = buff[22]+(buff[23]<<8); //Y
iBitCount = buff[28];
if(iWidth%2 != 0)
{
iWidth+=1;
}
PRINTF("BMP File Width=%u,Height=%u.\n",iWidth,iHeigth);//图片宽度 高度
switch(iBitCount)
{
case 1: //双色 黑白图片
PRINTF("2 color file \n");
filelen = buff[2]+(buff[3]<<8)+(buff[4]<<16)+(buff[5]<<24); //读取文件长度
iOffbits=buff[10]; //初始时,偏移量=62
PRINTF("BMP Offbits =%u \n",iOffbits);
colorlist = iWidth;
if(iWidth%32 != 0)
{
iWidth=(((iWidth>>5)+1)<<5);
}
work = (unsigned char*)malloc(480>>3);
while(py<iHeigth)
{
if(work == NULL)
{
while(px<iWidth)
{
f_lseek(&file,iOffbits);
f_read(&file, buff, 1, &rb);//buff[0]=fgetc(file);
iOffbits +=1;
for(i=0;i<8;i++)
{
if(buff[0]&0x80)
{
//color=GLCD_COLOR_WHITE;
}
else
{
//color=GLCD_COLOR_BLACK;
}
buff[0]=(buff[0]<<1);
//lcd_point(px+i+x,iHeigth-py+y-1,color);
}
px+=8;
}
}else
{
f_lseek(&file,iOffbits);//找到偏移的位置
f_read(&file, work, 16, &rb);//读取偏移量指定的一行
// bmp2里面一共有16*64,共计1024个数据,行头元素分别是0 16 32 48.。。。。1008
for(m=0;m<16;m++)
{
bmp2[1008+62-iOffbits+m]=byte_change(work[m]);
//将此行读到的每个数据倒向后,赋值给bmp2
}
iOffbits += 16;//偏移量每次增加16
//PRINTF("BMP Offbits =%u \n",iOffbits);
}
px=0;
py++;
if (iOffbits>=1070)
{
u8g2_FirstPage(&u8g2);
do
{
u8g2_SetFontMode(&u8g2, 1);
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
u8g2_DrawXBMP(&u8g2,0,0, bmp_x, bmp_y, bmp2);
} while( u8g2_NextPage(&u8g2) );
}
if(iOffbits > 1070) //读完整个文件
{
break;
}
}
if(work != NULL) free(work);
break;
default :
PRINTF("- not 32/24/16 bmp color file \n");
}
}
f_close(&file);
PRINTF("BMP File OK.\n");
}
}
sdcard_fatfs.c 主要代码
int main(void)
{
const TCHAR driverNumberBuffer[3U] = {SDDISK + '0', ':', '/'};
/* set BOD VBAT level to 1.65V */
POWER_SetBodVbatLevel(kPOWER_BodVbatLevel1650mv, kPOWER_BodHystLevel50mv, false);
CLOCK_EnableClock(kCLOCK_InputMux);
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
BOARD_InitPins();
BOARD_BootClockPLL150M();
BOARD_InitDebugConsole();
PRINTF("FATFS example to demonstrate how to use FATFS with SD card.\r\n");
PRINTF("Please insert a card into board.\r\n");
#ifdef SSD1306_USE_I2C_HW
u8g2_Setup_ssd1306_i2c_128x64_noname_2(&u8g2, U8G2_R0, u8x8_byte_hw_i2c_lpc55, u8x8_gpio_and_delay_lpc55);
#endif
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
if (sdcardWaitCardInsert() != kStatus_Success)
{
return -1;
}
if (f_mount(&g_fileSystem, driverNumberBuffer, 0U))
{
PRINTF("Mount volume failed.\r\n");
return -1;
}
scan_files(path);
while(1)
{
if(iPageID==0)
{ BMPShow("2:/MEDIA/nxp.bmp",0,0);
iPageID=1;
SDK_DelayAtLeastUs(3000000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
}
else if(iPageID==1)
{ BMPShow("2:/MEDIA/digikey.bmp",0,0);
iPageID=2;
SDK_DelayAtLeastUs(3000000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
}
else if(iPageID==2)
{ BMPShow("2:/MEDIA/yhxt.bmp",0,0);
iPageID=0;
SDK_DelayAtLeastUs(3000000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
}
}
}
三、演示效果
开发板上电,依次循环显示三个LOGO.
四、心得体会
4.1 如何配置IDE
需要注意设置头文件和源文件路径。
4.2 如何刷hex文件
进入ISP模式,直接刷flash。
4.3 如何配置Keil
待完成
参考文献
[1]如何利用u8g2在LPC55的IOTKIT上驱动oled并跑两个小游戏
[2]LPC55S69+tf卡+lcd实现lcd显示jpg图片