Lucheni

  • 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胶水进行的固定 最终我复刻的成品如图    

  • 2023-11-15
  • 回复了主题帖: 【得捷电子Follow me第2期】空气质量传感器和网络控制led显示展示

    lansebuluo 发表于 2023-11-15 08:44 能不能交替显示 网络控制的字体可以变大,空气质量显示的字体其实已经挺极限了,屏幕就这么高,要显示5 ... 可以的,按你说的最简单其实是所有数据滚动显示,就跟边上的led一样

  • 2023-11-14
  • 回复了主题帖: 【得捷电子Follow me第2期】空气质量传感器和网络控制led显示展示

    lansebuluo 发表于 2023-11-14 18:19 空气质量传感器和网络控制led显示,字体好小啊,能不能放大一些 网络控制的字体可以变大,空气质量显示的字体其实已经挺极限了,屏幕就这么高,要显示5行

  • 回复了主题帖: 【得捷电子Follow me第2期】空气质量传感器和网络控制led显示展示

    秦天qintian0303 发表于 2023-11-14 16:55 这个全彩点阵屏挺不错啊,特意做的吗?   Adafruit官方的,板子叫dotstar wing,用的是apa102灯珠,理论通信速度高于ws2812

  • 加入了学习《【得捷电子Follow me第2期】空气质量传感器与基于wifi的led控制系统》,观看 基于Adafruit esp32s3 tft feather的空气质量传感器与基于wifi的led控制系统

  • 发表了主题帖: 【得捷电子Follow me第2期】空气质量传感器和网络控制led显示展示

    本帖最后由 Lucheni 于 2023-11-14 16:43 编辑 本帖子为Follow me第二期最终的作业提交展示,共完成了两个项目 ①空气质量传感器 ②基于板载按键和网页按键的rgb led控制系统 具体项目展示可参考视频:https://training.eeworld.com.cn/video/38390?md5__2280=eqRxy7GQeiu49DBqDTClYOi%3Dq0Qweq%2Bex&alichlgref=http%3A%2F%2Ftraining.eeworld.com.cn%2Fcourse%2F68227 空气质量传感器项目介绍及关键代码说明: 此项目使用Adafruit ESP32-S3 TFT Feather板卡连接了SGP40和SCD41传感器,这两颗传感器可以分别测量voc、温度、湿度以及真实二氧化碳数值,开发板板载有iic接口,同时还有一块TFT彩屏,非常适合用来进行传感器的链接以及数据显示,所以本项目基于这三个部分搭建了空气质量传感器的demo 代码采用arduino编写,各个模块初始化、数据读取以及显示刷新都被我封装为函数,在setup及loop函数中直接进行调用,个人认为可阅读性还是非常高的 以下是传感器数据读取函数,其中要注意的是sgp40内部计算voc时需要将scd41获取的温湿度数据传入 void ScdRead(uint16_t &co2, float &temperature, float &humidity){ bool isDataReady = false; uint16_t error; error = scd4x.getDataReadyFlag(isDataReady); if (isDataReady) { delay(10); error = scd4x.readMeasurement(co2, temperature, humidity); if (error) { Serial.print("Error trying to execute readMeasurement(): "); errorToString(error, errorMessage, 256); Serial.println(errorMessage); } else if (co2 == 0) { Serial.println("Invalid sample detected, skipping."); } } } void SgpRead(uint16_t &sraw, int32_t &voc_index, float &temperature, float &humidity){ sraw = sgp.measureRaw(temperature, humidity); voc_index = sgp.measureVocIndex(temperature, humidity); } 以下为tft刷新函数,每次仅局部刷新数据部分显示块 void TftRefresh() { // tft print function! tft.fillRect(60, 0, 60, 135, ST77XX_BLACK); tft.setCursor(60, 10); tft.setTextColor(ST77XX_RED); tft.setTextSize(2); tft.println(temperature); tft.setCursor(60, 35); tft.setTextColor(ST77XX_YELLOW); tft.setTextSize(2); tft.println(humidity); tft.setCursor(60, 60); tft.setTextColor(ST77XX_GREEN); tft.setTextSize(2); tft.println("null"); tft.setCursor(60, 85); tft.setTextColor(ST77XX_BLUE); tft.setTextSize(2); tft.println(co2); tft.setCursor(60, 110); tft.setTextColor(ST77XX_WHITE); tft.setTextSize(2); tft.println(voc_index); } tft端的数据采用中文显示,本代码将中文字转为位图,调用drawbitmap函数进行处理(处理方法可以看之前发的速通教程https://bbs.eeworld.com.cn/thread-1254565-1-1.html): const uint8_t PROGMEM str1[]= {0x00,0x00,0x23,0xF8,0x12,0x08,0x12,0x08,0x83,0xF8,0x42,0x08,0x42,0x08,0x13,0xF8,0x10,0x00,0x27,0xFC,0xE4,0xA4,0x24,0xA4,0x24,0xA4,0x24,0xA4,0x2F,0xFE,0x00,0x00};/*"温",1*/ const uint8_t PROGMEM str2[]= {0x01,0x00,0x00,0x80,0x3F,0xFE,0x22,0x20,0x22,0x20,0x3F,0xFC,0x22,0x20,0x22,0x20,0x23,0xE0,0x20,0x00,0x2F,0xF0,0x24,0x10,0x42,0x20,0x41,0xC0,0x86,0x30,0x38,0x0E};/*"度",1*/ const uint8_t PROGMEM str3[]= {0x00,0x00,0x27,0xF8,0x14,0x08,0x14,0x08,0x87,0xF8,0x44,0x08,0x44,0x08,0x17,0xF8,0x11,0x20,0x21,0x20,0xE9,0x24,0x25,0x28,0x23,0x30,0x21,0x20,0x2F,0xFE,0x00,0x00};/*"湿",0*/ const uint8_t PROGMEM str4[]= {0x10,0x00,0x10,0x00,0x3F,0xFC,0x20,0x00,0x4F,0xF0,0x80,0x00,0x3F,0xF0,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x0A,0x00,0x0A,0x00,0x06,0x00,0x02};/*"气",0*/ const uint8_t PROGMEM str5[]= {0x00,0x00,0x3F,0xFE,0x20,0x00,0x20,0x80,0x20,0x80,0x20,0x80,0x20,0x80,0x2F,0xFC,0x20,0x80,0x20,0x80,0x20,0x90,0x20,0x88,0x20,0x88,0x40,0x80,0x5F,0xFE,0x80,0x00};/*"压",1*/ 本项目实际完成了任务一在屏幕上显示中文的要求 基于板载按键和网页按键的rgb led控制系统介绍及关键代码说明: 本项目实现了板载按键控制板载led颜色变化,网页按键控制外接led阵列显示内容的效果。 loop函数轮询按键是否被按下,若按下则板载led开始变色(这里可以优化为按键中断,不过目前项目轮询也可以就没有修改) if (!digitalRead(0)) { TB.setColor(TB.Wheel(j++)); } 同时loop函数轮询esp32生成网页页面中的按键是否被按下,若按下则修改标志位 WiFiClient client = server.available(); if (client) { // if you get a client, Serial.println("New Client."); // print a message out the serial port String currentLine = ""; // make a String to hold incoming data from the client while (client.connected()) { // loop while the client's connected if (client.available()) { // if there's bytes to read from the client, char c = client.read(); // read a byte, then Serial.write(c); // print it out the serial monitor if (c == '\n') { // if the byte is a newline character // if the current line is blank, you got two newline characters in a row. // that's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) // and a content-type so the client knows what's coming, then a blank line: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println(); // the content of the HTTP response follows the header: client.print("Click <a href=\"/H\">here</a> to move LED.<br>"); client.print("Click <a href=\"/L\">here</a> to stop LED.<br>"); // The HTTP response ends with another blank line: client.println(); // break out of the while loop: break; } else { // if you got a newline, then clear currentLine: currentLine = ""; } } else if (c != '\r') { // if you got anything else but a carriage return character, currentLine += c; // add it to the end of the currentLine } // Check to see if the client request was "GET /H" or "GET /L": if (currentLine.endsWith("GET /H")) { go=1; // GET /H turns the LED on } if (currentLine.endsWith("GET /L")) { go=0; // GET /L turns the LED off } } 最后根据标志位和当前时间判断是否对外接led阵列上显示内容进行滑动 if ((((t=millis()%65536)-ledprevtime)>SHIFTDELAY)&&(go==1)){ matrix.fillScreen(0); matrix.setCursor(x, 5); for (byte i = 0; i < strlen(adafruit); i++) { // set the color matrix.setTextColor(adaColors[i]); // write the letter matrix.print(adafruit[i]); } if (--x < -50) { x = matrix.width(); } matrix.show(); ledprevtime = millis()%65536; } 本项目实际完成了任务二、任务三以及任务四的分任务二,实现了wifi生成网页和控制,板载按键的使用,板载rgb led使用,外接6*12led阵列的使用四个小项目 以上项目代码可以在这里找到:https://download.eeworld.com.cn/detail/Lucheni/629865 总结: 本次活动研究了基于esp32s3的wifi控制,led控制,传感器数据读取及中文显示,收获还是很多的,当然我个人最喜欢的还是这次活动的开发板,在小巧的feather版型上放下了tft彩屏,还板载电量计以及预留了温湿度传感器的焊接接口,双面布局,对于开发板而言是非常精致了,期待以后的活动能玩到更多更好的板子!  

  • 2023-11-13
  • 上传了资料: 得捷电子Follow me第2期 lucheni项目代码

  • 2023-10-31
  • 加入了学习《Digi-Key: Follow Me 系列(2) 直播回放》,观看 Adafruit ESP32-S3 TFT Feather开发板使用入门

  • 2023-08-31
  • 回复了主题帖: 【得捷电子Follow me第2期】任务1:控制屏幕显示中文

    jobszheng5 发表于 2023-8-31 09:43 现在商家的套路也很明显啊! 这一拖,不仅赚了钱,还省去了维修费了   话说:CircuitPython ... 你可以理解为MicroPython的变种

  • 2023-08-29
  • 发表了主题帖: 【得捷电子Follow me第2期】Arduino速通教程

    本帖最后由 Lucheni 于 2023-8-29 14:41 编辑 本教程基于Adafruit 5693 ESP32-S3 Reverse TFT Feather(后简称Reverse板),与活动开发板Adafruit 5483 ESP32-S3 TFT Feather非常类似,但可能有细微电路差别,仅作为Arduino环境下的快速上手教程,具体使用请查阅Adafruit官方教程:Overview | Adafruit ESP32-S3 TFT Feather | Adafruit Learning System 若英文阅读有困难,推荐edge浏览器打开后使用自带翻译功能 本教程为速通教程,追求的是最快速地完成所有分支任务,并不适合想要刨根问底学习的开发者,若想要学习驱动原理或代码逻辑,请仔细阅读Adafruit官方教程 上手第一步:熟悉开发板 出厂Demo     拿到开发板的第一步可以使用Type-C数据线供电,此时开发板应如图显示,第一行为开发板名称(红色),第二行为例程名称(黄色),第三行为电量信息(绿色),usb供电下电量信息可能会显示超过100%,这是正常现象,第四行为板卡连接的I2C设备地址(蓝色),此板卡到货状态应该只焊接了MAX17048电池监视器,此传感器通信地址为0x36,此时屏幕上显示内容应为I2C:0x36 出厂例程Adafruit应该是基于Arduino环境编写的,官方也开放了Reverse板的出厂Demo代码:Factory Shipped Demo | Adafruit ESP32-S3 Reverse TFT Feather | Adafruit Learning System 清除老旧库文件 Arduino环境开发很容易出现硬件库冲突的情况,比如同事安装了tft_espi显示库和Adafruit官方显示库,编译过程中就有可能报错,所以建议先清除之前Arduino已安装的库文件,或将他们备份到另一个文件夹里,待本项目开发完毕后再恢复。 各人Arduino软件安装流程不同,库文件保存地址也不同,本例程参考默认保存地址为C:\Users\Lucheni\Documents\Arduino,库文件在此地址下的libraries文件夹下,建议备份后清空此文件夹。   安装相关库文件 此时可以安装Adafruit为此开发板相关硬件所设计的库文件,Arduino下安装库文件方式为工具-管理库   在搜索栏输入你想要的库文件,依次安装Adafruit MAX1704X,Adafruit ST7735 and ST7789 Library,安装过程中可能会询问是否安装所有依赖库,点击安装所有。   任务一:控制屏幕显示中文 屏幕显示测试 既然要显示中文,我们首先进行屏幕显示的测试,测试例程为文件-示例-Adafruit ST7735 and ST7789 Library-graphicstest_feather_esp32s2_tft   打开例程,选择开发板和串口号后就可以上传,此时开发板屏幕即开始显示测试,会显示不同尺寸、颜色的字符并显示不同的图形,以及最后的屏幕翻转显示。简单浏览代码可以发现Adafruit将繁杂的显示任务抽象为了简单的draw函数,显示文字为drawtext,显示像素为drawPixel,以及线以及各种形状的显示:drawFastHLine、drawFastVLine、drawCircle、drawTriangle。这些显示函数本次速通并不会使用,若想学习用法,请参考Adafruit官方教程:Graphics Primitives | Adafruit GFX Graphics Library | Adafruit Learning System 显示中文 对于速通教程来说,显示中文虽然是第一个任务,但可能是最难的任务,因为其他的任务都有Adafruit官方硬件库支持,而官方显示库仅能显示英文,中文显示需要自己一点点办法 对于常规的开发来说,显示中文的正常开发逻辑是先用第三方软件生成中文字库,之后导入到已有的显示库中,再逐一显示。但基于速通的目的,本教程采用了一点偷懒的办法~   由于任务1要求仅为显示中文,所以我个人认为在只需要显示几个中文字符的需求下,以位图显示中文相对简单直接,故本次速通教程将使用官方教程末尾的一个函数:drawBitmap。 生成中文字 本次生成中文字使用软件为正点原子开发板资料里的PCtoLCD2002,实际网上还有大量类似软件,Adafruit官方也推荐了位图-内存图的网页工具image2cpp (javl.github.io) 本速通教程仅显示四个中文汉字:中文显示 首先打开软件的设置菜单,对生成的格式进行设置,本次设置格式为:阴码、逐行式、C51格式 其他相关设置可以自己尝试,本次教程追求速通就不赘述了   选择确定,回到初始页面,在输入行输入本次需要显示的四个中文字符:中文显示(步骤①)   点击生成子模(步骤②)后下方即为本次需要使用的字符数据(步骤③) 按照教程使用PROGMEM指令将字符数据保存于内存中,再使用drawBitmap命令显示(drawBitmap函数6个参数分别为显示位置x,y,数据地址,位图宽度,位图高度,显示颜色)即可。 参考代码: #include <Adafruit_GFX.h> // Core graphics library #include <Adafruit_ST7789.h> // Hardware-specific library for ST7789 #include <SPI.h> // Use dedicated hardware SPI pins Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); const uint8_t PROGMEM str1[]= {此处字符请按需自行生成!}; const uint8_t PROGMEM str2[]= {此处字符请按需自行生成!}; const uint8_t PROGMEM str3[]= {此处字符请按需自行生成!}; const uint8_t PROGMEM str4[]= {此处字符请按需自行生成!}; void setup(void) { Serial.begin(9600); delay(500); Serial.print(F("Hello! Feather TFT Test")); // turn on backlite pinMode(TFT_BACKLITE, OUTPUT); digitalWrite(TFT_BACKLITE, HIGH); // turn on the TFT / I2C power supply pinMode(TFT_I2C_POWER, OUTPUT); digitalWrite(TFT_I2C_POWER, HIGH); delay(10); // initialize TFT tft.init(135, 240); // Init ST7789 240x135 tft.setRotation(1); tft.fillScreen(ST77XX_BLACK); Serial.println(F("Initialized")); uint16_t time = millis(); tft.fillScreen(ST77XX_BLACK); time = millis() - time; Serial.println(time, DEC); delay(500); // large block of text tft.fillScreen(ST77XX_BLACK); // tft print function! tft.drawBitmap(0, 0, str1, 16, 16, ST77XX_WHITE); tft.drawBitmap(16, 0, str2, 16, 16, ST77XX_WHITE); tft.drawBitmap(32, 0, str3, 16, 16, ST77XX_WHITE); tft.drawBitmap(48, 0, str4, 16, 16, ST77XX_WHITE); Serial.println("done"); delay(1000); } void loop() { tft.invertDisplay(true); delay(500); tft.invertDisplay(false); delay(500); } 显示效果如下(loop中加入了翻转显示,实际为底色和字符黑白交替显示):       任务二:网络功能使用 此任务可直接使用官方例程:示例-WiFi-WiFiAccessPoint   从注释可知本例程使用方式:  WiFiAccessPoint.ino创建一个WiFi接入点,并在其上提供一个网络服务器。  步骤:  1.连接到接入点“yourAp”  2.将您的网络浏览器指向http://192.168.4.1/H打开LED或http://192.168.4.1/L把它关掉或在PuTTY终端上运行原始TCP“GET/H”和“GET/L”,IP地址为192.168.4.1,端口为80 注意!!!!!!请按需修改ssid及password,例程的LED管脚并不是活动板卡的LED管脚,请自行修改,LED管脚号请参考官方例程:Pinouts | Adafruit ESP32-S3 TFT Feather | Adafruit Learning System 任务三:控制WS2812B 此任务可以直接从factory_test源码修改完成,以下仅展示LED显示关键代码 void setup() { TB.neopixelPin = PIN_NEOPIXEL;//板卡连接WS2812B引脚号 TB.neopixelNum = 1; //板卡连接WS2812B数量 TB.begin(); TB.setColor(WHITE);//设置LED为白色 } void loop() { TB.setColor(TB.Wheel(j++));//LED颜色循环显示 delay(10); return; } 任务四:WS2812B效果控制 既然为速通教程,我选择了最易实现的分任务2,使用Adafruit官方的https://www.adafruit.com/product/3449,直接使用官方例程示例-Adafruit_DotStarMatrix-dotstar_wing(记得修改例程中的开发板型号,否则需要重新烧写固件!)   简单修改显示内容后即可显示自己想要的内容:     此任务若追求速度,也可选择分任务4,使用Adafruit的音乐播放器模块:https://www.adafruit.com/product/3357,Adafruit也有基于此模块的详细例程,能很快上手。

  • 2023-08-28
  • 回复了主题帖: 【得捷电子Follow me第2期】CircuitPython入门到完成任务1-5详细教程

    ttxiaotaoge 发表于 2023-8-24 16:35 这个没有,我没买那些设备 Adafruit有music maker模块的,资料例程也都有,去官网搜下看下资料然后国内找类似的就行

最近访客

< 1/1 >

统计信息

已有3人来访过

  • 芯积分:58
  • 好友:--
  • 主题:4
  • 回复:7

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言