硬件介绍:
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卡来做测试啦。
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里,设置一下这两个地方。
这里单片机处理图片和平时电脑编程中处理图片方式有所不同。电脑上习惯都是将图片文件整个读取后再做处理,单片机这里受内存的限制,就不能这么处理了。TJpgDec的库处理图片流程是每次读取图片的一小部分就调用一次处理函数,然后循环,直至遍历完整个图片。
按这个处理流程对图片进行处理。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;
}
图片经过缩小——灰度化——二值化后细节丢失严重,显示出的图片只能和原图神似了。
心得体会:
感谢funpack带来的这期活动。让我接触到了NXP的这款优秀的开发板。向往着LPCX55S69双核强大的功能,可以在硬禾学堂中跟着大神们学习,体味着玩单片机带来的快乐!