Funpack第11期 基于LPC55S69的实时OLED电脑显示器
在完成任务二的基础上,经过一番魔改,制作了一块能够实时显示电脑图像的OLED显示器
标签
Funpack参赛
LPC55S69
rt-thread
枫雪天
更新2021-11-01
842

任务介绍

    本项目实现了Funpack第11期活动的任务二,读取SD卡中预先存入的图像,显示在OLED屏幕上。

硬件平台

    本期活动的主角:恩智浦推出的LPC55S69开发板。它的主控芯片使用的是双核基于Cortex-M33的微控制器,在设计中能够灵活地平衡性能和功耗,这块开发板搭载了丰富的板载资源,预留了足够多的IO接口,并且配备了SD卡槽、加速度计和音频编解码器,我们可以用它实现各种复杂的功能

project-new.png

任务分析与实现

    对任务二做一个简单的分解,可以分成三个部分:SD卡读取、图像处理、OLED显示,我们依次实现它们

    首先是SD卡读取,这一部分实现比较简单,在官方SDK和RT-Thread的BSP中都做好了适配。我选用的是RT-Thread实现,需要在RT-Thread的配置中启用FAT文件系统的支持,重新编译,在工程中先将SD卡设备挂载文件系统,然后就可以通过标准的文件API读写数据。

FsJ2ofjh7yRTR4ASy5O7jfIKmUs6

    第二步是图像处理,因为OLED可以显示的图像数据和原始图像数据是不一样的,需要我们进行预处理,这里可以先在电脑上进行处理,将预处理后的图像文件放到SD卡上,这样可以简化单片机上的程序逻辑。我写了一个图像处理的Python程序,可以将任意格式和大小的原始图像,处理并转化为可以直接写入OLED显存的数据格式,并保存为一个文件。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PIL import ImageGrab, Image


############################### Global Variables ###############################

oledWidth  =  128  # Number of pixel columns handled by the OLED screen
oledHeight =  64   # Number of pixel rows managed by the OLED screen
threshold  =  127  # Below this contrast value, pixel is considered as activated

def getImage():
    # Check number of arguments
    if len(sys.argv) == 3:
        # Try to open the image
        try:
            im = Image.open(sys.argv[1])
            # im = ImageGrab.grab()
            im = im.convert(mode="L")
        except:
            print("Error: unable to grab", sys.argv[1], file=sys.stderr)
            exit(-1)

        # Check image dimensions
        width,height = im.size
        if (width != oledWidth or height != oledHeight):
            im = im.resize((oledWidth, oledHeight), Image.ANTIALIAS)
        
        return im
    else :
        print("Error: invalid number of arguments", file=sys.stderr)
        print("Usage:")
        print("python " + sys.argv[0] + " <input-filename> <output-bin-filename>")
        exit(-1)

def grabDesktop():
    im = ImageGrab.grab()
    im = im.convert(mode="L")
    im = im.resize((oledWidth, oledHeight), Image.ANTIALIAS)
    return im

def convert(pixels) :
    data = [[0 for x in range(oledHeight//8)] for x in range(oledWidth)]

    for i in range(oledWidth):
        for j in range(oledHeight//8):
            for bit in range(8):
                data[i][j] |= (pixels[i][j*8 + bit] << bit)
    return data

def toBinary(im):
    # Convert image to monochrome if necessary
    if (im.mode != "1"):
        im.convert("1")

    # Allocate array to hold binary values
    binary = [[0 for x in range(oledHeight)] for x in range(oledWidth)]

    # Convert to binary values by using threshold
    for j in range(oledHeight):
        for i in range(oledWidth):
            value = im.getpixel((i, j))
            # print(value)
            # Set bit if the pixel contrast is below the threshold value
            binary[i][j] = int(value < threshold)

    return binary

def output_file(data):
    buffer = b''
    for j in range(oledHeight//8):
        for i in range(oledWidth):
            buffer += data[i][j].to_bytes(1, 'little')

    # print(buffer)
    header_file = open(sys.argv[2], "wb")
    header_file.write(buffer)
    header_file.close()
    return buffer

def output(data):
    buffer = b''
    for j in range(oledHeight//8):
        for i in range(oledWidth):
            buffer += data[i][j].to_bytes(1, 'little')
    return buffer

def getFrame():
    image = grabDesktop()
    binary = toBinary(image)
    data = convert(binary)
    return output(data)


#################################### Main ######################################

if __name__ == '__main__':
    image = getImage()
    binary = toBinary(image)
    data = convert(binary)
    output_file(data)
    # print(output_file(data))

    第三步是OLED显示,这一步我们同样可以使用RT-Thread提供好的SSD1306程序包,只需要在menuconfig中打开相应的配置。

FiO--F_ScqA56cLJ-8gQCLCt4k-r

    经过以上三步准备,我们编写一个60行代码的程序,读取SD卡中的数据,并写入OLED的显存,任务二就可以完成了。

static void SD_Picture(void* arg)
{
	  rt_thread_mdelay(100);
    //mkfs("elm","sd0");
    if(dfs_mount("sdcard0","/","elm",0,0)==0)
    {
        rt_kprintf("dfs mount success\n");
    }
    else
    {
        rt_kprintf("dfs mount failed\n");
				return;
    }
		
    uint8_t rbuf[1024] = {0};
    int rsize = 0;
    int fd = 0;

    fd = open("/mybin.bin.\n", O_RDONLY);
    if(fd>0)
    {
        rsize = read(fd, rbuf, 1024);
        close(fd);
        if(rsize>0)
        {
            rt_kprintf("READ (%d) byte.\n",rsize);
						ssd1306_Init();
						ssd1306_Fill(Black);
						ssd1306_FillBuffer(rbuf, sizeof(rbuf));
						ssd1306_UpdateScreen();
        }
    }
    else
    {
        rt_kprintf("can not open file.\n");
    }
}

效果展示

FpjgR0ffGfLXRRQCcr68XN4ac-qj

任务扩展-基于LPC55S69的实时OLED电脑显示器

   既然现在我们已经可以将任意图像显示到OLED上,能不能再进一步,把OLED变成一个实时的超低画质电脑显示器呢?这完全是可行的,但是为了保证数据流的实时传输,就不能使用SD卡读写的方法了,我们可以尝试基于串口、USB,甚至是网络来实现传输。

   由于手上正好有一块ESP8266模块,这里我决定使用网络来进行传输,首先将ESP8266模块写入标准的AT固件,这一点官网有教程,不再赘述。随后我们打开RT-Thread的网络以及AT设备支持,现在我们的开发板就可以支持网络通信了。

static uint32_t PlayStream(void)
{
    uint32_t frameId = 0;
    struct sockaddr_in client_addr;
    struct sockaddr_in server_addr;
    struct netdev *netdev = RT_NULL;
    int sockfd = -1;

    netdev = netdev_get_by_name("esp0");
    if (netdev == RT_NULL)
    {
        rt_kprintf("get network interface esp0 failed.\n");
        return -RT_ERROR;
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        rt_kprintf("Socket create failed.\n");
        return -RT_ERROR;
    }


    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_HOST);
    rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
    {
        rt_kprintf("socket connect failed!\n");
        closesocket(sockfd);
        return -RT_ERROR;
    }
    else
    {
        rt_kprintf("connect to server %s success!\r\n", g_serverIp);
    }

    frameId = 0;
    while (1) {
			
	uint32_t start = rt_tick_get();
        uint32_t request = htonl(frameId); // to big endian
        ssize_t retval = send(sockfd, &request, sizeof(request), 0);
        if (retval < 0) {
            break;
        }

        uint32_t status = 0;
        retval = recv(sockfd, &status, sizeof(status), 0);
        if (retval != sizeof(status)) {
            printf("lwip_recv status for frame %d failed or done, %d!\r\n", frameId, retval);
            break;
        }
        status = ntohl(status);
        if (status != STATUS_OK) {
            break;
        }

        ssize_t bodyLen = 0;
        retval = recv(sockfd, &bodyLen, sizeof(bodyLen), 0);
        if (retval != sizeof(bodyLen)) {
            printf("lwip_recv bodyLen for frame %d failed or done, %d!\r\n", frameId, retval);
            break;
        }
        bodyLen = ntohl(bodyLen);

        ssize_t bodyReceived = 0;
        while (bodyReceived < bodyLen) {
            retval = recv(sockfd, &g_streamBuffer[bodyReceived], bodyLen, 0);
            bodyReceived += retval;
        }
	    ssd1306_FillBuffer(g_streamBuffer, sizeof(g_streamBuffer));
	    ssd1306_UpdateScreen();
				
        frameId++;
	    uint32_t end = rt_tick_get();
	    printf("FPS: %.2f\r\n", RT_TICK_PER_SECOND / (float)(end - start));
    }
    printf("playing video done, played frames: %d!\r\n", frameId);

do_close:
    closesocket(sockfd);
    return frameId;
}

   随后,我们可以简单改写任务二的程序,把单片机中文件读写的逻辑改为TCP通信,把电脑端读取任意图像文件的程序改为抓取桌面图片,并启动一个TCP服务器,只要收到来自单片机的图片请求,就不断地通过TCP网络发送图像数据。经过这样一波魔改,开发板就成功地变成了一个电脑显示器,实际的效果可以在视频中看到。

活动感想

   很荣幸参加本期的Funpack活动,这也是我第三次参加Funpack系列的活动。通过这一次活动,我第一次正式地接触并使用LPC系列的单片机,在以往的印象里,NXP一直专注于汽车电子,门槛高,只有非常专业的领域大佬才能玩转。但这次我欣喜于LPC系列的配置工具和SDK支持的完善程度,甚至在RT-Thread中也对本次任务的LPC55S69-EVK开发板做了相对完善的板级支持,这对我们开发人员和爱好者来说是非常友好的。希望NXP能通过类似的活动加强宣传和推广,也进一步完善自己的开发工具和软件生态。

  最后,感谢硬禾学堂和得捷电子联合举办的Funpack活动,祝硬禾的活动越办越好!

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