From 0a6ba79e8dfdfd2554432955dcb40c0d0c8787a4 Mon Sep 17 00:00:00 2001 From: radex Date: Fri, 17 May 2024 21:51:29 +0200 Subject: [PATCH] =?UTF-8?q?reading=20gfx=20from=20sd=20card=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firmware/src/audio.cpp | 2 - firmware/src/audio.h | 2 + firmware/src/gfx_decoder.cpp | 48 ++++++ firmware/src/gfx_decoder.h | 13 ++ firmware/src/leds.cpp | 170 +++++++++++++++++++++ firmware/src/leds.h | 32 ++++ firmware/src/main.cpp | 287 ++++------------------------------- firmware/src/sd.cpp | 109 ++++++++----- firmware/src/sd.h | 4 +- 9 files changed, 363 insertions(+), 304 deletions(-) create mode 100644 firmware/src/gfx_decoder.cpp create mode 100644 firmware/src/gfx_decoder.h create mode 100644 firmware/src/leds.cpp create mode 100644 firmware/src/leds.h diff --git a/firmware/src/audio.cpp b/firmware/src/audio.cpp index 85ecf86..378a0cd 100644 --- a/firmware/src/audio.cpp +++ b/firmware/src/audio.cpp @@ -9,8 +9,6 @@ // Adapted from https://github.com/rgrosset/pico-pwm-audio -#define AUDIO_PIN 23 - #define MAX_PWM_POS (BUFFER_LEN << 3) uint8_t wav_buffer_0[BUFFER_LEN] = {0}; diff --git a/firmware/src/audio.h b/firmware/src/audio.h index d1c1d2a..211bebd 100644 --- a/firmware/src/audio.h +++ b/firmware/src/audio.h @@ -3,6 +3,8 @@ #ifndef _audio_h #define _audio_h +#define AUDIO_PIN 2 + #define AUDIO_RATE 44000.0f #define BUFFER_LEN 16384 #define BUFFER_LEN_MS (BUFFER_LEN / AUDIO_RATE) * 1000.0f diff --git a/firmware/src/gfx_decoder.cpp b/firmware/src/gfx_decoder.cpp new file mode 100644 index 0000000..8e59ee9 --- /dev/null +++ b/firmware/src/gfx_decoder.cpp @@ -0,0 +1,48 @@ +#include "gfx_decoder.h" +#include "sd.h" +#include "lodepng.h" +#include "leds.h" + +uint16_t gfxFrameLengthsBuffer[12000] = {0}; +uint16_t frameCount = 0; + +uint8_t gfxFrameBuffer[2048] = {0}; + +bool gfx_decoder_loadNextFrame() { + // load frame from SD card + auto frameSize = sd_loadNextFrame(); + if (frameSize < 0) { + return false; + } + + // decode PNG + unsigned error; + unsigned char *buffer = 0; + unsigned width, height; + + size_t pngSize = frameSize; + error = lodepng_decode_memory(&buffer, &width, &height, gfxFrameBuffer, pngSize, LCT_GREY, 8); + + // handle errors + if (error) { + Serial.print("PNG decode error "); + Serial.print(error); + Serial.print(": "); + Serial.println(lodepng_error_text(error)); + free(buffer); + return false; + } else if (width != ROW_COUNT || height != COL_COUNT) { + Serial.print("Bad dimensions: "); + Serial.print(width); + Serial.print("x"); + Serial.println(height); + free(buffer); + return false; + } + + // copy to framebuffer + // TODO: mutex? double buffer? or something... + memcpy(framebuffer, buffer, ROW_COUNT * COL_COUNT); + free(buffer); + return true; +} diff --git a/firmware/src/gfx_decoder.h b/firmware/src/gfx_decoder.h new file mode 100644 index 0000000..3958ff4 --- /dev/null +++ b/firmware/src/gfx_decoder.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef GFX_DECODER_H +#define GFX_DECODER_H + +#include + +extern uint16_t gfxFrameLengthsBuffer[12000]; +extern uint16_t frameCount; +extern uint8_t gfxFrameBuffer[2048]; + +bool gfx_decoder_loadNextFrame(); + +#endif diff --git a/firmware/src/leds.cpp b/firmware/src/leds.cpp new file mode 100644 index 0000000..ae7b2b9 --- /dev/null +++ b/firmware/src/leds.cpp @@ -0,0 +1,170 @@ +#include +#include "hardware/gpio.h" +#include "mbed_wait_api.h" +#include "pico/multicore.h" + +#include "leds.h" + +inline void pulsePin(uint8_t pin) { + gpio_put(pin, HIGH); + gpio_put(pin, LOW); +} + +void clearShiftReg(uint8_t srclk, uint8_t srclr) { + gpio_put(srclr, LOW); + pulsePin(srclk); + gpio_put(srclr, HIGH); +} + +inline void outputEnable(uint8_t pin, bool enable) { + gpio_put(pin, !enable); +} + +uint16_t frameIndex = 0; +uint16_t lastRenderedFrameIndex = 0; +unsigned long frameLastChangedAt; + +// we have 4-bit color depth, so 16 levels of brightness +// we go from phase 0 to phase 3 +uint8_t brightnessPhase = 0; +uint8_t brightnessPhaseDelays[] = {1, 10, 30, 100}; + +uint8_t framebuffer[ROW_COUNT * COL_COUNT] = {0}; + +void leds_init() { + memset(framebuffer, 0, sizeof(framebuffer)); + + // disable output + outputEnable(COL_OE, false); + outputEnable(ROW_OE, false); + + // set up col pins + pinMode(COL_SER, OUTPUT); + pinMode(COL_OE, OUTPUT); + pinMode(COL_RCLK, OUTPUT); + pinMode(COL_SRCLK, OUTPUT); + pinMode(COL_SRCLR, OUTPUT); + + // set up row pins + pinMode(ROW_SER, OUTPUT); + pinMode(ROW_OE, OUTPUT); + pinMode(ROW_RCLK, OUTPUT); + pinMode(ROW_SRCLK, OUTPUT); + pinMode(ROW_SRCLR, OUTPUT); + + // clear output - cols + clearShiftReg(COL_SRCLK, COL_SRCLR); + pulsePin(COL_RCLK); + outputEnable(COL_OE, true); + + // clear output - rows + clearShiftReg(ROW_SRCLK, ROW_SRCLR); + pulsePin(ROW_RCLK); + + // clear frames + frameIndex = 0; + frameLastChangedAt = millis(); + + /* + // launch core1 + // NOTE: For some reason, without delay, core1 doesn't start? + delay(500); + multicore_reset_core1(); + multicore_launch_core1(main2); + */ +} + +void leds_disable() { + outputEnable(ROW_OE, false); +} + +void leds_loop() { + // game of life step + // auto now = millis(); + // if (now - frameLastChangedAt > 100) { + // frameLastChangedAt = now; + // life_step(); + // for (int y = 0; y < ROW_COUNT; y++) { + // for (int x = 0; x < COL_COUNT; x++) { + // framebuffer[y * ROW_COUNT + x] = cells[y * ROW_COUNT + x] ? 255 : 0; + // } + // } + // } + + leds_render(); +} + +void leds_render() { + // hide output + outputEnable(ROW_OE, false); + + // clear rows + clearShiftReg(ROW_SRCLK, ROW_SRCLR); + + // start selecting rows + digitalWrite(ROW_SER, HIGH); + + for (int yCount = 0; yCount < ROW_COUNT; yCount++) { + int y = ROW_COUNT - 1 - yCount; + // brigthness - pushing data takes 40us, so to maximize brightness (at high brightness phases) + // we want to keep the matrix on during update (except during latch). At low brightness phases, + // we want it off to actually be dim + bool brightPhase = brightnessPhase >= 2; + // digitalWrite(ROW_OE, !brightPhase); + + // next row + pulsePin(ROW_SRCLK); + // only one row + digitalWrite(ROW_SER, LOW); + + // we use 7/8 stages on shift registers + 1 is unused + int moduleY = yCount % 20; + if (moduleY == 0) { + pulsePin(ROW_SRCLK); + } + + if (moduleY == 7 || moduleY == 14 || (moduleY == 0 && yCount != 0)) { + pulsePin(ROW_SRCLK); + } + + // clear columns + clearShiftReg(COL_SRCLK, COL_SRCLR); + + // set row data + for (int x = 0; x < COL_COUNT; x++) { + // get value + // NOTE: values are loaded right-left + uint8_t pxValue = framebuffer[y * ROW_COUNT + x]; + // apply brightness + bool gotLight = (pxValue >> (4 + brightnessPhase)) & 1; + // set value (note: inverted logic) + gpio_put(COL_SER, !gotLight); + // push value + pulsePin(COL_SRCLK); + + // we use 7/8 stages on shift registers + 1 is unused + int moduleX = x % 20; + if (moduleX == 0) { + pulsePin(COL_SRCLK); + } + if (moduleX == 6 || moduleX == 13 || moduleX == 19) { + pulsePin(COL_SRCLK); + } + } + + // disable columns before latch + outputEnable(ROW_OE, false); + + // latch rows and columns + pulsePin(ROW_RCLK); + pulsePin(COL_RCLK); + + // show for a certain period + outputEnable(ROW_OE, true); + delayMicroseconds(brightnessPhaseDelays[brightnessPhase]); + outputEnable(ROW_OE, false); + } + + // next brightness phase + brightnessPhase = (brightnessPhase + 1) % 4; +} diff --git a/firmware/src/leds.h b/firmware/src/leds.h new file mode 100644 index 0000000..e38955a --- /dev/null +++ b/firmware/src/leds.h @@ -0,0 +1,32 @@ +#pragma once +#ifndef LEDS_H +#define LEDS_H + +#include + +#define COL_SER 20 +#define COL_OE 21 +#define COL_RCLK 22 +#define COL_SRCLK 26 +#define COL_SRCLR 27 + +#define ROW_SER 14 +#define ROW_OE 13 +#define ROW_RCLK 12 +#define ROW_SRCLK 11 +#define ROW_SRCLR 10 + +#define ROW_COUNT 40 +#define COL_COUNT 40 + +#define FPS 30 +#define MS_PER_FRAME 1000 / FPS + +void leds_init(); +void leds_disable(); +void leds_loop(); +void leds_render(); + +extern uint8_t framebuffer[ROW_COUNT * COL_COUNT]; + +#endif diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 203ab9c..8ee0a2f 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -1,64 +1,12 @@ #include -#include "gfx_png.h" -#include "lodepng.h" -#include "pico/multicore.h" #include "hardware/gpio.h" -#include "mbed_wait_api.h" #include "audio.h" #include "sd.h" - -// #define Serial Serial1 - -#define COL_SER 20 -#define COL_OE 21 -#define COL_RCLK 22 -#define COL_SRCLK 26 -#define COL_SRCLR 27 - -#define ROW_SER 14 -#define ROW_OE 13 -#define ROW_RCLK 12 -#define ROW_SRCLK 11 -#define ROW_SRCLR 10 - -inline void pulsePin(uint8_t pin) { - gpio_put(pin, HIGH); - gpio_put(pin, LOW); -} - -void clearShiftReg(uint8_t srclk, uint8_t srclr) { - gpio_put(srclr, LOW); - pulsePin(srclk); - gpio_put(srclr, HIGH); -} - -inline void outputEnable(uint8_t pin, bool enable) { - gpio_put(pin, !enable); -} - -#define ROW_COUNT 40 -#define COL_COUNT 40 - -#define FPS 30 -#define MS_PER_FRAME 1000 / FPS - -uint16_t frameIndex = 0; -uint16_t lastRenderedFrameIndex = 0; -unsigned long frameLastChangedAt; - -// we have 4-bit color depth, so 16 levels of brightness -// we go from phase 0 to phase 3 -uint8_t brightnessPhase = 0; -uint8_t brightnessPhaseDelays[] = {1, 10, 30, 100}; - -uint8_t framebuffer[ROW_COUNT * COL_COUNT] = {0}; - -void main2(); -void life_setup(); -void life_step(); -extern bool cells[ROW_COUNT * COL_COUNT]; +#include "leds.h" +#include "gfx_decoder.h" void setup() { + leds_init(); setupSDPins(); delay(2000); @@ -67,232 +15,51 @@ void setup() { init_audio(); - while (!isSDCardInserted()) { - Serial.println("SD card not connected, waiting..."); - delay(1000); - } - delay(100); + // while (!isSDCardInserted()) { + // Serial.println("SD card not connected, waiting..."); + // delay(1000); + // } + // delay(100); setupSD(); - // sd_getAudio(); - sd_getGfx(); + // // sd_getAudio(); + // sd_getGfx(); - return; + // return; - memset(framebuffer, 0, sizeof(framebuffer)); + if (!sd_loadGfxFrameLengths()) { + Serial.println("Failed to load gfx frame lengths"); + while (true) {} + } - // disable output - outputEnable(COL_OE, false); - outputEnable(ROW_OE, false); - - // set up col pins - pinMode(COL_SER, OUTPUT); - pinMode(COL_OE, OUTPUT); - pinMode(COL_RCLK, OUTPUT); - pinMode(COL_SRCLK, OUTPUT); - pinMode(COL_SRCLR, OUTPUT); - - // set up row pins - pinMode(ROW_SER, OUTPUT); - pinMode(ROW_OE, OUTPUT); - pinMode(ROW_RCLK, OUTPUT); - pinMode(ROW_SRCLK, OUTPUT); - pinMode(ROW_SRCLR, OUTPUT); - - // clear output - cols - clearShiftReg(COL_SRCLK, COL_SRCLR); - pulsePin(COL_RCLK); - outputEnable(COL_OE, true); - - // clear output - rows - clearShiftReg(ROW_SRCLK, ROW_SRCLR); - pulsePin(ROW_RCLK); - - // clear frames - frameIndex = 0; - frameLastChangedAt = millis(); - - // launch core1 - // NOTE: For some reason, without delay, core1 doesn't start? - delay(500); - multicore_reset_core1(); - multicore_launch_core1(main2); - - // setup_audio(); - - // life_setup(); - - // // copy cells to framebuffer - // for (int y = 0; y < ROW_COUNT; y++) { - // for (int x = 0; x < COL_COUNT; x++) { - // framebuffer[y * ROW_COUNT + x] = cells[y * ROW_COUNT + x] ? 255 : 0; - // } - // } -} - - -void loop2(); -void main2() { - while (true) { - loop2(); + if (!sd_loadGfxBlob()) { + Serial.println("Failed to load gfx blob"); + while (true) {} } } void loop() { - - return; if (Serial.available() > 0) { char c = Serial.read(); if (c == 'p') { Serial.println("Paused. Press any key to continue."); - outputEnable(ROW_OE, false); + leds_disable(); while (Serial.available() == 0) { Serial.read(); delay(50); } - } else if (c == 'r') { - Serial.println("Restarting..."); - + Serial.println("Continuing..."); } } - if (multicore_fifo_rvalid()) { - uint32_t value = multicore_fifo_pop_blocking(); - if (value == 21372137) { - Serial.println("Invalid frame size"); - } else { - Serial.print("PNG decode error "); - Serial.print(value); - Serial.print(": "); - Serial.println(lodepng_error_text(value)); - } + + if (!gfx_decoder_loadNextFrame()) { + Serial.println("Failed to load frame..."); } - if (frameIndex != lastRenderedFrameIndex) { - Serial.print("Going to frame "); - Serial.println(frameIndex); - lastRenderedFrameIndex = frameIndex; - } - - // game of life step - // auto now = millis(); - // if (now - frameLastChangedAt > 100) { - // frameLastChangedAt = now; - // life_step(); - // for (int y = 0; y < ROW_COUNT; y++) { - // for (int x = 0; x < COL_COUNT; x++) { - // framebuffer[y * ROW_COUNT + x] = cells[y * ROW_COUNT + x] ? 255 : 0; - // } - // } - // } - - // hide output - outputEnable(ROW_OE, false); - - // clear rows - clearShiftReg(ROW_SRCLK, ROW_SRCLR); - - // start selecting rows - digitalWrite(ROW_SER, HIGH); - - for (int yCount = 0; yCount < ROW_COUNT; yCount++) { - int y = ROW_COUNT - 1 - yCount; - // brigthness - pushing data takes 40us, so to maximize brightness (at high brightness phases) - // we want to keep the matrix on during update (except during latch). At low brightness phases, - // we want it off to actually be dim - bool brightPhase = brightnessPhase >= 2; - // digitalWrite(ROW_OE, !brightPhase); - - // next row - pulsePin(ROW_SRCLK); - // only one row - digitalWrite(ROW_SER, LOW); - - // we use 7/8 stages on shift registers + 1 is unused - int moduleY = yCount % 20; - if (moduleY == 0) { - pulsePin(ROW_SRCLK); - } - - if (moduleY == 7 || moduleY == 14 || (moduleY == 0 && yCount != 0)) { - pulsePin(ROW_SRCLK); - } - - // clear columns - clearShiftReg(COL_SRCLK, COL_SRCLR); - - // set row data - for (int x = 0; x < COL_COUNT; x++) { - // get value - // NOTE: values are loaded right-left - uint8_t pxValue = framebuffer[y * ROW_COUNT + x]; - // apply brightness - bool gotLight = (pxValue >> (4 + brightnessPhase)) & 1; - // set value (note: inverted logic) - gpio_put(COL_SER, !gotLight); - // push value - pulsePin(COL_SRCLK); - - // we use 7/8 stages on shift registers + 1 is unused - int moduleX = x % 20; - if (moduleX == 0) { - pulsePin(COL_SRCLK); - } - if (moduleX == 6 || moduleX == 13 || moduleX == 19) { - pulsePin(COL_SRCLK); - } - } - - // disable columns before latch - outputEnable(ROW_OE, false); - - // latch rows and columns - pulsePin(ROW_RCLK); - pulsePin(COL_RCLK); - - // show for a certain period - outputEnable(ROW_OE, true); - delayMicroseconds(brightnessPhaseDelays[brightnessPhase]); - outputEnable(ROW_OE, false); - } - - // next brightness phase - brightnessPhase = (brightnessPhase + 1) % 4; -} - -void loop2() { - unsigned error; - unsigned char *buffer = 0; - unsigned width, height; - - // decode png - const uint8_t *png = png_frames[frameIndex]; - size_t pngSize = png_frame_sizes[frameIndex]; - - error = lodepng_decode_memory(&buffer, &width, &height, png, pngSize, LCT_GREY, 8); - - // push errors onto queue to be reported on core0, can't use serial here - if (error) { - free(buffer); - multicore_fifo_push_blocking(error); - return; - } else if (width != ROW_COUNT || height != COL_COUNT) { - free(buffer); - multicore_fifo_push_blocking(21372137); - return; - } - - // copy to framebuffer - // TODO: mutex? double buffer? or something... - // TODO: learn to use memcpy lmao - memcpy(framebuffer, buffer, ROW_COUNT * COL_COUNT); - - free(buffer); - - // wait until next frame - // TODO: measure time to decode png - busy_wait_ms(MS_PER_FRAME); - - frameIndex = (frameIndex + 1) % PNG_COUNT; + leds_render(); + leds_render(); + leds_render(); + leds_render(); } diff --git a/firmware/src/sd.cpp b/firmware/src/sd.cpp index 0eb2710..9e770b5 100644 --- a/firmware/src/sd.cpp +++ b/firmware/src/sd.cpp @@ -3,6 +3,7 @@ #include #include "sd.h" #include "audio.h" +#include "gfx_decoder.h" #define SD_DET_PIN 28 @@ -206,60 +207,86 @@ void sd_getAudio() { } } -uint16_t lenBuffer[12000] = {0}; -uint8_t dataBuffer[2048] = {0}; -uint16_t frameCount = 0; +bool sd_loadGfxFrameLengths() { + auto path = "badapple/gfx_len.bin"; -void sd_getGfx() { - // read frame lengths - auto lengthsFile = SD.open("badapple/gfx_len.bin", FILE_READ); - frameCount = lengthsFile.size() / sizeof(uint16_t); + if (!SD.exists(path)) { + Serial.println("Frame lengths file not found :("); + return false; + } - Serial.print("Frames: "); - Serial.println(frameCount); + auto lengthsFile = SD.open(path, FILE_READ); + auto fileSize = lengthsFile.size(); + + if (fileSize > sizeof(gfxFrameLengthsBuffer)) { + Serial.println("Frame lengths file too large"); + return false; + } + + frameCount = fileSize / sizeof(uint16_t); while (lengthsFile.available()) { - lengthsFile.read(&lenBuffer, sizeof(lenBuffer)); + lengthsFile.read(&gfxFrameLengthsBuffer, sizeof(gfxFrameLengthsBuffer)); } lengthsFile.close(); Serial.println("Done reading frame lengths"); - auto gfxBlobFile = SD.open("badapple/gfx.bin", FILE_READ); + return true; +} - auto b4 = millis(); - for (uint16_t i = 0; i < frameCount; i++) { - Serial.print("Frame "); - Serial.print(i); - Serial.print(" - length "); +File gfxFile; - auto len = lenBuffer[i]; +uint16_t frameIdx = 0; - Serial.print(lenBuffer[i]); - Serial.print(" - read in "); +bool sd_loadGfxBlob() { + auto path = "badapple/gfx.bin"; - if (len > sizeof(dataBuffer)) { - Serial.println("Frame too large"); - return; - } - - auto bytesRead = gfxBlobFile.read(&dataBuffer, len); - if (bytesRead < len) { - Serial.println("Could not read the entire frame"); - return; - } - - auto now = millis(); - auto time = now - b4; - Serial.print(time); - Serial.println("ms"); - if (time > 20) { - Serial.println("Frame took too long to read"); - return; - } - b4 = now; + if (!SD.exists(path)) { + Serial.println("Gfx blob file not found :("); + return false; } - gfxBlobFile.close(); - Serial.println("Done reading frames"); + gfxFile = SD.open(path, FILE_READ); + Serial.println("Opened video frames"); + + return true; +} + +// Returns size of frame read or -1 if error +int32_t sd_loadNextFrame() { + if (!gfxFile || !gfxFile.available()) { + Serial.println("Gfx file not available"); + return -1; + } + + if (frameIdx >= frameCount) { + Serial.println("Frame out of range"); + return -1; + } + + // get size of frame png + auto frameSize = gfxFrameLengthsBuffer[frameIdx]; + if (frameSize > sizeof(gfxFrameBuffer)) { + Serial.println("Frame too large"); + return -1; + } + + // read data + auto bytesRead = gfxFile.read(&gfxFrameBuffer, frameSize); + if (bytesRead < frameSize) { + Serial.println("Could not read the entire frame"); + return -1; + } + + // increment + if (frameIdx == frameCount - 1) { + Serial.println("Last frame, rewinding..."); + gfxFile.seek(0); + frameIdx = 0; + } else { + frameIdx++; + } + + return frameSize; } diff --git a/firmware/src/sd.h b/firmware/src/sd.h index c9a3764..b6eddd5 100644 --- a/firmware/src/sd.h +++ b/firmware/src/sd.h @@ -6,4 +6,6 @@ void setupSD(); bool isSDCardInserted(); void sd_getAudio(); -void sd_getGfx(); +bool sd_loadGfxFrameLengths(); +bool sd_loadGfxBlob(); +int32_t sd_loadNextFrame();