您的位置:  首页 > 技术杂谈 > 正文

开发板连续显示图片 | BAD APPLE 万耦中的二次元世界

2022-01-17 17:00 https://my.oschina.net/u/5443273/blog/5400117 中国移动OneOS 次阅读 条评论

本文分享自中移OneOS微信公众号《万耦中的二次元世界!》,作者:小M哥。

 BAD APPLE原本是东方游戏里的一首歌曲,后来被加了一个MAD,由于MAD非常惊艳华丽,使得BAD APPLE被大家喜爱。

在程序员圈子里,有一个传言:“有屏幕的地方,就应该有bad apple”。于是乎,不管是示波器、点阵屏,还是液晶显示器,都表演起了刷bad apple的视频。

既然万耦开发板上也有屏幕,那么就应该满足“有屏幕的地方就应该有bad apple”的魔咒。

// 基础知识

什么是视频?

视频(Video)泛指将一系列静态影像以电信号的方式加以捕捉、记录、处理、储存、传送与重现的各种技术。

连续的图像变化每秒超过24帧(frame)画面以上时,根据视觉暂留原理,人眼无法辨别单幅的静态画面;

看上去是平滑连续的视觉效果,这样连续的画面叫做视频。

什么是视觉暂留原理?

视觉暂留(Persistence of vision)现象是光对视网膜所产生的视觉在光停止作用后,大脑神经网络中仍保留其影像0.1-0.4秒左右的图像,电影电视的拍摄和放映就是根据这个原理。

原因是视神经的反应速度造成的。

划重点:快速连续地播放图片,由于视觉暂留的原理,给人的感觉就是在播放视频。

实现方案

根据上面的基础知识,首先需要在万耦开发板上实现图片的显示。

要刷bad apple的视频,肯定需要很多张图片,那么就要解决图片从哪儿来、图片存在哪儿的问题。

先来计算一下一张图片的数据大小,万耦开发板上屏幕是1.3寸240x240像素的TFT屏幕,每一个像素点是16位的,即2个字节。那么一张图片的数据大小就是:240*240*2= 115200,即112.5kB。

按照1秒钟5张图片的速度来播放,这种情况下是能感觉到卡顿的,最好是1秒钟10张图片,能取得较流畅的效果。我们就按照1秒钟5张图片来计算,那么播放1秒钟的视频需要的存储空间为:112.5*5 = 562.5kB。

万耦开发板上的微处理器芯片是STM32L475VGT6,128kB RAM/1024kB Flash,除开系统本身占用的Flash空间之外,应该也就还剩余六七百kB的Flash空间,按照上面计算的存储空间要求,也就能存下不到2秒的视频图片。把图片存在芯片内部的Flash上,这个方案可能就行不通了。

内部的Flash存不下,万耦开发板上还有8MB的外部SPI Flash,计算一下能存多长时间的视频:8*1024/562.5=14.56秒。这才十几秒的视频,满足不了我们的远大理想。

OneOS有非常高效的驱动框架,带来了稳定易用的好处,但是丝毫没有影响性能。

串口驱动分层

这就带来了另外一个实现思路,图片数据放在电脑上面,通过串口以921600的高速波特率将图片数据实时发送到万耦开发板,万耦开发板收到数据后再实时显示出来,只要接收处理数据的速度够快、显示图片的速度够快,就能播放出视频来。由于是在线实时显示,不管多长时间的视频都可以播放出来,即便是个美国队长的电影,也没问题。

视频文件的转换

上面已经将实现方案大致定下来了,现在来按照既定思路实现以下。

首先去网上找到bad apple的视频,最好是高清的视频,下载到本地,因为我们需要先把视频转换成图片。

B站地址:https://www.bilibili.com/video/BV17K411s7pt?from=search&seid=9747428269435705565

将bad apple的视频下载好之后,然后还需要KMPlayer这个播放软件,KMPlayer可以把视频连续截屏成多张图片。在视频播放界面单击右键,在弹出的列表框里选择“捕获”->“画面:高级捕获”,如下图所示:

弹出高级捕获对话框:

第一步:输入保存路径
第二步:图片名称,这里自动是以数字命名,前缀自己随便输入,什么aaa、000都是可以的。后面的位数,图片数量有1千张以上的,就配置成4位,自动从0000开始,然后0001、0002这样依次累加。
第三步:图像格式,选择“位图(快)”
第四步:捕获的数量,选择“连续”,捕获的帧,选择“1秒内10帧”
第五步:捕获尺寸,要根据屏幕像素来选,我们万耦开发板上的屏幕是240x240像素的,那么我们可设置成480x480,或者240x240.
最后一步:先点KMPlayer的播放按钮,然后立即点捕获对话框的“开始”按钮,把视频播放整个过程都给捕获成图片。

捕获结果

到目前为止,我们已经将bad apple视频转换成了1500多张bmp图片了。

BMP位图是有一定格式的,直接通过串口发送到万耦开发板上进行显示的话,就需要万耦开发板对BMP图片进行解码,为了达到流畅的播放效果,需要解码的事情先不考虑。

BMP图片数据分成四个部分:

1. 文件头(bmp file header),提供文件的格式、大小等信息。
2. 位图信息头(bitmap information),提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息。
3. 调色板(color palette),可选的,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表。
4. 位图数据(bitmap data),就是真正的图像数据了。

关于位图数据,最常见的就是24位图,所谓的24位图,就是说一个像素的颜色信息用24位来表示,也就是说,对于三原色BRG,每一个颜色都用以字节(8)位来表示。除了24位图,还有1位(单色),2位(4色,CGA),4位(16色,VGA),8位(256色),16位(增强色),24位(真彩色)和32位等。

这里还要注意,24位图的图像数据是按照B(蓝)、G(绿)、R(红)的顺序排列的,而且是从左下角开始,一行一行的图像数据排列,一直到右上角结束。

把BMP图片的格式研究清楚后,就需要考虑如何把1500多张图片发送到万耦开发板上进行实时显示了。通过电脑发送数据给万耦开发板,大家首先想到的是串口,没错,就是UART。

串口是我们嵌入式系统里最基础的通信接口,打印调试日志、完成简单的数据通信都可以通过串口实现。

OneOS设备框架

OneOS有一套完善的设备框架以及丰富的驱动框架,方便对各种外设进行管理和使用。

设备管理架构

OneOS研发团队设计开发了嵌入式系统常用的各种驱动框架,也适配了各大主流的MCU芯片,具体详情可参考OneOS源码中的drivers目录,如下图所示的驱动类型:

丰富的驱动类型

上层应用直接调用os_device_open、os_device_read等标准API接口,即可对底层设备进行操作及读写。系统内部的调用流程如下如所示:

设备驱动调用流程

以我们万耦开发板上的STM32L475VGT6芯片为例,详细讲一下OneOS内部针对ST芯片的serial驱动实现流程。鉴于ST已经有完善的hal驱动库,OneOS的serial驱动就是基于官方hal库设计实现的,提炼出了一套统一的serial驱动框架,可兼容所有的STM32芯片。serial驱动读取串口数据的详细流程如下:

OneOS串口设备的读写

万耦开发板上除了调试串口外,一个串口连接了Wi-Fi模块,一个串口连接到了mini-PCIe上了,在预留的外部接口上还有个串口。通过查看原理图,发现引到外部接口的串口是STM32L475VGT6的PD5、PD6,也就是UART2。

读取串口数据,直接调用系统的标准接口os_device_read即可:

os_device_read指定从哪个串口设备读取数据,将数据保存在起始地址为buffer的内存空间,还有本次期望接收数据的大小size,os_device_read接口返回的是实际接收的数据长度。

当然,在读取数据之前需要先配置及打开相应的串口,os_device_control用于配置波特率、停止位、回调函数等,os_device_open用于打开串口设备,并附带打开串口的各种模式。

刷bad apple

有了以上的准备知识,就可以开始刷bad apple的视频了。

首先在OneOS源码目录下找到projects/stm32l475-cmcc-oneos工程,先通过STM32CubeMX工具打开UART2串口。

然后在用OneOS-Cube工具进行配置,将serial驱动的接收缓冲区修改为1024,高速数据收发的场景下,必须把接收缓冲区加大,不然可能会丢数据。
配置完之后,再用scons –ide=mdk5命令重新生成工程。

接着开始应用代码开发,我们创建两个task,一个task负责从串口读取数据,一个task负责将数据显示到屏幕。


/**
 ***********************************************************************************************************************
 * Copyright (c) 2020, China Mobile Communications Group Co.,Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * @file        bad apple.c
 *
 * @brief       User application entry
 *
 * @revision
 * Date         Author          Notes
 * 2020-02-20   OneOS Team      First Version
 ***********************************************************************************************************************
 */

#include <board.h>
#include <drv_cfg.h>

static void data_recv_task(void *parameter)
{
    int            ret;
    os_device_t   *uart;
    os_uint32_t    size;
    os_size_t      rx_cnt;
    unsigned int   length;
    unsigned char step, last_step;

    os_mb_init(&urx_mb, "urx_mb", mb_pool, sizeof(mb_pool) / sizeof(mb_pool[0]), OS_IPC_FLAG_FIFO);
    uart = os_device_find("uart2");
    OS_ASSERT(uart);

    struct os_device_cb_info cb_info = 
    {
        .type = OS_DEVICE_CB_TYPE_RX,
        .cb   = urx_done,
    };

    os_device_control(uart, IOC_SET_CB, &cb_info);

    os_device_open(uart, OS_SERIAL_FLAG_RX_NOPOLL | OS_SERIAL_FLAG_TX_NOPOLL | OS_DEVICE_OFLAG_RDWR);

    struct serial_configure config = OS_SERIAL_CONFIG_DEFAULT;
    config.baud_rate = BAUD_RATE_921600;
    ret = os_device_control(uart, OS_DEVICE_CTRL_CONFIG, &config);
    if (ret != 0)
    {
        os_kprintf("serial control fail %d\r\n", ret);
        goto failed;
    }

    while (1) {
        length = 0;
        last_step = 0;
        do {
            rx_cnt = os_device_read(uart, 0, pic_buffer+length, RX_BUFFER_SIZE-length);
            length += rx_cnt;
            if (rx_cnt == 0) {
                os_mb_recv(&urx_mb, &size, OS_IPC_WAITING_FOREVER);
            }
        }while(length < RX_BUFFER_SIZE);
        os_sem_post(&disp_sem);
    }

failed:
    os_device_close(uart);
}

static void disp_task(void *parameter)
{
    os_uint8_t    *rgb565;
    unsigned int i = 0, j = 0;
    unsigned int step = 0;
    unsigned char data;
    unsigned short *prgb;

    /* show CMCC logo */
    lcd_show_image(20, 50, 200, 61, gImage_cmcc);
    lcd_show_image(20, 150, 200, 52, gImage_oneos);

    rgb565 = os_malloc(240*120*2);
    OS_ASSERT(rgb565);

    while (1) {
        prgb = (unsigned short*)rgb565;
        os_sem_wait(&disp_sem, OS_IPC_WAITING_FOREVER);
        for (i = 0; i < 3600; i++) {
            data = pic_buffer[i];
            for (j = 0; j < 8; j++) {/* 每一位表示一个像素点 */
                if (data & (1 << j)) {
                    *prgb++ = WHITE;//white
                }
                else {
                    *prgb++ = BLACK;
                }
            }
        }
        lcd_show_image(0, 0, 240, 120, rgb565);

        prgb = (unsigned short*)rgb565;
        for (i = 3600; i < RX_BUFFER_SIZE; i++) {
            data = pic_buffer[i];
            for (j = 0; j < 8; j++) {
                if (data & (1 << j)) {
                    *prgb++ = WHITE;//white
                }
                else {
                    *prgb++ = BLACK;
                }
            }
        }
        lcd_show_image(0, 120, 240, 120, rgb565);
    }
}

负责显示的task,一开始就在等待一个信号量,收到信号量后,就直接调用lcd_show_image接口将buffer里的数据显示到屏幕上面。

根据上述介绍,BMP图片是不能直接发送到开发板上面进行显示的。我们还做了一个转换工具,用于将BMP图片转换成能直接在开发板上显示的数据文件。

先发送一张图片,测试万耦开发板能否正确地将图片显示出来。如果能显示出来,再用串口工具sscom5.13.1将所有的图片数据连续不断地发送给万耦开发板来进行显示。为了能达到流畅的效果,串口波特率设置为921600,发送文件设置选择连续发送。

串口工具设置

现在已经可以正常显示图片了,但是显示连续图片还是没有达到预期,卡顿严重。

提升性能

由于一张图片的数据就有112.5kB,将很多张图片连续显示出来,感觉是在播放幻灯片,卡顿很严重。

我们发现bad apple的视频是黑白色的,根据这个特点,我们可以大幅减小一张图片的有效数据。一个像素点要么是黑色要么是白色,只需要用0和1来表示即可,之前一个像素点需要2byte,现在一个像素点只需要1bit,数据量就减少了16倍。

其实这就是二值化的原理,重新修改优化我们的转换工具,将BMP图片二值化处理后,每一个像素点仅用1bit表示,8个像素点的数据压缩成1字节。压缩之后的数据量就非常小了,再将压缩后的数据通过串口工具发送到万耦开发板上。

再优化显示task的代码,由于收到的数据是压缩后的数据,在显示之前还得将数据还原,毕竟写入到屏幕的数据,仍然是每一个像素点需要2字节。将一字节的数据,还原成8个像素点的数据,也就是 16字节,再将还原后的数据写入到万耦开发板的屏幕上。

点击跳转视频链接

总的来说,在万耦开发板上刷bad apple视频的流程就是:

OneOS是中国移动针对物联网领域推出的轻量级操作系统,具有可裁剪、跨平台、低功耗、高安全等特点,支持ARM Cortex-M/R/A、MIPS、RISC-V等主流CPU架构,兼容POSIX、CMSIS等标准接口,支持Micropython语言开发,提供图形化开发工具,能够有效提高开发效率并降低开发成本,帮助客户开发稳定可靠、安全易用的物联网应用。 官网地址:https://os.iot.10086.cn/
OneOS软件地址:http://www.oschina.net/p/cmcc-oneos
OneOS项目地址:https://gitee.com/cmcc-oneos/OneOS
OneOS技术交流群:158631242

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接