blob: c3c51df9495b879218b04f6f803d63601444ceaa [file]
#include "flash-loader.hpp"
#include "graphics/color.hpp"
#include <cmath>
#include "quadspi.h"
#include "CDCCommandStream.h"
#include <cstring>
#include <stdio.h>
#include <stdlib.h>
using namespace blit;
extern CDCCommandStream g_commandStream;
constexpr uint32_t qspi_flash_sector_size = 64 * 1024;
std::vector<FileInfo> files;
Vec2 file_list_scroll_offset(20.0f, 0.0f);
FlashLoader flashLoader;
inline bool ends_with(std::string const &value, std::string const &ending)
{
if(ending.size() > value.size()) return false;
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
void load_file_list() {
files.clear();
for(auto file : list_files("")) {
if(ends_with(file.name, ".bin")) {
files.push_back(file);
}
}
sort(files.begin(), files.end());
}
void select_file(const char *filename) {
for(auto i = 0; i < files.size(); i++) {
if(files[i].name == filename) {
persist.selected_menu_item = i;
}
}
}
Vec2 list_offset(5.0f, 0.0f);
void init()
{
set_screen_mode(ScreenMode::hires);
load_file_list();
// register PROG
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'P', 'R', 'O', 'G'>::value, &flashLoader);
// register SAVE
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'S', 'A', 'V', 'E'>::value, &flashLoader);
// register LS
g_commandStream.AddCommandHandler(CDCCommandHandler::CDCFourCCMake<'_', '_', 'L', 'S'>::value, &flashLoader);
}
void background(uint32_t time_ms) {
constexpr uint8_t blob_count = 50;
static Vec3 blobs[blob_count];
static bool first = true;
/*
for(int y = 0; y < 240; y++) {
screen.pen = Pen(y, 0, 0);
screen.rectangle(Rect(0, y, 80, 1));
screen.pen = Pen(0, y, 0);
screen.rectangle(Rect(80, y, 80, 1));
screen.pen = Pen(0, 0, y);
screen.rectangle(Rect(160, y, 80, 1));
screen.pen = Pen(y, y, y);
screen.rectangle(Rect(240, y, 80, 1));
}
screen.pen = Pen(0, 0, 0);
screen.rectangle(Rect(100, 100, 100, 100));
return;
*/
for(uint16_t y = 0; y < 420; y++) {
for(uint16_t x = 0; x < 320; x++) {
screen.pen = Pen(x >> 1, y, 255 - (x >> 1));
screen.pixel(Point(x,y));
}
}
// constexpr float pi = 3.1415927;
if(first) {
for(auto &blob : blobs) {
blob.x = (random() % 320);
blob.y = (random() % 240);
blob.z = (random() % 20) + 20;
}
first = false;
}
for(uint8_t i = 0; i < blob_count; i++) {
float step = (time_ms + i * 200) / 1000.0f;
Vec3 &blob = blobs[i];
screen.pen = Pen(0, 0, 0, 20);
int x = blob.x + sin(step) * i * 2.0f;
int y = blob.y + cos(step) * i * 2.0f;
int r = blob.z;// * (sin(step) + cos(step) + 1.0f);
screen.circle(Point(x, y), r);
}
}
void render(uint32_t time_ms)
{
screen.pen = Pen(5, 8, 12);
screen.clear();
background(time_ms);
screen.pen = Pen(0, 0, 0, 100);
screen.rectangle(Rect(10, 0, 100, 240));
for(uint32_t i = 0; i < files.size(); i++) {
FileInfo *file = &files[i];
screen.pen = Pen(80, 100, 120);
if(i == persist.selected_menu_item) {
screen.pen = Pen(235, 245, 255);
}
std::string display_name = file->name.substr(0, file->name.size() - 4);
screen.text(display_name, minimal_font, Point(file_list_scroll_offset.x, 115 + i * 10 - file_list_scroll_offset.y));
}
screen.watermark();
progress.draw();
}
int32_t modulo(int32_t x, int32_t n) {
return (x % n + n) % n;
}
void update(uint32_t time)
{
static uint32_t last_buttons;
static uint32_t up_repeat = 0;
static uint32_t down_repeat = 0;
bool up_pressed = (buttons & DPAD_UP) && !(last_buttons & DPAD_UP);
bool down_pressed = (buttons & DPAD_DOWN) && !(last_buttons & DPAD_DOWN);
up_repeat = (buttons & DPAD_UP) ? up_repeat + 1 : 0;
down_repeat = (buttons & DPAD_DOWN) ? down_repeat + 1 : 0;
uint8_t repeat_count = 25;
if(up_repeat > repeat_count) {
up_pressed = true;
up_repeat = 0;
}
if(down_repeat > repeat_count) {
down_pressed = true;
down_repeat = 0;
}
bool a_pressed = (buttons & A) && !(last_buttons & A);
// handle up/down clicks to select files in the list
if(up_pressed) { persist.selected_menu_item--; }
if(down_pressed) { persist.selected_menu_item++; }
int32_t file_count = files.size();
persist.selected_menu_item = modulo(persist.selected_menu_item, file_count);
// scroll list towards selected item
file_list_scroll_offset.y += ((persist.selected_menu_item * 10) - file_list_scroll_offset.y) / 5.0f;
// select current item in list to launch
if(a_pressed) {
flash_from_sd_to_qspi_flash(files[persist.selected_menu_item].name);
blit_switch_execution();
}
last_buttons = buttons;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool flash_from_sd_to_qspi_flash(const std::string &filename)
{
static uint8_t file_buffer[4 * 1024];
FIL file;
if(f_open(&file, filename.c_str(), FA_READ) != FR_OK) {
return false;
}
UINT bytes_total = f_size(&file);
// erase the sectors needed to write the image
uint32_t sector_count = (bytes_total / qspi_flash_sector_size) + 1;
progress.show("Erasing flash sectors...", sector_count);
for(uint32_t sector = 0; sector < sector_count; sector++) {
qspi_sector_erase(sector * qspi_flash_sector_size);
progress.update(sector);
}
// read the image from as card and write it to the qspi flash
progress.show("Copying from SD card to flash...", bytes_total);
UINT bytes_flashed = 0;
while(bytes_flashed < bytes_total) {
UINT bytes_read = 0;
// read up to 4KB from the file on sd card
if(f_read(&file, (void *)file_buffer, sizeof(file_buffer), &bytes_read) != FR_OK) {
return false;
}
// write the read data to the external qspi flash
if(qspi_write_buffer(bytes_flashed, file_buffer, bytes_read) != QSPI_OK) {
return false;
}
bytes_flashed += bytes_read;
// TODO: is it worth reading the data back and performing a verify here? not sure...
progress.update(bytes_flashed);
}
f_close(&file);
progress.hide();
return true;
}
/*
// RenderSaveFile() Render file save progress %
void FlashLoader::RenderSaveFile(uint32_t time)
{
screen.pen = Pen(0,0,0);
screen.rectangle(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
screen.pen = Pen(255, 255, 255);
char buffer[128];
sprintf(buffer, "Saving %.2u%%", (uint16_t)m_fPercent);
screen.text(buffer, minimal_font, ROW(0));
}
// RenderFlashCDC() Render flashing progress %
void FlashLoader::RenderFlashCDC(uint32_t time)
{
screen.pen = Pen(0,0,0);
screen.rectangle(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
screen.pen = Pen(255, 255, 255);
char buffer[128];
sprintf(buffer, "Flashing %.2u%%", (uint16_t)m_fPercent);
screen.text(buffer, minimal_font, ROW(0));
}
*/
/*
void FlashLoader::Update(uint32_t time)
{
if(m_state == stLS)
{
load_file_list();
m_state = stFlashFile;
}
}
*/
//////////////////////////////////////////////////////////////////////
// Streaming Code
// The streaming code works with a simple state machine,
// current state is in m_parseState, the states parse index is
// in m_uParseState
//////////////////////////////////////////////////////////////////////
// StreamInit() Initialise state machine
bool FlashLoader::StreamInit(CDCFourCC uCommand)
{
//printf("streamInit()\n\r");
m_fPercent = 0.0f;
bool bNeedStream = true;
switch(uCommand)
{
case CDCCommandHandler::CDCFourCCMake<'P', 'R', 'O', 'G'>::value:
m_state = stFlashCDC;
m_parseState = stFilename;
m_uParseIndex = 0;
break;
case CDCCommandHandler::CDCFourCCMake<'S', 'A', 'V', 'E'>::value:
m_state = stSaveFile;
m_parseState = stFilename;
m_uParseIndex = 0;
break;
case CDCCommandHandler::CDCFourCCMake<'_', '_', 'L', 'S'>::value:
m_state = stLS;
bNeedStream = false;
break;
}
return bNeedStream;
}
// FlashData() Flash data to the QSPI flash
// Note: currently qspi_write_buffer only works for sizes of 256 max
/*
bool FlashLoader::FlashData(uint32_t uOffset, uint8_t *pBuffer, uint32_t uLen)
{
bool bResult = false;
if(QSPI_OK == qspi_write_buffer(uOffset, pBuffer, uLen))
{
if(QSPI_OK == qspi_read_buffer(uOffset, m_verifyBuffer, uLen))
{
// compare buffers
bResult = true;
for(uint32_t uB = 0; bResult && uB < uLen; uB++)
bResult = pBuffer[uB] == m_verifyBuffer[uB];
}
}
return bResult;
}
*/
// SaveData() Saves date to file on SDCard
bool FlashLoader::SaveData(uint8_t *pBuffer, uint32_t uLen)
{
UINT uWritten;
FRESULT res = f_write(&m_file, pBuffer, uLen, &uWritten);
return !res && (uWritten == uLen);
}
void copy_from_usb_to_sd_card() {
}
// StreamData() Handle streamed data
// State machine has three states:
// stFilename : Parse filename
// stLength : Parse length, this is sent as an ascii string
// stData : The binary data (.bin file)
// when a command is issued (e.g. "PROG" or "SAVE") then this function is called
// whenever new data is received to allow the firmware to process it.
CDCCommandHandler::StreamResult FlashLoader::StreamData(CDCDataStream &stream)
{
enum class StreamState { NONE, NAME, LENGTH, DATA };
static StreamState state = StreamState::NONE;
static char name_buffer[256];
static char length_buffer[ 16];
static char data_buffer[256];
static FIL file;
static UINT bytes_total;
static UINT bytes_read = 0;
static char *p = nullptr;
if(state == StreamState::NONE) {
p = name_buffer;
state = StreamState::NAME;
}
// loop through all bytes in the input stream
uint8_t byte;
while(stream.Get(byte)) {
*p++ = byte;
switch(state) {
// sending filename
case StreamState::NAME:
if(byte == '\0') {
p = length_buffer;
state = StreamState::LENGTH;
}
break;
// sending file length
case StreamState::LENGTH:
if(byte == '\0') {
bytes_read = 0;
bytes_total = atol(length_buffer);
p = data_buffer;
if(f_open(&file, name_buffer, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) {
progress.show(std::to_string(srError), 1);
return srError;
}
state = StreamState::DATA;
progress.show("Copying to SD card...", bytes_total);
}
break;
// sending file data
case StreamState::DATA:
bytes_read++;
// completed the read or filled the buffer? write to the file
if(bytes_read == bytes_total || p == (data_buffer + sizeof(data_buffer))) {
UINT bytes_written;
if(f_write(&file, data_buffer, p - data_buffer, &bytes_written) != FR_OK) {
return srError;
}
p = data_buffer;
progress.update(bytes_read);
}
// completed the write? close the file
if(bytes_read == bytes_total) {
f_close(&file);
state = StreamState::NONE;
progress.hide();
load_file_list();
select_file(name_buffer);
return srFinish;
}
break;
}
}
return srContinue;
/*
CDCCommandHandler::StreamResult result = srContinue;
uint8_t byte;
while(dataStream.GetStreamLength() && result == srContinue)
{
switch (m_parseState)
{
case stFilename:
if(m_uParseIndex < MAX_FILENAME)
{
while(result == srContinue && m_parseState == stFilename && dataStream.Get(byte))
{
m_sFilename[m_uParseIndex++] = byte;
if (byte == 0)
{
m_parseState = stLength;
m_uParseIndex = 0;
}
}
}
else
{
printf("Failed to read filename\n\r");
result =srError;
}
break;
case stLength:
if(m_uParseIndex < MAX_FILELEN)
{
while(result == srContinue && m_parseState == stLength && dataStream.Get(byte))
{
m_sFilelen[m_uParseIndex++] = byte;
if (byte == 0)
{
m_parseState = stData;
m_uParseIndex = 0;
char *pEndPtr;
m_uFilelen = strtoul(m_sFilelen, &pEndPtr, 10);
if(m_uFilelen)
{
// init file or flash
switch(m_state)
{
case stSaveFile:
{
FRESULT res = f_open(&m_file, m_sFilename, FA_CREATE_ALWAYS | FA_WRITE);
if(res)
{
printf("Failed to create file (%s)\n\r", m_sFilename);
result = srError;
}
}
break;
case stFlashCDC:
qspi_chip_erase();
break;
default:
break;
}
}
else
{
printf("Failed to parse filelen\n\r");
result =srError;
}
}
}
}
else
{
printf("Failed to read filelen\n\r");
result =srError;
}
break;
case stData:
while((result == srContinue) && (m_parseState == stData) && (m_uParseIndex <= m_uFilelen) && dataStream.Get(byte))
{
uint32_t uByteOffset = m_uParseIndex % PAGE_SIZE;
m_buffer[uByteOffset] = byte;
// check buffer needs writing
volatile uint32_t uWriteLen = 0;
bool bEOS = false;
if (m_uParseIndex == m_uFilelen-1)
{
uWriteLen = uByteOffset+1;
bEOS = true;
}
else
if(uByteOffset == PAGE_SIZE-1)
uWriteLen = PAGE_SIZE;
if(uWriteLen)
{
switch(m_state)
{
case stSaveFile:
// save data
if(!SaveData(m_buffer, uWriteLen))
{
printf("Failed to save to SDCard\n\r");
result = srError;
}
// end of stream close up
if(bEOS)
{
f_close(&m_file);
m_bFsInit = false;
m_state = stFlashFile;
if(result != srError)
result = srFinish;
}
break;
case stFlashCDC:
{
// save data
volatile uint32_t uPage = (m_uParseIndex / PAGE_SIZE);
if(!FlashData(uPage*PAGE_SIZE, m_buffer, uWriteLen))
{
printf("Failed to write to flash\n\r");
result = srError;
}
// end of stream close up
if(bEOS)
{
if(result != srError)
{
result = srFinish;
m_state = stSwitch;
}
else
m_state = stFlashFile;
}
}
break;
default:
break;
}
}
m_uParseIndex++;
m_uBytesHandled = m_uParseIndex;
m_fPercent = ((float)m_uParseIndex/(float)m_uFilelen)* 100.0f;
}
break;
}
}
if(result==srError)
m_state = stFlashFile;*/
//return result;
}