mirror of https://github.com/radex/radmatrix.git
reading gfx from sd card…
parent
db2cb2bc43
commit
0a6ba79e8d
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
// Adapted from https://github.com/rgrosset/pico-pwm-audio
|
// Adapted from https://github.com/rgrosset/pico-pwm-audio
|
||||||
|
|
||||||
#define AUDIO_PIN 23
|
|
||||||
|
|
||||||
#define MAX_PWM_POS (BUFFER_LEN << 3)
|
#define MAX_PWM_POS (BUFFER_LEN << 3)
|
||||||
|
|
||||||
uint8_t wav_buffer_0[BUFFER_LEN] = {0};
|
uint8_t wav_buffer_0[BUFFER_LEN] = {0};
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#ifndef _audio_h
|
#ifndef _audio_h
|
||||||
#define _audio_h
|
#define _audio_h
|
||||||
|
|
||||||
|
#define AUDIO_PIN 2
|
||||||
|
|
||||||
#define AUDIO_RATE 44000.0f
|
#define AUDIO_RATE 44000.0f
|
||||||
#define BUFFER_LEN 16384
|
#define BUFFER_LEN 16384
|
||||||
#define BUFFER_LEN_MS (BUFFER_LEN / AUDIO_RATE) * 1000.0f
|
#define BUFFER_LEN_MS (BUFFER_LEN / AUDIO_RATE) * 1000.0f
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -1,64 +1,12 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "gfx_png.h"
|
|
||||||
#include "lodepng.h"
|
|
||||||
#include "pico/multicore.h"
|
|
||||||
#include "hardware/gpio.h"
|
#include "hardware/gpio.h"
|
||||||
#include "mbed_wait_api.h"
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "sd.h"
|
#include "sd.h"
|
||||||
|
#include "leds.h"
|
||||||
// #define Serial Serial1
|
#include "gfx_decoder.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
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
leds_init();
|
||||||
setupSDPins();
|
setupSDPins();
|
||||||
|
|
||||||
delay(2000);
|
delay(2000);
|
||||||
|
@ -67,232 +15,51 @@ void setup() {
|
||||||
|
|
||||||
init_audio();
|
init_audio();
|
||||||
|
|
||||||
while (!isSDCardInserted()) {
|
// while (!isSDCardInserted()) {
|
||||||
Serial.println("SD card not connected, waiting...");
|
// Serial.println("SD card not connected, waiting...");
|
||||||
delay(1000);
|
// delay(1000);
|
||||||
}
|
// }
|
||||||
delay(100);
|
// delay(100);
|
||||||
|
|
||||||
setupSD();
|
setupSD();
|
||||||
|
|
||||||
// sd_getAudio();
|
// // sd_getAudio();
|
||||||
sd_getGfx();
|
// sd_getGfx();
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
memset(framebuffer, 0, sizeof(framebuffer));
|
if (!sd_loadGfxFrameLengths()) {
|
||||||
|
Serial.println("Failed to load gfx frame lengths");
|
||||||
|
while (true) {}
|
||||||
|
}
|
||||||
|
|
||||||
// disable output
|
if (!sd_loadGfxBlob()) {
|
||||||
outputEnable(COL_OE, false);
|
Serial.println("Failed to load gfx blob");
|
||||||
outputEnable(ROW_OE, false);
|
while (true) {}
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|
||||||
return;
|
|
||||||
if (Serial.available() > 0) {
|
if (Serial.available() > 0) {
|
||||||
char c = Serial.read();
|
char c = Serial.read();
|
||||||
if (c == 'p') {
|
if (c == 'p') {
|
||||||
Serial.println("Paused. Press any key to continue.");
|
Serial.println("Paused. Press any key to continue.");
|
||||||
outputEnable(ROW_OE, false);
|
leds_disable();
|
||||||
while (Serial.available() == 0) {
|
while (Serial.available() == 0) {
|
||||||
Serial.read();
|
Serial.read();
|
||||||
delay(50);
|
delay(50);
|
||||||
}
|
}
|
||||||
} else if (c == 'r') {
|
Serial.println("Continuing...");
|
||||||
Serial.println("Restarting...");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multicore_fifo_rvalid()) {
|
|
||||||
uint32_t value = multicore_fifo_pop_blocking();
|
if (!gfx_decoder_loadNextFrame()) {
|
||||||
if (value == 21372137) {
|
Serial.println("Failed to load frame...");
|
||||||
Serial.println("Invalid frame size");
|
|
||||||
} else {
|
|
||||||
Serial.print("PNG decode error ");
|
|
||||||
Serial.print(value);
|
|
||||||
Serial.print(": ");
|
|
||||||
Serial.println(lodepng_error_text(value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frameIndex != lastRenderedFrameIndex) {
|
leds_render();
|
||||||
Serial.print("Going to frame ");
|
leds_render();
|
||||||
Serial.println(frameIndex);
|
leds_render();
|
||||||
lastRenderedFrameIndex = frameIndex;
|
leds_render();
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <RP2040_SD.h>
|
#include <RP2040_SD.h>
|
||||||
#include "sd.h"
|
#include "sd.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
#include "gfx_decoder.h"
|
||||||
|
|
||||||
#define SD_DET_PIN 28
|
#define SD_DET_PIN 28
|
||||||
|
|
||||||
|
@ -206,60 +207,86 @@ void sd_getAudio() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t lenBuffer[12000] = {0};
|
bool sd_loadGfxFrameLengths() {
|
||||||
uint8_t dataBuffer[2048] = {0};
|
auto path = "badapple/gfx_len.bin";
|
||||||
uint16_t frameCount = 0;
|
|
||||||
|
|
||||||
void sd_getGfx() {
|
if (!SD.exists(path)) {
|
||||||
// read frame lengths
|
Serial.println("Frame lengths file not found :(");
|
||||||
auto lengthsFile = SD.open("badapple/gfx_len.bin", FILE_READ);
|
return false;
|
||||||
frameCount = lengthsFile.size() / sizeof(uint16_t);
|
}
|
||||||
|
|
||||||
Serial.print("Frames: ");
|
auto lengthsFile = SD.open(path, FILE_READ);
|
||||||
Serial.println(frameCount);
|
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()) {
|
while (lengthsFile.available()) {
|
||||||
lengthsFile.read(&lenBuffer, sizeof(lenBuffer));
|
lengthsFile.read(&gfxFrameLengthsBuffer, sizeof(gfxFrameLengthsBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
lengthsFile.close();
|
lengthsFile.close();
|
||||||
Serial.println("Done reading frame lengths");
|
Serial.println("Done reading frame lengths");
|
||||||
|
|
||||||
auto gfxBlobFile = SD.open("badapple/gfx.bin", FILE_READ);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto b4 = millis();
|
File gfxFile;
|
||||||
for (uint16_t i = 0; i < frameCount; i++) {
|
|
||||||
Serial.print("Frame ");
|
|
||||||
Serial.print(i);
|
|
||||||
Serial.print(" - length ");
|
|
||||||
|
|
||||||
auto len = lenBuffer[i];
|
uint16_t frameIdx = 0;
|
||||||
|
|
||||||
Serial.print(lenBuffer[i]);
|
bool sd_loadGfxBlob() {
|
||||||
Serial.print(" - read in ");
|
auto path = "badapple/gfx.bin";
|
||||||
|
|
||||||
if (len > sizeof(dataBuffer)) {
|
if (!SD.exists(path)) {
|
||||||
Serial.println("Frame too large");
|
Serial.println("Gfx blob file not found :(");
|
||||||
return;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gfxBlobFile.close();
|
gfxFile = SD.open(path, FILE_READ);
|
||||||
Serial.println("Done reading frames");
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,6 @@ void setupSD();
|
||||||
bool isSDCardInserted();
|
bool isSDCardInserted();
|
||||||
|
|
||||||
void sd_getAudio();
|
void sd_getAudio();
|
||||||
void sd_getGfx();
|
bool sd_loadGfxFrameLengths();
|
||||||
|
bool sd_loadGfxBlob();
|
||||||
|
int32_t sd_loadNextFrame();
|
||||||
|
|
Loading…
Reference in New Issue