Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • flow3r/flow3r-firmware
  • Vespasian/flow3r-firmware
  • alxndr42/flow3r-firmware
  • pl/flow3r-firmware
  • Kari/flow3r-firmware
  • raimue/flow3r-firmware
  • grandchild/flow3r-firmware
  • mu5tach3/flow3r-firmware
  • Nervengift/flow3r-firmware
  • arachnist/flow3r-firmware
  • TheNewCivilian/flow3r-firmware
  • alibi/flow3r-firmware
  • manuel_v/flow3r-firmware
  • xeniter/flow3r-firmware
  • maxbachmann/flow3r-firmware
  • yGifoom/flow3r-firmware
  • istobic/flow3r-firmware
  • EiNSTeiN_/flow3r-firmware
  • gnudalf/flow3r-firmware
  • 999eagle/flow3r-firmware
  • toerb/flow3r-firmware
  • pandark/flow3r-firmware
  • teal/flow3r-firmware
  • x42/flow3r-firmware
  • alufers/flow3r-firmware
  • dos/flow3r-firmware
  • yrlf/flow3r-firmware
  • LuKaRo/flow3r-firmware
  • ThomasElRubio/flow3r-firmware
  • ai/flow3r-firmware
  • T_X/flow3r-firmware
  • highTower/flow3r-firmware
  • beanieboi/flow3r-firmware
  • Woazboat/flow3r-firmware
  • gooniesbro/flow3r-firmware
  • marvino/flow3r-firmware
  • kressnerd/flow3r-firmware
  • quazgar/flow3r-firmware
  • aoid/flow3r-firmware
  • jkj/flow3r-firmware
  • naomi/flow3r-firmware
41 results
Select Git revision
Show changes
Showing
with 2045 additions and 381 deletions
...@@ -23,21 +23,11 @@ int64_t st3m_counter_rate_average(st3m_counter_rate_t *rate) { ...@@ -23,21 +23,11 @@ int64_t st3m_counter_rate_average(st3m_counter_rate_t *rate) {
if (start < 0) start = 9; if (start < 0) start = 9;
if (end < 0) end = 9; if (end < 0) end = 9;
int i = start; int32_t count = (end - start + 10) % 10;
int64_t sum = 0;
int64_t count = 0;
while (i != end) {
int j = (i + 1) % 10;
int64_t diff = rate->sample_us[j] - rate->sample_us[i];
sum += diff;
count++;
i = j;
}
if (count == 0) { if (count == 0) {
return INT64_MAX; return INT64_MAX;
} }
int32_t sum = rate->sample_us[end] - rate->sample_us[start];
return sum / count; return sum / count;
} }
...@@ -62,7 +52,7 @@ void st3m_counter_timer_sample(st3m_counter_timer_t *timer, int64_t val) { ...@@ -62,7 +52,7 @@ void st3m_counter_timer_sample(st3m_counter_timer_t *timer, int64_t val) {
} }
} }
int64_t st3m_counter_timer_average(st3m_counter_timer_t *timer) { int32_t st3m_counter_timer_average(st3m_counter_timer_t *timer) {
int start = timer->read_offs - 1; int start = timer->read_offs - 1;
int end = timer->write_offs - 1; int end = timer->write_offs - 1;
if (start < 0) start = 9; if (start < 0) start = 9;
......
...@@ -25,5 +25,5 @@ typedef struct { ...@@ -25,5 +25,5 @@ typedef struct {
void st3m_counter_timer_init(st3m_counter_timer_t *rate); void st3m_counter_timer_init(st3m_counter_timer_t *rate);
void st3m_counter_timer_sample(st3m_counter_timer_t *timer, int64_t val); void st3m_counter_timer_sample(st3m_counter_timer_t *timer, int64_t val);
int64_t st3m_counter_timer_average(st3m_counter_timer_t *rate); int32_t st3m_counter_timer_average(st3m_counter_timer_t *rate);
uint8_t st3m_counter_timer_report(st3m_counter_timer_t *rate, int seconds); uint8_t st3m_counter_timer_report(st3m_counter_timer_t *rate, int seconds);
...@@ -263,6 +263,10 @@ static esp_err_t _st3m_fs_sd_probe_unlocked(st3m_fs_sd_props_t *props) { ...@@ -263,6 +263,10 @@ static esp_err_t _st3m_fs_sd_probe_unlocked(st3m_fs_sd_props_t *props) {
esp_err_to_name(ret)); esp_err_to_name(ret));
return ret; return ret;
} }
// useful for collecting info about SDcards that are problematic, ask users
// about this to potentially add to blocklist. Also in about menu.
ESP_LOGI(TAG, "SD CID: mfg_id: %i, oem_id: %i, date: %i", _card.cid.mfg_id,
_card.cid.oem_id, _card.cid.date);
_status = st3m_fs_sd_status_probed; _status = st3m_fs_sd_status_probed;
...@@ -309,6 +313,22 @@ esp_err_t st3m_fs_sd_probe(st3m_fs_sd_props_t *props) { ...@@ -309,6 +313,22 @@ esp_err_t st3m_fs_sd_probe(st3m_fs_sd_props_t *props) {
return ret; return ret;
} }
int32_t st3m_fs_sd_oem_id() {
if (_status == st3m_fs_sd_status_probed ||
_status == st3m_fs_sd_status_mounted) {
return _card.cid.oem_id;
}
return -1;
}
int32_t st3m_fs_sd_mfg_id() {
if (_status == st3m_fs_sd_status_probed ||
_status == st3m_fs_sd_status_mounted) {
return _card.cid.mfg_id;
}
return -1;
}
int32_t st3m_fs_sd_read10(uint8_t lun, uint32_t lba, uint32_t offset, int32_t st3m_fs_sd_read10(uint8_t lun, uint32_t lba, uint32_t offset,
void *buffer, uint32_t bufsize) { void *buffer, uint32_t bufsize) {
if (offset != 0) { if (offset != 0) {
......
...@@ -42,6 +42,12 @@ esp_err_t st3m_fs_sd_unmount(void); ...@@ -42,6 +42,12 @@ esp_err_t st3m_fs_sd_unmount(void);
// No-op if card is already ejected. // No-op if card is already ejected.
esp_err_t st3m_fs_sd_eject(void); esp_err_t st3m_fs_sd_eject(void);
// for identifying sdcards in python land to warn about known problematic
// SDcards
int32_t st3m_fs_sd_oem_id();
int32_t st3m_fs_sd_mfg_id();
// SCSI Read (10) and Write (10) compatible operations. Offset must be 0. If an // SCSI Read (10) and Write (10) compatible operations. Offset must be 0. If an
// error occurs during the read/write operation, the card will be checked for // error occurs during the read/write operation, the card will be checked for
// errors, and might get ejected if deemed uanvailable/broken. // errors, and might get ejected if deemed uanvailable/broken.
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "esp_timer.h" #include "esp_timer.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "freertos/semphr.h"
// clang-format off // clang-format off
#include "ctx_config.h" #include "ctx_config.h"
...@@ -18,39 +19,201 @@ ...@@ -18,39 +19,201 @@
#include "st3m_counter.h" #include "st3m_counter.h"
#include "st3m_version.h" #include "st3m_version.h"
static const char *TAG = "st3m-gfx"; #define ST3M_GFX_BLIT_TASK 1
#if CTX_ST3M_FB_INTERNAL_RAM
#undef ST3M_GFX_BLIT_TASK
#define ST3M_GFX_BLIT_TASK 0
#endif
#define ST3M_GFX_DEFAULT_MODE (16 | st3m_gfx_osd)
static st3m_gfx_mode default_mode = ST3M_GFX_DEFAULT_MODE;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
EXT_RAM_BSS_ATTR static uint8_t
st3m_fb2[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT * 4];
#endif
// Framebuffer descriptors, containing framebuffer and ctx for each of the #if CTX_ST3M_FB_INTERNAL_RAM
// framebuffers. // without EXT_RAM_BSS_ATTR is removed 8bit and 16bit modes go
// faster but it is not possible to enable wifi
static uint16_t st3m_fb[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT];
uint8_t st3m_pal[256 * 3];
#else
EXT_RAM_BSS_ATTR uint8_t st3m_pal[256 * 3];
EXT_RAM_BSS_ATTR static uint8_t
st3m_fb[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT * 4];
#endif
#if ST3M_GFX_BLIT_TASK
EXT_RAM_BSS_ATTR static uint8_t
st3m_fb_copy[FLOW3R_BSP_DISPLAY_WIDTH * FLOW3R_BSP_DISPLAY_HEIGHT * 2];
#endif
EXT_RAM_BSS_ATTR static uint8_t scratch[40 * 1024];
// Get a free drawlist ctx to draw into.
// //
// These live in SPIRAM, as we don't have enough space in SRAM/IRAM. // ticks_to_wait can be used to limit the time to wait for a free ctx
EXT_RAM_BSS_ATTR static st3m_framebuffer_desc_t // descriptor, or portDELAY_MAX can be specified to wait forever. If the timeout
framebuffer_descs[ST3M_GFX_NBUFFERS]; // expires, NULL will be returned.
static Ctx *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait);
// Submit a filled ctx descriptor to the rasterization pipeline.
static void st3m_gfx_pipe_put(void);
static const char *TAG = "st3m-gfx";
#define N_DRAWLISTS 3
// we keep the OSD buffer the same size as the main framebuffer,
// allowing us to do different combos of which buffer is osd and not
static st3m_gfx_mode _st3m_gfx_mode = st3m_gfx_default + 1;
static Ctx *ctx = NULL;
// each frame buffer has an associated rasterizer context
static Ctx *fb_ctx = NULL;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static Ctx *osd_ctx = NULL;
#define ST3M_OSD_LOCK_TIMEOUT 500
SemaphoreHandle_t st3m_osd_lock;
#endif
SemaphoreHandle_t st3m_fb_lock;
SemaphoreHandle_t st3m_fb_copy_lock;
static st3m_ctx_desc_t dctx_descs[ST3M_GFX_NCTX]; typedef struct {
Ctx *user_ctx;
// Queue of free framebuffer descriptors, written into by crtc once rendered, int osd_y0;
// read from by rasterizer when new frame starts. int osd_x0;
static QueueHandle_t framebuffer_freeq = NULL; int osd_y1;
// Queue of framebuffer descriptors to blit out. int osd_x1;
static QueueHandle_t framebuffer_blitq = NULL;
static st3m_counter_rate_t blit_rate; int blit_x; // upper left pixel in framebuffer coordinates
static st3m_counter_timer_t blit_read_time; int blit_y; //
static st3m_counter_timer_t blit_work_time;
static st3m_counter_timer_t blit_write_time;
static QueueHandle_t dctx_freeq = NULL; st3m_gfx_mode mode;
static QueueHandle_t dctx_rastq = NULL; uint8_t *blit_src;
} st3m_gfx_drawlist;
static st3m_gfx_drawlist drawlists[N_DRAWLISTS];
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static int _st3m_osd_y1 =
0; // the corner coordinates of the part of osd that needs to
static int _st3m_osd_x1 = 0; // be composited - more might be composited
static int _st3m_osd_y0 = 0;
static int _st3m_osd_x0 = 0;
#endif
static float smoothed_fps = 0.0f;
static QueueHandle_t user_ctx_freeq = NULL;
static QueueHandle_t user_ctx_rastq = NULL;
static QueueHandle_t user_ctx_blitq = NULL;
static st3m_counter_rate_t rast_rate; static st3m_counter_rate_t rast_rate;
static st3m_counter_timer_t rast_read_fb_time; static TaskHandle_t graphics_rast_task;
static st3m_counter_timer_t rast_read_dctx_time; #if ST3M_GFX_BLIT_TASK
static st3m_counter_timer_t rast_work_time; static TaskHandle_t graphics_blit_task;
static st3m_counter_timer_t rast_write_time; #endif
static int _st3m_gfx_low_latency = 0;
static int st3m_gfx_fb_width = 0;
static int st3m_gfx_fb_height = 0;
static int st3m_gfx_blit_x = 0;
static int st3m_gfx_blit_y = 0;
static int st3m_gfx_geom_dirty = 0;
static inline int st3m_gfx_scale(st3m_gfx_mode mode) {
switch ((int)(mode & st3m_gfx_4x)) {
case st3m_gfx_4x:
return 4;
case st3m_gfx_3x:
return 3;
case st3m_gfx_2x:
return 2;
}
return 1;
}
///////////////////////////////////////////////////////
// get the bits per pixel for a given mode
static inline int _st3m_gfx_bpp(st3m_gfx_mode mode) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if (mode == st3m_gfx_default) {
mode = set_mode;
} else if (mode == st3m_gfx_osd) {
if ((st3m_gfx_bpp(set_mode) == 16) || (st3m_gfx_bpp(set_mode) == 8))
return 32;
else
return 16;
}
int bpp = (mode & 63);
if (bpp >= 2 && bpp < 4) bpp = 2;
if (bpp >= 4 && bpp < 8) bpp = 4;
if (bpp >= 8 && bpp < 16) bpp = 8;
return bpp;
}
int st3m_gfx_bpp(st3m_gfx_mode mode) { return _st3m_gfx_bpp(mode); }
static Ctx *st3m_gfx_ctx_int(st3m_gfx_mode mode) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
if (mode == st3m_gfx_osd) {
if ((_st3m_gfx_bpp(set_mode) == 16) || (st3m_gfx_bpp(set_mode) == 8))
return osd_ctx;
return osd_ctx;
}
#endif
Ctx *ctx = st3m_gfx_drawctx_free_get(1000);
static TaskHandle_t crtc_task; if (set_mode & st3m_gfx_direct_ctx) {
static TaskHandle_t rast_task; if (set_mode & st3m_gfx_smart_redraw) return ctx;
return fb_ctx;
}
if (!ctx) return NULL;
return ctx;
}
static void st3m_gfx_viewport_transform(Ctx *ctx, int reset) {
int scale = st3m_gfx_scale(_st3m_gfx_mode ? _st3m_gfx_mode : default_mode);
int32_t offset_x = FLOW3R_BSP_DISPLAY_WIDTH / 2 / scale;
int32_t offset_y = FLOW3R_BSP_DISPLAY_HEIGHT / 2 / scale;
if (reset)
ctx_identity(
ctx); // this might break/need revisiting with tiled rendering
ctx_apply_transform(ctx, 1.0 / scale, 0, offset_x, 0, 1.0 / scale, offset_y,
0, 0, 1);
}
static void st3m_gfx_start_frame(Ctx *ctx) {
int scale = st3m_gfx_scale(_st3m_gfx_mode);
if (scale > 1) {
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_clip(ctx);
}
}
Ctx *st3m_gfx_ctx(st3m_gfx_mode mode) {
Ctx *ctx = st3m_gfx_ctx_int(mode);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
if (mode == st3m_gfx_osd) {
xSemaphoreTake(st3m_osd_lock,
ST3M_OSD_LOCK_TIMEOUT / portTICK_PERIOD_MS);
}
#endif
ctx_save(ctx);
if (mode != st3m_gfx_osd) st3m_gfx_viewport_transform(ctx, 0);
st3m_gfx_start_frame(ctx);
return ctx;
}
// Attempt to receive from a queue forever, but log an error if it takes longer // Attempt to receive from a queue forever, but log an error if it takes longer
// than two seconds to get something. // than two seconds to get something.
...@@ -68,114 +231,459 @@ static void xQueueReceiveNotifyStarved(QueueHandle_t q, void *dst, ...@@ -68,114 +231,459 @@ static void xQueueReceiveNotifyStarved(QueueHandle_t q, void *dst,
} }
} }
static void st3m_gfx_crtc_task(void *_arg) { float st3m_gfx_fps(void) { return smoothed_fps; }
(void)_arg;
while (true) { void st3m_gfx_set_palette(uint8_t *pal_in, int count) {
int descno; if (count > 256) count = 256;
if (count < 0) count = 0;
for (int i = 0; i < count * 3; i++) st3m_pal[i] = pal_in[i];
}
int64_t start = esp_timer_get_time(); void st3m_gfx_set_default_mode(st3m_gfx_mode mode) {
xQueueReceiveNotifyStarved(framebuffer_blitq, &descno, if ((mode & (1 | 2 | 4 | 8 | 16 | 32)) == mode) {
"crtc task starved!"); default_mode &= ~(1 | 2 | 4 | 8 | 16 | 32);
int64_t end = esp_timer_get_time(); default_mode |= mode;
st3m_counter_timer_sample(&blit_read_time, end - start); } else if (mode == st3m_gfx_2x) {
default_mode &= ~st3m_gfx_4x;
default_mode |= st3m_gfx_2x;
} else if (mode == st3m_gfx_3x) {
default_mode &= ~st3m_gfx_4x;
default_mode |= st3m_gfx_3x;
} else if (mode == st3m_gfx_4x) {
default_mode &= ~st3m_gfx_4x;
default_mode |= st3m_gfx_4x;
} else if (mode == st3m_gfx_osd) {
default_mode |= st3m_gfx_osd;
} else if (mode == st3m_gfx_low_latency) {
default_mode |= st3m_gfx_low_latency;
} else if (mode == st3m_gfx_lock) {
default_mode |= st3m_gfx_lock;
} else if (mode == st3m_gfx_direct_ctx) {
default_mode |= st3m_gfx_direct_ctx;
} else
default_mode = mode;
if (default_mode & st3m_gfx_smart_redraw) {
default_mode &= ~63;
default_mode |= 16;
}
start = esp_timer_get_time(); if (default_mode & st3m_gfx_lock) {
flow3r_bsp_display_send_fb(framebuffer_descs[descno].buffer); default_mode &= ~st3m_gfx_lock;
end = esp_timer_get_time(); _st3m_gfx_mode = default_mode + 1;
st3m_counter_timer_sample(&blit_work_time, end - start); st3m_gfx_set_mode(st3m_gfx_default);
default_mode |= st3m_gfx_lock;
} else {
_st3m_gfx_mode = default_mode + 1;
st3m_gfx_set_mode(st3m_gfx_default);
}
}
start = esp_timer_get_time(); static void st3m_gfx_init_palette(st3m_gfx_mode mode) {
xQueueSend(framebuffer_freeq, &descno, portMAX_DELAY); switch (mode & 0xf) {
end = esp_timer_get_time(); case 1:
st3m_counter_timer_sample(&blit_write_time, end - start); for (int i = 0; i < 2; i++) {
st3m_pal[i * 3 + 0] = i * 255;
st3m_pal[i * 3 + 1] = i * 255;
st3m_pal[i * 3 + 2] = i * 255;
}
break;
case 2:
for (int i = 0; i < 4; i++) {
st3m_pal[i * 3 + 0] = (i * 255) / 3;
st3m_pal[i * 3 + 1] = (i * 255) / 3;
st3m_pal[i * 3 + 2] = (i * 255) / 3;
}
break;
case 4: {
#if 0
// ega palette
int idx = 0;
for (int i = 0; i < 2; i++)
for (int r = 0; r < 2; r++)
for (int g = 0; g < 2; g++)
for (int b = 0; b < 2; b++) {
st3m_pal[idx++] = (r * 127) * (i * 2);
st3m_pal[idx++] = (g * 127) * (i * 2);
st3m_pal[idx++] = (b * 127) * (i * 2);
}
#else
// night-mode
for (int i = 0; i < 16; i++) {
st3m_pal[i * 3 + 0] = (i * 255) / 15;
st3m_pal[i * 3 + 1] = ((i * 255) / 15) / 3;
st3m_pal[i * 3 + 2] = ((i * 255) / 15) / 5;
}
break;
#endif
} break;
case 8: // grayscale
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] = i;
st3m_pal[i * 3 + 1] = i;
st3m_pal[i * 3 + 2] = i;
}
break;
case st3m_gfx_rgb332:
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] = (((i >> 5) & 7) * 255) / 7;
st3m_pal[i * 3 + 1] = (((i >> 2) & 7) * 255) / 7;
st3m_pal[i * 3 + 2] =
((((i & 3) << 1) | ((i >> 2) & 1)) * 255) / 7;
}
break;
case st3m_gfx_sepia:
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] = i;
st3m_pal[i * 3 + 1] = (i / 255.0) * (i / 255.0) * 255;
st3m_pal[i * 3 + 2] =
(i / 255.0) * (i / 255.0) * (i / 255.0) * 255;
}
break;
case st3m_gfx_cool:
for (int i = 0; i < 256; i++) {
st3m_pal[i * 3 + 0] =
(i / 255.0) * (i / 255.0) * (i / 255.0) * 255;
st3m_pal[i * 3 + 1] = (i / 255.0) * (i / 255.0) * 255;
st3m_pal[i * 3 + 2] = i;
}
break;
}
}
st3m_gfx_mode st3m_gfx_set_mode(st3m_gfx_mode mode) {
if ((mode == _st3m_gfx_mode) || (0 != (default_mode & st3m_gfx_lock))) {
return (mode ? mode : default_mode);
}
if (mode == st3m_gfx_default)
mode = default_mode;
else if (mode == st3m_gfx_low_latency)
mode = default_mode | st3m_gfx_low_latency;
else if (mode == st3m_gfx_osd)
mode = default_mode | st3m_gfx_osd;
_st3m_gfx_mode = (mode == default_mode) ? st3m_gfx_default : mode;
if (((mode & st3m_gfx_low_latency) != 0) ||
((mode & st3m_gfx_direct_ctx) != 0))
_st3m_gfx_low_latency = (N_DRAWLISTS - 1);
else
_st3m_gfx_low_latency = 0;
st3m_counter_rate_sample(&blit_rate); st3m_gfx_fbconfig(240, 240, 0, 0);
if (st3m_counter_rate_report(&blit_rate, 1)) { return mode;
float rate = 1000000.0 / st3m_counter_rate_average(&blit_rate);
float read = st3m_counter_timer_average(&blit_read_time) / 1000.0;
float work = st3m_counter_timer_average(&blit_work_time) / 1000.0;
float write = st3m_counter_timer_average(&blit_write_time) / 1000.0;
// Mark variables as used even if debug is disabled.
(void)rate;
(void)read;
(void)work;
(void)write;
ESP_LOGD(
TAG,
"blitting: %.3f/sec, read %.3fms, work %.3fms, write %.3fms",
(double)rate, (double)read, (double)work, (double)write);
} }
st3m_gfx_mode st3m_gfx_get_mode(void) {
return _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
}
uint8_t *st3m_gfx_fb(st3m_gfx_mode mode, int *width, int *height, int *stride) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
int bpp = _st3m_gfx_bpp(set_mode);
if (mode == st3m_gfx_palette) {
if (stride) *stride = 3;
if (width) *width = 1;
if (height) *height = 256;
return st3m_pal;
} else if (mode == st3m_gfx_default) {
if (stride) *stride = st3m_gfx_fb_width * bpp / 8;
if (width) *width = FLOW3R_BSP_DISPLAY_WIDTH;
if (height) *height = FLOW3R_BSP_DISPLAY_HEIGHT;
return ((uint8_t *)st3m_fb);
}
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
else if (mode == st3m_gfx_osd) {
if (stride) *stride = FLOW3R_BSP_DISPLAY_WIDTH * bpp / 8;
if (width) *width = FLOW3R_BSP_DISPLAY_WIDTH;
if (height) *height = FLOW3R_BSP_DISPLAY_HEIGHT;
return st3m_fb2;
} }
int scale = st3m_gfx_scale(set_mode);
if (stride) *stride = FLOW3R_BSP_DISPLAY_WIDTH * bpp / 8;
if (width) *width = FLOW3R_BSP_DISPLAY_WIDTH / scale;
if (height) *height = FLOW3R_BSP_DISPLAY_HEIGHT / scale;
#endif
return (uint8_t *)st3m_fb;
} }
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static void *osd_fb = st3m_fb2;
#endif
static void st3m_gfx_blit(st3m_gfx_drawlist *drawlist) {
st3m_gfx_mode set_mode = drawlist->mode;
uint8_t *blit_src = drawlist->blit_src;
int bits = _st3m_gfx_bpp(set_mode);
static st3m_gfx_mode prev_mode;
if (set_mode != prev_mode) {
st3m_gfx_init_palette(set_mode);
}
xSemaphoreTake(st3m_fb_copy_lock, portMAX_DELAY);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
int scale = st3m_gfx_scale(set_mode);
int osd_x0 = drawlist->osd_x0, osd_x1 = drawlist->osd_x1,
osd_y0 = drawlist->osd_y0, osd_y1 = drawlist->osd_y1;
if ((scale > 1) || ((set_mode & st3m_gfx_osd) && (osd_y0 != osd_y1))) {
if (((set_mode & st3m_gfx_osd) && (osd_y0 != osd_y1))) {
if ((set_mode & 0xf) == st3m_gfx_rgb332) bits = 9;
xSemaphoreTake(st3m_osd_lock,
ST3M_OSD_LOCK_TIMEOUT / portTICK_PERIOD_MS);
flow3r_bsp_display_send_fb_osd(blit_src, bits, scale, osd_fb,
osd_x0, osd_y0, osd_x1, osd_y1);
xSemaphoreGive(st3m_osd_lock);
} else {
if ((set_mode & 0xf) == st3m_gfx_rgb332) bits = 9;
flow3r_bsp_display_send_fb_osd(blit_src, bits, scale, NULL, 0, 0, 0,
0);
}
} else
#endif
{
if ((set_mode & 0xf) == st3m_gfx_rgb332) bits = 9;
flow3r_bsp_display_send_fb(blit_src, bits);
}
xSemaphoreGive(st3m_fb_copy_lock);
prev_mode = set_mode;
}
#if ST3M_GFX_BLIT_TASK
static void st3m_gfx_blit_task(void *_arg) {
while (true) {
int desc_no = 0;
xQueueReceiveNotifyStarved(user_ctx_blitq, &desc_no,
"blit task starved (user_ctx)!");
st3m_gfx_drawlist *drawlist = &drawlists[desc_no];
st3m_gfx_blit(drawlist);
xQueueSend(user_ctx_freeq, &desc_no, portMAX_DELAY);
}
}
#endif
static void st3m_gfx_rast_task(void *_arg) { static void st3m_gfx_rast_task(void *_arg) {
(void)_arg; (void)_arg;
st3m_gfx_set_mode(st3m_gfx_default);
int bits = 0;
st3m_gfx_mode prev_set_mode = ST3M_GFX_DEFAULT_MODE - 1;
#if ST3M_GFX_BLIT_TASK
int direct_blit = 0;
#endif
while (true) { while (true) {
int fb_descno, dctx_descno; int desc_no = 0;
int64_t start = esp_timer_get_time(); int tc = ctx_textureclock(fb_ctx) + 1;
xQueueReceiveNotifyStarved(framebuffer_freeq, &fb_descno, xQueueReceiveNotifyStarved(user_ctx_rastq, &desc_no,
"rast task starved (freeq)!"); "rast task starved (user_ctx)!");
st3m_framebuffer_desc_t *fb = &framebuffer_descs[fb_descno]; st3m_gfx_drawlist *drawlist = &drawlists[desc_no];
int64_t end = esp_timer_get_time(); st3m_gfx_mode set_mode = drawlist->mode;
st3m_counter_timer_sample(&rast_read_fb_time, end - start);
xSemaphoreTake(st3m_fb_lock, portMAX_DELAY);
start = esp_timer_get_time();
xQueueReceiveNotifyStarved(dctx_rastq, &dctx_descno, ctx_set_textureclock(fb_ctx, tc);
"rast task starved (dctx)!"); #if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_ctx_desc_t *draw = &dctx_descs[dctx_descno]; ctx_set_textureclock(osd_ctx, tc);
end = esp_timer_get_time(); ctx_set_textureclock(ctx, tc);
st3m_counter_timer_sample(&rast_read_dctx_time, end - start); #endif
if (st3m_gfx_geom_dirty || (prev_set_mode != set_mode)) {
ctx_set_textureclock(framebuffer_descs[0].ctx, int was_geom_dirty = (prev_set_mode == set_mode);
ctx_textureclock(framebuffer_descs[0].ctx) + 1); bits = _st3m_gfx_bpp(set_mode);
st3m_gfx_geom_dirty = 0;
// Render drawctx into fbctx.
start = esp_timer_get_time(); #if ST3M_GFX_BLIT_TASK
ctx_render_ctx(draw->ctx, fb->ctx); if ((bits > 16))
ctx_drawlist_clear(draw->ctx); direct_blit = 1;
end = esp_timer_get_time(); else
st3m_counter_timer_sample(&rast_work_time, end - start); direct_blit = 0;
#endif
start = esp_timer_get_time();
xQueueSend(dctx_freeq, &dctx_descno, portMAX_DELAY); int stride = (bits * st3m_gfx_fb_width) / 8;
xQueueSend(framebuffer_blitq, &fb_descno, portMAX_DELAY); switch (bits) {
end = esp_timer_get_time(); #if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_counter_timer_sample(&rast_write_time, end - start); case 1:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride, CTX_FORMAT_GRAY1);
break;
case 2:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride, CTX_FORMAT_GRAY2);
break;
case 4:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride, CTX_FORMAT_GRAY4);
break;
case 8:
case 9:
if ((set_mode & 0xf) == 9)
ctx_rasterizer_reinit(
fb_ctx, st3m_fb, 0, 0, st3m_gfx_fb_width,
st3m_gfx_fb_height, stride, CTX_FORMAT_RGB332);
else
ctx_rasterizer_reinit(
fb_ctx, st3m_fb, 0, 0, st3m_gfx_fb_width,
st3m_gfx_fb_height, stride, CTX_FORMAT_GRAY8);
break;
#endif
case 16:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0,
st3m_gfx_fb_width, st3m_gfx_fb_height,
stride,
CTX_FORMAT_RGB565_BYTESWAPPED);
break;
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
case 24:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0, 240, 240,
240 * 3, CTX_FORMAT_RGB8);
break;
case 32:
ctx_rasterizer_reinit(fb_ctx, st3m_fb, 0, 0, 240, 240,
240 * 4, CTX_FORMAT_RGBA8);
break;
#endif
}
if ((set_mode & st3m_gfx_smart_redraw) == 0) {
if (!was_geom_dirty) memset(st3m_fb, 0, sizeof(st3m_fb));
}
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_gfx_viewport_transform(osd_ctx, 1);
if (!was_geom_dirty) memset(st3m_fb2, 0, sizeof(st3m_fb2));
#endif
prev_set_mode = set_mode;
}
st3m_counter_rate_sample(&rast_rate); if ((set_mode & st3m_gfx_direct_ctx) == 0) {
if ((set_mode & st3m_gfx_smart_redraw)) {
ctx_start_frame(ctx);
ctx_render_ctx(drawlist->user_ctx, ctx);
ctx_end_frame(ctx);
} else {
ctx_save(fb_ctx);
ctx_render_ctx(drawlist->user_ctx, fb_ctx);
ctx_restore(fb_ctx);
}
ctx_drawlist_clear(drawlist->user_ctx);
}
#if ST3M_GFX_BLIT_TASK
if (direct_blit) {
#endif
drawlist->blit_src = st3m_fb;
st3m_gfx_blit(drawlist);
xSemaphoreGive(st3m_fb_lock);
xQueueSend(user_ctx_freeq, &desc_no, portMAX_DELAY);
#if ST3M_GFX_BLIT_TASK
} else {
drawlist->blit_src = st3m_fb_copy;
xSemaphoreTake(st3m_fb_copy_lock, portMAX_DELAY);
int disp_stride = 240 * bits / 8;
if ((st3m_gfx_fb_width == 240) && (drawlist->blit_x == 0)) {
int blit_offset = st3m_gfx_blit_y * 240 * bits / 8;
int overlap = (st3m_gfx_blit_y + 240) - st3m_gfx_fb_height;
if (overlap > 0) {
// vertical overlap, 2 memcpys
int start_scans = 240 - overlap;
memcpy(st3m_fb_copy, st3m_fb + blit_offset,
start_scans * disp_stride);
memcpy(st3m_fb_copy + start_scans * disp_stride, st3m_fb,
overlap * disp_stride);
} else { // best case
memcpy(st3m_fb_copy, st3m_fb + blit_offset,
240 * disp_stride);
}
} else {
int fb_stride = st3m_gfx_fb_width * bits / 8;
int scan_offset = drawlist->blit_x * bits / 8;
int scan_overlap = (drawlist->blit_x + 240) - st3m_gfx_fb_width;
if (scan_overlap <= 0) { // only vertical wrap-around
int blit_offset =
(st3m_gfx_blit_y * 240 + drawlist->blit_x) * bits / 8;
int overlap = (st3m_gfx_blit_y + 240) - st3m_gfx_fb_height;
if (overlap <= 0) overlap = 0;
int start_scans = 240 - overlap;
for (int i = 0; i < start_scans; i++)
memcpy(st3m_fb_copy + i * disp_stride,
st3m_fb + blit_offset + i * fb_stride,
disp_stride);
for (int i = 0; i < overlap; i++)
memcpy(st3m_fb_copy + (i + start_scans) * disp_stride,
st3m_fb + (drawlist->blit_x * bits / 8) +
i * fb_stride,
disp_stride);
} else { // generic case, handles both horizontal and vertical
// wrap-around
int start_bit = 240 - scan_overlap;
int blit_offset = (st3m_gfx_blit_y)*fb_stride;
int overlap = (st3m_gfx_blit_y + 240) - st3m_gfx_fb_height;
if (overlap <= 0) overlap = 0;
int start_scans = 240 - overlap;
int start_bytes = start_bit * bits / 8;
int scan_overlap_bytes = scan_overlap * bits / 8;
for (int i = 0; i < start_scans; i++)
memcpy(
st3m_fb_copy + i * disp_stride,
st3m_fb + blit_offset + i * fb_stride + scan_offset,
start_bytes);
for (int i = 0; i < overlap; i++)
memcpy(st3m_fb_copy + (i + start_scans) * disp_stride,
st3m_fb + scan_offset + i * fb_stride,
start_bytes);
// second pass over scanlines, filling in second half (which
// is wrapped to start of fb-scans)
for (int i = 0; i < start_scans; i++)
memcpy(st3m_fb_copy + i * disp_stride + start_bytes,
st3m_fb + blit_offset + i * fb_stride,
scan_overlap_bytes);
for (int i = 0; i < overlap; i++)
memcpy(st3m_fb_copy + (i + start_scans) * disp_stride +
start_bytes,
st3m_fb + i * fb_stride, scan_overlap_bytes);
}
}
xSemaphoreGive(st3m_fb_copy_lock);
xSemaphoreGive(st3m_fb_lock);
xQueueSend(user_ctx_blitq, &desc_no, portMAX_DELAY);
}
#endif
if (st3m_counter_rate_report(&rast_rate, 1)) { st3m_counter_rate_sample(&rast_rate);
float rate = 1000000.0 / st3m_counter_rate_average(&rast_rate); float rate = 1000000.0 / st3m_counter_rate_average(&rast_rate);
float read_fb = smoothed_fps = smoothed_fps * 0.6 + 0.4 * rate;
st3m_counter_timer_average(&rast_read_fb_time) / 1000.0;
float read_dctx =
st3m_counter_timer_average(&rast_read_dctx_time) / 1000.0;
float work = st3m_counter_timer_average(&rast_work_time) / 1000.0;
float write = st3m_counter_timer_average(&rast_write_time) / 1000.0;
// Mark variables as used even if debug is disabled.
(void)rate;
(void)read_fb;
(void)read_dctx;
(void)work;
(void)write;
ESP_LOGD(TAG,
"rasterization: %.3f/sec, read fb %.3fms, read dctx "
"%.3fms, work %.3fms, write %.3fms",
(double)rate, (double)read_fb, (double)read_dctx,
(double)work, (double)write);
}
} }
} }
void st3m_gfx_flow3r_logo(Ctx *ctx, float x, float y, float dim) { void st3m_gfx_flow3r_logo(Ctx *ctx, float x, float y, float dim) {
static int frameno = 0;
static int dir = 1;
frameno += dir;
if (frameno > 7 || frameno < -6) {
dir *= -1;
frameno += dir;
}
ctx_save(ctx); ctx_save(ctx);
ctx_translate(ctx, x, y); ctx_translate(ctx, x, y);
ctx_scale(ctx, dim, dim); ctx_scale(ctx, dim, dim);
ctx_translate(ctx, -0.5f, -0.5f); ctx_translate(ctx, -0.5f, -0.5f);
ctx_linear_gradient(ctx, 0.18f, 0.5f, 0.95f, 0.5f); ctx_linear_gradient(ctx, 0.18f - frameno * 0.02, 0.5f,
0.95f + frameno * 0.02, 0.5f);
ctx_gradient_add_stop(ctx, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f); ctx_gradient_add_stop(ctx, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
ctx_gradient_add_stop(ctx, 0.2f, 1.0f, 1.0f, 0.0f, 1.0f); ctx_gradient_add_stop(ctx, 0.2f, 1.0f, 1.0f, 0.0f, 1.0f);
ctx_gradient_add_stop(ctx, 0.4f, 0.0f, 1.0f, 0.0f, 1.0f); ctx_gradient_add_stop(ctx, 0.4f, 0.0f, 1.0f, 0.0f, 1.0f);
...@@ -351,176 +859,279 @@ void st3m_gfx_show_textview(st3m_gfx_textview_t *tv) { ...@@ -351,176 +859,279 @@ void st3m_gfx_show_textview(st3m_gfx_textview_t *tv) {
return; return;
} }
st3m_ctx_desc_t *target = st3m_gfx_drawctx_free_get(portMAX_DELAY); Ctx *ctx = st3m_gfx_ctx(st3m_gfx_default);
ctx_save(target->ctx); st3m_gfx_fbconfig(240, 240, 0, 0);
ctx_save(ctx);
// Draw background. // Draw background.
ctx_rgb(target->ctx, 0, 0, 0); ctx_rgb(ctx, 0, 0, 0);
ctx_rectangle(target->ctx, -120, -120, 240, 240); ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_fill(target->ctx); ctx_fill(ctx);
st3m_gfx_flow3r_logo(target->ctx, 0, -30, 150); st3m_gfx_flow3r_logo(ctx, 0, -30, 150);
int y = 20; int y = 20;
ctx_gray(target->ctx, 1.0); ctx_gray(ctx, 1.0);
ctx_text_align(target->ctx, CTX_TEXT_ALIGN_CENTER); ctx_text_align(ctx, CTX_TEXT_ALIGN_CENTER);
ctx_text_baseline(target->ctx, CTX_TEXT_BASELINE_MIDDLE); ctx_text_baseline(ctx, CTX_TEXT_BASELINE_MIDDLE);
ctx_font_size(target->ctx, 20.0); ctx_font_size(ctx, 20.0);
// Draw title, if any. // Draw title, if any.
if (tv->title != NULL) { if (tv->title != NULL) {
ctx_move_to(target->ctx, 0, y); ctx_move_to(ctx, 0, y);
ctx_text(target->ctx, tv->title); ctx_text(ctx, tv->title);
y += 20; y += 20;
} }
ctx_font_size(target->ctx, 15.0); ctx_font_size(ctx, 15.0);
ctx_gray(target->ctx, 0.8); ctx_gray(ctx, 0.8);
// Draw messages. // Draw messages.
const char **lines = tv->lines; const char **lines = tv->lines;
if (lines != NULL) { if (lines != NULL) {
while (*lines != NULL) { while (*lines != NULL) {
const char *text = *lines++; const char *text = *lines++;
ctx_move_to(target->ctx, 0, y); ctx_move_to(ctx, 0, y);
ctx_text(target->ctx, text); ctx_text(ctx, text);
y += 15; y += 15;
} }
} }
// Draw version. // Draw version.
ctx_font_size(target->ctx, 15.0); ctx_font_size(ctx, 15.0);
ctx_gray(target->ctx, 0.6); ctx_gray(ctx, 0.6);
ctx_move_to(target->ctx, 0, 100); ctx_move_to(ctx, 0, 90);
ctx_text(target->ctx, st3m_version); ctx_text(ctx, st3m_version);
ctx_restore(ctx);
ctx_restore(target->ctx); st3m_gfx_end_frame(ctx);
}
st3m_gfx_drawctx_pipe_put(target); static void set_pixels_ctx(Ctx *ctx, void *user_data, int x, int y, int w,
int h, void *buf) {
uint16_t *src = buf;
for (int scan = y; scan < y + h; scan++) {
uint16_t *dst = (uint16_t *)&st3m_fb[(scan * 240 + x) * 2];
for (int u = 0; u < w; u++) *(dst++) = *(src++);
}
} }
void st3m_gfx_init(void) { void st3m_gfx_init(void) {
// Make sure we're not being re-initialized. // Make sure we're not being re-initialized.
assert(framebuffer_freeq == NULL);
st3m_counter_rate_init(&blit_rate);
st3m_counter_timer_init(&blit_read_time);
st3m_counter_timer_init(&blit_work_time);
st3m_counter_timer_init(&blit_write_time);
st3m_counter_rate_init(&rast_rate); st3m_counter_rate_init(&rast_rate);
st3m_counter_timer_init(&rast_read_fb_time);
st3m_counter_timer_init(&rast_read_dctx_time);
st3m_counter_timer_init(&rast_work_time);
st3m_counter_timer_init(&rast_write_time);
flow3r_bsp_display_init(); flow3r_bsp_display_init();
// Create framebuffer queues.
framebuffer_freeq = xQueueCreate(ST3M_GFX_NBUFFERS + 1, sizeof(int));
assert(framebuffer_freeq != NULL);
framebuffer_blitq = xQueueCreate(ST3M_GFX_NBUFFERS + 1, sizeof(int));
assert(framebuffer_blitq != NULL);
// Create drawlist ctx queues. // Create drawlist ctx queues.
dctx_freeq = xQueueCreate(ST3M_GFX_NCTX + 1, sizeof(int)); user_ctx_freeq = xQueueCreate(N_DRAWLISTS, sizeof(int));
assert(dctx_freeq != NULL); assert(user_ctx_freeq != NULL);
dctx_rastq = xQueueCreate(ST3M_GFX_NCTX + 1, sizeof(int)); user_ctx_rastq = xQueueCreate(1, sizeof(int));
assert(dctx_rastq != NULL); assert(user_ctx_rastq != NULL);
user_ctx_blitq = xQueueCreate(1, sizeof(int));
for (int i = 0; i < ST3M_GFX_NBUFFERS; i++) { assert(user_ctx_blitq != NULL);
// Setup framebuffer descriptor.
st3m_framebuffer_desc_t *fb_desc = &framebuffer_descs[i]; #if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
fb_desc->num = i; st3m_osd_lock = xSemaphoreCreateMutex();
fb_desc->ctx = ctx_new_for_framebuffer( #endif
fb_desc->buffer, FLOW3R_BSP_DISPLAY_WIDTH, st3m_fb_lock = xSemaphoreCreateMutex();
FLOW3R_BSP_DISPLAY_HEIGHT, FLOW3R_BSP_DISPLAY_WIDTH * 2, st3m_fb_copy_lock = xSemaphoreCreateMutex();
CTX_FORMAT_RGB565_BYTESWAPPED);
if (i) { ctx = ctx_new_cb(FLOW3R_BSP_DISPLAY_WIDTH, FLOW3R_BSP_DISPLAY_HEIGHT,
ctx_set_texture_source(fb_desc->ctx, framebuffer_descs[0].ctx); CTX_FORMAT_RGB565_BYTESWAPPED, set_pixels_ctx, NULL, NULL,
ctx_set_texture_cache(fb_desc->ctx, framebuffer_descs[0].ctx); NULL, sizeof(scratch), scratch,
} CTX_FLAG_HASH_CACHE | CTX_FLAG_KEEP_DATA);
assert(fb_desc->ctx != NULL); assert(ctx != NULL);
// Rotate by 180 deg and translate x and y by 120 px to have (0,0) at
// the center of the screen // Setup rasterizers for frame buffer formats
int32_t offset_x = FLOW3R_BSP_DISPLAY_WIDTH / 2; fb_ctx = ctx_new_for_framebuffer(
int32_t offset_y = FLOW3R_BSP_DISPLAY_HEIGHT / 2; st3m_fb, FLOW3R_BSP_DISPLAY_WIDTH, FLOW3R_BSP_DISPLAY_HEIGHT,
ctx_apply_transform(fb_desc->ctx, -1, 0, offset_x, 0, -1, offset_y, 0, FLOW3R_BSP_DISPLAY_WIDTH * 2, CTX_FORMAT_RGB565_BYTESWAPPED);
0, 1); assert(fb_ctx != NULL);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
// Push descriptor to freeq. osd_ctx = ctx_new_for_framebuffer(
BaseType_t res = xQueueSend(framebuffer_freeq, &i, 0); st3m_fb2, FLOW3R_BSP_DISPLAY_WIDTH, FLOW3R_BSP_DISPLAY_HEIGHT,
assert(res == pdTRUE); FLOW3R_BSP_DISPLAY_WIDTH * 4, CTX_FORMAT_RGBA8);
} assert(osd_ctx != NULL);
for (int i = 0; i < ST3M_GFX_NCTX; i++) { st3m_gfx_viewport_transform(osd_ctx, 0);
// Setup dctx descriptor.
st3m_ctx_desc_t *dctx_desc = &dctx_descs[i]; ctx_set_texture_source(osd_ctx, fb_ctx);
dctx_desc->num = i; ctx_set_texture_cache(osd_ctx, fb_ctx);
dctx_desc->ctx = ctx_new_drawlist(FLOW3R_BSP_DISPLAY_WIDTH, ctx_set_texture_source(ctx, fb_ctx);
ctx_set_texture_cache(ctx, fb_ctx);
#endif
// Setup user_ctx descriptor.
for (int i = 0; i < N_DRAWLISTS; i++) {
drawlists[i].user_ctx = ctx_new_drawlist(FLOW3R_BSP_DISPLAY_WIDTH,
FLOW3R_BSP_DISPLAY_HEIGHT); FLOW3R_BSP_DISPLAY_HEIGHT);
ctx_set_texture_cache(dctx_desc->ctx, framebuffer_descs[0].ctx); assert(drawlists[i].user_ctx != NULL);
assert(dctx_desc->ctx != NULL); ctx_set_texture_cache(drawlists[i].user_ctx, fb_ctx);
// Push descriptor to freeq. BaseType_t res = xQueueSend(user_ctx_freeq, &i, 0);
BaseType_t res = xQueueSend(dctx_freeq, &i, 0);
assert(res == pdTRUE); assert(res == pdTRUE);
} }
// Start crtc. // Start rasterization, scan-out
BaseType_t res = xTaskCreate(st3m_gfx_crtc_task, "crtc", 4096, NULL, BaseType_t res =
ESP_TASK_PRIO_MIN + 3, &crtc_task); xTaskCreatePinnedToCore(st3m_gfx_rast_task, "gfx-rast", 8192, NULL,
ESP_TASK_PRIO_MIN + 1, &graphics_rast_task, 0);
assert(res == pdPASS); assert(res == pdPASS);
#if ST3M_GFX_BLIT_TASK
// Start rast. res = xTaskCreate(st3m_gfx_blit_task, "gfx-blit", 2048, NULL,
res = xTaskCreate(st3m_gfx_rast_task, "rast", 8192, NULL, ESP_TASK_PRIO_MIN + 2, &graphics_blit_task);
ESP_TASK_PRIO_MIN + 1, &rast_task);
assert(res == pdPASS); assert(res == pdPASS);
#endif
} }
static int last_descno = 0;
static Ctx *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait) {
BaseType_t res = xQueueReceive(user_ctx_freeq, &last_descno, ticks_to_wait);
if (res != pdTRUE) return NULL;
st3m_gfx_drawlist *drawlist = &drawlists[last_descno];
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
drawlist->mode = set_mode;
st3m_ctx_desc_t *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait) { if (set_mode & st3m_gfx_direct_ctx) {
int descno; while (!uxSemaphoreGetCount(st3m_fb_lock)) vTaskDelay(0);
BaseType_t res = xQueueReceive(dctx_freeq, &descno, ticks_to_wait);
if (res != pdTRUE) { if ((set_mode & st3m_gfx_smart_redraw)) {
return NULL; ctx_start_frame(ctx);
ctx_save(ctx);
return ctx;
}
return fb_ctx;
} }
return &dctx_descs[descno];
return drawlist->user_ctx;
} }
void st3m_gfx_drawctx_pipe_put(st3m_ctx_desc_t *desc) { static void st3m_gfx_pipe_put(void) {
xQueueSend(dctx_rastq, &desc->num, portMAX_DELAY); #if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
st3m_gfx_drawlist *drawlist = &drawlists[last_descno];
drawlist->osd_x0 = _st3m_osd_x0;
drawlist->osd_y0 = _st3m_osd_y0;
drawlist->osd_x1 = _st3m_osd_x1;
drawlist->osd_y1 = _st3m_osd_y1;
drawlist->blit_x = st3m_gfx_blit_x;
drawlist->blit_y = st3m_gfx_blit_y;
#endif
xQueueSend(user_ctx_rastq, &last_descno, portMAX_DELAY);
} }
uint8_t st3m_gfx_drawctx_pipe_full(void) { static Ctx *st3m_gfx_ctx_int(st3m_gfx_mode mode);
return uxQueueSpacesAvailable(dctx_rastq) == 0; void st3m_gfx_end_frame(Ctx *ctx) {
ctx_restore(ctx);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
if (ctx == st3m_gfx_ctx_int(st3m_gfx_osd)) {
xSemaphoreGive(st3m_osd_lock);
return;
} }
#endif
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if ((set_mode & st3m_gfx_smart_redraw) && (set_mode & st3m_gfx_direct_ctx))
ctx_end_frame(ctx);
void st3m_gfx_flush(void) { st3m_gfx_pipe_put();
}
uint8_t st3m_gfx_pipe_available(void) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if ((set_mode & st3m_gfx_EXPERIMENTAL_think_per_draw) &&
(smoothed_fps > 13.0))
return 1;
return uxQueueMessagesWaiting(user_ctx_freeq) > _st3m_gfx_low_latency;
}
uint8_t st3m_gfx_pipe_full(void) {
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
if ((set_mode & st3m_gfx_EXPERIMENTAL_think_per_draw) &&
(smoothed_fps > 13.0))
return 0;
return uxQueueSpacesAvailable(user_ctx_rastq) == 0;
}
void st3m_gfx_flush(int timeout_ms) {
ESP_LOGW(TAG, "Pipeline flush/reset requested..."); ESP_LOGW(TAG, "Pipeline flush/reset requested...");
// Drain all workqs and freeqs. // Drain all workqs and freeqs.
xQueueReset(dctx_freeq); xQueueReset(user_ctx_freeq);
xQueueReset(dctx_rastq); xQueueReset(user_ctx_rastq);
xQueueReset(framebuffer_freeq);
xQueueReset(framebuffer_blitq);
// Delay, making sure pipeline tasks have returned all used descriptors. One // Delay, making sure pipeline tasks have returned all used descriptors. One
// second is enough to make sure we've processed everything. // second is enough to make sure we've processed everything.
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(timeout_ms / portTICK_PERIOD_MS);
// And drain again. // And drain again.
xQueueReset(framebuffer_freeq); xQueueReset(user_ctx_freeq);
xQueueReset(dctx_freeq);
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
// Now, re-submit all descriptors to freeqs. _st3m_osd_x0 = 0;
for (int i = 0; i < ST3M_GFX_NBUFFERS; i++) { _st3m_osd_y0 = 0;
BaseType_t res = xQueueSend(framebuffer_freeq, &i, 0); _st3m_osd_x1 = 0;
assert(res == pdTRUE); _st3m_osd_y1 = 0;
} #endif
for (int i = 0; i < ST3M_GFX_NCTX; i++) {
BaseType_t res = xQueueSend(dctx_freeq, &i, 0); for (int i = 0; i < N_DRAWLISTS; i++) {
ctx_drawlist_clear(drawlists[i].user_ctx);
BaseType_t res = xQueueSend(user_ctx_freeq, &i, 0);
assert(res == pdTRUE); assert(res == pdTRUE);
} }
ESP_LOGW(TAG, "Pipeline flush/reset done."); ESP_LOGW(TAG, "Pipeline flush/reset done.");
} }
#if CONFIG_FLOW3R_CTX_FLAVOUR_FULL
void st3m_gfx_overlay_clip(int x0, int y0, int x1, int y1) {
if (y1 < 0) y1 = 0;
if (y1 > FLOW3R_BSP_DISPLAY_HEIGHT) y1 = FLOW3R_BSP_DISPLAY_HEIGHT;
if (y0 < 0) y0 = 0;
if (y0 > FLOW3R_BSP_DISPLAY_HEIGHT) y0 = FLOW3R_BSP_DISPLAY_HEIGHT;
if (x1 < 0) x1 = 0;
if (x1 > FLOW3R_BSP_DISPLAY_WIDTH) x1 = FLOW3R_BSP_DISPLAY_WIDTH;
if (x0 < 0) x0 = 0;
if (x0 > FLOW3R_BSP_DISPLAY_WIDTH) x0 = FLOW3R_BSP_DISPLAY_WIDTH;
if ((x1 < x0) || (y1 < y0)) {
_st3m_osd_x0 = _st3m_osd_y0 = _st3m_osd_x1 = _st3m_osd_y1 = 0;
} else {
_st3m_osd_x0 = x0;
_st3m_osd_y0 = y0;
_st3m_osd_x1 = x1;
_st3m_osd_y1 = y1;
}
}
#endif
void st3m_gfx_fbconfig(int width, int height, int blit_x, int blit_y) {
if (width <= 0) width = st3m_gfx_fb_width;
if (height <= 0) height = st3m_gfx_fb_height;
st3m_gfx_mode set_mode = _st3m_gfx_mode ? _st3m_gfx_mode : default_mode;
int bits = st3m_gfx_bpp(set_mode);
if (width > CTX_MAX_SCANLINE_LENGTH) width = CTX_MAX_SCANLINE_LENGTH;
if ((width * height * bits) / 8 > (240 * 240 * 4))
height = 240 * 240 * 4 * 8 / (width * bits);
blit_x %= width;
blit_y %= height;
if ((st3m_gfx_fb_width != width) || (st3m_gfx_fb_height != height)) {
st3m_gfx_fb_width = width;
st3m_gfx_fb_height = height;
st3m_gfx_geom_dirty++;
}
st3m_gfx_blit_x = blit_x;
st3m_gfx_blit_y = blit_y;
}
void st3m_gfx_get_fbconfig(int *width, int *height, int *blit_x, int *blit_y) {
if (width) *width = st3m_gfx_fb_width;
if (height) *height = st3m_gfx_fb_height;
if (blit_x) *blit_x = st3m_gfx_blit_x;
if (blit_y) *blit_y = st3m_gfx_blit_y;
}
...@@ -7,45 +7,109 @@ ...@@ -7,45 +7,109 @@
#include "ctx.h" #include "ctx.h"
// clang-format on // clang-format on
// Each buffer takes ~116kB SPIRAM. While one framebuffer is being blitted, the typedef enum {
// other one is being written to by the rasterizer. st3m_gfx_default = 0,
#define ST3M_GFX_NBUFFERS 2 // bitmask flag over base bpp to turn on OSD, only 16bpp for now will
// More ctx drawlists than buffers so that micropython doesn't get starved when // become available for other bitdepths as grayscale rather than color
// pipeline runs in lockstep. // overlays.
#define ST3M_GFX_NCTX 2
// lock the graphics mode, this makes st3m_gfx_set_mode() a no-op
// A framebuffer descriptor, pointing at a framebuffer. st3m_gfx_lock = 1 << 8,
typedef struct {
// The numeric ID of this descriptor. // directly manipulate target framebuffer instead of having
int num; // separate rasterization task - this causes the rasterization overhead
// SPIRAM buffer. // to occur in the micropython task rather than the graphics rasterization
uint16_t buffer[240 * 240]; // task.
Ctx *ctx; st3m_gfx_direct_ctx = 1 << 9,
} st3m_framebuffer_desc_t;
// enable osd compositing, for a small performance boost
st3m_gfx_osd = 1 << 10,
// shallower pipeline, prioritize short time from drawing until shown on
// screen over frame rate
st3m_gfx_low_latency = 1 << 11,
// boost FPS by always reporting readiness for drawing, this gets disabled
// dynamically if FPS falls <13fps
st3m_gfx_EXPERIMENTAL_think_per_draw = 1 << 12,
// pixel-doubling
st3m_gfx_2x = 1 << 13,
st3m_gfx_3x = 1 << 14,
st3m_gfx_4x = st3m_gfx_2x | st3m_gfx_3x,
// keep track of what is drawn and only redraw the bounding box
st3m_gfx_smart_redraw = 1 << 15,
// 4 and 8bpp modes use the configured palette, the palette resides
// in video ram and is lost upon mode change
st3m_gfx_1bpp = 1,
st3m_gfx_2bpp = 2,
st3m_gfx_4bpp = 4,
st3m_gfx_8bpp = 8, // bare 8bpp mode is grayscale
st3m_gfx_rgb332 = 9, // variant of 8bpp mode using RGB
st3m_gfx_sepia = 10, // grayscale rendering with sepia palette
st3m_gfx_cool = 11, // grayscale rendering with cool palette
st3m_gfx_palette = 15,
// 16bpp modes have the lowest blit overhead - no osd for now
st3m_gfx_16bpp = 16,
st3m_gfx_24bpp = 24,
// 32bpp modes - are slightly faster at doing compositing, but the memory
// overhead of higher bitdepths cancels out the overhead of converting back
// and forth between rgb565 and RGBA8, 24 and 32bit modes are here
// mostly because it leads to nicer math in python.
st3m_gfx_32bpp = 32,
} st3m_gfx_mode;
// sets the system graphics mode, this is the mode you get to
// when calling st3m_gfx_set_mode(st3m_gfx_default);
void st3m_gfx_set_default_mode(st3m_gfx_mode mode);
// sets the current graphics mode
st3m_gfx_mode st3m_gfx_set_mode(st3m_gfx_mode mode);
// gets the current graphics mode
st3m_gfx_mode st3m_gfx_get_mode(void);
// returns a ctx for drawing at the specified mode/target
// should be paired with a st3m_ctx_end_frame
// normal values are st3m_gfx_default and st3m_gfx_osd for base framebuffer
// and overlay drawing context.
Ctx *st3m_gfx_ctx(st3m_gfx_mode mode);
// get the framebuffer associated with graphics mode
// if you ask for st3m_gfx_default you get the current modes fb
// and if you ask for st3m_gfx_osd you get the current modes overlay fb
uint8_t *st3m_gfx_fb(st3m_gfx_mode mode, int *width, int *height, int *stride);
// get the bits per pixel for a given mode
int st3m_gfx_bpp(st3m_gfx_mode mode);
// sets the palette, pal_in is an array with 3 uint8_t's per entry,
// support values for count is 1-256, used only in 4bpp and 8bpp
// graphics modes.
void st3m_gfx_set_palette(uint8_t *pal_in, int count);
// specifies the corners of the clipping rectangle
// for compositing overlay
void st3m_gfx_overlay_clip(int x0, int y0, int x1, int y1);
// returns a running average of fps
float st3m_gfx_fps(void);
// temporary, signature compatible
// with ctx_end_frame()
void st3m_gfx_end_frame(Ctx *ctx);
// Initialize the gfx subsystem of st3m, includng the rasterization and // Initialize the gfx subsystem of st3m, includng the rasterization and
// crtx/blitter pipeline. // crtx/blitter pipeline.
void st3m_gfx_init(void); void st3m_gfx_init(void);
// A drawlist ctx descriptor, pointing to a drawlist-backed Ctx. // Returns true if we right now cannot accept another frame
typedef struct { uint8_t st3m_gfx_pipe_full(void);
// The numeric ID of this descriptor.
int num;
Ctx *ctx;
} st3m_ctx_desc_t;
// Get a free drawlist ctx to draw into. // Returns true if there's a free drawlist available to retrieve
// uint8_t st3m_gfx_pipe_available(void);
// ticks_to_wait can be used to limit the time to wait for a free ctx
// descriptor, or portDELAY_MAX can be specified to wait forever. If the timeout
// expires, NULL will be returned.
st3m_ctx_desc_t *st3m_gfx_drawctx_free_get(TickType_t ticks_to_wait);
// Submit a filled ctx descriptor to the rasterization pipeline.
void st3m_gfx_drawctx_pipe_put(st3m_ctx_desc_t *desc);
// Returns true if the rasterizaiton pipeline submission would block.
uint8_t st3m_gfx_drawctx_pipe_full(void);
// Flush any in-flight pipelined work, resetting the free ctx/framebuffer queues // Flush any in-flight pipelined work, resetting the free ctx/framebuffer queues
// to their initial state. This should be called if there has been any drawlist // to their initial state. This should be called if there has been any drawlist
...@@ -53,8 +117,8 @@ uint8_t st3m_gfx_drawctx_pipe_full(void); ...@@ -53,8 +117,8 @@ uint8_t st3m_gfx_drawctx_pipe_full(void);
// wasn't, for exaple if Micropython restarted). // wasn't, for exaple if Micropython restarted).
// //
// This causes a graphical disturbance and shouldn't be called during normal // This causes a graphical disturbance and shouldn't be called during normal
// operation. // operation. wait_ms is waited for drawlits to clear.
void st3m_gfx_flush(void); void st3m_gfx_flush(int wait_ms);
typedef struct { typedef struct {
const char *title; const char *title;
...@@ -75,3 +139,15 @@ void st3m_gfx_splash(const char *text); ...@@ -75,3 +139,15 @@ void st3m_gfx_splash(const char *text);
// Draw the flow3r multi-coloured logo at coordinates x,y and with given // Draw the flow3r multi-coloured logo at coordinates x,y and with given
// dimension (approx. bounding box size). // dimension (approx. bounding box size).
void st3m_gfx_flow3r_logo(Ctx *ctx, float x, float y, float dim); void st3m_gfx_flow3r_logo(Ctx *ctx, float x, float y, float dim);
// configure virtual viewport, the default values are 0, 0, 240, 240 which
// also gives room for a copy of the fb for separate rasterize/blit in 16bpp
//
// with, height: width and height of virtual framebuffer
//
// changing the viewport should be done after setting the graphics mode - upon
// graphics mode setting viewport is reset to 240, 240, 0,0,0,0
void st3m_gfx_fbconfig(int width, int height, int blit_x, int blit_y);
// get fbconfig values, arguments passed NULL ptrs are ignored.
void st3m_gfx_get_fbconfig(int *width, int *height, int *blit_x, int *blit_y);
...@@ -5,36 +5,71 @@ static const char *TAG = "st3m-io"; ...@@ -5,36 +5,71 @@ static const char *TAG = "st3m-io";
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "flow3r_bsp.h" #include "flow3r_bsp.h"
#include "flow3r_bsp_i2c.h" #include "flow3r_bsp_i2c.h"
#include "st3m_audio.h" #include "st3m_audio.h"
static bool _app_button_left = true;
static SemaphoreHandle_t _mu = NULL;
static st3m_tripos _left_button_state;
static st3m_tripos _right_button_state;
static void _update_button_state() { static void _update_button_state() {
esp_err_t ret = flow3r_bsp_spio_update(); esp_err_t ret = flow3r_bsp_spio_update();
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "update failed: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "update failed: %s", esp_err_to_name(ret));
} }
st3m_tripos state = flow3r_bsp_spio_left_button_get();
if (state) {
_left_button_state = state;
} }
state = flow3r_bsp_spio_right_button_get();
void init_buttons() { if (state) {
esp_err_t ret = flow3r_bsp_spio_init(); _right_button_state = state;
if (ret != ESP_OK) {
ESP_LOGE(TAG, "init failed: %s", esp_err_to_name(ret));
for (;;) {
}
} }
} }
bool st3m_io_charger_state_get() { return flow3r_bsp_spio_charger_state_get(); } bool st3m_io_charger_state_get() { return flow3r_bsp_spio_charger_state_get(); }
st3m_tripos st3m_io_left_button_get() { void st3m_io_app_button_configure(bool left) {
return flow3r_bsp_spio_left_button_get(); xSemaphoreTake(_mu, portMAX_DELAY);
_app_button_left = left;
xSemaphoreGive(_mu);
} }
st3m_tripos st3m_io_right_button_get() { bool st3m_io_app_button_is_left(void) {
return flow3r_bsp_spio_right_button_get(); xSemaphoreTake(_mu, portMAX_DELAY);
bool res = _app_button_left;
xSemaphoreGive(_mu);
return res;
}
st3m_tripos st3m_io_app_button_get() {
if (st3m_io_app_button_is_left()) {
st3m_tripos state = _left_button_state;
_left_button_state = flow3r_bsp_spio_left_button_get();
return state;
} else {
st3m_tripos state = _right_button_state;
_right_button_state = flow3r_bsp_spio_right_button_get();
return state;
}
}
st3m_tripos st3m_io_os_button_get() {
if (st3m_io_app_button_is_left()) {
st3m_tripos state = _right_button_state;
_right_button_state = flow3r_bsp_spio_right_button_get();
return state;
} else {
st3m_tripos state = _left_button_state;
_left_button_state = flow3r_bsp_spio_left_button_get();
return state;
}
} }
static uint8_t badge_link_enabled = 0; static uint8_t badge_link_enabled = 0;
...@@ -102,6 +137,10 @@ static void _task(void *data) { ...@@ -102,6 +137,10 @@ static void _task(void *data) {
} }
void st3m_io_init(void) { void st3m_io_init(void) {
assert(_mu == NULL);
_mu = xSemaphoreCreateMutex();
assert(_mu != NULL);
esp_err_t ret = flow3r_bsp_spio_init(); esp_err_t ret = flow3r_bsp_spio_init();
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "spio init failed: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "spio init failed: %s", esp_err_to_name(ret));
......
...@@ -19,11 +19,20 @@ typedef enum { ...@@ -19,11 +19,20 @@ typedef enum {
st3m_tripos_right = 1, st3m_tripos_right = 1,
} st3m_tripos; } st3m_tripos;
/* Read the state of the left/right button. /* Configure whether the app button is on the left (default) or on the right.
* This ignores user preference and should be used only with good reason.
*/ */
st3m_tripos st3m_io_left_button_get(); void st3m_io_app_button_configure(bool left);
st3m_tripos st3m_io_right_button_get();
/* Returns true if the app button is on the left (default), false otherwise.
*/
bool st3m_io_app_button_is_left(void);
/* Read the state of the application and OS buttons. By default, the application
* button is on the left and the OS button is on the right. However, the user
* can change that preference - see st3m_io_app_button_configure.
*/
st3m_tripos st3m_io_app_button_get();
st3m_tripos st3m_io_os_button_get();
#define BADGE_LINK_PIN_MASK_LINE_IN_TIP 0b0001 #define BADGE_LINK_PIN_MASK_LINE_IN_TIP 0b0001
#define BADGE_LINK_PIN_MASK_LINE_IN_RING 0b0010 #define BADGE_LINK_PIN_MASK_LINE_IN_RING 0b0010
...@@ -58,3 +67,7 @@ uint8_t st3m_io_badge_link_disable(uint8_t pin_mask); ...@@ -58,3 +67,7 @@ uint8_t st3m_io_badge_link_disable(uint8_t pin_mask);
* a while. Warn user. * a while. Warn user.
*/ */
uint8_t st3m_io_badge_link_enable(uint8_t pin_mask); uint8_t st3m_io_badge_link_enable(uint8_t pin_mask);
/* Returns true if the battery is currently being charged.
*/
bool st3m_io_charger_state_get();
\ No newline at end of file
...@@ -14,24 +14,43 @@ ...@@ -14,24 +14,43 @@
#include "flow3r_bsp.h" #include "flow3r_bsp.h"
#include "st3m_colors.h" #include "st3m_colors.h"
#define TAU360 0.017453292519943295
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
static const char *TAG = "st3m-leds"; static const char *TAG = "st3m-leds";
typedef struct { typedef struct {
uint8_t lut[256]; uint8_t lut[256];
} st3m_leds_gamma_table_t; } st3m_leds_gamma_table_t;
typedef struct {
uint16_t r;
uint16_t g;
uint16_t b;
} st3m_u16_rgb_t;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} st3m_u8_rgb_t;
typedef struct { typedef struct {
uint8_t brightness; uint8_t brightness;
uint8_t slew_rate; uint8_t slew_rate;
bool auto_update; bool auto_update;
bool is_steady;
uint8_t timer;
st3m_leds_gamma_table_t gamma_red; st3m_leds_gamma_table_t gamma_red;
st3m_leds_gamma_table_t gamma_green; st3m_leds_gamma_table_t gamma_green;
st3m_leds_gamma_table_t gamma_blue; st3m_leds_gamma_table_t gamma_blue;
st3m_rgb_t target[40]; st3m_u8_rgb_t target[40];
st3m_rgb_t target_buffer[40]; st3m_u8_rgb_t target_buffer[40];
st3m_rgb_t hardware_value[40]; st3m_u16_rgb_t slew_output[40];
st3m_u8_rgb_t ret_prev[40];
} st3m_leds_state_t; } st3m_leds_state_t;
static st3m_leds_state_t state; static st3m_leds_state_t state;
...@@ -44,12 +63,22 @@ SemaphoreHandle_t mutex_incoming; ...@@ -44,12 +63,22 @@ SemaphoreHandle_t mutex_incoming;
#define LOCK_INCOMING xSemaphoreTake(mutex_incoming, portMAX_DELAY) #define LOCK_INCOMING xSemaphoreTake(mutex_incoming, portMAX_DELAY)
#define UNLOCK_INCOMING xSemaphoreGive(mutex_incoming) #define UNLOCK_INCOMING xSemaphoreGive(mutex_incoming)
static void set_single_led(uint8_t index, st3m_rgb_t c) { static void set_single_led(uint8_t index, st3m_u8_rgb_t c) {
index = (index + 29) % 40; index = (index + 29) % 40;
flow3r_bsp_leds_set_pixel(index, c.r, c.g, c.b); flow3r_bsp_leds_set_pixel(index, c.r, c.g, c.b);
} }
uint8_t led_get_slew(int16_t old, int16_t new, int16_t slew) { static uint16_t led_get_slew(uint16_t old, uint16_t new, uint16_t slew,
uint16_t factor) {
new = new << 8;
if (slew == 255 || (old == new)) return new;
int16_t bonus = ((int16_t)slew) - 225;
slew = 30 + (slew << 2) + ((slew * slew) >> 3);
if (bonus > 0) {
slew += 62 * bonus * bonus;
}
slew = ((uint32_t)slew * factor) >> 8;
if (new > old + slew) { if (new > old + slew) {
return old + slew; return old + slew;
} else if (new > old) { } else if (new > old) {
...@@ -80,29 +109,67 @@ void st3m_leds_update_hardware() { ...@@ -80,29 +109,67 @@ void st3m_leds_update_hardware() {
if (state.auto_update) leds_update_target(); if (state.auto_update) leds_update_target();
bool is_different = false;
bool is_steady = true;
for (int i = 0; i < 40; i++) { for (int i = 0; i < 40; i++) {
st3m_rgb_t c = state.target[i]; st3m_u8_rgb_t ret = state.target[i];
c.r = c.r * state.brightness / 255; st3m_u16_rgb_t prev = state.slew_output[i];
c.g = c.g * state.brightness / 255; st3m_u16_rgb_t c = prev;
c.b = c.b * state.brightness / 255; uint16_t diff_r = abs((int32_t)prev.r - (ret.r << 8u));
uint16_t diff_g = abs((int32_t)prev.g - (ret.g << 8u));
uint16_t diff_b = abs((int32_t)prev.b - (ret.b << 8u));
uint16_t max_diff = MAX(MAX(diff_r, diff_g), diff_b);
if (max_diff) {
c.r = led_get_slew(prev.r, ret.r, state.slew_rate,
((uint32_t)diff_r * 256) / max_diff);
c.g = led_get_slew(prev.g, ret.g, state.slew_rate,
((uint32_t)diff_g * 256) / max_diff);
c.b = led_get_slew(prev.b, ret.b, state.slew_rate,
((uint32_t)diff_b * 256) / max_diff);
is_steady = false;
}
state.slew_output[i] = c;
c.r = state.gamma_red.lut[c.r]; c.r = ((uint32_t)c.r * state.brightness) >> 8;
c.g = state.gamma_red.lut[c.g]; c.g = ((uint32_t)c.g * state.brightness) >> 8;
c.b = state.gamma_red.lut[c.b]; c.b = ((uint32_t)c.b * state.brightness) >> 8;
c.r = led_get_slew(state.hardware_value[i].r, c.r, state.slew_rate); ret.r = state.gamma_red.lut[c.r >> 8];
c.g = led_get_slew(state.hardware_value[i].g, c.g, state.slew_rate); ret.g = state.gamma_green.lut[c.g >> 8];
c.b = led_get_slew(state.hardware_value[i].b, c.b, state.slew_rate); ret.b = state.gamma_blue.lut[c.b >> 8];
state.hardware_value[i] = c;
set_single_led(i, c); if ((ret.r != state.ret_prev[i].r) || (ret.g != state.ret_prev[i].g) ||
(ret.b != state.ret_prev[i].b)) {
set_single_led(i, ret);
state.ret_prev[i] = ret;
is_different = true;
}
} }
state.is_steady = is_steady;
UNLOCK; UNLOCK;
if (is_different || (state.timer > 10)) {
esp_err_t ret = flow3r_bsp_leds_refresh(portMAX_DELAY); esp_err_t ret = flow3r_bsp_leds_refresh(portMAX_DELAY);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "LED refresh failed: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "LED refresh failed: %s", esp_err_to_name(ret));
} }
state.timer = 0;
} else {
state.timer += 1;
}
}
void st3m_leds_set_single_rgba(uint8_t index, float red, float green,
float blue, float alpha) {
float r, g, b;
st3m_leds_get_single_rgb(index, &r, &g, &b);
float nalpha = 1 - alpha;
r = alpha * red + nalpha * r;
g = alpha * green + nalpha * g;
b = alpha * blue + nalpha * blue;
st3m_leds_set_single_rgb(index, r, g, b);
} }
void st3m_leds_set_single_rgb(uint8_t index, float red, float green, void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
...@@ -115,18 +182,27 @@ void st3m_leds_set_single_rgb(uint8_t index, float red, float green, ...@@ -115,18 +182,27 @@ void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
state.target_buffer[index].r = (uint8_t)(red * 255); state.target_buffer[index].r = (uint8_t)(red * 255);
state.target_buffer[index].g = (uint8_t)(green * 255); state.target_buffer[index].g = (uint8_t)(green * 255);
state.target_buffer[index].b = (uint8_t)(blue * 255); state.target_buffer[index].b = (uint8_t)(blue * 255);
state.is_steady = false;
UNLOCK_INCOMING;
}
void st3m_leds_get_single_rgb(uint8_t index, float *red, float *green,
float *blue) {
LOCK_INCOMING;
*red = ((float)state.target_buffer[index].r) / 255;
*green = ((float)state.target_buffer[index].g) / 255;
*blue = ((float)state.target_buffer[index].b) / 255;
UNLOCK_INCOMING; UNLOCK_INCOMING;
} }
void st3m_leds_set_single_hsv(uint8_t index, float hue, float sat, float val) { void st3m_leds_set_single_hsv(uint8_t index, float hue, float sat, float val) {
st3m_hsv_t hsv = { st3m_hsv_t hsv = {
.h = hue, .h = hue * TAU360,
.s = sat, .s = sat,
.v = val, .v = val,
}; };
LOCK_INCOMING; st3m_rgb_t rgb = st3m_hsv_to_rgb(hsv);
state.target_buffer[index] = st3m_hsv_to_rgb(hsv); st3m_leds_set_single_rgb(index, rgb.r, rgb.g, rgb.b);
UNLOCK_INCOMING;
} }
void st3m_leds_set_all_rgb(float red, float green, float blue) { void st3m_leds_set_all_rgb(float red, float green, float blue) {
...@@ -135,9 +211,21 @@ void st3m_leds_set_all_rgb(float red, float green, float blue) { ...@@ -135,9 +211,21 @@ void st3m_leds_set_all_rgb(float red, float green, float blue) {
} }
} }
void st3m_leds_set_all_hsv(float h, float s, float v) { void st3m_leds_set_all_rgba(float red, float green, float blue, float alpha) {
for (int i = 0; i < 40; i++) {
st3m_leds_set_single_rgba(i, red, green, blue, alpha);
}
}
void st3m_leds_set_all_hsv(float hue, float sat, float val) {
st3m_hsv_t hsv = {
.h = hue * TAU360,
.s = sat,
.v = val,
};
st3m_rgb_t rgb = st3m_hsv_to_rgb(hsv);
for (int i = 0; i < 40; i++) { for (int i = 0; i < 40; i++) {
st3m_leds_set_single_hsv(i, h, s, v); st3m_leds_set_single_rgb(i, rgb.r, rgb.g, rgb.b);
} }
} }
...@@ -148,7 +236,7 @@ static void _leds_task(void *_data) { ...@@ -148,7 +236,7 @@ static void _leds_task(void *_data) {
TickType_t last_wake = xTaskGetTickCount(); TickType_t last_wake = xTaskGetTickCount();
while (true) { while (true) {
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(100)); // 10 Hz vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(20)); // 50 Hz
st3m_leds_update_hardware(); st3m_leds_update_hardware();
} }
} }
...@@ -160,9 +248,10 @@ void st3m_leds_init() { ...@@ -160,9 +248,10 @@ void st3m_leds_init() {
assert(mutex_incoming != NULL); assert(mutex_incoming != NULL);
memset(&state, 0, sizeof(state)); memset(&state, 0, sizeof(state));
state.brightness = 69; state.brightness = 70;
state.slew_rate = 255; state.slew_rate = 235;
state.auto_update = false; state.auto_update = false;
state.timer = 255;
for (uint16_t i = 0; i < 256; i++) { for (uint16_t i = 0; i < 256; i++) {
state.gamma_red.lut[i] = i; state.gamma_red.lut[i] = i;
...@@ -207,6 +296,13 @@ uint8_t st3m_leds_get_slew_rate() { ...@@ -207,6 +296,13 @@ uint8_t st3m_leds_get_slew_rate() {
return res; return res;
} }
bool st3m_leds_get_steady() {
LOCK;
uint8_t res = state.is_steady;
UNLOCK;
return res;
}
void st3m_leds_set_auto_update(bool on) { void st3m_leds_set_auto_update(bool on) {
LOCK; LOCK;
state.auto_update = on; state.auto_update = on;
...@@ -222,12 +318,7 @@ bool st3m_leds_get_auto_update() { ...@@ -222,12 +318,7 @@ bool st3m_leds_get_auto_update() {
void st3m_leds_set_gamma(float red, float green, float blue) { void st3m_leds_set_gamma(float red, float green, float blue) {
LOCK; LOCK;
for (uint16_t i = 0; i < 256; i++) { for (uint16_t i = 1; i < 256; i++) {
if (i == 0) {
state.gamma_red.lut[i] = 0;
state.gamma_green.lut[i] = 0;
state.gamma_blue.lut[i] = 0;
}
float step = ((float)i) / 255.; float step = ((float)i) / 255.;
state.gamma_red.lut[i] = (uint8_t)(254. * (powf(step, red)) + 1); state.gamma_red.lut[i] = (uint8_t)(254. * (powf(step, red)) + 1);
state.gamma_green.lut[i] = (uint8_t)(254. * (powf(step, green)) + 1); state.gamma_green.lut[i] = (uint8_t)(254. * (powf(step, green)) + 1);
......
...@@ -25,9 +25,14 @@ void st3m_leds_init(); ...@@ -25,9 +25,14 @@ void st3m_leds_init();
// autoupdates. // autoupdates.
void st3m_leds_set_single_rgb(uint8_t index, float red, float green, void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
float blue); float blue);
void st3m_leds_set_single_rgba(uint8_t index, float red, float green,
float blue, float alpha);
void st3m_leds_set_single_hsv(uint8_t index, float hue, float sat, float value); void st3m_leds_set_single_hsv(uint8_t index, float hue, float sat, float value);
void st3m_leds_set_all_rgb(float red, float green, float blue); void st3m_leds_set_all_rgb(float red, float green, float blue);
void st3m_leds_set_all_rgba(float red, float green, float blue, float alpha);
void st3m_leds_set_all_hsv(float hue, float sat, float value); void st3m_leds_set_all_hsv(float hue, float sat, float value);
void st3m_leds_get_single_rgb(uint8_t index, float* red, float* green,
float* blue);
// Set/get global LED brightness, 0-255. Default 69. // Set/get global LED brightness, 0-255. Default 69.
// //
...@@ -36,9 +41,10 @@ void st3m_leds_set_brightness(uint8_t brightness); ...@@ -36,9 +41,10 @@ void st3m_leds_set_brightness(uint8_t brightness);
uint8_t st3m_leds_get_brightness(); uint8_t st3m_leds_get_brightness();
// Set/get maximum change rate of brightness. Set to 1-3 for fade effects, set // Set/get maximum change rate of brightness. Set to 1-3 for fade effects, set
// to 255 to disable. Currently clocks at 10Hz. // to 255 to disable. Currently clocks at 50Hz.
void st3m_leds_set_slew_rate(uint8_t slew_rate); void st3m_leds_set_slew_rate(uint8_t slew_rate);
uint8_t st3m_leds_get_slew_rate(); uint8_t st3m_leds_get_slew_rate();
bool st3m_leds_get_steady();
// Update LEDs. Ie., copy the LED state from the first buffer into the second // Update LEDs. Ie., copy the LED state from the first buffer into the second
// buffer, effectively scheduling the LED state to be presented to the user. // buffer, effectively scheduling the LED state to be presented to the user.
......
#include "st3m_media.h"
#include "st3m_audio.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "esp_log.h"
#include "esp_task.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#define ST3M_PCM_BUF_SIZE (16384)
static st3m_media *media_item = NULL;
#ifdef CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static TaskHandle_t media_task;
static bool media_pending_destroy = false;
static SemaphoreHandle_t media_lock;
static void st3m_media_task(void *_arg) {
(void)_arg;
TickType_t wake_time = xTaskGetTickCount();
TickType_t last_think = wake_time;
while (!media_pending_destroy) {
TickType_t ticks;
vTaskDelayUntil(&wake_time, 20 / portTICK_PERIOD_MS);
if (media_item->think) {
if (xSemaphoreTake(media_lock, portMAX_DELAY)) {
bool seeking = media_item->seek != -1;
ticks = xTaskGetTickCount();
media_item->think(media_item,
(ticks - last_think) * portTICK_PERIOD_MS);
last_think = ticks;
if (seeking && media_item->seek == -1) {
// don't count seeking time into delta
last_think = xTaskGetTickCount();
}
xSemaphoreGive(media_lock);
}
}
// don't starve lower priority tasks if think takes a long time
if ((xTaskGetTickCount() - wake_time) * portTICK_PERIOD_MS > 10) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
if (media_item->destroy) media_item->destroy(media_item);
media_item = NULL;
vSemaphoreDelete(media_lock);
media_pending_destroy = false;
vTaskDelete(NULL);
}
void st3m_media_stop(void) {
if (!media_item) return;
media_pending_destroy = true;
while (media_pending_destroy) {
vTaskDelay(10);
}
}
void st3m_media_pause(void) {
if (!media_item) return;
media_item->paused = 1;
}
void st3m_media_play(void) {
if (!media_item) return;
media_item->paused = 0;
}
bool st3m_media_is_playing(void) {
if (!media_item) return 0;
return !media_item->paused;
}
bool st3m_media_has_video(void) {
if (!media_item) return false;
return media_item->has_video;
}
bool st3m_media_has_audio(void) {
if (!media_item) return false;
return media_item->has_audio;
}
bool st3m_media_is_visual(void) {
if (!media_item) return false;
return media_item->is_visual;
}
float st3m_media_get_duration(void) {
if (!media_item) return 0;
return media_item->duration;
}
float st3m_media_get_position(void) {
if (!media_item) return 0;
return media_item->position;
}
float st3m_media_get_time(void) {
if (!media_item) return 0;
if (media_item->time <= 0) return media_item->time;
return media_item->time - st3m_pcm_queued() / 48000.0 / 2.0;
}
void st3m_media_seek(float position) {
if (!media_item) return;
if (position < 0.0) position = 0.0f;
media_item->seek = position;
}
void st3m_media_seek_relative(float time) {
if (!media_item) return;
st3m_media_seek((media_item->position * media_item->duration) + time);
}
void st3m_media_set_volume(float volume) { st3m_pcm_set_volume(volume); }
float st3m_media_get_volume(void) { return st3m_pcm_get_volume(); }
void st3m_media_draw(Ctx *ctx) {
if (media_item && media_item->draw) {
if (xSemaphoreTake(media_lock, portMAX_DELAY)) {
media_item->draw(media_item, ctx);
xSemaphoreGive(media_lock);
}
}
}
void st3m_media_think(float ms) { (void)ms; }
char *st3m_media_get_string(const char *key) {
if (!media_item) return NULL;
if (!media_item->get_string) return NULL;
return media_item->get_string(media_item, key);
}
static float _st3m_media_zoom = 1.0f;
static float _st3m_media_cx = 0.0f;
static float _st3m_media_cy = 0.0f;
float st3m_media_get(const char *key) {
if (!strcmp(key, "zoom")) return _st3m_media_zoom;
if (!strcmp(key, "cx")) return _st3m_media_cx;
if (!strcmp(key, "cy")) return _st3m_media_cy;
if (!media_item || !media_item->get) return -1.0f;
return media_item->get(media_item, key);
}
void st3m_media_set(const char *key, float value) {
if (!strcmp(key, "zoom")) {
_st3m_media_zoom = value;
return;
}
if (!strcmp(key, "cx")) {
_st3m_media_cx = value;
return;
}
if (!strcmp(key, "cy")) {
_st3m_media_cy = value;
return;
}
if (!media_item || !media_item->set) return;
return media_item->set(media_item, key, value);
}
st3m_media *st3m_media_load_mpg1(const char *path);
st3m_media *st3m_media_load_mod(const char *path);
st3m_media *st3m_media_load_gif(const char *path);
st3m_media *st3m_media_load_mp3(const char *path);
st3m_media *st3m_media_load_txt(const char *path);
st3m_media *st3m_media_load_bin(const char *path);
static int file_get_contents(const char *path, uint8_t **contents,
size_t *length) {
FILE *file;
long size;
long remaining;
uint8_t *buffer;
file = fopen(path, "rb");
if (!file) {
return -1;
}
fseek(file, 0, SEEK_END);
size = remaining = ftell(file);
if (length) {
*length = size;
}
rewind(file);
buffer = malloc(size + 2);
if (!buffer) {
fclose(file);
return -1;
}
remaining -= fread(buffer, 1, remaining, file);
if (remaining) {
fclose(file);
free(buffer);
return -1;
}
fclose(file);
*contents = (unsigned char *)buffer;
buffer[size] = 0;
return 0;
}
bool st3m_media_load(const char *path, bool paused) {
struct stat statbuf;
if (!strncmp(path, "http://", 7)) {
st3m_media_stop();
media_item = st3m_media_load_mp3(path);
} else if (stat(path, &statbuf)) {
st3m_media_stop();
media_item = st3m_media_load_txt(path);
} else if (strstr(path, ".mp3") == strrchr(path, '.')) {
st3m_media_stop();
media_item = st3m_media_load_mp3(path);
} else if (strstr(path, ".mpg")) {
st3m_media_stop();
media_item = st3m_media_load_mpg1(path);
} else if (strstr(path, ".gif")) {
st3m_media_stop();
media_item = st3m_media_load_gif(path);
} else if ((strstr(path, ".mod") == strrchr(path, '.'))) {
st3m_media_stop();
media_item = st3m_media_load_mod(path);
} else if ((strstr(path, ".json") == strrchr(path, '.')) ||
(strstr(path, ".txt") == strrchr(path, '.')) ||
(strstr(path, "/README") == strrchr(path, '/')) ||
(strstr(path, ".toml") == strrchr(path, '.')) ||
(strstr(path, ".py") == strrchr(path, '.'))) {
st3m_media_stop();
media_item = st3m_media_load_txt(path);
}
if (!media_item) {
media_item = st3m_media_load_txt(path);
}
media_item->volume = 4096;
media_item->paused = paused;
media_lock = xSemaphoreCreateMutex();
BaseType_t res =
xTaskCreatePinnedToCore(st3m_media_task, "media", 16384, NULL,
ESP_TASK_PRIO_MIN + 3, &media_task, 1);
return res == pdPASS;
}
typedef struct {
st3m_media control;
char *data;
size_t size;
float scroll_pos;
char *path;
} txt_state;
static void txt_destroy(st3m_media *media) {
txt_state *self = (void *)media;
if (self->data) free(self->data);
if (self->path) free(self->path);
free(self);
}
static void txt_draw(st3m_media *media, Ctx *ctx) {
txt_state *self = (void *)media;
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_gray(ctx, 0);
ctx_fill(ctx);
ctx_gray(ctx, 1.0);
ctx_move_to(ctx, -85, -70);
ctx_font(ctx, "mono");
ctx_font_size(ctx, 13.0);
// ctx_text (ctx, self->path);
ctx_text(ctx, self->data);
}
static void txt_think(st3m_media *media, float ms_elapsed) {
// txt_state *self = (void*)media;
}
st3m_media *st3m_media_load_txt(const char *path) {
txt_state *self = (txt_state *)malloc(sizeof(txt_state));
memset(self, 0, sizeof(txt_state));
self->control.draw = txt_draw;
self->control.think = txt_think;
self->control.destroy = txt_destroy;
file_get_contents(path, (void *)&self->data, &self->size);
if (!self->data) {
self->data = malloc(strlen(path) + 64);
sprintf(self->data, "40x - %s", path);
self->size = strlen((char *)self->data);
}
self->path = strdup(path);
return (void *)self;
}
st3m_media *st3m_media_load_bin(const char *path) {
txt_state *self = (txt_state *)malloc(sizeof(txt_state));
memset(self, 0, sizeof(txt_state));
self->control.draw = txt_draw;
self->control.destroy = txt_destroy;
file_get_contents(path, (void *)&self->data, &self->size);
if (!self->data) {
self->data = malloc(strlen(path) + 64);
sprintf(self->data, "40x - %s", path);
self->size = strlen(self->data);
}
self->path = strdup(path);
return (void *)self;
}
st3m_media_tags_t *audio_mp3_get_tags(const char *path);
st3m_media_tags_t *st3m_media_get_tags(const char *path) {
// only implemented for mp3 for now
return audio_mp3_get_tags(path);
}
void st3m_media_free_tags(st3m_media_tags_t *tags) {
if (!tags) return;
free(tags->artist);
free(tags->title);
free(tags->album);
free(tags);
}
#endif
#pragma once
#include <ctx.h>
#include <stdbool.h>
#include <stdint.h>
#include "st3m_pcm.h"
typedef struct _st3m_media st3m_media;
struct _st3m_media {
// set a tunable, to a numeric value - available tunables depends on
// decoder
void (*set)(st3m_media *media, const char *key, float value);
// get a property/or tunable, defaulting to -1 for nonexisting keys
float (*get)(st3m_media *media, const char *key);
// get a string property/metadata, NULL if not existing or a string
// to be freed
char *(*get_string)(st3m_media *media, const char *key);
// free resources used by this media instance
void (*destroy)(st3m_media *media);
// draw the current frame / visualization / metadata screen
void (*draw)(st3m_media *media, Ctx *ctx);
// do decoding work corresponding to passed time
void (*think)(st3m_media *media, float ms);
// Duration of media in seconds or -1 for infinite/streaming media
// at worst approximation of some unit, set by decoder.
float duration;
// currently played back position - set by decoder
float position;
// currently played back position, in seconds - set by decoder
float time; // time might be precise even when duration is not
// until first play through of some media, when time
// duration should also be set exact.
// whether the loaded file contains a video stream - set by decoder
bool has_video;
// whether the loaded file contains an audio stream - set by decoder
bool has_audio;
// whether the loaded file contains visual content - set by decoder
bool is_visual;
// decoder should seek to this relative if not -1, and set it to -1
float seek;
// audio volume
int volume;
// if set to 1 playback is momentarily stopped but can be resumed,
// this is toggled by st3m_media_play | st3m_media_pause
bool paused;
};
// stops the currently playing media item
void st3m_media_stop(void);
// set a new media item
bool st3m_media_load(const char *path_or_uri, bool paused);
// decode current media item ms ahead (unless paused)
void st3m_media_think(float ms);
// draw the codecs own view of itself / its meta data - progress
// for video or animations formats this should draw the media-content
// other codecs can find mixes of debug visualizations.
void st3m_media_draw(Ctx *ctx);
// controls whether we are playing
void st3m_media_pause(void);
void st3m_media_play(void);
bool st3m_media_is_playing(void);
// whether there's a video stream being played
bool st3m_media_has_video(void);
// whether there's a video stream being played
bool st3m_media_has_audio(void);
// whether the played file has visual content
bool st3m_media_is_visual(void);
// get duration in seconds
float st3m_media_get_duration(void);
// get current playback time in seconds
float st3m_media_get_time(void);
// get current playback position relative to overall duration
float st3m_media_get_position(void);
// seek to a position relative to overall duration
void st3m_media_seek(float position);
// seek a relative amount of seconds forward or with negative values back
void st3m_media_seek_relative(float seconds_jump);
// set audio volume (0.0 - 1.0)
void st3m_media_set_volume(float volume);
// get audio volume
float st3m_media_get_volume(void);
// get decoder specific string or NULL if not existing, free returned value
// common values:
// "title" "artist"
char *st3m_media_get_string(const char *key);
// get a decoder specific numeric value, defaulting to -1 for nonexisting values
float st3m_media_get(const char *key);
// set a decoder specific floating point value
// example posible/or already used values:
//
// "grayscale" 0 or 1 - drop color bits for performance
//
// some keys are global and stored outside codecs, they can be queried
// and set both by apps and codecs:
//
// "zoom" 0.0 - 1.0 - how large part of display video cover
// "cx" "cy" -120 - 120 - where to put the center of video textures
void st3m_media_set(const char *key, float value);
typedef struct {
char *artist;
char *title;
char *album;
int year;
int track_number;
} st3m_media_tags_t;
// get collection of tags
st3m_media_tags_t *st3m_media_get_tags(const char *path);
void st3m_media_free_tags(st3m_media_tags_t *tags);
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "st3m_fs_sd.h" #include "st3m_fs_sd.h"
#include "st3m_gfx.h" #include "st3m_gfx.h"
#include "st3m_io.h" #include "st3m_io.h"
#include "st3m_media.h"
#include "st3m_usb.h" #include "st3m_usb.h"
static const char *TAG = "st3m-mode"; static const char *TAG = "st3m-mode";
...@@ -87,6 +88,11 @@ void st3m_mode_set(st3m_mode_kind_t kind, const char *message) { ...@@ -87,6 +88,11 @@ void st3m_mode_set(st3m_mode_kind_t kind, const char *message) {
_mode.message = NULL; _mode.message = NULL;
} }
if (_mode.kind != kind) {
st3m_media_stop();
st3m_gfx_flush(200);
}
_mode.kind = kind; _mode.kind = kind;
_mode.shown = false; _mode.shown = false;
...@@ -123,10 +129,12 @@ void st3m_mode_update_display(bool *restartable) { ...@@ -123,10 +129,12 @@ void st3m_mode_update_display(bool *restartable) {
case st3m_mode_kind_disk_sd: case st3m_mode_kind_disk_sd:
case st3m_mode_kind_disk_flash: { case st3m_mode_kind_disk_flash: {
const char *lines[] = { const char *lines[] = {
"Press left shoulder button", (st3m_io_app_button_is_left()) ? "Press right shoulder button"
"to exit.", : "Press left shoulder button",
"to restart.",
NULL, NULL,
}; };
// st3m_io_app_button_is_left
st3m_gfx_textview_t tv = { st3m_gfx_textview_t tv = {
.title = "Disk Mode", .title = "Disk Mode",
.lines = lines, .lines = lines,
...@@ -139,7 +147,9 @@ void st3m_mode_update_display(bool *restartable) { ...@@ -139,7 +147,9 @@ void st3m_mode_update_display(bool *restartable) {
_mode.shown = true; _mode.shown = true;
const char *lines[] = { const char *lines[] = {
"Send Ctrl-D over USB", "Send Ctrl-D over USB",
"or press left shoulder button", (st3m_io_app_button_is_left())
? "or press right shoulder button"
: "or press left shoulder button",
"to restart.", "to restart.",
NULL, NULL,
}; };
...@@ -158,7 +168,8 @@ void st3m_mode_update_display(bool *restartable) { ...@@ -158,7 +168,8 @@ void st3m_mode_update_display(bool *restartable) {
} }
const char *lines[] = { const char *lines[] = {
msg, msg,
"Press left shoulder button", (st3m_io_app_button_is_left()) ? "Press right shoulder button"
: "Press left shoulder button",
"to restart.", "to restart.",
NULL, NULL,
}; };
...@@ -186,13 +197,13 @@ static void _task(void *arg) { ...@@ -186,13 +197,13 @@ static void _task(void *arg) {
st3m_mode_update_display(&restartable); st3m_mode_update_display(&restartable);
if (restartable) { if (restartable) {
st3m_tripos tp = st3m_io_left_button_get(); st3m_tripos tp = st3m_io_os_button_get();
if (tp == st3m_tripos_mid) { if (tp == st3m_tripos_mid) {
st3m_gfx_textview_t tv = { st3m_gfx_textview_t tv = {
.title = "Restarting...", .title = "Restarting...",
}; };
st3m_gfx_show_textview(&tv); st3m_gfx_show_textview(&tv);
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
esp_restart(); esp_restart();
} }
} }
......
#include "st3m_pcm.h"
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#define ST3M_PCM_BUF_SIZE (16384)
static int st3m_pcm_gain = 4096;
int st3m_pcm_queue_length(void) { return ST3M_PCM_BUF_SIZE - 4000; }
EXT_RAM_BSS_ATTR static int16_t st3m_pcm_buffer[ST3M_PCM_BUF_SIZE];
static int st3m_pcm_w = 1;
static int st3m_pcm_r = 0;
static inline int16_t apply_gain(int16_t sample, int16_t gain) {
if (gain == 4096) return sample;
int32_t val = (sample * st3m_pcm_gain) >> 12;
if (gain < 4096) return val;
if (val > 32767) {
return 32767;
} else if (val < -32767) {
return -32767;
}
return val;
}
bool st3m_pcm_audio_render(int16_t *rx, int16_t *tx, uint16_t len) {
if (((((st3m_pcm_r + 1) % ST3M_PCM_BUF_SIZE)) == st3m_pcm_w)) return false;
for (int i = 0; i < len; i++) {
if ((st3m_pcm_r + 1 != st3m_pcm_w) &&
(st3m_pcm_r + 1 - ST3M_PCM_BUF_SIZE != st3m_pcm_w)) {
tx[i] = st3m_pcm_buffer[st3m_pcm_r++];
st3m_pcm_r %= ST3M_PCM_BUF_SIZE;
} else {
tx[i] = 0;
}
}
return true;
}
static int phase = 0;
void st3m_pcm_queue_s16(int hz, int ch, int count, int16_t *data) {
if (hz == 48000) {
if (ch == 2) {
if (st3m_pcm_gain == 4096) {
for (int i = 0; i < count * 2; i++) {
st3m_pcm_buffer[st3m_pcm_w++] = data[i];
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
} else {
for (int i = 0; i < count * 2; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
}
} else if (ch == 1) {
if (st3m_pcm_gain == 4096) {
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] = data[i];
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] = data[i];
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
} else {
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
}
}
} else {
int fraction = ((48000.0 / hz) - 1.0) * 65536;
if (ch == 2) {
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i * 2], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i * 2 + 1], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
if (phase > 65536) {
phase -= 65536;
i--;
continue;
}
phase += fraction;
}
} else if (ch == 1) {
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i], st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
if (phase > 65536) {
phase -= 65536;
i--;
continue;
}
phase += fraction;
}
}
}
}
void st3m_pcm_queue_float(int hz, int ch, int count, float *data) {
if (hz == 48000) {
if (ch == 2) {
if (st3m_pcm_gain == 4096)
for (int i = 0; i < count * 2; i++) {
st3m_pcm_buffer[st3m_pcm_w++] = data[i] * 32767;
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
else
for (int i = 0; i < count * 2; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
} else if (ch == 1) {
if (st3m_pcm_gain == 4096)
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] = data[i] * 32767;
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] = data[i] * 32767;
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
else
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
}
}
} else {
int fraction = ((48000.0 / hz) - 1.0) * 65536;
if (ch == 2) {
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i * 2] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i * 2 + 1] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
if (phase >= 65536) {
phase -= 65536;
i--;
continue;
}
phase += fraction;
}
} else if (ch == 1) {
for (int i = 0; i < count; i++) {
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
st3m_pcm_buffer[st3m_pcm_w++] =
apply_gain(data[i] * 32767, st3m_pcm_gain);
st3m_pcm_w %= ST3M_PCM_BUF_SIZE;
if (phase >= 65536) {
phase -= 65536;
i--;
continue;
}
phase += fraction;
}
}
}
}
int st3m_pcm_queued(void) {
if (st3m_pcm_r > st3m_pcm_w)
return (ST3M_PCM_BUF_SIZE - st3m_pcm_r) + st3m_pcm_w;
return st3m_pcm_w - st3m_pcm_r;
}
void st3m_pcm_set_volume(float volume) { st3m_pcm_gain = volume * 4096; }
float st3m_pcm_get_volume(void) { return st3m_pcm_gain / 4096.0; }
#pragma once
#include <stdbool.h>
#include <stdint.h>
// audio rendering function used by st3m_audio
bool st3m_pcm_audio_render(int16_t *rx, int16_t *tx, uint16_t len);
// query how many pcm samples have been queued for output
int st3m_pcm_queued(void);
// returns the internal PCM buffer length, this return the maximum number
// of samples that can be queued - the buffer might internally be larger.
int st3m_pcm_queue_length(void);
// queue signed 16bit samples, supports hz is up to 48000, ch 1 for mono
// or 2 for stereo
void st3m_pcm_queue_s16(int hz, int ch, int count, int16_t *data);
// queue 32bit float samples, supports hz is up to 48000 ch 1 for mono or 2
// for stereo
void st3m_pcm_queue_float(int hz, int ch, int count, float *data);
// set audio volume (0.0 - 1.0)
void st3m_pcm_set_volume(float volume);
// get audio volume
float st3m_pcm_get_volume(void);
...@@ -49,7 +49,8 @@ void st3m_scope_init(void) { ...@@ -49,7 +49,8 @@ void st3m_scope_init(void) {
memset(scope.read_buffer, 0, sizeof(int16_t) * scope.buffer_size); memset(scope.read_buffer, 0, sizeof(int16_t) * scope.buffer_size);
scope.write_head_position = 0; scope.write_head_position = 0;
scope.prev_write_attempt = 0; scope.prev_value = 0;
scope.zero_crossing_occurred = false;
ESP_LOGI(TAG, "initialized"); ESP_LOGI(TAG, "initialized");
} }
...@@ -58,39 +59,45 @@ void st3m_scope_write(int16_t value) { ...@@ -58,39 +59,45 @@ void st3m_scope_write(int16_t value) {
return; return;
} }
int16_t prev_write_attempt = scope.prev_write_attempt; if ((!scope.zero_crossing_occurred) && (value > 0) &&
scope.prev_write_attempt = value; (scope.prev_value <= 0)) {
scope.write_head_position = 0;
// If we're about to write the first sample, make sure we do so at a scope.zero_crossing_occurred = true;
// positive zero crossing.
if (scope.write_head_position == 0) {
// Calculate 'positivity' sign of this value and previous value.
int16_t this = value > 0;
int16_t prev = prev_write_attempt > 0;
if (this != 1 || prev != 0) {
return;
}
} }
if (scope.write_head_position >= scope.buffer_size) { if (scope.write_head_position >= scope.buffer_size) {
scope.write_buffer = Atomic_SwapPointers_p32( scope.write_buffer = Atomic_SwapPointers_p32(
(void *volatile *)&scope.exchange_buffer, scope.write_buffer); (void *volatile *)&scope.exchange_buffer, scope.write_buffer);
scope.write_head_position = 0; scope.write_head_position = 0;
scope.zero_crossing_occurred = false;
} else { } else {
scope.write_buffer[scope.write_head_position] = value; scope.write_buffer[scope.write_head_position] = value;
scope.write_head_position++; scope.write_head_position++;
} }
scope.prev_value = value;
} }
void st3m_scope_draw(Ctx *ctx) { size_t st3m_scope_get_buffer_x(int16_t **buf) {
if (scope.write_buffer == NULL) { if (scope.write_buffer == NULL) {
return; return 0;
} }
scope.read_buffer = Atomic_SwapPointers_p32( scope.read_buffer = Atomic_SwapPointers_p32(
(void *volatile *)&scope.exchange_buffer, scope.read_buffer); (void *volatile *)&scope.exchange_buffer, scope.read_buffer);
if (buf) {
*buf = scope.read_buffer;
}
return scope.buffer_size;
}
void st3m_scope_draw(Ctx *ctx) {
if (st3m_scope_get_buffer_x(NULL) == 0) {
return;
}
// How much to divide the values persisted in the buffer to scale to // How much to divide the values persisted in the buffer to scale to
// -120+120. // -120+120.
// //
...@@ -105,7 +112,7 @@ void st3m_scope_draw(Ctx *ctx) { ...@@ -105,7 +112,7 @@ void st3m_scope_draw(Ctx *ctx) {
// //
// decimate == 2 -> every second sample is drawn (240/2 == 120 line segments // decimate == 2 -> every second sample is drawn (240/2 == 120 line segments
// are drawn). Looks good enough. // are drawn). Looks good enough.
size_t decimate = 2; size_t decimate = 1;
int x = -120; int x = -120;
int y = scope.read_buffer[0] >> shift; int y = scope.read_buffer[0] >> shift;
...@@ -116,8 +123,8 @@ void st3m_scope_draw(Ctx *ctx) { ...@@ -116,8 +123,8 @@ void st3m_scope_draw(Ctx *ctx) {
ctx_line_to(ctx, x, y); ctx_line_to(ctx, x, y);
} }
ctx_line_to(ctx, 130, 0); ctx_save(ctx);
ctx_line_to(ctx, 130, -130); ctx_line_width(ctx, -1.0f);
ctx_line_to(ctx, -130, -130); ctx_stroke(ctx);
ctx_line_to(ctx, -130, 0); ctx_restore(ctx);
} }
#pragma once #pragma once
// st3m_scope implements an oscilloscope-style music visualizer. // st3m_scope implements a basic scope.
// //
// The audio subsystem will continuously send the global mixing output result // The audio subsystem will continuously send the global mixing output result
// into the oscilloscope. User code can decide when to draw said scope. // into the oscilloscope. User code can decide when to draw said scope.
...@@ -24,9 +24,8 @@ typedef struct { ...@@ -24,9 +24,8 @@ typedef struct {
// Offset where the write handler should write the next sample. // Offset where the write handler should write the next sample.
uint32_t write_head_position; uint32_t write_head_position;
// Previous sample that was attempted to be written. Used for int16_t prev_value;
// zero-detection. bool zero_crossing_occurred;
int16_t prev_write_attempt;
} st3m_scope_t; } st3m_scope_t;
// Initialize global scope. Must be performed before any other access to scope // Initialize global scope. Must be performed before any other access to scope
...@@ -39,12 +38,11 @@ void st3m_scope_init(void); ...@@ -39,12 +38,11 @@ void st3m_scope_init(void);
// Write a sound sample to the scope. // Write a sound sample to the scope.
void st3m_scope_write(int16_t value); void st3m_scope_write(int16_t value);
// Retrieve scope's data buffer. Remains valid until the next
// st3m_scope_get_buffer_x or st3m_scope_draw call. Returns buffer's length.
size_t st3m_scope_get_buffer_x(int16_t **buf);
// Draw the scope at bounding box -120/-120 +120/+120. // Draw the scope at bounding box -120/-120 +120/+120.
// //
// The scope will be drawn as a closable line segment starting at x:-120 // The user is responsible for clearing background and setting a color.
// y:sample[0], through x:120 y:sample[239], then going through x:130 y:130,
// x:-130 y:130.
//
// The user is responsible for setting a color and running a fill/stroke
// afterwards.
void st3m_scope_draw(Ctx *ctx); void st3m_scope_draw(Ctx *ctx);
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include "esp_log.h" #include "esp_log.h"
#include "miniz.h" #include "miniz.h"
#define READ_BUF_SIZE 4096
static const char *TAG = "st3m-tar"; static const char *TAG = "st3m-tar";
void st3m_tar_parser_init(st3m_tar_parser_t *parser) { void st3m_tar_parser_init(st3m_tar_parser_t *parser) {
...@@ -201,9 +203,14 @@ static int _mkpath(char *file_path, mode_t mode) { ...@@ -201,9 +203,14 @@ static int _mkpath(char *file_path, mode_t mode) {
static void _extractor_on_file_start(void *user, const char *name, size_t size, static void _extractor_on_file_start(void *user, const char *name, size_t size,
char type) { char type) {
st3m_tar_extractor_t *extractor = (st3m_tar_extractor_t *)user; st3m_tar_extractor_t *extractor = (st3m_tar_extractor_t *)user;
if (extractor->cur_file != NULL) { if (extractor->cur_file_contents != NULL) {
fclose(extractor->cur_file); free(extractor->cur_file_contents);
extractor->cur_file = NULL; extractor->cur_file_contents = NULL;
extractor->cur_file_offset = NULL;
}
if (extractor->cur_file_path != NULL) {
free(extractor->cur_file_path);
extractor->cur_file_path = NULL;
} }
if (size == 0 || type != '0') { if (size == 0 || type != '0') {
...@@ -222,33 +229,88 @@ static void _extractor_on_file_start(void *user, const char *name, size_t size, ...@@ -222,33 +229,88 @@ static void _extractor_on_file_start(void *user, const char *name, size_t size,
ESP_LOGW(TAG, "Failed to create parent directory for %s: %s", path, ESP_LOGW(TAG, "Failed to create parent directory for %s: %s", path,
strerror(errno)); strerror(errno));
} }
extractor->cur_file = fopen(path, "w");
if (extractor->cur_file == NULL) { extractor->cur_file_path = path;
ESP_LOGW(TAG, "Failed to create %s: %s", path, strerror(errno));
} extractor->cur_file_contents = malloc(size);
extractor->cur_file_offset = extractor->cur_file_contents;
extractor->cur_file_size = size;
if (extractor->on_file != NULL) { if (extractor->on_file != NULL) {
extractor->on_file(path); extractor->on_file(path);
} }
free(path);
} }
static void _extractor_on_file_data(void *user, const uint8_t *buffer, static void _extractor_on_file_data(void *user, const uint8_t *buffer,
size_t bufsize) { size_t bufsize) {
st3m_tar_extractor_t *extractor = (st3m_tar_extractor_t *)user; st3m_tar_extractor_t *extractor = (st3m_tar_extractor_t *)user;
if (extractor->cur_file == NULL) { if (extractor->cur_file_contents == NULL) {
return; return;
} }
fwrite(buffer, 1, bufsize, extractor->cur_file);
memcpy(extractor->cur_file_offset, buffer, bufsize);
extractor->cur_file_offset += bufsize;
}
static bool _is_write_needed(char *path, uint8_t *contents, size_t size) {
struct stat sb;
if (stat(path, &sb) != 0 || sb.st_size != size) {
// file doesn't exist or size changed
return true;
}
// go on to compare file contents
FILE *file = fopen(path, "r");
if (file == NULL) {
return true;
}
// malloc because we don't have that much stack
uint8_t *buf = malloc(READ_BUF_SIZE);
for (size_t off = 0; off < size; off += READ_BUF_SIZE) {
size_t leftover = (size - off);
size_t read_size =
(leftover < READ_BUF_SIZE) ? leftover : READ_BUF_SIZE;
if ((fread(buf, 1, read_size, file) != read_size) ||
(memcmp(buf, contents + off, read_size) != 0)) {
fclose(file);
free(buf);
return true;
}
}
fclose(file);
free(buf);
return false;
} }
static void _extractor_on_file_end(void *user) { static void _extractor_on_file_end(void *user) {
st3m_tar_extractor_t *extractor = (st3m_tar_extractor_t *)user; st3m_tar_extractor_t *extractor = (st3m_tar_extractor_t *)user;
if (extractor->cur_file == NULL) { if (extractor->cur_file_contents == NULL) {
return; return;
} }
fclose(extractor->cur_file);
extractor->cur_file = NULL; if (_is_write_needed(extractor->cur_file_path, extractor->cur_file_contents,
extractor->cur_file_size)) {
FILE *cur_file = fopen(extractor->cur_file_path, "w");
if (cur_file == NULL) {
ESP_LOGW(TAG, "Failed to create %s: %s", extractor->cur_file_path,
strerror(errno));
} else {
fwrite(extractor->cur_file_contents, 1, extractor->cur_file_size,
cur_file);
fflush(cur_file);
fclose(cur_file);
}
}
free(extractor->cur_file_contents);
free(extractor->cur_file_path);
extractor->cur_file_contents = NULL;
extractor->cur_file_offset = NULL;
extractor->cur_file_path = NULL;
} }
void st3m_tar_extractor_init(st3m_tar_extractor_t *extractor) { void st3m_tar_extractor_init(st3m_tar_extractor_t *extractor) {
......
...@@ -51,7 +51,11 @@ typedef struct { ...@@ -51,7 +51,11 @@ typedef struct {
// If set, will be called any file begins extraction. Useful for feedback. // If set, will be called any file begins extraction. Useful for feedback.
void (*on_file)(const char *name); void (*on_file)(const char *name);
FILE *cur_file; char *cur_file_path;
uint8_t *cur_file_contents;
uint8_t *cur_file_offset;
size_t cur_file_size;
st3m_tar_parser_t parser; st3m_tar_parser_t parser;
} st3m_tar_extractor_t; } st3m_tar_extractor_t;
......
#include "st3m_usb.h" #include "st3m_usb.h"
#ifndef CONFIG_DEBUG_GDB_ENABLED
#ifndef CONFIG_DEBUG_GDB_DISABLED
#error "st3m: no debug flags"
#endif
#endif
#ifdef CONFIG_DEBUG_GDB_ENABLED
#ifdef CONFIG_DEBUG_GDB_DISABLED
#error "st3m: invalid debug flags"
#endif
#endif
static const char *TAG = "st3m-usb"; static const char *TAG = "st3m-usb";
...@@ -11,6 +21,8 @@ static const char *TAG = "st3m-usb"; ...@@ -11,6 +21,8 @@ static const char *TAG = "st3m-usb";
#include "esp_private/usb_phy.h" #include "esp_private/usb_phy.h"
#include "tusb.h" #include "tusb.h"
#include "st3m_console.h"
static SemaphoreHandle_t _mu = NULL; static SemaphoreHandle_t _mu = NULL;
static st3m_usb_mode_kind_t _mode = st3m_usb_mode_kind_disabled; static st3m_usb_mode_kind_t _mode = st3m_usb_mode_kind_disabled;
static usb_phy_handle_t phy_hdl; static usb_phy_handle_t phy_hdl;
...@@ -45,12 +57,16 @@ static void _generate_serial(void) { ...@@ -45,12 +57,16 @@ static void _generate_serial(void) {
} }
void st3m_usb_init(void) { void st3m_usb_init(void) {
#ifdef CONFIG_DEBUG_GDB_ENABLED
return;
#endif
assert(_mu == NULL); assert(_mu == NULL);
_mu = xSemaphoreCreateMutex(); _mu = xSemaphoreCreateMutex();
assert(_mu != NULL); assert(_mu != NULL);
_mode = st3m_usb_mode_kind_disabled; _mode = st3m_usb_mode_kind_disabled;
_generate_serial(); _generate_serial();
#ifndef CONFIG_DEBUG_GDB_ENABLED
st3m_usb_cdc_init(); st3m_usb_cdc_init();
usb_phy_config_t phy_conf = { usb_phy_config_t phy_conf = {
.controller = USB_PHY_CTRL_OTG, .controller = USB_PHY_CTRL_OTG,
...@@ -68,9 +84,13 @@ void st3m_usb_init(void) { ...@@ -68,9 +84,13 @@ void st3m_usb_init(void) {
xTaskCreate(_usb_task, "usb", 4096, NULL, 5, NULL); xTaskCreate(_usb_task, "usb", 4096, NULL, 5, NULL);
ESP_LOGI(TAG, "USB stack started"); ESP_LOGI(TAG, "USB stack started");
#endif
} }
void st3m_usb_mode_switch(st3m_usb_mode_t *mode) { void st3m_usb_mode_switch(st3m_usb_mode_t *mode) {
#ifdef CONFIG_DEBUG_GDB_ENABLED
return;
#endif
xSemaphoreTake(_mu, portMAX_DELAY); xSemaphoreTake(_mu, portMAX_DELAY);
bool running = false; bool running = false;
...@@ -120,6 +140,9 @@ void st3m_usb_mode_switch(st3m_usb_mode_t *mode) { ...@@ -120,6 +140,9 @@ void st3m_usb_mode_switch(st3m_usb_mode_t *mode) {
} }
bool st3m_usb_connected(void) { bool st3m_usb_connected(void) {
#ifdef CONFIG_DEBUG_GDB_ENABLED
return false;
#endif
xSemaphoreTake(_mu, portMAX_DELAY); xSemaphoreTake(_mu, portMAX_DELAY);
bool res = _connected; bool res = _connected;
xSemaphoreGive(_mu); xSemaphoreGive(_mu);
...@@ -144,3 +167,24 @@ void tud_suspend_cb(bool remote_wakeup_en) { ...@@ -144,3 +167,24 @@ void tud_suspend_cb(bool remote_wakeup_en) {
st3m_usb_cdc_detached(); st3m_usb_cdc_detached();
} }
} }
void st3m_usb_startup() {
#ifdef CONFIG_DEBUG_GDB_ENABLED
return;
#endif
st3m_usb_app_conf_t app = {
.fn_rx = st3m_console_cdc_on_rx,
.fn_txpoll = st3m_console_cdc_on_txpoll,
.fn_detach = st3m_console_cdc_on_detach,
};
st3m_usb_mode_t usb_mode = {
.kind = st3m_usb_mode_kind_app,
.app = &app,
};
st3m_usb_mode_switch(&usb_mode);
puts(" ___ _ ___ _ _");
puts("| _| |___ _ _ _|_ |___| |_ ___ _| |___ ___");
puts("| _| | . | | | |_ | _| . | .'| . | . | -_|");
puts("|_| |_|___|_____|___|_| |___|__,|___|_ |___|");
puts(" |___|");
}