5267|7

18

帖子

3

TA的资源

一粒金砂(中级)

楼主
 

【复刻】Adafruit桌面火炉项目 [复制链接]

本帖最后由 Lucheni 于 2024-1-25 14:31 编辑

不久前在油管上看到了Adafruit的桌面火炉作品,感觉看起来很有意思,于是就尝试复刻下,此贴为复刻过程和注意点的总结。

个人感觉此项目的特点在于使用ESP32S3驱动RGB-666接口的高分辨率屏幕,且设置显示内容的方式只需将视频转为MJPEG格式后存入sd卡即可,十分简便。


项目教程页面:https://learn.adafruit.com/qualia-s3-fireplace

如图,整个项目的电路组件全部来自于Adafruit,包括了5800 Qualia ESP32-S3主控板,4682 Adafruit Micro SD分线板,5797 Rectangle Bar RGB TTL TFT Display - 3.2" 320x820 with Cap Touch以及一张tf卡

此图为主控板与tf卡分线板的电路连接,其实个人感觉这块主控板完全可以板载tf卡接口,不过可能是考虑到后期项目加装外壳后,tf卡槽需要在外壳边缘位置,而这块开发板的左边沿已经有了三个按键和type-c口,所以选择这种后期用电线焊接连接tf卡分线板的方式。
项目代码如下
// SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
//
// SPDX-License-Identifier: MIT

/*******************************************************************************
 * Motion JPEG Image Viewer
 * This is a simple Motion JPEG image viewer example

encode with
ffmpeg -i "wash.mp4" -vf "fps=10,vflip,hflip,scale=-1:480:flags=lanczos,crop=480:480" -pix_fmt yuvj420p -q:v 9 wash.mjpeg

 ******************************************************************************/
#define MJPEG_FOLDER       "/videos" // cannot be root!
#define MJPEG_OUTPUT_SIZE  (820 * 320 * 2)      // memory for a output image frame
#define MJPEG_BUFFER_SIZE (MJPEG_OUTPUT_SIZE / 5) // memory for a single JPEG frame
#define MJPEG_LOOPS        0

#include <Arduino_GFX_Library.h>
#include "Adafruit_FT6206.h"
//#include <SD.h>      // uncomment either SD or SD_MMC
#include <SD_MMC.h>

Arduino_XCA9554SWSPI *expander = new Arduino_XCA9554SWSPI(
    PCA_TFT_RESET, PCA_TFT_CS, PCA_TFT_SCK, PCA_TFT_MOSI,
    &Wire, 0x3F);
    
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    TFT_DE, TFT_VSYNC, TFT_HSYNC, TFT_PCLK,
    TFT_R1, TFT_R2, TFT_R3, TFT_R4, TFT_R5,
    TFT_G0, TFT_G1, TFT_G2, TFT_G3, TFT_G4, TFT_G5,
    TFT_B1, TFT_B2, TFT_B3, TFT_B4, TFT_B5,
    1 /* hsync_polarity */, 50 /* hsync_front_porch */, 2 /* hsync_pulse_width */, 44 /* hsync_back_porch */,
    1 /* vsync_polarity */, 16 /* vsync_front_porch */, 2 /* vsync_pulse_width */, 18 /* vsync_back_porch */
    //,1, 30000000
    );

Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
/* 3.2" bar */
    320 /* width */, 820 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
    expander, GFX_NOT_DEFINED /* RST */, tl032fwv01_init_operations, sizeof(tl032fwv01_init_operations));

Adafruit_FT6206 ctp = Adafruit_FT6206();  // This library also supports FT6336U!
#define I2C_TOUCH_ADDR 0x38
bool touchOK = false;

#include <SD_MMC.h>

#include "MjpegClass.h"
static MjpegClass mjpeg;
File mjpegFile, video_dir;
uint8_t *mjpeg_buf;
uint16_t *output_buf;

unsigned long total_show_video = 0;

void setup()
{
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  //while(!Serial) delay(10);
  Serial.println("MJPEG Video Playback Demo");

#ifdef GFX_EXTRA_PRE_INIT
  GFX_EXTRA_PRE_INIT();
#endif

  // Init Display
  Wire.setClock(400000); // speed up I2C 
  if (!gfx->begin()) {
    Serial.println("gfx->begin() failed!");
  }
  gfx->fillScreen(BLUE);

  expander->pinMode(PCA_TFT_BACKLIGHT, OUTPUT);
  expander->digitalWrite(PCA_TFT_BACKLIGHT, HIGH);

  //while (!SD.begin(ss, SPI, 64000000UL))
  //SD_MMC.setPins(SCK /* CLK */, MOSI /* CMD/MOSI */, MISO /* D0/MISO */);
  SD_MMC.setPins(SCK, MOSI /* CMD/MOSI */, MISO /* D0/MISO */, A0 /* D1 */, A1 /* D2 */, SS /* D3/CS */); // quad MMC!
  while (!SD_MMC.begin("/root", true))
  {
    Serial.println(F("ERROR: File System Mount Failed!"));
    gfx->println(F("ERROR: File System Mount Failed!"));
    delay(1000);
  }
  Serial.println("Found SD Card");

  //  open filesystem
  //video_dir = SD.open(MJPEG_FOLDER);
  video_dir = SD_MMC.open(MJPEG_FOLDER);
  if (!video_dir || !video_dir.isDirectory()){
     Serial.println("Failed to open " MJPEG_FOLDER " directory");
     while (1) delay(100);
  }
  Serial.println("Opened Dir");

  mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE);
  if (!mjpeg_buf) {
    Serial.println(F("mjpeg_buf malloc failed!"));
    while (1) delay(100);
  }
  Serial.println("Allocated decoding buffer");

  output_buf = (uint16_t *)heap_caps_aligned_alloc(16, MJPEG_OUTPUT_SIZE, MALLOC_CAP_8BIT);
  if (!output_buf) {
    Serial.println(F("output_buf malloc failed!"));
    while (1) delay(100);
  }

  expander->pinMode(PCA_BUTTON_UP, INPUT);
  expander->pinMode(PCA_BUTTON_DOWN, INPUT);

  if (!ctp.begin(0, &Wire, I2C_TOUCH_ADDR)) {
    Serial.println("No touchscreen found");
    touchOK = false;
  } else {
    Serial.println("Touchscreen found");
    touchOK = true;
  }
}

void loop()
{
  /* variables */
  int total_frames = 0;
  unsigned long total_read_video = 0;
  unsigned long total_decode_video = 0;
  unsigned long start_ms, curr_ms;
  uint8_t check_UI_count = 0;
  int16_t x = -1, y = -1, w = -1, h = -1;
  total_show_video = 0;

  if (mjpegFile) mjpegFile.close();
  Serial.println("looking for a file...");

  if (!video_dir || !video_dir.isDirectory()){
     Serial.println("Failed to open " MJPEG_FOLDER " directory");
     while (1) delay(100);
  }

  // look for first mjpeg file
  while ((mjpegFile = video_dir.openNextFile()) != 0) {
    if (!mjpegFile.isDirectory()) {
      Serial.print("  FILE: ");
      Serial.print(mjpegFile.name());
      Serial.print("  SIZE: ");
      Serial.println(mjpegFile.size());
      if ((strstr(mjpegFile.name(), ".mjpeg") != 0) || (strstr(mjpegFile.name(), ".MJPEG") != 0)) {
        Serial.println("   <---- found a video!");
        break;
      }
    }
    if (mjpegFile) mjpegFile.close();
  }

  if (!mjpegFile || mjpegFile.isDirectory())
  {
    Serial.println(F("ERROR: Failed to find a MJPEG file for reading, resetting..."));
    //gfx->println(F("ERROR: Failed to find a MJPEG file for reading"));

    // We kept getting hard crashes when trying to rewindDirectory or close/open dir
    // so we're just going to do a softreset
    esp_sleep_enable_timer_wakeup(1000);
    esp_deep_sleep_start(); 
  }

  bool done_looping = false;
  while (!done_looping) {
    mjpegFile.seek(0);
    total_frames = 0;
    total_read_video = 0;
    total_decode_video = 0;
    total_show_video = 0;

    Serial.println(F("MJPEG start"));
  
    start_ms = millis();
    curr_ms = millis();
    if (! mjpeg.setup(&mjpegFile, mjpeg_buf, output_buf, MJPEG_OUTPUT_SIZE, true /* useBigEndian */)) {
       Serial.println("mjpeg.setup() failed");
       while (1) delay(100);
    }
  
    while (mjpegFile.available() && mjpeg.readMjpegBuf())
    {
      // Read video
      total_read_video += millis() - curr_ms;
      curr_ms = millis();

      // Play video
      mjpeg.decodeJpg();
      total_decode_video += millis() - curr_ms;
      curr_ms = millis();

      if (x == -1) {
        w = mjpeg.getWidth();
        h = mjpeg.getHeight();
        x = (w > gfx->width()) ? 0 : ((gfx->width() - w) / 2);
        y = (h > gfx->height()) ? 0 : ((gfx->height() - h) / 2);
      }
      gfx->draw16bitBeRGBBitmap(x, y, output_buf, w, h);
      total_show_video += millis() - curr_ms;

      curr_ms = millis();
      total_frames++;
      check_UI_count++;
      if (check_UI_count >= 5) {
        check_UI_count = 0;
        Serial.print('.');
        
        if (! expander->digitalRead(PCA_BUTTON_DOWN)) {
          Serial.println("\nDown pressed");
          done_looping = true;
          while (! expander->digitalRead(PCA_BUTTON_DOWN)) delay(10);
          break;
        }
        if (! expander->digitalRead(PCA_BUTTON_UP)) {
          Serial.println("\nUp pressed");
          done_looping = true;
          while (! expander->digitalRead(PCA_BUTTON_UP)) delay(10);
          break;
        }
  
        if (touchOK && ctp.touched()) {
          TS_Point p = ctp.getPoint(0);
          Serial.printf("(%d, %d)\n", p.x, p.y);
          done_looping = true;
          break;
        }
      }
    }
    int time_used = millis() - start_ms;
    Serial.println(F("MJPEG end"));
    
    float fps = 1000.0 * total_frames / time_used;
    total_decode_video -= total_show_video;
    Serial.printf("Total frames: %d\n", total_frames);
    Serial.printf("Time used: %d ms\n", time_used);
    Serial.printf("Average FPS: %0.1f\n", fps);
    Serial.printf("Read MJPEG: %lu ms (%0.1f %%)\n", total_read_video, 100.0 * total_read_video / time_used);
    Serial.printf("Decode video: %lu ms (%0.1f %%)\n", total_decode_video, 100.0 * total_decode_video / time_used);
    Serial.printf("Show video: %lu ms (%0.1f %%)\n", total_show_video, 100.0 * total_show_video / time_used);
  }
}

 

结构部分此项目主要由一个包含了所有电路组件的方形外壳和在此外壳基础上的一个外轮廓组成,本次复刻仅尝试了电路组件的方形外壳,主要部件如下图所示,共4个零件

实际打印出来后,发现按键模型有点小问题,中间按键的按键内部没有做填充,仅两边的按键做了内部填充,导致安装后,开发板的up按键无法按下。

此外,此外壳设计时没有对内部主控板纵向空间进行限位,仅是将其固定在底板上,而底板和外壳间没有任何固定,导致实际按键可能按不下或手感有点差。

所有的螺丝孔为沉头m2.5,但可能是3d打印的微小误差,也可能是模型本身孔位设计略大,导致无法使用螺丝直接固定,实际我是用螺丝加E6000胶水进行的固定

最终我复刻的成品如图

 

 

最新回复

三个打印厂商打的,真牛,我的3D打印机只能打印白色的。。。   详情 回复 发表于 2024-1-26 13:06
点赞 关注
 

回复
举报

7158

帖子

2

TA的资源

版主

沙发
 

看教程感觉挺容易的,这些外壳和配件有现成的吗?

点评

外壳都是3d打印的,电路硬件淘宝买的,复刻的确不难,后续输如果要输出音频啥的会有点小麻烦,而且体积不大,音频质量也不会好  详情 回复 发表于 2024-1-25 16:16
 
 

回复

18

帖子

3

TA的资源

一粒金砂(中级)

板凳
 
wangerxian 发表于 2024-1-25 16:03 看教程感觉挺容易的,这些外壳和配件有现成的吗?

外壳都是3d打印的,电路硬件淘宝买的,复刻的确不难,后续输如果要输出音频啥的会有点小麻烦,而且体积不大,音频质量也不会好

点评

3D打印是在外面打印的吗?还是自己3D打印机打印的。  详情 回复 发表于 2024-1-26 11:23
 
 
 

回复

175

帖子

0

TA的资源

一粒金砂(中级)

4
 

牛批!


 
 
 

回复

136

帖子

0

TA的资源

一粒金砂(中级)

5
 

啥也不用说了,直接点赞就行了

 
 
 

回复

7158

帖子

2

TA的资源

版主

6
 
Lucheni 发表于 2024-1-25 16:16 外壳都是3d打印的,电路硬件淘宝买的,复刻的确不难,后续输如果要输出音频啥的会有点小麻烦,而且体积不 ...

3D打印是在外面打印的吗?还是自己3D打印机打印的。

点评

嘉立创打的,黑色外壳是激光烧结的尼龙,白色底板是光固化树脂,这个模型自己有fdm机器其实很好打,不过我的卧龙用的太少已经被我卖了  详情 回复 发表于 2024-1-26 12:04
 
 
 

回复

18

帖子

3

TA的资源

一粒金砂(中级)

7
 
wangerxian 发表于 2024-1-26 11:23 3D打印是在外面打印的吗?还是自己3D打印机打印的。

嘉立创打的,黑色外壳是激光烧结的尼龙,白色底板是光固化树脂,这个模型自己有fdm机器其实很好打,不过我的卧龙用的太少已经被我卖了

点评

三个打印厂商打的,真牛,我的3D打印机只能打印白色的。。。  详情 回复 发表于 2024-1-26 13:06
 
 
 

回复

7158

帖子

2

TA的资源

版主

8
 
Lucheni 发表于 2024-1-26 12:04 嘉立创打的,黑色外壳是激光烧结的尼龙,白色底板是光固化树脂,这个模型自己有fdm机器其实很好打,不过 ...

三个打印厂商打的,真牛,我的3D打印机只能打印白色的。。。

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表