Funpack11 使用LPC55S69播放Badapple
Funpack11 使用LPC55S69播放Badapple,在任务二要求上进行扩展,使用开发板完成了动画播放任务。
标签
嵌入式系统
显示
2x3j
更新2021-11-02
1219

项目描述及需要实现的功能

本次活动给出的可选任务如下:

任务一:

读取SD卡(SD卡自备)中的音频文件,使用板卡上的3.5mm音频接口播放音乐。

任务二:

读取SD卡中预先存入的图像,显示在屏幕上(OLED或LCD)。

任务三:

实现USB-UART Hub,自己设计通信方式,实现USB到多路UART的收发。

任务四:

移植Micropython,并实现串口通信和控制IO口。

 

本人对任务二进行了实现,使用LPCX55S69开发板配合使用IIC协议、的OLED屏幕,在显示图像的基础上完成了BadApple动画播放功能。

FqlnOcN_jAzOuniIj_dlx937hfyL

设计思路

本项目的设计思路总体比较简单,由以下两个功能组成:

  • SD卡文件读写

  • OLED屏幕刷新

本次活动采用的LPC55S69芯片性能非常强大,NXP公司官方的SDK中提供了较多示例代码,其中就包括SD卡读写的相关示例代码。在实现屏幕刷新功能时,主要使用U8g2库完成。

 

项目解析图片数据生成

项目使用的屏幕尺寸是128*64,为了能够正常显示,可以将图片提前处理成易于显示的格式。当获取视频文件后,使用ffmpeg工具将视频文件输出为图片,参考命令如下:

ffmpeg -i badapple.mp4 -r 30 -qscale:v 2 source/%07d.jpg

后续编写Python脚本,对每张图片进行处理,并将所有图片存储到一个DAT文件当中。对于一张图片的处理流程如下:

  • 使用PIL库读入图片文件,并将尺寸修改为128*64

  • 将图片转化为二值图

  • 将图片数据转化为一维数组,八位一组以字节形式写入到文件

 

from PIL import Image
import numpy as np
import os
​
​
​
def img2bmp(file, source_folder, aim_folder):
   im = Image.open(source_folder +
                   '/' + file)
​
   new_im_size = np.array([128, 64]).astype(int)
   im = im.resize(new_im_size)
​
​
   im = im.convert("L")
   threshold = 200     # 阈值
   table = []
   for i in range(256):
       if i < threshold:
           table.append(0)
       else:
           table.append(1)
   
   # 转化为二值图
   im = im.point(table, "1")
   im.save("test.png")
​
​
   # 写入二进制文件
   im_np = np.array(im)
   im_np = im_np.flatten()
   # print(im_np)
​
   # 已知共有128 64个像素点,每八位写入一次 共写入128*8=1024次
   fh = open('ba15.DAT', 'ab')
   for i in range(1024):
       start = i * 8
       end = start + 8
       cur_byte = ""
       for k in range(start, end):
           if im_np[k]:
               cur_byte += '1'
           else:
               cur_byte += '0'
​
       # print(cur_byte)
       cur_byte = cur_byte[::-1]   # u8g2按小端方式渲染 需要对字符串反转后再处理
       if cur_byte == '':
           print(1)
           continue
       fh.write(int(cur_byte, 2).to_bytes(1, 'big'))
​
​
source_folder = "source_15"
aim_folder = "out"
source_file = [f for f in os.listdir(source_folder)]
count = 0
for f in source_file:
   img2bmp(f, source_folder, aim_folder)
   count += 1
   print(count)
print("finish!")

 

SD卡读取及图片绘制

因为任务较为简单,加上对较为复杂的MCU开发经验不足,代码主要在官方SD卡示例上修改得到,同时参考了U8g2的应用笔记,主体如下:

/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
​
#include <stdio.h>
#include <string.h>
#include "fsl_sd.h"
#include "fsl_debug_console.h"
#include "ff.h"
#include "diskio.h"
#include "fsl_sd_disk.h"
#include "pin_mux.h"
#include "board.h"
#include "sdmmc_config.h"
#include <stdbool.h>
#include "fsl_iocon.h"
​
#include "fsl_power.h"
#include "driver_oled_ssd1306.h"
#include "u8g2.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/
​
/* buffer size (in byte) for read/write operations */
#define BUFFER_SIZE (1024U)
/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*!
 * @brief wait card insert function.
 */
static status_t sdcardWaitCardInsert(void);
​
/*******************************************************************************
 * Variables
 ******************************************************************************/
static FATFS g_fileSystem; /* File system object */
static FIL g_fileObject;   /* File object */
u8g2_t u8g2;
​
/*! @brief Data read from the card */
SDK_ALIGN(uint8_t g_bufferRead[BUFFER_SIZE],
          BOARD_SDMMC_DATA_BUFFER_ALIGN_SIZE);
/*******************************************************************************
 * Code
 ******************************************************************************/
​
/*!
 * @brief Main function
 */
int main(void) {
    FRESULT error;
    UINT bytesRead;
    const TCHAR driverNumberBuffer[3U] = {SDDISK + '0', ':', '/'};
    BYTE work[FF_MAX_SS];
​
    /* 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();
​
    CLOCK_EnableClock(kCLOCK_Gpio0);
    CLOCK_EnableClock(kCLOCK_Gpio1);
​
    /* setup display by different mode */
#ifdef SSD1306_USE_I2C_GPIO
    u8g2_Setup_ssd1306_i2c_128x64_noname_2(&u8g2, U8G2_R0, u8x8_byte_sw_i2c,
                                           u8x8_gpio_and_delay_lpc55);
#endif
#ifdef SSD1306_USE_I2C_HW
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(
        &u8g2, U8G2_R0, u8x8_byte_hw_i2c_lpc55, u8x8_gpio_and_delay_lpc55);
#endif
#ifdef SSD1306_USE_SPI_GPIO
    u8g2_Setup_ssd1306_128x64_noname_2(&u8g2, U8G2_R0, u8x8_byte_4wire_sw_spi,
                                       u8x8_gpio_and_delay_lpc55);
#endif
#ifdef SSD1306_USE_SPI_HW
    u8g2_Setup_ssd1306_128x64_noname_2(&u8g2, U8G2_R0,
                                       u8x8_byte_4wire_hw_spi_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;
    }
​
#if (FF_FS_RPATH >= 2U)
    error = f_chdrive((char const *)&driverNumberBuffer[0U]);
    if (error)
    {
        PRINTF("Change drive failed.\r\n");
        return -1;
    }
#endif
​
​
    error = f_open(&g_fileObject, _T("BA15.dat"), (FA_READ));
    if (error) {
        PRINTF("%d", error);
        PRINTF("Open file failed.\r\n");
        return -1;
    }
    while (1) {
        // 打开文件
        error = f_read(&g_fileObject, g_bufferRead, sizeof(g_bufferRead),
                       &bytesRead);
        if ((error) || (bytesRead != sizeof(g_bufferRead))) {
            PRINTF("Read file failed. \r\n");
            PRINTF("%d", error);
            return -1;
        }
​
        u8g2_ClearBuffer(&u8g2);
        u8g2_DrawXBMP(&u8g2, 0, 0, 128, 64, g_bufferRead);
        u8g2_SendBuffer(&u8g2);
    }
​
    if (f_close(&g_fileObject)) {
        PRINTF("\r\nClose file failed.\r\n");
        return -1;
    }
​
    while (true) {
    }
}
​
static status_t sdcardWaitCardInsert(void) {
    BOARD_SD_Config(&g_sd, NULL, BOARD_SDMMC_SD_HOST_IRQ_PRIORITY, NULL);
​
    /* SD host init function */
    if (SD_HostInit(&g_sd) != kStatus_Success) {
        PRINTF("\r\nSD host init fail\r\n");
        return kStatus_Fail;
    }
​
    /* wait card insert */
    if (SD_PollingCardInsert(&g_sd, kSD_Inserted) == kStatus_Success) {
        PRINTF("\r\nCard inserted.\r\n");
        /* power off card */
        SD_SetCardPower(&g_sd, false);
        /* power on the card */
        SD_SetCardPower(&g_sd, true);
    } else {
        PRINTF("\r\nCard detect fail.\r\n");
        return kStatus_Fail;
    }
​
    return kStatus_Success;
}

代码总体比较简单易懂,首先打开SD卡中的数据文件,接着使用f_read函数进行读取,并刷新屏幕。这里将输入缓冲区的尺寸设置为1024,读取一次得到的数据刚好用于刷新整个屏幕,同时不需要考虑额外的终止条件,只需要一直读取一直刷新直到文件末尾即可。

U8g2库在使用时需要指定对应的构造器,应用笔记中给出的示例代码对当前使用的协议进行了宏定义,只需要修改宏定义的值便能正确初始化,如果没有深度使用需要不去仔细研究U8g2构造原理也能正常刷新屏幕。

 

总结与心得体会

首先感谢Funpack活动提供了让我接触LPC55S69芯片的机会,在感叹官方SDK代码的丰富和工整的同时我也认识到了自己在MCU开发领域技能上的欠缺。平心而论,本项目实现的功能并不复杂,但对于只接触过arduino的我来说,IDE的使用确实是一个比较大的挑战。项目代码的构成方式、三方库导入方法、管脚分配,在不断地尝试和查找资料的过程中,我着实积累了一些开发经验。

 

参考:

如何移植U8g2 到LPC55(S)6x 并驱动单色OLED 屏幕 (nxp.com)

MSDFATFSAPIRM, Freescale MSD FATFS API - Reference Manual (nxp.com.cn)

u8g2绘制方法 - 简书 (jianshu.com)

Python 读写二进制文件 以及Numpy读写二进制文件 - 云远·笨小孩 - 博客园 (cnblogs.com)

附件下载
lpcxpresso55s69_sdcard_oled_2.zip
u8g2代码 需要解压到项目代码中
lpcxpresso55s69_sdcard_oled.zip
项目代码
BA15.DAT
badapple动画
团队介绍
团队成员
2x3j
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号