Funpack第11期用LPC55S69在OLED上显示jpg图片
Funpack 第11期 ,LPC55S69,SD卡,i2c,Oled,显示图片,jpg解码,u8g2
标签
嵌入式系统
测试
显示
aramy
更新2021-10-21
1185

 

硬件介绍
funpack第11期活动是基于NXP的LPCX55S69开发板。刚拿到这块开发板,人是懵的。板子很大,LPCXpresso55S69是QFP100的封装,超多的GPIO口。查看官网资料介绍:LPC55S6x/LPC55S2x/LPC552x 是基于 Arm Cortex®-M33 的嵌入式应用微控制器。主控配备高达 320 kB 的片上 SRAM、高达 640 kB 的片上闪存、带全速无晶振操作的高速和全速 USB 主机与设备接口、一个 SD/MMC/SDIO 接口、五个通用定时器,一个 CTimer/PWM、一个 RTC/警报定时器、一个 24 位多速率定时器(MRT)、一个窗口看门狗定时器(WWDT)、一个高速 SPI(50 MHz)、八个灵活串行通信外设(每个外设可以是 USART、SPI、I2C 或 I2S 接口)、一个 16 位 1.0 Msamples/sec ADC、温度传感器。并且官网提供了开发工具IDE。可以通过USB线方便地烧写调试板子。

 任务:
LPCXpresso55S69功能很强大,但是就是功能强大,感觉很难上手。在拿到板子后,自己尝试玩了一下,仅仅实现了LED灯的点亮,用官方IDE生成的项目感觉功能太多,不知如何入手。等到了直播课,听老师详细讲解后,才大概有了个前进的方向。这里选择了任务2:读取SD卡中预先存入的图像,显示在屏幕上(OLED或LCD)。正好手头有个SSD1306的128x64的oled屏幕,就选择了这个任务。

实现过程
1 SD卡的读取。板子上有个微型SD卡插槽。官网提供的例程里有读取SD卡的例程,按提示一步步的用例程建立好MDK的工程文件做测试,发现手头的一张小的SD卡(250M),无法读取,另一张32G的SD卡,测试就OK,原因尚不清楚,就先用32G的SD卡来做测试啦。
FjsTkaI2N2WeH0Pe4LZpCG74fulh
FrKvWhNXhuz4ZhfNczJ6miKHsSdZ
2 OLED显示屏。手头正好有一块SSD1306的Oled屏幕,是I2C接口的,接的正好是mikroBUS,可以很方便地接到开发板上。参考着恩智浦的官网论坛中有一些例子讲的很详细,用来做参考,跟着一步一步地做。在官网的例程(sdcard_fatfs)中drivers添加i2c的驱动文件,然后添加U8G2的包。使用的Oled是I2C接口ssd1306驱动的硬件,所以初始化屏幕。

//初始化OLED
void oled_init(){
	u8g2_Setup_ssd1306_i2c_128x64_noname_2(&u8g2, U8G2_R0, u8x8_byte_hw_i2c_lpc55, u8x8_gpio_and_delay_lpc55);
	u8g2_InitDisplay(&u8g2);
  u8g2_SetPowerSave(&u8g2, 0);
}

3 图片解码和显示。使用的显示屏是Oled屏幕,分辨率为128x64,颜色为蓝色的单色屏幕。所以显示用图片不能够太大,并且最终颜色都将转为单色显示。最简单的就是直接显示单色位图,但是这么强大的开发板,仅仅显示单色位图,太浪费了。参考了论坛的例子,在这里使用了jpg的图片。使用tjpgd的库来对jpg图片进行解码。解码库地址:https://github.com/RT-Thread-packages/TJpgDec。这里要留意jpg解码使用了malloc和free内存操作,TJpgDec解码最少需要3K多的堆空间用于内部内存分配,工程默认的堆和栈就不够用了。在keil里,设置一下这两个地方。
FiXDAfLyGUheYnRYtXSKo6AieAh7FmvSXqYGiHfKiakO_mTcr1DVqz2K
这里单片机处理图片和平时电脑编程中处理图片方式有所不同。电脑上习惯都是将图片文件整个读取后再做处理,单片机这里受内存的限制,就不能这么处理了。TJpgDec的库处理图片流程是每次读取图片的一小部分就调用一次处理函数,然后循环,直至遍历完整个图片。FnKcnofmBhustVL99TwEBQzQtrrY
按这个处理流程对图片进行处理。Decode_Jpg函数负责载入图片文件,然后申请一个3200的内存区域,依次读取图片文件,每次读取原图的一个小矩形,调用jd_decomp进行解码处理。jd_decomp函数有三个入口参数。jdec指向图片的指针。output_func是一个回调函数,每次解码一小块图片后就会调用这个函数,用户的处理可以写在这个回调函数里。scale缩放比例,这个缩放比例可以为【0~3】对应是原图的1、1/2、1/4、1/8。最多就只能为原图的1/8倍。

IODEV devid; /* User defined device identifier */
int Decode_Jpg(uint8_t *file_name,uint16_t x,uint16_t y){
	void *work;  /* Pointer to the decompressor work area */
	JDEC jdec;   /* Decompression object */
	JRESULT res; /* Result code of TJpgDec API */
	uint8_t scale;

	/* Open a JPEG file */
	res=f_open(&devid.hin,(char*)file_name, FA_READ|FA_OPEN_ALWAYS);
	if ( res!= FR_OK ) return -1;
	_sx = x;
	_sy = y;
	/* Allocate a work area for TJpgDec */
	work = malloc(3200);
	if(work == NULL) return -1;
	/* Prepare to decompress */
	res = jd_prepare(&jdec, input_func, work, 3200, &devid);
	if (res == JDR_OK){			//如果读取图片成功
		if((jdec.width/OLED_W)>(jdec.height/OLED_H)){
			scale = (uint8_t)(sqrt((jdec.width-1)/(float)OLED_W)+0.5);
			//PRINTF("W  scale:%d\n", scale);
		}else{			
			scale = (uint8_t)(sqrt((jdec.height-1)/(float)OLED_H)+0.5);
			//PRINTF("Here  scale:%d,[%d,%d]\n", scale,(jdec.height-1),OLED_H);
		}
		if(scale>3)		scale=3;		
		//scale=0;
		//PRINTF("W:H=%d * %d,Scale=%d\n", jdec.width,jdec.height,scale);
		/* Ready to dcompress. Image info is available here. */
		res = jd_decomp(&jdec, output_func, scale);   /* Start to decompress with 1/2 scaling */
		
	} else 	{
		PRINTF("Failed to prepare: rc=%d\n", res);
	}
	free(work);             	/* Discard work area */
	f_close(&devid.hin);       /* Close the JPEG file */
	return res;
}
uint16_t output_func (JDEC* jd,		/* Decompression object */void* bitmap,	/* Bitmap data to be output */  JRECT* rect		/* Rectangular region to output */){
	uint8_t *src,x=0,y=0,w=0,h=0,point,bmp[32];
	uint16_t points=0;
	IODEV *dev = (IODEV*)jd->device;
	/* Put progress indicator */
	//if (rect->left == 0){
	//	PRINTF("%u%%", (rect->top << jd->scale) * 100UL / jd->height);
	//}
	src = (uint8_t*)bitmap;
	x=rect->left + _sx;
	y=rect->top + _sy;
	w=(rect->right - rect->left)+1;
	h=(rect->bottom - rect->top)+1;
	points=w*h;				//一共的点数
	//PRINTF("[%d,%d,%d,%d]\r\n",rect->left + _sx,rect->top + _sy,(rect->right - rect->left)+1,(rect->bottom - rect->top)+1);
	//PRINTF("[%d,%d,%d,%d]\r\n",x,y,w,h);
	//PRINTF("[");
	//for(int i=0;i<32;i++) bmp[i]=0x00;
	memset(bmp,0,32);
	for(int i=0;i<points;i++){
		//PRINTF("%X,",src[i*2+1]*256+src[i*2]);
		//把每一个点转为二值化的值
		point=rgb565togray(src[i*2+1]*256+src[i*2]);
		bmp[i/8]=(bmp[i/8]<<1)+point;
		//PRINTF("%X ",bmp[i/8]);
	}
	//PRINTF("\r\n");
	//PRINTF("%d,%d,    %X,%X,%X,%X,%X,%X,%X,%X\r\n",x,y,bmp[0],bmp[1],bmp[2],bmp[3],bmp[4],bmp[5],bmp[6],bmp[7]);
	//PRINTF("%d,%d,%d,%d\r\n",x,y,w,h);
	drawsubimg(x,y,w,h,bmp);
	//lcd_show_image(rect->left + _sx,rect->top + _sy,(rect->right - rect->left)+1,(rect->bottom - rect->top)+1,src);
	//lcd_fill_buff(rect->left + _sx, rect->top + _sy, rect->right  + _sx,rect->bottom + _sy,src);
	return 1;	/* Continue to decompress */
}

RGB565是使用两个字节16位数据表示一个点的信息。高5位为R分量,中间6位为G分量,低5位为B分量。先将RGB三色按  Grey = 0.299*R + 0.587*G + 0.114*B  这个公式转换为灰度图。代码中我使用的公式是 gray = (r_color*30 + g_color*59 + b_color*11+50)/100 这是标准公式的一个变形,隐去了浮点计算,改为整形计算,应该会节约计算资源。灰度图每个点的范围是0~255,所以这里使用127作为阈值,直接将数据二值化。二值化后的数据调用u8g2的drawsubimg函数就可以在Oled上显示了。

//RGB565 转 二值图
uint8_t rgb565togray(uint16_t sour_color){
	//先把RGB565转RGB值
	uint8_t  r_color = 0, g_color = 0, b_color = 0,gray=0;
	r_color = ((sour_color>>11)&0xff)<<3;
	g_color = ((sour_color>>5)&0x3f)<<2;
	b_color = (sour_color&0x1f)<<3;
	//再把彩色转灰度图
	gray = (r_color*30 + g_color*59 + b_color*11+50)/100;
	//PRINTF("%X  [%d,%d,%d G:%d]\r\n",sour_color, r_color,g_color,b_color,gray);
	if(gray>127) return 1;
	else return 0;
}

图片经过缩小——灰度化——二值化后细节丢失严重,显示出的图片只能和原图神似了。
FpLPYmhWawArBLgEwu-H7qm333e0Fmt6xy91W37BLOhB_-xBk3AaGSY-Fm1-_ho_sRRVMZ_JU_11UhErg1y0FvRyG1XznH9-yP2NkxN_LIxEL-SB

 

心得体会
感谢funpack带来的这期活动。让我接触到了NXP的这款优秀的开发板。向往着LPCX55S69双核强大的功能,可以在硬禾学堂中跟着大神们学习,体味着玩单片机带来的快乐!

附件下载
LPC55S69_oled_disp_jpg.7z
团队介绍
团队成员
aramy
单片机业余爱好者,瞎捣鼓小能手。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号