1
0
Fork 0

reading gfx from sd card…

main
radex 2024-05-17 21:51:29 +02:00
parent db2cb2bc43
commit 0a6ba79e8d
Signed by: radex
SSH Key Fingerprint: SHA256:hvqRXAGG1h89yqnS+cyFTLKQbzjWD4uXIqw7Y+0ws30
9 changed files with 363 additions and 304 deletions

View File

@ -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};

View File

@ -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

View File

@ -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;
}

View File

@ -0,0 +1,13 @@
#pragma once
#ifndef GFX_DECODER_H
#define GFX_DECODER_H
#include <Arduino.h>
extern uint16_t gfxFrameLengthsBuffer[12000];
extern uint16_t frameCount;
extern uint8_t gfxFrameBuffer[2048];
bool gfx_decoder_loadNextFrame();
#endif

170
firmware/src/leds.cpp Normal file
View File

@ -0,0 +1,170 @@
#include <Arduino.h>
#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;
}

32
firmware/src/leds.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#ifndef LEDS_H
#define LEDS_H
#include <Arduino.h>
#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

View File

@ -1,64 +1,12 @@
#include <Arduino.h>
#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();
}

View File

@ -3,6 +3,7 @@
#include <RP2040_SD.h>
#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;
}

View File

@ -6,4 +6,6 @@ void setupSD();
bool isSDCardInserted();
void sd_getAudio();
void sd_getGfx();
bool sd_loadGfxFrameLengths();
bool sd_loadGfxBlob();
int32_t sd_loadNextFrame();