Compare commits
No commits in common. 'multi-mode' and 'master' have entirely different histories.
multi-mode
...
master
@ -1,130 +1,113 @@ |
||||
#include "max2719.h" |
||||
#include "dotmatrix.h" |
||||
#include "malloc_safe.h" |
||||
#include "com/debug.h" |
||||
|
||||
DotMatrix_Cfg *dmtx; |
||||
|
||||
|
||||
DotMatrix_Cfg* dmtx_init(DotMatrix_Init *init) |
||||
{ |
||||
DotMatrix_Cfg *disp = calloc_s(1, sizeof(DotMatrix_Cfg)); |
||||
DotMatrix_Cfg *dmtx = calloc_s(1, sizeof(DotMatrix_Cfg)); |
||||
|
||||
disp->drv.SPIx = init->SPIx; |
||||
disp->drv.CS_GPIOx = init->CS_GPIOx; |
||||
disp->drv.CS_PINx = init->CS_PINx; |
||||
disp->drv.chain_len = init->cols * init->rows; |
||||
disp->cols = init->cols; |
||||
disp->rows = init->rows; |
||||
dmtx->drv.SPIx = init->SPIx; |
||||
dmtx->drv.CS_GPIOx = init->CS_GPIOx; |
||||
dmtx->drv.CS_PINx = init->CS_PINx; |
||||
dmtx->drv.chain_len = init->cols * init->rows; |
||||
dmtx->cols = init->cols; |
||||
dmtx->rows = init->rows; |
||||
|
||||
disp->screen = calloc_s(init->cols * init->rows * 8, 1); // 8 bytes per driver
|
||||
dmtx->screen = calloc_s(init->cols * init->rows * 8, 1); // 8 bytes per driver
|
||||
|
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_DECODE_MODE, 0x00); // no decode
|
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_SCAN_LIMIT, 0x07); // scan all 8
|
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, 0x01); // not shutdown
|
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_DISPLAY_TEST, 0x00); // not test
|
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_INTENSITY, 0x07); // half intensity
|
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DECODE_MODE, 0x00); // no decode
|
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SCAN_LIMIT, 0x07); // scan all 8
|
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SHUTDOWN, 0x01); // not shutdown
|
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DISPLAY_TEST, 0x00); // not test
|
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_INTENSITY, 0x07); // half intensity
|
||||
|
||||
// clear
|
||||
for (uint8_t i = 0; i < 8; i++) { |
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_DIGIT0+i, 0); |
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DIGIT0+i, 0); |
||||
} |
||||
|
||||
return disp; |
||||
return dmtx; |
||||
} |
||||
|
||||
void dmtx_show(DotMatrix_Cfg* disp) |
||||
void dmtx_show(DotMatrix_Cfg* dmtx) |
||||
{ |
||||
for (uint8_t i = 0; i < 8; i++) { |
||||
// show each digit's array in turn
|
||||
max2719_cmd_all_data(&disp->drv, MAX2719_CMD_DIGIT0+i, disp->screen + (i * disp->drv.chain_len)); |
||||
max2719_cmd_all_data(&dmtx->drv, MAX2719_CMD_DIGIT0+i, dmtx->screen + (i * dmtx->drv.chain_len)); |
||||
} |
||||
} |
||||
|
||||
void dmtx_clear(DotMatrix_Cfg* disp) |
||||
void dmtx_clear(DotMatrix_Cfg* dmtx) |
||||
{ |
||||
memset(disp->screen, 0, disp->drv.chain_len*8); |
||||
memset(dmtx->screen, 0, dmtx->drv.chain_len*8); |
||||
} |
||||
|
||||
void dmtx_intensity(DotMatrix_Cfg* disp, uint8_t intensity) |
||||
void dmtx_intensity(DotMatrix_Cfg* dmtx, uint8_t intensity) |
||||
{ |
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_INTENSITY, intensity & 0x0F); |
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_INTENSITY, intensity & 0x0F); |
||||
} |
||||
|
||||
void dmtx_blank(DotMatrix_Cfg* disp, bool blank) |
||||
void dmtx_blank(DotMatrix_Cfg* dmtx, bool blank) |
||||
{ |
||||
max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, (!blank) & 0x01); |
||||
max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SHUTDOWN, blank & 0x01); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Get a cell pointer |
||||
* @param disp : driver inst |
||||
* @param dmtx : driver inst |
||||
* @param x : x coord |
||||
* @param y : y coord |
||||
* @param xd : pointer to store the offset in the cell |
||||
* @return cell ptr |
||||
*/ |
||||
static uint8_t* cell_ptr(DotMatrix_Cfg* disp, int32_t x, int32_t y, uint8_t *xd) |
||||
static uint8_t* cell_ptr(DotMatrix_Cfg* dmtx, int32_t x, int32_t y, uint8_t *xd) |
||||
{ |
||||
if (x < 0 || y < 0) return NULL; |
||||
if ((uint32_t)x >= disp->cols*8 || (uint32_t)y >= disp->rows*8) return NULL; |
||||
if ((uint32_t)x >= dmtx->cols*8 || (uint32_t)y >= dmtx->rows*8) return NULL; |
||||
|
||||
uint32_t cell_x = (uint32_t)x >> 3; |
||||
*xd = x & 7; |
||||
|
||||
// resolve cell
|
||||
uint32_t digit = y & 7; |
||||
cell_x += ((uint32_t)y >> 3) * disp->cols; |
||||
cell_x += ((uint32_t)y >> 3) * dmtx->cols; |
||||
|
||||
uint32_t cell_idx = (digit * disp->drv.chain_len) + cell_x; |
||||
uint32_t cell_idx = (digit * dmtx->drv.chain_len) + cell_x; |
||||
|
||||
return &disp->screen[cell_idx]; |
||||
return &dmtx->screen[cell_idx]; |
||||
} |
||||
|
||||
|
||||
bool dmtx_get(DotMatrix_Cfg* disp, int32_t x, int32_t y) |
||||
bool dmtx_get(DotMatrix_Cfg* dmtx, int32_t x, int32_t y) |
||||
{ |
||||
uint8_t xd; |
||||
uint8_t *cell = cell_ptr(disp, x, y, &xd); |
||||
uint8_t *cell = cell_ptr(dmtx, x, y, &xd); |
||||
if (cell == NULL) return 0; |
||||
|
||||
return (*cell >> xd) & 1; |
||||
return (bool)(*cell & (1 << xd)); |
||||
} |
||||
|
||||
|
||||
void dmtx_toggle(DotMatrix_Cfg* disp, int32_t x, int32_t y) |
||||
void dmtx_toggle(DotMatrix_Cfg* dmtx, int32_t x, int32_t y) |
||||
{ |
||||
uint8_t xd; |
||||
uint8_t *cell = cell_ptr(disp, x, y, &xd); |
||||
uint8_t *cell = cell_ptr(dmtx, x, y, &xd); |
||||
if (cell == NULL) return; |
||||
|
||||
*cell ^= 1 << xd; |
||||
} |
||||
|
||||
|
||||
void dmtx_set(DotMatrix_Cfg* disp, int32_t x, int32_t y, bool bit) |
||||
void dmtx_set(DotMatrix_Cfg* dmtx, int32_t x, int32_t y, bool bit) |
||||
{ |
||||
uint8_t xd; |
||||
uint8_t *cell = cell_ptr(disp, x, y, &xd); |
||||
uint8_t *cell = cell_ptr(dmtx, x, y, &xd); |
||||
if (cell == NULL) return; |
||||
|
||||
if (bit) { |
||||
*cell |= 1 << xd; |
||||
*cell |= bit << xd; |
||||
} else { |
||||
*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); |
||||
} |
||||
*cell &= ~(bit << xd); |
||||
} |
||||
} |
||||
|
@ -1,108 +0,0 @@ |
||||
#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
|
@ -1,175 +0,0 @@ |
||||
#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); |
||||
} |
@ -1,10 +0,0 @@ |
||||
#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
|
@ -1,465 +0,0 @@ |
||||
#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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,11 +0,0 @@ |
||||
#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
|
@ -1,560 +0,0 @@ |
||||
#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); |
||||
} |
||||
} |
@ -1,11 +0,0 @@ |
||||
#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
|
@ -1,58 +0,0 @@ |
||||
#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); |
||||
} |
||||
} |
@ -1,12 +0,0 @@ |
||||
#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