Compare commits
15 Commits
master
...
multi-mode
Author | SHA1 | Date |
---|---|---|
Ondřej Hruška | 09e08e58a6 | 9 years ago |
Ondřej Hruška | 02641ac008 | 9 years ago |
Ondřej Hruška | c0d2b618da | 9 years ago |
Ondřej Hruška | 43ab7abce7 | 9 years ago |
Ondřej Hruška | 736567af8c | 9 years ago |
Ondřej Hruška | a4400c25f5 | 9 years ago |
Ondřej Hruška | e99ea3d0ec | 9 years ago |
Ondřej Hruška | d49b10a802 | 9 years ago |
Ondřej Hruška | 56a0d0c63b | 9 years ago |
Ondřej Hruška | 7fe3863f1b | 9 years ago |
Ondřej Hruška | d4e386daa9 | 9 years ago |
Ondřej Hruška | b4fe9dc546 | 9 years ago |
Ondřej Hruška | 0f07ce0a4a | 9 years ago |
Ondřej Hruška | dbd2d6a7ea | 9 years ago |
Ondřej Hruška | 7af5f0e35e | 9 years ago |
@ -1,113 +1,130 @@ |
|||||||
#include "max2719.h" |
|
||||||
#include "dotmatrix.h" |
#include "dotmatrix.h" |
||||||
#include "malloc_safe.h" |
#include "malloc_safe.h" |
||||||
#include "com/debug.h" |
#include "com/debug.h" |
||||||
|
|
||||||
|
DotMatrix_Cfg *dmtx; |
||||||
|
|
||||||
|
|
||||||
DotMatrix_Cfg* dmtx_init(DotMatrix_Init *init) |
DotMatrix_Cfg* dmtx_init(DotMatrix_Init *init) |
||||||
{ |
{ |
||||||
DotMatrix_Cfg *dmtx = calloc_s(1, sizeof(DotMatrix_Cfg)); |
DotMatrix_Cfg *disp = calloc_s(1, sizeof(DotMatrix_Cfg)); |
||||||
|
|
||||||
dmtx->drv.SPIx = init->SPIx; |
disp->drv.SPIx = init->SPIx; |
||||||
dmtx->drv.CS_GPIOx = init->CS_GPIOx; |
disp->drv.CS_GPIOx = init->CS_GPIOx; |
||||||
dmtx->drv.CS_PINx = init->CS_PINx; |
disp->drv.CS_PINx = init->CS_PINx; |
||||||
dmtx->drv.chain_len = init->cols * init->rows; |
disp->drv.chain_len = init->cols * init->rows; |
||||||
dmtx->cols = init->cols; |
disp->cols = init->cols; |
||||||
dmtx->rows = init->rows; |
disp->rows = init->rows; |
||||||
|
|
||||||
dmtx->screen = calloc_s(init->cols * init->rows * 8, 1); // 8 bytes per driver
|
disp->screen = calloc_s(init->cols * init->rows * 8, 1); // 8 bytes per driver
|
||||||
|
|
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DECODE_MODE, 0x00); // no decode
|
max2719_cmd_all(&disp->drv, MAX2719_CMD_DECODE_MODE, 0x00); // no decode
|
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SCAN_LIMIT, 0x07); // scan all 8
|
max2719_cmd_all(&disp->drv, MAX2719_CMD_SCAN_LIMIT, 0x07); // scan all 8
|
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SHUTDOWN, 0x01); // not shutdown
|
max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, 0x01); // not shutdown
|
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DISPLAY_TEST, 0x00); // not test
|
max2719_cmd_all(&disp->drv, MAX2719_CMD_DISPLAY_TEST, 0x00); // not test
|
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_INTENSITY, 0x07); // half intensity
|
max2719_cmd_all(&disp->drv, MAX2719_CMD_INTENSITY, 0x07); // half intensity
|
||||||
|
|
||||||
// clear
|
// clear
|
||||||
for (uint8_t i = 0; i < 8; i++) { |
for (uint8_t i = 0; i < 8; i++) { |
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DIGIT0+i, 0); |
max2719_cmd_all(&disp->drv, MAX2719_CMD_DIGIT0+i, 0); |
||||||
} |
} |
||||||
|
|
||||||
return dmtx; |
return disp; |
||||||
} |
} |
||||||
|
|
||||||
void dmtx_show(DotMatrix_Cfg* dmtx) |
void dmtx_show(DotMatrix_Cfg* disp) |
||||||
{ |
{ |
||||||
for (uint8_t i = 0; i < 8; i++) { |
for (uint8_t i = 0; i < 8; i++) { |
||||||
// show each digit's array in turn
|
// show each digit's array in turn
|
||||||
max2719_cmd_all_data(&dmtx->drv, MAX2719_CMD_DIGIT0+i, dmtx->screen + (i * dmtx->drv.chain_len)); |
max2719_cmd_all_data(&disp->drv, MAX2719_CMD_DIGIT0+i, disp->screen + (i * disp->drv.chain_len)); |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
void dmtx_clear(DotMatrix_Cfg* dmtx) |
void dmtx_clear(DotMatrix_Cfg* disp) |
||||||
{ |
{ |
||||||
memset(dmtx->screen, 0, dmtx->drv.chain_len*8); |
memset(disp->screen, 0, disp->drv.chain_len*8); |
||||||
} |
} |
||||||
|
|
||||||
void dmtx_intensity(DotMatrix_Cfg* dmtx, uint8_t intensity) |
void dmtx_intensity(DotMatrix_Cfg* disp, uint8_t intensity) |
||||||
{ |
{ |
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_INTENSITY, intensity & 0x0F); |
max2719_cmd_all(&disp->drv, MAX2719_CMD_INTENSITY, intensity & 0x0F); |
||||||
} |
} |
||||||
|
|
||||||
void dmtx_blank(DotMatrix_Cfg* dmtx, bool blank) |
void dmtx_blank(DotMatrix_Cfg* disp, bool blank) |
||||||
{ |
{ |
||||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SHUTDOWN, blank & 0x01); |
max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, (!blank) & 0x01); |
||||||
} |
} |
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a cell pointer |
* @brief Get a cell pointer |
||||||
* @param dmtx : driver inst |
* @param disp : driver inst |
||||||
* @param x : x coord |
* @param x : x coord |
||||||
* @param y : y coord |
* @param y : y coord |
||||||
* @param xd : pointer to store the offset in the cell |
* @param xd : pointer to store the offset in the cell |
||||||
* @return cell ptr |
* @return cell ptr |
||||||
*/ |
*/ |
||||||
static uint8_t* cell_ptr(DotMatrix_Cfg* dmtx, int32_t x, int32_t y, uint8_t *xd) |
static uint8_t* cell_ptr(DotMatrix_Cfg* disp, int32_t x, int32_t y, uint8_t *xd) |
||||||
{ |
{ |
||||||
if (x < 0 || y < 0) return NULL; |
if (x < 0 || y < 0) return NULL; |
||||||
if ((uint32_t)x >= dmtx->cols*8 || (uint32_t)y >= dmtx->rows*8) return NULL; |
if ((uint32_t)x >= disp->cols*8 || (uint32_t)y >= disp->rows*8) return NULL; |
||||||
|
|
||||||
uint32_t cell_x = (uint32_t)x >> 3; |
uint32_t cell_x = (uint32_t)x >> 3; |
||||||
*xd = x & 7; |
*xd = x & 7; |
||||||
|
|
||||||
// resolve cell
|
// resolve cell
|
||||||
uint32_t digit = y & 7; |
uint32_t digit = y & 7; |
||||||
cell_x += ((uint32_t)y >> 3) * dmtx->cols; |
cell_x += ((uint32_t)y >> 3) * disp->cols; |
||||||
|
|
||||||
uint32_t cell_idx = (digit * dmtx->drv.chain_len) + cell_x; |
uint32_t cell_idx = (digit * disp->drv.chain_len) + cell_x; |
||||||
|
|
||||||
return &dmtx->screen[cell_idx]; |
return &disp->screen[cell_idx]; |
||||||
} |
} |
||||||
|
|
||||||
|
|
||||||
bool dmtx_get(DotMatrix_Cfg* dmtx, int32_t x, int32_t y) |
bool dmtx_get(DotMatrix_Cfg* disp, int32_t x, int32_t y) |
||||||
{ |
{ |
||||||
uint8_t xd; |
uint8_t xd; |
||||||
uint8_t *cell = cell_ptr(dmtx, x, y, &xd); |
uint8_t *cell = cell_ptr(disp, x, y, &xd); |
||||||
if (cell == NULL) return 0; |
if (cell == NULL) return 0; |
||||||
|
|
||||||
return (bool)(*cell & (1 << xd)); |
return (*cell >> xd) & 1; |
||||||
} |
} |
||||||
|
|
||||||
|
|
||||||
void dmtx_toggle(DotMatrix_Cfg* dmtx, int32_t x, int32_t y) |
void dmtx_toggle(DotMatrix_Cfg* disp, int32_t x, int32_t y) |
||||||
{ |
{ |
||||||
uint8_t xd; |
uint8_t xd; |
||||||
uint8_t *cell = cell_ptr(dmtx, x, y, &xd); |
uint8_t *cell = cell_ptr(disp, x, y, &xd); |
||||||
if (cell == NULL) return; |
if (cell == NULL) return; |
||||||
|
|
||||||
*cell ^= 1 << xd; |
*cell ^= 1 << xd; |
||||||
} |
} |
||||||
|
|
||||||
|
|
||||||
void dmtx_set(DotMatrix_Cfg* dmtx, int32_t x, int32_t y, bool bit) |
void dmtx_set(DotMatrix_Cfg* disp, int32_t x, int32_t y, bool bit) |
||||||
{ |
{ |
||||||
uint8_t xd; |
uint8_t xd; |
||||||
uint8_t *cell = cell_ptr(dmtx, x, y, &xd); |
uint8_t *cell = cell_ptr(disp, x, y, &xd); |
||||||
if (cell == NULL) return; |
if (cell == NULL) return; |
||||||
|
|
||||||
if (bit) { |
if (bit) { |
||||||
*cell |= bit << xd; |
*cell |= 1 << xd; |
||||||
} else { |
} else { |
||||||
*cell &= ~(bit << xd); |
*cell &= ~(1 << xd); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void dmtx_set_block(DotMatrix_Cfg* disp, int32_t startX, int32_t startY, uint32_t *data_rows, uint32_t width, uint16_t height) |
||||||
|
{ |
||||||
|
for (uint32_t y = 0; y < height; y++) { |
||||||
|
uint32_t row = data_rows[y]; |
||||||
|
|
||||||
|
for (uint32_t x = 0; x < width; x++) { |
||||||
|
int xx = startX + (int)x; |
||||||
|
int yy = startY + (int)y; |
||||||
|
bool val = (row >> (width - x - 1)) & 1; |
||||||
|
|
||||||
|
dmtx_set(disp, xx, yy, val); |
||||||
|
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,108 @@ |
|||||||
|
#ifndef FONT_H |
||||||
|
#define FONT_H |
||||||
|
|
||||||
|
#define FONT_MIN ' ' |
||||||
|
#define FONT_MAX '~' |
||||||
|
|
||||||
|
#define FONT_WIDTH 6 |
||||||
|
|
||||||
|
const unsigned char font[96][6] = { |
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00}, //
|
||||||
|
{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
|
||||||
|
{0x06,0x00,0x06,0x00,0x00,0x00}, // "
|
||||||
|
{0x28,0x7c,0x28,0x7c,0x28,0x00}, // #
|
||||||
|
{0x5c,0x54,0xfe,0x54,0x74,0x00}, // $
|
||||||
|
{0x44,0x20,0x10,0x08,0x44,0x00}, // %
|
||||||
|
{0x28,0x54,0x54,0x20,0x50,0x00}, // &
|
||||||
|
{0x06,0x00,0x00,0x00,0x00,0x00}, // '
|
||||||
|
{0x38,0x44,0x00,0x00,0x00,0x00}, // (
|
||||||
|
{0x44,0x38,0x00,0x00,0x00,0x00}, // )
|
||||||
|
{0x02,0x07,0x02,0x00,0x00,0x00}, // *
|
||||||
|
{0x10,0x10,0x7c,0x10,0x10,0x00}, // +
|
||||||
|
{0xc0,0x00,0x00,0x00,0x00,0x00}, // ,
|
||||||
|
{0x10,0x10,0x10,0x10,0x10,0x00}, // -
|
||||||
|
{0x40,0x00,0x00,0x00,0x00,0x00}, // .
|
||||||
|
{0x60,0x10,0x0c,0x00,0x00,0x00}, // /
|
||||||
|
{0x7c,0x64,0x54,0x4c,0x7c,0x00}, // 0
|
||||||
|
{0x48,0x7c,0x40,0x00,0x00,0x00}, // 1
|
||||||
|
{0x64,0x54,0x54,0x54,0x48,0x00}, // 2
|
||||||
|
{0x44,0x54,0x54,0x54,0x6c,0x00}, // 3
|
||||||
|
{0x3c,0x20,0x70,0x20,0x20,0x00}, // 4
|
||||||
|
{0x5c,0x54,0x54,0x54,0x24,0x00}, // 5
|
||||||
|
{0x7c,0x54,0x54,0x54,0x74,0x00}, // 6
|
||||||
|
{0x04,0x04,0x64,0x14,0x0c,0x00}, // 7
|
||||||
|
{0x7c,0x54,0x54,0x54,0x7c,0x00}, // 8
|
||||||
|
{0x5c,0x54,0x54,0x54,0x7c,0x00}, // 9
|
||||||
|
{0x44,0x00,0x00,0x00,0x00,0x00}, // :
|
||||||
|
{0xc4,0x00,0x00,0x00,0x00,0x00}, // ;
|
||||||
|
{0x10,0x28,0x44,0x00,0x00,0x00}, // <
|
||||||
|
{0x28,0x28,0x28,0x28,0x28,0x00}, // =
|
||||||
|
{0x44,0x28,0x10,0x00,0x00,0x00}, // >
|
||||||
|
{0x08,0x04,0x54,0x08,0x00,0x00}, // ?
|
||||||
|
{0x7c,0x44,0x54,0x54,0x5c,0x00}, // @
|
||||||
|
{0x7c,0x24,0x24,0x24,0x7c,0x00}, // A
|
||||||
|
{0x7c,0x54,0x54,0x54,0x6c,0x00}, // B
|
||||||
|
{0x7c,0x44,0x44,0x44,0x44,0x00}, // C
|
||||||
|
{0x7c,0x44,0x44,0x44,0x38,0x00}, // D
|
||||||
|
{0x7c,0x54,0x54,0x54,0x44,0x00}, // E
|
||||||
|
{0x7c,0x14,0x14,0x14,0x04,0x00}, // F
|
||||||
|
{0x7c,0x44,0x44,0x54,0x74,0x00}, // G
|
||||||
|
{0x7c,0x10,0x10,0x10,0x7c,0x00}, // H
|
||||||
|
{0x44,0x44,0x7c,0x44,0x44,0x00}, // I
|
||||||
|
{0x60,0x40,0x40,0x44,0x7c,0x00}, // J
|
||||||
|
{0x7c,0x10,0x10,0x28,0x44,0x00}, // K
|
||||||
|
{0x7c,0x40,0x40,0x40,0x40,0x00}, // L
|
||||||
|
{0x7c,0x08,0x10,0x08,0x7c,0x00}, // M
|
||||||
|
{0x7c,0x08,0x10,0x20,0x7c,0x00}, // N
|
||||||
|
{0x38,0x44,0x44,0x44,0x38,0x00}, // O
|
||||||
|
{0x7c,0x14,0x14,0x14,0x08,0x00}, // P
|
||||||
|
{0x3c,0x24,0x64,0x24,0x3c,0x00}, // Q
|
||||||
|
{0x7c,0x14,0x14,0x14,0x68,0x00}, // R
|
||||||
|
{0x5c,0x54,0x54,0x54,0x74,0x00}, // S
|
||||||
|
{0x04,0x04,0x7c,0x04,0x04,0x00}, // T
|
||||||
|
{0x7c,0x40,0x40,0x40,0x7c,0x00}, // U
|
||||||
|
{0x0c,0x30,0x40,0x30,0x0c,0x00}, // V
|
||||||
|
{0x3c,0x40,0x30,0x40,0x3c,0x00}, // W
|
||||||
|
{0x44,0x28,0x10,0x28,0x44,0x00}, // X
|
||||||
|
{0x0c,0x10,0x60,0x10,0x0c,0x00}, // Y
|
||||||
|
{0x44,0x64,0x54,0x4c,0x44,0x00}, // Z
|
||||||
|
{0x7c,0x44,0x00,0x00,0x00,0x00}, // [
|
||||||
|
{0x0c,0x10,0x60,0x00,0x00,0x00}, // "\"
|
||||||
|
{0x44,0x7c,0x00,0x00,0x00,0x00}, // ]
|
||||||
|
{0x00,0x01,0x00,0x01,0x00,0x00}, // ^
|
||||||
|
{0x40,0x40,0x40,0x40,0x40,0x40}, // _
|
||||||
|
{0x00,0x01,0x00,0x00,0x00,0x00}, // `
|
||||||
|
{0x7c,0x24,0x24,0x24,0x7c,0x00}, // a
|
||||||
|
{0x7c,0x54,0x54,0x54,0x6c,0x00}, // b
|
||||||
|
{0x7c,0x44,0x44,0x44,0x44,0x00}, // c
|
||||||
|
{0x7c,0x44,0x44,0x44,0x38,0x00}, // d
|
||||||
|
{0x7c,0x54,0x54,0x54,0x44,0x00}, // e
|
||||||
|
{0x7c,0x14,0x14,0x14,0x04,0x00}, // f
|
||||||
|
{0x7c,0x44,0x44,0x54,0x74,0x00}, // g
|
||||||
|
{0x7c,0x10,0x10,0x10,0x7c,0x00}, // h
|
||||||
|
{0x44,0x44,0x7c,0x44,0x44,0x00}, // i
|
||||||
|
{0x60,0x40,0x40,0x44,0x7c,0x00}, // j
|
||||||
|
{0x7c,0x10,0x10,0x28,0x44,0x00}, // k
|
||||||
|
{0x7c,0x40,0x40,0x40,0x40,0x00}, // l
|
||||||
|
{0x7c,0x08,0x10,0x08,0x7c,0x00}, // m
|
||||||
|
{0x7c,0x08,0x10,0x20,0x7c,0x00}, // n
|
||||||
|
{0x38,0x44,0x44,0x44,0x38,0x00}, // o
|
||||||
|
{0x7c,0x14,0x14,0x14,0x08,0x00}, // p
|
||||||
|
{0x3c,0x24,0x64,0x24,0x3c,0x00}, // q
|
||||||
|
{0x7c,0x14,0x14,0x14,0x68,0x00}, // r
|
||||||
|
{0x5c,0x54,0x54,0x54,0x74,0x00}, // s
|
||||||
|
{0x04,0x04,0x7c,0x04,0x04,0x00}, // t
|
||||||
|
{0x7c,0x40,0x40,0x40,0x7c,0x00}, // u
|
||||||
|
{0x0c,0x30,0x40,0x30,0x0c,0x00}, // v
|
||||||
|
{0x3c,0x40,0x30,0x40,0x3c,0x00}, // w
|
||||||
|
{0x44,0x28,0x10,0x28,0x44,0x00}, // x
|
||||||
|
{0x0c,0x10,0x60,0x10,0x0c,0x00}, // y
|
||||||
|
{0x44,0x64,0x54,0x4c,0x44,0x00}, // z
|
||||||
|
{0x10,0x7c,0x44,0x00,0x00,0x00}, // {
|
||||||
|
{0x6c,0x00,0x00,0x00,0x00,0x00}, // |
|
||||||
|
{0x44,0x7c,0x10,0x00,0x00,0x00}, // }
|
||||||
|
{0x02,0x01,0x02,0x01,0x00,0x00}, // ~
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00} |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // FONT_H
|
@ -0,0 +1,175 @@ |
|||||||
|
#include "mode_audio.h" |
||||||
|
#include <arm_math.h> |
||||||
|
#include "bus/event_queue.h" |
||||||
|
#include "utils/timebase.h" |
||||||
|
|
||||||
|
#include "dotmatrix.h" |
||||||
|
|
||||||
|
static bool audio_mode_active = true; |
||||||
|
|
||||||
|
static volatile bool capture_pending = false; |
||||||
|
//static volatile bool print_next_fft = false;
|
||||||
|
|
||||||
|
#define SAMP_BUF_LEN 256 |
||||||
|
|
||||||
|
union samp_buf_union { |
||||||
|
uint32_t uints[SAMP_BUF_LEN]; |
||||||
|
float floats[SAMP_BUF_LEN]; |
||||||
|
uint8_t as_bytes[SAMP_BUF_LEN * sizeof(uint32_t)]; |
||||||
|
}; |
||||||
|
|
||||||
|
// sample buffers (static - invalidated when sampling starts anew).
|
||||||
|
static union samp_buf_union samp_buf; |
||||||
|
|
||||||
|
static task_pid_t capture_task_id; |
||||||
|
|
||||||
|
|
||||||
|
// prototypes
|
||||||
|
static void audio_capture_done(void* unused); |
||||||
|
static void capture_audio(void *unused); |
||||||
|
|
||||||
|
|
||||||
|
static void boot_animation(void) |
||||||
|
{ |
||||||
|
dmtx_clear(dmtx); |
||||||
|
|
||||||
|
// Boot animation (for FFT)
|
||||||
|
for (int i = 0; i < SCREEN_W; i++) { |
||||||
|
dmtx_set(dmtx, i, 0, 1); |
||||||
|
dmtx_show(dmtx); |
||||||
|
delay_ms(20); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Init audio mode */ |
||||||
|
void mode_audio_init(void) |
||||||
|
{ |
||||||
|
capture_task_id = add_periodic_task(capture_audio, NULL, 10, false); |
||||||
|
enable_periodic_task(capture_task_id, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** Start audio mode */ |
||||||
|
void mode_audio_start(void) |
||||||
|
{ |
||||||
|
boot_animation(); |
||||||
|
|
||||||
|
audio_mode_active = true; |
||||||
|
enable_periodic_task(capture_task_id, true); |
||||||
|
} |
||||||
|
|
||||||
|
/** Stop audio mode */ |
||||||
|
void mode_audio_stop(void) |
||||||
|
{ |
||||||
|
audio_mode_active = false; |
||||||
|
enable_periodic_task(capture_task_id, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** Start DMA capture */ |
||||||
|
static void start_adc_dma(uint32_t *memory, uint32_t count) |
||||||
|
{ |
||||||
|
ADC_Cmd(ADC1, DISABLE); |
||||||
|
DMA_DeInit(DMA1_Channel1); |
||||||
|
DMA_InitTypeDef dma_cnf; |
||||||
|
dma_cnf.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; |
||||||
|
dma_cnf.DMA_MemoryBaseAddr = (uint32_t)memory; |
||||||
|
dma_cnf.DMA_DIR = DMA_DIR_PeripheralSRC; |
||||||
|
dma_cnf.DMA_BufferSize = count; |
||||||
|
dma_cnf.DMA_PeripheralInc = DMA_PeripheralInc_Disable; |
||||||
|
dma_cnf.DMA_MemoryInc = DMA_MemoryInc_Enable; |
||||||
|
dma_cnf.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; |
||||||
|
dma_cnf.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; |
||||||
|
dma_cnf.DMA_Mode = DMA_Mode_Normal; |
||||||
|
dma_cnf.DMA_Priority = DMA_Priority_Low; |
||||||
|
dma_cnf.DMA_M2M = DMA_M2M_Disable; |
||||||
|
DMA_Init(DMA1_Channel1, &dma_cnf); |
||||||
|
DMA_ITConfig(DMA1_Channel1, DMA1_IT_TC1, ENABLE); |
||||||
|
|
||||||
|
ADC_Cmd(ADC1, ENABLE); |
||||||
|
ADC_DMACmd(ADC1, ENABLE); |
||||||
|
DMA_Cmd(DMA1_Channel1, ENABLE); |
||||||
|
TIM_Cmd(TIM3, ENABLE); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** IRQ */ |
||||||
|
void DMA1_Channel1_IRQHandler(void) |
||||||
|
{ |
||||||
|
DMA_ClearITPendingBit(DMA1_IT_TC1); |
||||||
|
DMA_ClearITPendingBit(DMA1_IT_TE1); |
||||||
|
|
||||||
|
DMA_DeInit(DMA1_Channel1); |
||||||
|
TIM_Cmd(TIM3, DISABLE); |
||||||
|
ADC_DMACmd(ADC1, DISABLE); |
||||||
|
|
||||||
|
tq_post(audio_capture_done, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Capture done callback */ |
||||||
|
static void audio_capture_done(void* unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
|
||||||
|
if (! audio_mode_active) { |
||||||
|
capture_pending = false; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const int samp_count = SAMP_BUF_LEN / 2; |
||||||
|
const int bin_count = SAMP_BUF_LEN / 4; |
||||||
|
|
||||||
|
float *bins = samp_buf.floats; |
||||||
|
|
||||||
|
// Convert to floats
|
||||||
|
for (int i = 0; i < samp_count; i++) { |
||||||
|
samp_buf.floats[i] = (float)samp_buf.uints[i]; |
||||||
|
} |
||||||
|
|
||||||
|
// normalize
|
||||||
|
float mean; |
||||||
|
arm_mean_f32(samp_buf.floats, samp_count, &mean); |
||||||
|
|
||||||
|
for (int i = 0; i < samp_count; i++) { |
||||||
|
samp_buf.floats[i] -= mean; |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = samp_count - 1; i >= 0; i--) { |
||||||
|
bins[i * 2 + 1] = 0; // imaginary
|
||||||
|
bins[i * 2] = samp_buf.floats[i]; // real
|
||||||
|
} |
||||||
|
|
||||||
|
const arm_cfft_instance_f32 *S; |
||||||
|
S = &arm_cfft_sR_f32_len128; |
||||||
|
|
||||||
|
arm_cfft_f32(S, bins, 0, true); // bit reversed FFT
|
||||||
|
arm_cmplx_mag_f32(bins, bins, bin_count); // get magnitude (extract real values)
|
||||||
|
|
||||||
|
// normalize
|
||||||
|
dmtx_clear(dmtx); |
||||||
|
|
||||||
|
float factor = (1.0f / bin_count) * 0.25f; |
||||||
|
for (int i = 0; i < MIN(bin_count, SCREEN_W) + 1; i++) { // +1 because bin 0 is always 0
|
||||||
|
bins[i] *= factor; |
||||||
|
|
||||||
|
for (int j = 0; j < 1 + floorf(bins[i]); j++) { |
||||||
|
dmtx_set(dmtx, i - 1, j, 1); // hide zero 0th bin
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dmtx_show(dmtx); |
||||||
|
|
||||||
|
capture_pending = false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void capture_audio(void *unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
if (capture_pending) return; |
||||||
|
if (! audio_mode_active) return; |
||||||
|
|
||||||
|
capture_pending = true; |
||||||
|
|
||||||
|
start_adc_dma(samp_buf.uints, SAMP_BUF_LEN / 2); |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
#ifndef MODE_AUDIO_H |
||||||
|
#define MODE_AUDIO_H |
||||||
|
|
||||||
|
#include "main.h" |
||||||
|
|
||||||
|
void mode_audio_init(void); |
||||||
|
void mode_audio_start(void); |
||||||
|
void mode_audio_stop(void); |
||||||
|
|
||||||
|
#endif // MODE_AUDIO_H
|
@ -0,0 +1,465 @@ |
|||||||
|
#include "mode_life.h" |
||||||
|
#include "utils/timebase.h" |
||||||
|
#include "com/debug.h" |
||||||
|
|
||||||
|
#include "dotmatrix.h" |
||||||
|
|
||||||
|
static bool life_enabled = false; |
||||||
|
|
||||||
|
static void show_board(bool do_show); |
||||||
|
static void apply_paint(void); |
||||||
|
|
||||||
|
#define BOARD_W SCREEN_W |
||||||
|
#define BOARD_H SCREEN_H |
||||||
|
|
||||||
|
#define SIZEOF_BOARD (BOARD_H*sizeof(uint32_t)) |
||||||
|
|
||||||
|
// 32-bit for easy blit
|
||||||
|
static bool is_orig_board = true; // marks that currently displayed board is the orig board to reset back to.
|
||||||
|
static uint32_t board_orig[BOARD_H]; |
||||||
|
|
||||||
|
static uint32_t board_prev[BOARD_H]; |
||||||
|
static uint32_t board[BOARD_H]; |
||||||
|
|
||||||
|
static task_pid_t task_gametick; // game update tick
|
||||||
|
static task_pid_t task_movetick; // repeated move
|
||||||
|
static task_pid_t task_start_moveticks; // scheduled task to start moveticks after a delay
|
||||||
|
|
||||||
|
static task_pid_t task_movepress; // move on press (delayed to allow for diagonals)
|
||||||
|
|
||||||
|
static task_pid_t task_blink; // periodic blink task
|
||||||
|
static task_pid_t task_blink2; // scheduled 'unblink' task
|
||||||
|
|
||||||
|
static ms_time_t step_time = 200; // game step time
|
||||||
|
|
||||||
|
#define BLINKLEN 350 // blink on time (total)
|
||||||
|
#define BLINKLEN_ONE 300 // blink on time for '1'
|
||||||
|
#define BLINKLEN_ZERO 10 // blink on time for '0'
|
||||||
|
|
||||||
|
#define MOVE_TIME 80 // mouse move
|
||||||
|
#define MOVE_START_TIME 600 |
||||||
|
|
||||||
|
#define WRAPPING 0 |
||||||
|
|
||||||
|
static int cursorX = BOARD_W / 2 - 1; |
||||||
|
static int cursorY = BOARD_H / 2 - 1; |
||||||
|
|
||||||
|
static bool running = false; |
||||||
|
|
||||||
|
static bool painting_1 = false; |
||||||
|
static bool painting_0 = false; |
||||||
|
|
||||||
|
static bool modA_down = false; |
||||||
|
static bool modB_down = false; |
||||||
|
|
||||||
|
// X+,X-,Y+,Y-
|
||||||
|
typedef enum { |
||||||
|
DIR_NONE = 0b0000, |
||||||
|
// straight
|
||||||
|
DIR_N = 0b0010, |
||||||
|
DIR_S = 0b0001, |
||||||
|
DIR_E = 0b1000, |
||||||
|
DIR_W = 0b0100, |
||||||
|
// diagonals
|
||||||
|
DIR_NE = DIR_N | DIR_E, |
||||||
|
DIR_NW = DIR_N | DIR_W, |
||||||
|
DIR_SE = DIR_S | DIR_E, |
||||||
|
DIR_SW = DIR_S | DIR_W, |
||||||
|
} MoveDir; |
||||||
|
|
||||||
|
static MoveDir active_dir = DIR_NONE; |
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
static void board_set(int x, int y, bool color) |
||||||
|
{ |
||||||
|
if (x < 0 || y < 0 || x >= BOARD_W || y >= BOARD_H) return; |
||||||
|
|
||||||
|
uint32_t mask = 1 << (BOARD_W - x - 1); |
||||||
|
|
||||||
|
if (color) { |
||||||
|
board[y] |= mask; |
||||||
|
} else { |
||||||
|
board[y] &= ~mask; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static bool board_get(uint32_t *brd, int x, int y) |
||||||
|
{ |
||||||
|
#if WRAPPING |
||||||
|
while (x < 0) x += BOARD_W; |
||||||
|
while (y < 0) y += BOARD_H; |
||||||
|
while (x >= BOARD_W) x -= BOARD_W; |
||||||
|
while (y >= BOARD_H) y -= BOARD_H; |
||||||
|
#else |
||||||
|
if (x < 0 || y < 0 || x >= BOARD_W || y >= BOARD_H) return 0; |
||||||
|
#endif |
||||||
|
|
||||||
|
return (brd[y] >> (BOARD_W - x - 1)) & 1; |
||||||
|
} |
||||||
|
|
||||||
|
static void move_cursor(void) |
||||||
|
{ |
||||||
|
if (active_dir & DIR_N) { |
||||||
|
cursorY++; |
||||||
|
} else if (active_dir & DIR_S) { |
||||||
|
cursorY--; |
||||||
|
} |
||||||
|
|
||||||
|
if (active_dir & DIR_E) { |
||||||
|
cursorX++; |
||||||
|
} else if (active_dir & DIR_W) { |
||||||
|
cursorX--; |
||||||
|
} |
||||||
|
|
||||||
|
// clamp
|
||||||
|
if (cursorX >= BOARD_W) cursorX = BOARD_W - 1; |
||||||
|
if (cursorX < 0) cursorX = 0; |
||||||
|
|
||||||
|
if (cursorY >= BOARD_H) cursorY = BOARD_H - 1; |
||||||
|
if (cursorY < 0) cursorY = 0; |
||||||
|
|
||||||
|
// apply paint if active
|
||||||
|
apply_paint(); |
||||||
|
|
||||||
|
show_board(true); |
||||||
|
dmtx_set(dmtx, cursorX, cursorY, 1); |
||||||
|
abort_scheduled_task(task_blink2); // abort unblink task FIXME ???
|
||||||
|
} |
||||||
|
|
||||||
|
static void apply_paint(void) |
||||||
|
{ |
||||||
|
if (painting_0) { |
||||||
|
board_set(cursorX, cursorY, 0); |
||||||
|
} |
||||||
|
|
||||||
|
if (painting_1) { |
||||||
|
board_set(cursorX, cursorY, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** start periodic move */ |
||||||
|
static void start_moveticks_cb(void *unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
if (!life_enabled) return; |
||||||
|
|
||||||
|
task_start_moveticks = 0; |
||||||
|
enable_periodic_task(task_movetick, true); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Perform one game step */ |
||||||
|
static void gametick_cb(void *unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
if (!life_enabled) return; |
||||||
|
|
||||||
|
dbg("Game tick!"); |
||||||
|
|
||||||
|
memcpy(board_prev, board, SIZEOF_BOARD); |
||||||
|
for (int i = 0; i < BOARD_W; i++) { |
||||||
|
for (int j = 0; j < BOARD_H; j++) { |
||||||
|
int count = 0; |
||||||
|
|
||||||
|
// Above
|
||||||
|
count += board_get(board_prev, i - 1, j - 1); |
||||||
|
count += board_get(board_prev, i, j - 1); |
||||||
|
count += board_get(board_prev, i + 1, j - 1); |
||||||
|
|
||||||
|
// Sides
|
||||||
|
count += board_get(board_prev, i - 1, j); |
||||||
|
count += board_get(board_prev, i + 1, j); |
||||||
|
|
||||||
|
// Below
|
||||||
|
count += board_get(board_prev, i - 1, j + 1); |
||||||
|
count += board_get(board_prev, i, j + 1); |
||||||
|
count += board_get(board_prev, i + 1, j + 1); |
||||||
|
|
||||||
|
bool at = board_get(board_prev, i, j); |
||||||
|
|
||||||
|
//board_set(i, j, count == 2 || count == 3);
|
||||||
|
|
||||||
|
if (at) { |
||||||
|
// live cell
|
||||||
|
board_set(i, j, count == 2 || count == 3); |
||||||
|
} else { |
||||||
|
board_set(i, j, count == 3); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
show_board(true); |
||||||
|
} |
||||||
|
|
||||||
|
/** Move cursor one step in active direction */ |
||||||
|
static void movetick_cb(void *unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
if (!life_enabled) return; |
||||||
|
|
||||||
|
move_cursor(); |
||||||
|
} |
||||||
|
|
||||||
|
static void blink_cb(void *arg) |
||||||
|
{ |
||||||
|
if (!life_enabled) return; // pending leftover...
|
||||||
|
|
||||||
|
uint32_t which = (uint32_t)arg; |
||||||
|
|
||||||
|
if (which == 0) { |
||||||
|
// first
|
||||||
|
show_board(false); |
||||||
|
|
||||||
|
bool at = board_get(board, cursorX, cursorY); |
||||||
|
dmtx_set(dmtx, cursorX, cursorY, 1); |
||||||
|
dmtx_show(dmtx); |
||||||
|
|
||||||
|
// schedule unblink
|
||||||
|
schedule_task(blink_cb, (void*)1, at ? BLINKLEN_ONE : BLINKLEN_ZERO, true); |
||||||
|
|
||||||
|
} else { |
||||||
|
show_board(false); |
||||||
|
dmtx_set(dmtx, cursorX, cursorY, 0); |
||||||
|
dmtx_show(dmtx); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
static void show_board(bool do_show) |
||||||
|
{ |
||||||
|
dmtx_set_block(dmtx, 0, 0, board, 16, 16); |
||||||
|
if (do_show) dmtx_show(dmtx); |
||||||
|
} |
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
void mode_life_init(void) |
||||||
|
{ |
||||||
|
// prepare tasks
|
||||||
|
task_gametick = add_periodic_task(gametick_cb, NULL, step_time, true); |
||||||
|
task_movetick = add_periodic_task(movetick_cb, NULL, MOVE_TIME, true); |
||||||
|
task_blink = add_periodic_task(blink_cb, 0, BLINKLEN, true); |
||||||
|
|
||||||
|
// stop all - we may not be starting this mode just yet
|
||||||
|
enable_periodic_task(task_gametick, false); |
||||||
|
enable_periodic_task(task_movetick, false); |
||||||
|
enable_periodic_task(task_blink, false); |
||||||
|
} |
||||||
|
|
||||||
|
void mode_life_start(void) |
||||||
|
{ |
||||||
|
life_enabled = true; |
||||||
|
|
||||||
|
enable_periodic_task(task_blink, true); |
||||||
|
|
||||||
|
// if (running) {
|
||||||
|
// enable_periodic_task(task_gametick, true);
|
||||||
|
// } else {
|
||||||
|
// enable_periodic_task(task_blink, true);
|
||||||
|
// }
|
||||||
|
} |
||||||
|
|
||||||
|
void mode_life_stop(void) |
||||||
|
{ |
||||||
|
enable_periodic_task(task_gametick, false); |
||||||
|
enable_periodic_task(task_movetick, false); |
||||||
|
enable_periodic_task(task_blink, false); |
||||||
|
|
||||||
|
abort_scheduled_task(task_blink2); |
||||||
|
abort_scheduled_task(task_movepress); |
||||||
|
abort_scheduled_task(task_start_moveticks); |
||||||
|
|
||||||
|
painting_1 = painting_0 = false; |
||||||
|
modA_down = modB_down = false; |
||||||
|
running = false; // stop
|
||||||
|
|
||||||
|
life_enabled = false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static void toggle_game(bool run) |
||||||
|
{ |
||||||
|
if (run) { |
||||||
|
running = true; |
||||||
|
enable_periodic_task(task_movetick, false); |
||||||
|
enable_periodic_task(task_blink, false); |
||||||
|
abort_scheduled_task(task_movepress); |
||||||
|
abort_scheduled_task(task_start_moveticks); |
||||||
|
|
||||||
|
// Go!!
|
||||||
|
enable_periodic_task(task_gametick, true); |
||||||
|
} else { |
||||||
|
running = false; |
||||||
|
enable_periodic_task(task_gametick, false); |
||||||
|
enable_periodic_task(task_blink, true); |
||||||
|
} |
||||||
|
|
||||||
|
show_board(true); |
||||||
|
} |
||||||
|
|
||||||
|
/** do move (had ample time to press both arrows for diagonal) */ |
||||||
|
static void movepress_cb(void *unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
if (!life_enabled) return; |
||||||
|
|
||||||
|
move_cursor(); |
||||||
|
task_movepress = 0; |
||||||
|
|
||||||
|
task_start_moveticks = schedule_task(start_moveticks_cb, NULL, MOVE_START_TIME, true); |
||||||
|
} |
||||||
|
|
||||||
|
void mode_life_btn(char key) |
||||||
|
{ |
||||||
|
// Common for run / stop mode
|
||||||
|
switch (key) { |
||||||
|
case 'A': modA_down = true; break; |
||||||
|
case 'a': modA_down = false; break; |
||||||
|
case 'B': modB_down = true; break; |
||||||
|
case 'b': modB_down = false; break; |
||||||
|
|
||||||
|
case 'K': |
||||||
|
// Reset btn
|
||||||
|
if (modB_down) { |
||||||
|
// total reset
|
||||||
|
dbg("Reset to blank"); |
||||||
|
memset(board_orig, 0, SIZEOF_BOARD); |
||||||
|
memset(board, 0, SIZEOF_BOARD); |
||||||
|
|
||||||
|
toggle_game(false); |
||||||
|
is_orig_board = true; |
||||||
|
} else { |
||||||
|
// reset to orig
|
||||||
|
dbg("Reset to original"); |
||||||
|
memcpy(board, board_orig, SIZEOF_BOARD); |
||||||
|
toggle_game(false); |
||||||
|
is_orig_board = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if (running) { |
||||||
|
|
||||||
|
switch (key) { |
||||||
|
case 'Y': // slower
|
||||||
|
if (step_time < 1000) { |
||||||
|
step_time += 50; |
||||||
|
set_periodic_task_interval(task_gametick, step_time); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case 'X': // faster
|
||||||
|
if (step_time > 50) { |
||||||
|
step_time -= 50; |
||||||
|
set_periodic_task_interval(task_gametick, step_time); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case 'J': // stop
|
||||||
|
dbg("STOP game!"); |
||||||
|
toggle_game(false); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
bool want_move = false; |
||||||
|
bool clear_moveticks = false; |
||||||
|
bool want_apply_paint = false; |
||||||
|
|
||||||
|
// Groups
|
||||||
|
switch (key) { |
||||||
|
case 'U': |
||||||
|
case 'D': |
||||||
|
case 'R': |
||||||
|
case 'L': |
||||||
|
want_move = true; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'u': |
||||||
|
case 'd': |
||||||
|
case 'r': |
||||||
|
case 'l': |
||||||
|
clear_moveticks = true; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'Y': // AA
|
||||||
|
case 'X': // BB
|
||||||
|
want_apply_paint = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Individual effects
|
||||||
|
switch (key) { |
||||||
|
// --- ARROW DOWN ---
|
||||||
|
|
||||||
|
case 'U': |
||||||
|
active_dir &= ~DIR_S; |
||||||
|
active_dir |= DIR_N; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'D': |
||||||
|
active_dir &= ~DIR_N; |
||||||
|
active_dir |= DIR_S; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'R': |
||||||
|
active_dir &= ~DIR_W; |
||||||
|
active_dir |= DIR_E; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'L': |
||||||
|
active_dir &= ~DIR_E; |
||||||
|
active_dir |= DIR_W; |
||||||
|
break; |
||||||
|
|
||||||
|
// --- ARROW UP ---
|
||||||
|
|
||||||
|
case 'u': active_dir &= ~DIR_N; break; |
||||||
|
case 'd': active_dir &= ~DIR_S; break; |
||||||
|
case 'r': active_dir &= ~DIR_E; break; |
||||||
|
case 'l': active_dir &= ~DIR_W; break; |
||||||
|
|
||||||
|
// --- Paint BTN ---
|
||||||
|
|
||||||
|
case 'X': painting_1 = true; break; |
||||||
|
case 'x': painting_1 = false; break; |
||||||
|
case 'Y': painting_0 = true; break; |
||||||
|
case 'y': painting_0 = false; break; |
||||||
|
|
||||||
|
// --- Control ---
|
||||||
|
|
||||||
|
case 'J': |
||||||
|
dbg("Start game!"); |
||||||
|
|
||||||
|
// starting
|
||||||
|
if (is_orig_board) { |
||||||
|
// save
|
||||||
|
memcpy(board_orig, board, SIZEOF_BOARD); |
||||||
|
} |
||||||
|
|
||||||
|
is_orig_board = false; |
||||||
|
|
||||||
|
toggle_game(true); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (clear_moveticks) { |
||||||
|
enable_periodic_task(task_movetick, false); |
||||||
|
abort_scheduled_task(task_start_moveticks); |
||||||
|
} |
||||||
|
|
||||||
|
if (want_apply_paint) { |
||||||
|
apply_paint(); |
||||||
|
} |
||||||
|
|
||||||
|
if (want_move) { |
||||||
|
if (task_movepress == 0) { |
||||||
|
task_movepress = schedule_task(movepress_cb, NULL, 10, true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
#ifndef MODE_LIFE_H |
||||||
|
#define MODE_LIFE_H |
||||||
|
|
||||||
|
#include "main.h" |
||||||
|
|
||||||
|
void mode_life_init(void); |
||||||
|
void mode_life_start(void); |
||||||
|
void mode_life_stop(void); |
||||||
|
void mode_life_btn(char key); |
||||||
|
|
||||||
|
#endif // MODE_LIFE_H
|
@ -0,0 +1,560 @@ |
|||||||
|
#include "mode_snake.h" |
||||||
|
#include "com/debug.h" |
||||||
|
#include "dotmatrix.h" |
||||||
|
#include "utils/timebase.h" |
||||||
|
|
||||||
|
#include "scrolltext.h" |
||||||
|
|
||||||
|
#define BOARD_W SCREEN_W |
||||||
|
#define BOARD_H SCREEN_H |
||||||
|
|
||||||
|
static bool snake_active = false; |
||||||
|
|
||||||
|
/** Snake movement direction */ |
||||||
|
typedef enum { |
||||||
|
NORTH, |
||||||
|
SOUTH, |
||||||
|
EAST, |
||||||
|
WEST |
||||||
|
} Direction; |
||||||
|
|
||||||
|
typedef enum { |
||||||
|
CELL_EMPTY, |
||||||
|
CELL_BODY, |
||||||
|
CELL_FOOD, |
||||||
|
CELL_WALL |
||||||
|
} CellType; |
||||||
|
|
||||||
|
/** Board cell */ |
||||||
|
typedef struct __attribute__((packed)) |
||||||
|
{ |
||||||
|
CellType type : 2; // Cell type
|
||||||
|
Direction dir : 2; // direction the head moved to from this tile, if body = 1
|
||||||
|
} Cell; |
||||||
|
|
||||||
|
/** Wall tile, invariant, used for out-of-screen coords */ |
||||||
|
static Cell WALL_TILE = {CELL_WALL, 0}; |
||||||
|
|
||||||
|
/** Game board */ |
||||||
|
static Cell board[BOARD_H][BOARD_W]; |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
int x; |
||||||
|
int y; |
||||||
|
} Coord; |
||||||
|
|
||||||
|
/** Snake coords */ |
||||||
|
static Coord head; |
||||||
|
static Coord tail; |
||||||
|
static Direction head_dir; |
||||||
|
|
||||||
|
/** Limit dir changes to 1 per move */ |
||||||
|
static bool changed_dir_this_tick = false; |
||||||
|
|
||||||
|
/** blinking to visually change 'color' */ |
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) |
||||||
|
{ |
||||||
|
bool pwm_bit : 1; |
||||||
|
bool offtask_pending : 1; |
||||||
|
task_pid_t on_task; // periodic
|
||||||
|
task_pid_t off_task; // scheduled
|
||||||
|
ms_time_t interval_ms; |
||||||
|
ms_time_t offtime_ms; |
||||||
|
} snake_pwm; |
||||||
|
|
||||||
|
|
||||||
|
static bool alive = false; |
||||||
|
static bool moving = false; |
||||||
|
static task_pid_t task_snake_move; |
||||||
|
|
||||||
|
|
||||||
|
static snake_pwm food_pwm = { |
||||||
|
.interval_ms = 400, |
||||||
|
.offtime_ms = 330 |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
static snake_pwm head_pwm = { |
||||||
|
.interval_ms = 100, |
||||||
|
.offtime_ms = 90 |
||||||
|
}; |
||||||
|
|
||||||
|
#define MIN_MOVE_INTERVAL 64 |
||||||
|
#define POINTS_TO_LEVEL_UP 5 |
||||||
|
#define PIECES_REMOVED_ON_LEVEL_UP 4 |
||||||
|
|
||||||
|
#define PREDEFINED_LEVELS 10 |
||||||
|
static const ms_time_t speeds_for_levels[PREDEFINED_LEVELS] = { |
||||||
|
260, 200, 160, 120, 95, 80, 65, 55, 50, 45 |
||||||
|
}; |
||||||
|
|
||||||
|
static ms_time_t move_interval; |
||||||
|
|
||||||
|
static uint32_t score; |
||||||
|
static uint32_t level_up_score; // counter
|
||||||
|
static uint32_t level; |
||||||
|
|
||||||
|
// used to reduce length seamlessly after a level-up
|
||||||
|
static uint32_t double_tail_move_cnt = 0; |
||||||
|
|
||||||
|
|
||||||
|
static Cell *cell_at_xy(int x, int y); |
||||||
|
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
static void show_board(void) |
||||||
|
{ |
||||||
|
if (!snake_active) return; |
||||||
|
|
||||||
|
dmtx_clear(dmtx); |
||||||
|
|
||||||
|
for (int x = 0; x < BOARD_W; x++) { |
||||||
|
for (int y = 0; y < BOARD_H; y++) { |
||||||
|
Cell *cell = cell_at_xy(x, y); |
||||||
|
|
||||||
|
bool set = 0; |
||||||
|
|
||||||
|
switch (cell->type) { |
||||||
|
case CELL_EMPTY: set = 0; break; |
||||||
|
case CELL_BODY: set = 1; break; |
||||||
|
case CELL_FOOD: |
||||||
|
set = food_pwm.pwm_bit; |
||||||
|
break; |
||||||
|
|
||||||
|
case CELL_WALL: set = 1; break; |
||||||
|
} |
||||||
|
|
||||||
|
if (x == head.x && y == head.y) set = head_pwm.pwm_bit; |
||||||
|
|
||||||
|
dmtx_set(dmtx, x, y, set); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dmtx_show(dmtx); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// --- Snake PWM ---
|
||||||
|
|
||||||
|
/** Turn off a PWM bit (scheduled callback) */ |
||||||
|
static void task_pwm_off_cb(void *ptr) |
||||||
|
{ |
||||||
|
if (!snake_active) return; |
||||||
|
snake_pwm* pwm = ptr; |
||||||
|
|
||||||
|
if (!pwm->offtask_pending) return; |
||||||
|
pwm->offtask_pending = false; |
||||||
|
|
||||||
|
pwm->pwm_bit = 0; |
||||||
|
show_board(); |
||||||
|
} |
||||||
|
|
||||||
|
/** Turn on a PWM bit and schedule the turn-off task (periodic callback) */ |
||||||
|
static void task_pwm_on_cb(void *ptr) |
||||||
|
{ |
||||||
|
if (!snake_active) return; |
||||||
|
snake_pwm* pwm = ptr; |
||||||
|
|
||||||
|
pwm->pwm_bit = 1; |
||||||
|
show_board(); |
||||||
|
|
||||||
|
pwm->offtask_pending = true; |
||||||
|
pwm->off_task = schedule_task(task_pwm_off_cb, ptr, pwm->offtime_ms, true); |
||||||
|
} |
||||||
|
|
||||||
|
/** Initialize a snake PWM channel */ |
||||||
|
static void snake_pwm_init(snake_pwm *pwm) |
||||||
|
{ |
||||||
|
pwm->on_task = add_periodic_task(task_pwm_on_cb, pwm, pwm->interval_ms, true); |
||||||
|
enable_periodic_task(pwm->on_task, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** Clear & start a snake PWM channel */ |
||||||
|
static void snake_pwm_start(snake_pwm *pwm) |
||||||
|
{ |
||||||
|
pwm->pwm_bit = 1; |
||||||
|
pwm->offtask_pending = false; |
||||||
|
reset_periodic_task(pwm->on_task); |
||||||
|
enable_periodic_task(pwm->on_task, true); |
||||||
|
} |
||||||
|
|
||||||
|
/** Stop a snake PWM channel */ |
||||||
|
static void snake_pwm_stop(snake_pwm *pwm) |
||||||
|
{ |
||||||
|
pwm->offtask_pending = false; |
||||||
|
enable_periodic_task(pwm->on_task, false); |
||||||
|
abort_scheduled_task(pwm->off_task); |
||||||
|
} |
||||||
|
|
||||||
|
/** Get board cell at coord (x,y) */ |
||||||
|
static Cell *cell_at_xy(int x, int y) |
||||||
|
{ |
||||||
|
if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H) { |
||||||
|
return &WALL_TILE; |
||||||
|
} |
||||||
|
|
||||||
|
return &board[y][x]; |
||||||
|
} |
||||||
|
|
||||||
|
/** Get board cell at coord */ |
||||||
|
static Cell *cell_at(Coord *coord) |
||||||
|
{ |
||||||
|
return cell_at_xy(coord->x, coord->y); |
||||||
|
} |
||||||
|
|
||||||
|
/** Place food in a random empty tile */ |
||||||
|
static void place_food(void) |
||||||
|
{ |
||||||
|
Coord food; |
||||||
|
|
||||||
|
// 1000 tries - timeout in case board is full of snake (?)
|
||||||
|
for (int i = 0; i < 1000; i++) { |
||||||
|
food.x = rand() % BOARD_W; |
||||||
|
food.y = rand() % BOARD_H; |
||||||
|
|
||||||
|
Cell *cell = cell_at(&food); |
||||||
|
if (cell->type == CELL_EMPTY) { |
||||||
|
cell->type = CELL_FOOD; |
||||||
|
|
||||||
|
dbg("Placed food at [%d, %d]", food.x, food.y); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// avoid "doubleblink" glitch
|
||||||
|
reset_periodic_task(food_pwm.on_task); |
||||||
|
abort_scheduled_task(food_pwm.off_task); |
||||||
|
task_pwm_on_cb(&food_pwm); |
||||||
|
} |
||||||
|
|
||||||
|
/** Clear the board and prepare for a new game */ |
||||||
|
static void new_game(void) |
||||||
|
{ |
||||||
|
dbg("Snake NEW GAME"); |
||||||
|
|
||||||
|
// Stop snake (make sure it's stopped)
|
||||||
|
reset_periodic_task(task_snake_move); |
||||||
|
enable_periodic_task(task_snake_move, false); |
||||||
|
|
||||||
|
moving = false; |
||||||
|
alive = true; |
||||||
|
|
||||||
|
changed_dir_this_tick = false; |
||||||
|
double_tail_move_cnt = 0; |
||||||
|
|
||||||
|
level = 0; |
||||||
|
score = 0; |
||||||
|
move_interval = speeds_for_levels[level]; |
||||||
|
level_up_score = 0; |
||||||
|
set_periodic_task_interval(task_snake_move, move_interval); |
||||||
|
|
||||||
|
// randomize (we can assume it takes random time before the user switches to the Snake mode)
|
||||||
|
srand(SysTick->VAL); |
||||||
|
|
||||||
|
// Erase the board
|
||||||
|
memset(board, 0, sizeof(board)); |
||||||
|
|
||||||
|
// Place initial snake
|
||||||
|
|
||||||
|
tail.x = BOARD_W / 2 - 5; |
||||||
|
tail.y = BOARD_H / 2; |
||||||
|
|
||||||
|
head.x = tail.x + 4; |
||||||
|
head.y = tail.y; |
||||||
|
|
||||||
|
head_dir = EAST; |
||||||
|
|
||||||
|
for (int x = tail.x; x < head.x; x++) { |
||||||
|
board[tail.y][x].type = CELL_BODY; |
||||||
|
board[tail.y][x].dir = EAST; |
||||||
|
} |
||||||
|
|
||||||
|
place_food(); |
||||||
|
|
||||||
|
snake_pwm_start(&head_pwm); |
||||||
|
snake_pwm_start(&food_pwm); |
||||||
|
|
||||||
|
show_board(); |
||||||
|
|
||||||
|
// Discard keys pressed during the death animation
|
||||||
|
uint8_t x; |
||||||
|
while(com_rx(gamepad_iface, &x)); |
||||||
|
} |
||||||
|
|
||||||
|
static bool press_any_key(void) |
||||||
|
{ |
||||||
|
bool press = false; |
||||||
|
if (com_rx_rdy(gamepad_iface)) { |
||||||
|
uint8_t x; |
||||||
|
while(com_rx(gamepad_iface, &x)) { |
||||||
|
if (x >= 'A' && x <= 'Z') press = true; |
||||||
|
} |
||||||
|
return press; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
static void snake_died(void) |
||||||
|
{ |
||||||
|
dbg("R.I.P. Snake"); |
||||||
|
dbg("Total Score %d, Level %d", score, level); |
||||||
|
|
||||||
|
moving = false; |
||||||
|
alive = false; |
||||||
|
enable_periodic_task(task_snake_move, false); |
||||||
|
|
||||||
|
// stop blinky animation of the snake head.
|
||||||
|
snake_pwm_stop(&head_pwm); |
||||||
|
head_pwm.pwm_bit = 1; |
||||||
|
|
||||||
|
// Let it sink...
|
||||||
|
until_timeout(600) { |
||||||
|
tq_poll(); // take care of periodic tasks (anim)
|
||||||
|
} |
||||||
|
|
||||||
|
snake_pwm_stop(&food_pwm); |
||||||
|
show_board(); |
||||||
|
|
||||||
|
snake_active = false; // suppress pending callbacks
|
||||||
|
|
||||||
|
delay_ms(400); |
||||||
|
dmtx_clear(dmtx); |
||||||
|
|
||||||
|
// score
|
||||||
|
char txt[6]; |
||||||
|
sprintf(txt, "%d", score); |
||||||
|
size_t len = strlen(txt); |
||||||
|
printtext(txt, (int)(SCREEN_W / 2 - (len * 5) / 2) - 1, SCREEN_H / 2 - 4); |
||||||
|
|
||||||
|
dmtx_show(dmtx); |
||||||
|
|
||||||
|
// discard input
|
||||||
|
uint8_t x; |
||||||
|
while(com_rx(gamepad_iface, &x)); |
||||||
|
|
||||||
|
bool interr = false; |
||||||
|
until_timeout(10000) { |
||||||
|
|
||||||
|
for (int x = 0; x < SCREEN_W; x++) { |
||||||
|
dmtx_set(dmtx, SCREEN_W - x - 1, 0, 1); |
||||||
|
dmtx_set(dmtx, x, SCREEN_H-1, 1); |
||||||
|
dmtx_show(dmtx); |
||||||
|
delay_ms(7); |
||||||
|
dmtx_set(dmtx, SCREEN_W - x - 1, 0, 0); |
||||||
|
dmtx_set(dmtx, x, SCREEN_H-1, 0); |
||||||
|
|
||||||
|
if (press_any_key()) { |
||||||
|
interr = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (interr) break; |
||||||
|
|
||||||
|
for (int y = SCREEN_H-1; y >= 0; y--) { |
||||||
|
dmtx_set(dmtx, 0, SCREEN_H - y - 1, 1); |
||||||
|
dmtx_set(dmtx, SCREEN_W-1, y, 1); |
||||||
|
dmtx_show(dmtx); |
||||||
|
delay_ms(7); |
||||||
|
dmtx_set(dmtx, 0, SCREEN_H - y - 1, 0); |
||||||
|
dmtx_set(dmtx, SCREEN_W-1, y, 0); |
||||||
|
|
||||||
|
if (press_any_key()) { |
||||||
|
interr = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (interr) break; |
||||||
|
} |
||||||
|
|
||||||
|
dmtx_clear(dmtx); |
||||||
|
dmtx_show(dmtx); |
||||||
|
|
||||||
|
snake_active = true; // resume snake
|
||||||
|
new_game(); |
||||||
|
} |
||||||
|
|
||||||
|
static void move_coord_by_dir(Coord *coord, Direction dir) |
||||||
|
{ |
||||||
|
switch (dir) { |
||||||
|
case NORTH: coord->y++; break; |
||||||
|
case SOUTH: coord->y--; break; |
||||||
|
case EAST: coord->x++; break; |
||||||
|
case WEST: coord->x--; break; |
||||||
|
} |
||||||
|
|
||||||
|
// Wrap-around
|
||||||
|
if (coord->x < 0) coord->x = BOARD_W - 1; |
||||||
|
if (coord->y < 0) coord->y = BOARD_H - 1; |
||||||
|
if (coord->x >= BOARD_W) coord->x = 0; |
||||||
|
if (coord->y >= BOARD_H) coord->y = 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void move_tail(void) |
||||||
|
{ |
||||||
|
Cell *tail_cell = cell_at(&tail); |
||||||
|
tail_cell->type = CELL_EMPTY; |
||||||
|
move_coord_by_dir(&tail, tail_cell->dir); |
||||||
|
} |
||||||
|
|
||||||
|
static void snake_move_cb(void *unused) |
||||||
|
{ |
||||||
|
(void)unused; |
||||||
|
|
||||||
|
changed_dir_this_tick = false; // allow dir change next move
|
||||||
|
bool want_new_food = false; |
||||||
|
|
||||||
|
Coord shadowhead = head; |
||||||
|
|
||||||
|
move_coord_by_dir(&shadowhead, head_dir); |
||||||
|
|
||||||
|
Cell *head_cell = cell_at(&head); |
||||||
|
Cell *future_head_cell = cell_at(&shadowhead); |
||||||
|
|
||||||
|
switch (future_head_cell->type) { |
||||||
|
case CELL_BODY: |
||||||
|
case CELL_WALL: |
||||||
|
// R.I.P.
|
||||||
|
snake_died(); |
||||||
|
return; |
||||||
|
|
||||||
|
case CELL_FOOD: |
||||||
|
// grow - no head move
|
||||||
|
score++; |
||||||
|
want_new_food = 1; |
||||||
|
level_up_score++; |
||||||
|
break; |
||||||
|
|
||||||
|
case CELL_EMPTY: |
||||||
|
// move tail
|
||||||
|
move_tail(); |
||||||
|
|
||||||
|
if (double_tail_move_cnt > 0) { |
||||||
|
move_tail(); // 2x
|
||||||
|
double_tail_move_cnt--; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// Move head
|
||||||
|
// (if died, already returned)
|
||||||
|
head_cell->dir = head_dir; |
||||||
|
head_cell->type = CELL_BODY; |
||||||
|
|
||||||
|
future_head_cell->type = CELL_BODY; |
||||||
|
|
||||||
|
// apply movement
|
||||||
|
head = shadowhead; |
||||||
|
|
||||||
|
if (want_new_food) { |
||||||
|
place_food(); |
||||||
|
} |
||||||
|
|
||||||
|
// render
|
||||||
|
show_board(); |
||||||
|
|
||||||
|
// level up
|
||||||
|
if (level_up_score == POINTS_TO_LEVEL_UP) { |
||||||
|
info("level up"); |
||||||
|
|
||||||
|
double_tail_move_cnt += PIECES_REMOVED_ON_LEVEL_UP; |
||||||
|
|
||||||
|
level++; |
||||||
|
level_up_score = 0; |
||||||
|
|
||||||
|
// go faster if not too fast yet
|
||||||
|
if (level < PREDEFINED_LEVELS) { |
||||||
|
move_interval = speeds_for_levels[level]; |
||||||
|
} |
||||||
|
|
||||||
|
set_periodic_task_interval(task_snake_move, move_interval); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** INIT snake */ |
||||||
|
void mode_snake_init(void) |
||||||
|
{ |
||||||
|
snake_pwm_init(&head_pwm); |
||||||
|
snake_pwm_init(&food_pwm); |
||||||
|
|
||||||
|
task_snake_move = add_periodic_task(snake_move_cb, NULL, move_interval, true); |
||||||
|
enable_periodic_task(task_snake_move, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** START playing */ |
||||||
|
void mode_snake_start(void) |
||||||
|
{ |
||||||
|
snake_active = true; |
||||||
|
|
||||||
|
new_game(); |
||||||
|
} |
||||||
|
|
||||||
|
/** STOP playing */ |
||||||
|
void mode_snake_stop(void) |
||||||
|
{ |
||||||
|
snake_active = false; |
||||||
|
|
||||||
|
snake_pwm_stop(&head_pwm); |
||||||
|
snake_pwm_stop(&food_pwm); |
||||||
|
|
||||||
|
enable_periodic_task(task_snake_move, false); |
||||||
|
} |
||||||
|
|
||||||
|
/** Change dir (safely) */ |
||||||
|
static void change_direction(Direction dir) |
||||||
|
{ |
||||||
|
if (changed_dir_this_tick) return; |
||||||
|
|
||||||
|
// This is a compensation for shitty gamepads
|
||||||
|
// with accidental arrow hits
|
||||||
|
Coord shadowhead = head; |
||||||
|
move_coord_by_dir(&shadowhead, dir); |
||||||
|
Cell *target = cell_at(&shadowhead); // Target cell
|
||||||
|
if (target->type == CELL_BODY) { |
||||||
|
move_coord_by_dir(&shadowhead, target->dir); |
||||||
|
if (shadowhead.x == head.x && shadowhead.y == head.y) { |
||||||
|
warn("Ignoring suicidal dir change"); |
||||||
|
return; // Would crash into own neck
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
head_dir = dir; |
||||||
|
changed_dir_this_tick = true; |
||||||
|
} |
||||||
|
|
||||||
|
/** User button */ |
||||||
|
void mode_snake_btn(char key) |
||||||
|
{ |
||||||
|
switch (key) { |
||||||
|
case 'U': change_direction(NORTH); break; |
||||||
|
case 'D': change_direction(SOUTH); break; |
||||||
|
case 'L': change_direction(WEST); break; |
||||||
|
case 'R': change_direction(EAST); break; |
||||||
|
|
||||||
|
case 'J': // Start
|
||||||
|
if (alive) break; |
||||||
|
// dead - reset by pressing 'start'
|
||||||
|
|
||||||
|
case 'K': // clear
|
||||||
|
// TODO reset animation
|
||||||
|
new_game(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (!moving && alive && (key == 'U' || key == 'D' || key == 'L' || key == 'R' || key == 'J')) { |
||||||
|
// start moving
|
||||||
|
moving = true; |
||||||
|
reset_periodic_task(task_snake_move); |
||||||
|
enable_periodic_task(task_snake_move, true); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// running + start -> 'pause'
|
||||||
|
if (alive && moving && key == 'J') { |
||||||
|
moving = false; |
||||||
|
enable_periodic_task(task_snake_move, false); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
#ifndef MODE_SNAKE_H |
||||||
|
#define MODE_SNAKE_H |
||||||
|
|
||||||
|
#include "main.h" |
||||||
|
|
||||||
|
void mode_snake_init(void); |
||||||
|
void mode_snake_start(void); |
||||||
|
void mode_snake_stop(void); |
||||||
|
void mode_snake_btn(char key); |
||||||
|
|
||||||
|
#endif // MODE_SNAKE_H
|
@ -0,0 +1,58 @@ |
|||||||
|
#include "scrolltext.h" |
||||||
|
|
||||||
|
#include "font.h" |
||||||
|
#include "com/debug.h" |
||||||
|
|
||||||
|
|
||||||
|
void printtext(const char *text, int x, int y) |
||||||
|
{ |
||||||
|
int totalX = 0; |
||||||
|
|
||||||
|
for (int textX = 0; textX < (int)strlen(text); textX++) { |
||||||
|
uint8_t ch = (uint8_t)text[textX]; |
||||||
|
if (ch < FONT_MIN) ch = '?'; |
||||||
|
if (ch > FONT_MAX) ch = '?'; |
||||||
|
ch -= ' '; // normalize for font table
|
||||||
|
|
||||||
|
if (ch == 0) { // space
|
||||||
|
totalX += 4; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// one letter
|
||||||
|
uint8_t blanks = 0; |
||||||
|
|
||||||
|
// skip empty space on right
|
||||||
|
for (int charX = FONT_WIDTH-1; charX >= 0; charX--) { |
||||||
|
uint8_t col = font[ch][charX]; |
||||||
|
if (col == 0x00) { |
||||||
|
blanks++; |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (int charX = 0; charX < FONT_WIDTH - blanks; charX++) { |
||||||
|
uint8_t col = font[ch][charX]; |
||||||
|
for (int charY = 0; charY < 8; charY++) { |
||||||
|
dmtx_set(dmtx, x + totalX, y + 8 - charY, (col >> charY) & 1); |
||||||
|
} |
||||||
|
totalX++; |
||||||
|
} |
||||||
|
|
||||||
|
totalX+= 2; // gap
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void scrolltext(const char *text, ms_time_t step) |
||||||
|
{ |
||||||
|
(void)step; |
||||||
|
|
||||||
|
for (int i = 0; i < (int)strlen(text)*(FONT_WIDTH+1) + SCREEN_W-1; i++) { |
||||||
|
if (i > 0) delay_ms(step); |
||||||
|
|
||||||
|
dmtx_clear(dmtx); |
||||||
|
printtext(text, (SCREEN_W-1)-i, SCREEN_H/2-4); |
||||||
|
dmtx_show(dmtx); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
#ifndef SCROLLTEXT_H |
||||||
|
#define SCROLLTEXT_H |
||||||
|
|
||||||
|
#include "main.h" |
||||||
|
#include "dotmatrix.h" |
||||||
|
#include "utils/timebase.h" |
||||||
|
|
||||||
|
void printtext(const char *text, int x, int y); |
||||||
|
|
||||||
|
void scrolltext(const char *text, ms_time_t step); |
||||||
|
|
||||||
|
#endif // SCROLLTEXT_H
|
Loading…
Reference in new issue