- 2024-11-01
-
回复了主题帖:
【2024 DigiKey 创意大赛】LED立方体
秦天qintian0303 发表于 2024-11-1 11:19
这个看着效果听炫酷的,这要是六面都带显示屏是不是会更好
目前是六面都是8*8的led,都是显示屏的话也行,不过tft屏幕亮度暗,可能效果反而不好
- 2024-10-31
-
发表了主题帖:
【2024 DigiKey 创意大赛】LED立方体
LED桌面立方体
一、作品简介
本作品为led桌面立方体,类似的作品其实之前在中文互联网上出现过很多,但大多没有框架或需要外接电源。
如图左,骰子为6片pcb焊接而成,优点是集成了电池,但单面led密度仅为3*3,同时焊接的连接方式较为麻烦,若量产需要专门定制治具,外漏的金属焊接点也易与桌面的物品碰撞导致短路。
图右使用了6片RGB LED点阵屏,屏幕本身有塑料框架,屏幕间使用角码固定,使用树莓派驱动,优点为led密度高,但因为功耗大(全套设备最大功耗近60w),使用外部供电,led立方体只能固定在底座上(电源集成在底座内)
本次作品解决了一些上述的问题,基于ESP32 S3/C6,实现了6面8*8密度的立方体,体积仅为3.5*3.5cm,内置电池,可当骰子在柔软的表面扔着玩,也可放置于桌面当桌面RGB摆件。基于ESP32的WiFi功能无需重复烧录代码,通过Adafruit IO云平台即可实现显示内容更改。当然还有一些问题等待后续改进。
二、系统框图
项目流程图如上,主控为QT PY ESP32S3,板对板焊接了一片锂电池充电管理板,用于实现设备开关及锂电池充电管理功能。开发板外接了一片LIS3DH 3轴加速度传感器,用于判断姿态。项目实现过程中也测试了ICM20948,但LIS3DH成本更低,目前选择采用这款传感器。6片led屏幕分为3组,顶面和底面单独控制,中间的四面为一组串级控制。
三、各部分功能说明
代码基于CircuitPython实现,使用了如下cpy库
其中adafruit_io用于实现开发板与adafruit云平台的数据交互,adafruit_led_animation及adafruit_dotstar实现了led矩阵屏的显示控制,adafruit_lis3dh用于姿态解算。
while True:
x, y, z = lis3dh.acceleration
oriented = orientation(x, y, z)
主循环开始后先进行姿态判断,根据姿态选择当前的显示模式。
if oriented == orientations[5]: # "back" side
cube.clear_cube(True)
continue
if oriented == orientations[1]:
upside_down = True
else:
upside_down = False
若立方体被翻转,则显示翻转内容。
if not upside_down:
if cube.done_scrolling:
update_data()
cube.update(CUBE_WORD, TOP_PIXELS_COLOR, TOP_PIXELS_ON)
cube.scroll_word_and_update_top()
若正常放在桌面上,正常重Adafruit IO上更新数据,并显示。
四、作品源码
https://download.eeworld.com.cn/detail/Lucheni/634868
五、作品功能演示视频
https://training.eeworld.com.cn/video/41503
六、项目总结
感谢得捷电子和EEWORLD举办的本次活动,让我有动力实现这个想了很久的创意,后续我也会继续改进这套硬件,让他更易用。
七、其他
本次项目最大的缺点有两个
充电及下载代码需要打开顶面led板,使用type-c接口连接主控板进行充电。
成本高,使用的是带时钟信号的rgb led,成本约为0.5元一颗,全套设备用了近400颗led,光物料成本就冲着200去了
充电方式我还在试着想办法改进,led面板已经重新绘制,使用普通ws2812 led,成本会降低到100附近,同时因为项目需要的主控性能不强,目前也实现了基于esp32 c3及c6的控制,后续找到更好的充电方式后会发帖一起更新。
-
上传了资料:
LED立方体代码
- 2024-08-23
-
回复了主题帖:
【2024 DigiKey 创意大赛】物料开箱
后续补发商品已收到(手快先用了一个)
- 2024-08-15
-
回复了主题帖:
【2024 DigiKey 创意大赛】物料开箱
sipower 发表于 2024-8-15 09:25
购买的4片esp32 c6模组,这是要做分布式网络吗
没有没有,一个创意的物联网设备吧,然后原项目是基于s3的,想做到c6然后改点功能而已,c6板子要重画就备了几个
-
回复了主题帖:
【2024 DigiKey 创意大赛】物料开箱
Jacktang 发表于 2024-8-15 07:27
发货发错还不常见,给得捷联系一下吧
联系了,然后又遇到了bug,要提交附件但是文件不能超过4m,我提交了俩图片一个excel超了然后邮件一直被退回,刚刚打客服电话才搞清楚
- 2024-08-14
-
发表了主题帖:
【2024 DigiKey 创意大赛】物料开箱
感谢EEWORLD和Digikey联合举办的 【2024 DigiKey 创意大赛】,7/30公布名单两周后的今天,我终于收到了本次活动的使用物料
不过这次收到后发现物料寄错了,本来购买的4片esp32 c6模组给我发了别人订单的保险丝座(图片最右边),目前正在联系得捷官方看是补货还是作退货处理。
- 2024-01-26
-
回复了主题帖:
【复刻】Adafruit桌面火炉项目
wangerxian 发表于 2024-1-26 11:23
3D打印是在外面打印的吗?还是自己3D打印机打印的。
嘉立创打的,黑色外壳是激光烧结的尼龙,白色底板是光固化树脂,这个模型自己有fdm机器其实很好打,不过我的卧龙用的太少已经被我卖了
- 2024-01-25
-
回复了主题帖:
【复刻】Adafruit桌面火炉项目
wangerxian 发表于 2024-1-25 16:03
看教程感觉挺容易的,这些外壳和配件有现成的吗?
外壳都是3d打印的,电路硬件淘宝买的,复刻的确不难,后续输如果要输出音频啥的会有点小麻烦,而且体积不大,音频质量也不会好
-
发表了主题帖:
【复刻】基于Adafruit开源硬件的桌面led阵列小电视
本帖最后由 Lucheni 于 2024-1-25 16:14 编辑
在学习led阵列控制时发现了Adafruit的桌面led阵列小电视项目,感觉看起来是个很有意思的小项目,于是就尝试复刻下,此贴为复刻过程和注意点的总结。
项目硬件电路部分组件如下
原项目采用了Adafruit的3403 Feather M0 Expres、2946 16x9 Charlieplexed PWM LED Matrix Driver以及2972 LED Charlieplexed Matrix - 9x16 LEDs,实际复刻过程中,我将主控板换成了3405 Huzzah32-ESP32 Feather开发板
2972和2946分别为led阵列和led阵列控制板,阵列控制板使用IS31FL3731,基于iic与主控板通信,控制整个led阵列的显示。控制板和led阵列板采用背对背直插焊接的方式。整个电路非常简洁。
项目还基于Feather开发板的电源使能接口外接了电源开关,并连接了电池,实现了在没有数据线连接开发板时也可正常使用。不过我只是尝试复刻,且没有使用电池的需求,所以并没有制作这一步
此项目需要3D打印的部件比较多,共由10个部件组成
具体安装步骤如下
本项目的示例代码如下(我也不知道为什么这里代码格式是居中的,修改了好几遍都是居中然后我放弃了,复制出来应该格式是没有问题的)
//--------------------------------------------------------------------------
// Animated Commodore PET plaything. Uses the following parts:
// - Feather M0 microcontroller (adafruit.com/products/2772)
// - 9x16 CharliePlex matrix (2972 is green, other colors avail.)
// - Optional LiPoly battery (1578) and power switch (805)
//
// This is NOT good "learn from" code for the IS31FL3731. Taking a cue from
// our animated flame pendant project, this code addresses the CharliePlex
// driver chip directly to achieve smooth full-screen animation. If you're
// new to graphics programming, download the Adafruit_IS31FL3731 and
// Adafruit_GFX libraries, with examples for drawing pixels, lines, etc.
//
// Animation cycles between different effects: typing code, Conway's Game
// of Life, The Matrix effect, and a blank screen w/blinking cursor (shown
// for a few seconds before each of the other effects; to imply "loading").
//--------------------------------------------------------------------------
#include <Wire.h>
#define I2C_ADDR 0x74 // I2C address of Charlieplex matrix
#define WIDTH 16 // Matrix size in pixels
#define HEIGHT 9
#define GAMMA 2.5 // Gamma-correction exponent
uint8_t img[WIDTH * HEIGHT], // 8-bit buffer for image rendering
bitmap[((WIDTH+7)/8) * HEIGHT], // 1-bit buffer for some modes
gamma8[256], // Gamma correction (brightness) table
page = 0, // Double-buffering front/back control
frame = 0; // Frame counter used by some animation modes
// More globals later, above code for each animation, and before setup()
// UTILITY FUNCTIONS -------------------------------------------------------
// Begin I2C transmission and write register address (data then follows)
uint8_t writeRegister(uint8_t n) {
Wire.beginTransmission(I2C_ADDR);
Wire.write(n); // No endTransmission() - left open for add'l writes
return 2; // Always returns 2; count of I2C address + register byte n
}
// Select one of eight IS31FL3731 pages, or the Function Registers
void pageSelect(uint8_t n) {
writeRegister(0xFD); // Command Register
Wire.write(n); // Page number (or 0xB = Function Registers)
Wire.endTransmission();
}
// Set bit at (x,y) in the bitmap buffer (no clear function, wasn't needed)
void bitmapSetPixel(int8_t x, int8_t y) {
bitmap[y * ((WIDTH + 7) / 8) + x / 8] |= 0x80 >> (x & 7);
}
// Read bit at (x,y) in bitmap buffer, returns nonzero (not always 1) if set
uint8_t bitmapGetPixel(int8_t x, int8_t y) {
return bitmap[y * ((WIDTH + 7) / 8) + x / 8] & (0x80 >> (x & 7));
}
// BLINKING CURSOR / LOADING EFFECT ----------------------------------------
// Minimal animation - just one pixel in the corner blinks on & off,
// meant to suggest "program loading" or similar busy effect.
void cursorLoop() {
img[0] = (frame & 1) * 255;
}
// TERMINAL TYPING EFFECT --------------------------------------------------
// I messed around trying to make a random "fake code generator," but it
// was getting out of hand. Instead, the typed "code" is just a bitmap!
const uint16_t codeBits[] = {
0b1110111110100000,
0b0011101100000000,
0b0011011111101000,
0b0000111111011100,
0b0000111011100000,
0b0010000000000000,
0b0011011111000000,
0b1000000000000000,
0b0000000000000000,
0b1111011010000000,
0b0011110111110110,
0b1000000000000000,
0b0000000000000000,
0b1110111101000000,
0b0011101011010000,
0b0011110111111000,
0b0011101101110000,
0b0011011111101000,
0b0000111011111100,
0b0010000000000000,
0b1000000000000000,
0b0000000000000000,
0b1110110100000000,
0b0011011111110100,
0b0000111101100000,
0b0010000000000000,
0b0011110111110000,
0b0011101111011000,
0b1000000000000000,
0b0000000000000000
};
uint8_t cursorX, cursorY, line;
void typingSetup() {
cursorX = cursorY = line = 0;
}
void typingLoop() {
img[cursorY * WIDTH + cursorX] = // If bit set, "type" random char
((codeBits[line] << cursorX) & 0x8000) ? random(32, 128) : 0;
cursorX++;
if(!(uint16_t)(codeBits[line] << cursorX)) { // End of line reached?
cursorX = 0;
if(cursorY >= HEIGHT-1) { // Cursor on last line?
uint8_t y;
for(y=0; y<HEIGHT-1; y++) // Move img[] buffer up one line
memcpy(&img[y * WIDTH], &img[(y+1) * WIDTH], WIDTH);
memset(&img[y * WIDTH], 0, WIDTH); // Clear last line
} else cursorY++;
if(++line >= (sizeof(codeBits) / sizeof(codeBits[0]))) line = 0;
}
img[cursorY * WIDTH + cursorX] = 255; // Draw cursor in new position
}
// MATRIX EFFECT -----------------------------------------------------------
// Inspired by "The Matrix" coding effect -- 'raindrops' travel down the
// screen, their 'tails' twinkle slightly and fade out.
#define N_DROPS 15
struct {
int8_t x, y; // Position of raindrop 'head'
uint8_t len; // Length of raindrop 'tail' (not incl head)
} drop[N_DROPS];
void matrixRandomizeDrop(uint8_t i) {
drop[i].x = random(WIDTH);
drop[i].y = random(-18, 0);
drop[i].len = random(9, 18);
}
void matrixSetup() {
for(uint8_t i=0; i<N_DROPS; i++) matrixRandomizeDrop(i);
}
void matrixLoop() {
uint8_t i, j;
int8_t y;
for(i=0; i<N_DROPS; i++) { // For each raindrop...
// If head is onscreen, overwrite w/random brightness 20-80
if((drop[i].y >= 0) && (drop[i].y < HEIGHT))
img[drop[i].y * WIDTH + drop[i].x] = random(20, 80);
// Move pos. down by one. If completely offscreen (incl tail), make anew
if((++drop[i].y - drop[i].len) >= HEIGHT) matrixRandomizeDrop(i);
for(j=0; j<drop[i].len; j++) { // For each pixel in drop's tail...
y = drop[i].y - drop[i].len + j; // Pixel Y coord
if((y >= 0) && (y < HEIGHT)) { // On screen?
// Make 4 pixels at end of tail fade out. For other tail pixels,
// there's a 1/10 chance of random brightness change 20-80
if(j < 4) img[y * WIDTH + drop[i].x] /= 2;
else if(!random(10)) img[y * WIDTH + drop[i].x] = random(20, 80);
}
}
if((drop[i].y >= 0) && (drop[i].y < HEIGHT)) // If head is onscreen,
img[drop[i].y * WIDTH + drop[i].x] = 255; // draw w/255 brightness
}
}
// CONWAY'S GAME OF LIFE ---------------------------------------------------
// The rules: if cell at (x,y) is currently populated, it stays populated
// if it has 2 or 3 populated neighbors, else is cleared. If cell at (x,y)
// is currently empty, populate it if 3 neighbors.
void lifeSetup() { // Fill bitmap with random data
for(uint8_t i=0; i<sizeof(bitmap); i++) bitmap[i] = random(256);
}
void lifeLoop() {
static const int8_t xo[] = { -1, 0, 1, -1, 1, -1, 0, 1 },
yo[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
int8_t x, y;
uint8_t i, n;
// Modify img[] based on old contents (dimmed) + new bitmap
for(i=y=0; y<HEIGHT; y++) {
for(x=0; x<WIDTH; x++, i++) {
if(bitmapGetPixel(x, y)) img[i] = 255;
else if(img[i] > 28) img[i] -= 28;
else img[i] = 0;
}
}
// Generate new bitmap (next frame) based on img[] contents + rules
memset(bitmap, 0, sizeof(bitmap));
for(y=0; y<HEIGHT; y++) {
for(x=0; x<WIDTH; x++) {
for(i=n=0; (i < sizeof(xo)) && (n < 4); i++)
n += (img[((y+yo[i])%HEIGHT) * WIDTH + ((x+xo[i])%WIDTH)] == 255);
if((n == 3) || ((n == 2) && (img[y * WIDTH + x] == 255)))
bitmapSetPixel(x, y);
}
}
// Every 32 frames, populate a random cell so animation doesn't stagnate
if(!(frame & 0x1F)) bitmapSetPixel(random(WIDTH), random(HEIGHT));
}
// MORE GLOBAL STUFF - ANIMATION STATES ------------------------------------
struct { // For each of the animation modes...
void (*setup)(void); // Animation setup func (run once on mode change)
void (*loop)(void); // Animation loop func (renders one frame)
uint8_t maxRunTime; // Animation run time in seconds
uint8_t fps; // Frames-per-second for this effect
} anim[] = {
NULL , cursorLoop, 3, 4,
typingSetup, typingLoop, 15, 15,
lifeSetup , lifeLoop , 12, 30,
matrixSetup, matrixLoop, 15, 10,
};
uint8_t seq[] = { 0, 1, 0, 2, 0, 3 }, // Sequence of animation modes
idx = sizeof(seq) - 1; // Current position in seq[]
uint32_t modeStartTime = 0x7FFFFFFF; // micros() when current mode started
// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------
void setup() {
uint16_t i;
uint8_t p, bytes;
randomSeed(analogRead(A0)); // Randomize w/unused analog pin
Wire.begin(); // Initialize I2C
Wire.setClock(400000L); // 400 KHz I2C = faster updates
// Initialize IS31FL3731 directly (no library)
pageSelect(0x0B); // Access the Function Registers
writeRegister(0); // Starting from first...
for(i=0; i<13; i++) Wire.write(10 == i); // Clear all except Shutdown
Wire.endTransmission();
for(p=0; p<2; p++) { // For each page used (0 & 1)...
pageSelect(p); // Access the Frame Registers
for(bytes=i=0; i<180; i++) { // For each register...
if(!bytes) bytes = writeRegister(i); // Buf empty? Start xfer @ reg i
Wire.write(0xFF * (i < 18)); // 0-17 = enable, 18+ = blink+PWM
if(++bytes >= SERIAL_BUFFER_SIZE) bytes = Wire.endTransmission();
}
if(bytes) Wire.endTransmission(); // Write any data left in buffer
}
for(i=0; i<256; i++) // Initialize gamma-correction table:
gamma8[i] = (uint8_t)(pow(((float)i / 255.0), GAMMA) * 255.0 + 0.5);
}
// LOOP - RUNS ONCE PER FRAME OF ANIMATION ---------------------------------
uint32_t prevTime = 0x7FFFFFFF; // Used for frame-to-frame animation timing
uint32_t frameUsec = 0L; // Frame interval in microseconds
void loop() {
// Wait for FPS interval to elapse (this approach is more consistent than
// delay() as the animation rendering itself takes indeterminate time).
uint32_t t;
while(((t = micros()) - prevTime) < frameUsec);
prevTime = t;
// Display frame rendered on prior pass. This is done immediately
// after the FPS sync (rather than after rendering) to ensure more
// uniform animation timing.
pageSelect(0x0B); // Function registers
writeRegister(0x01); // Picture Display reg
Wire.write(page); // Page #
Wire.endTransmission();
page ^= 1; // Flip front/back buffer index
anim[seq[idx]].loop(); // Render next frame
frameUsec = 1000000L / anim[seq[idx]].fps; // Frame hold time
// Write img[] array to matrix thru gamma correction table
uint8_t i, bytes; // Pixel #, Wire buffer counter
pageSelect(page); // Select background buffer
for(bytes=i=0; i<WIDTH*HEIGHT; i++) {
if(!bytes) bytes = writeRegister(0x24 + i);
Wire.write(gamma8[img[i]]);
if(++bytes >= SERIAL_BUFFER_SIZE) bytes = Wire.endTransmission();
}
if(bytes) Wire.endTransmission();
// Time for new mode?
if((t - modeStartTime) > (anim[seq[idx]].maxRunTime * 1000000L)) {
if(++idx >= sizeof(seq)) idx = 0;
memset(img, 0, sizeof(img));
if(anim[seq[idx]].setup) anim[seq[idx]].setup();
modeStartTime = t;
frame = 0;
} else frame++;
}
示例代码没有使用Adafruit官方提供的GFX和IS31FL3731驱动库,直接对IS31FL3731芯片进行寻址操作进行驱动,以达到更好的显示效果
如果是用正常的驱动库的话,则可以轻松在屏幕上显示各种图案,比如下图中的哭脸表情
- 2024-01-24
-
发表了主题帖:
【复刻】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胶水进行的固定
最终我复刻的成品如图