Compare commits

...

15 Commits

  1. 13
      f103-ledmatrix.pro
  2. 1
      project/com/com_fileio.c
  3. 4
      project/com/com_fileio.h
  4. 95
      project/dotmatrix.c
  5. 21
      project/dotmatrix.h
  6. 108
      project/font.h
  7. 86
      project/hw_init.c
  8. 2
      project/hw_init.h
  9. 212
      project/main.c
  10. 5
      project/main.h
  11. 175
      project/mode_audio.c
  12. 10
      project/mode_audio.h
  13. 465
      project/mode_life.c
  14. 11
      project/mode_life.h
  15. 560
      project/mode_snake.c
  16. 11
      project/mode_snake.h
  17. 58
      project/scrolltext.c
  18. 12
      project/scrolltext.h
  19. 15
      project/utils/timebase.c
  20. 3
      project/utils/timebase.h

@ -90,7 +90,12 @@ HEADERS += \
lib/cmsis/DSP_Lib/Include/arm_math.h \
lib/cmsis/DSP_Lib/Include/core_cmFunc.h \
lib/cmsis/DSP_Lib/Include/core_cmInstr.h \
lib/cmsis/DSP_Lib/Include/core_cmSimd.h
lib/cmsis/DSP_Lib/Include/core_cmSimd.h \
project/mode_audio.h \
project/font.h \
project/scrolltext.h \
project/mode_snake.h \
project/mode_life.h
SOURCES += \
lib/cmsis/core_cm3.c \
@ -420,7 +425,11 @@ SOURCES += \
lib/cmsis/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q15.c \
lib/cmsis/DSP_Lib/Source/TransformFunctions/arm_rfft_init_q31.c \
lib/cmsis/DSP_Lib/Source/TransformFunctions/arm_rfft_q15.c \
lib/cmsis/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c
lib/cmsis/DSP_Lib/Source/TransformFunctions/arm_rfft_q31.c \
project/mode_audio.c \
project/scrolltext.c \
project/mode_snake.c \
project/mode_life.c
DISTFILES += \
style.astylerc \

@ -7,6 +7,7 @@
// Holding fields for ifaces
ComIface *debug_iface = NULL;
ComIface *data_iface = NULL;
ComIface *gamepad_iface = NULL;
// --- File descriptor names ------------------------------

@ -8,8 +8,8 @@ extern ComIface *debug_iface;
/** ESP8266 com iface */
extern ComIface *data_iface;
/** Do-nothing iface */
extern ComIface *com_iface_noop;
/** Gamepad iface */
extern ComIface *gamepad_iface;
/** File descriptors for use with built-in "files" */

@ -1,113 +1,130 @@
#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 *dmtx = calloc_s(1, sizeof(DotMatrix_Cfg));
DotMatrix_Cfg *disp = calloc_s(1, sizeof(DotMatrix_Cfg));
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->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->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(&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
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
// clear
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++) {
// 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
* @param dmtx : driver inst
* @param disp : 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* 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 ((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;
*xd = x & 7;
// resolve cell
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 *cell = cell_ptr(dmtx, x, y, &xd);
uint8_t *cell = cell_ptr(disp, x, y, &xd);
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 *cell = cell_ptr(dmtx, x, y, &xd);
uint8_t *cell = cell_ptr(disp, x, y, &xd);
if (cell == NULL) return;
*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 *cell = cell_ptr(dmtx, x, y, &xd);
uint8_t *cell = cell_ptr(disp, x, y, &xd);
if (cell == NULL) return;
if (bit) {
*cell |= bit << xd;
*cell |= 1 << xd;
} 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);
}
}
}

@ -4,6 +4,7 @@
#include "main.h"
#include "max2719.h"
typedef struct {
MAX2719_Cfg drv;
uint8_t *screen; /*!< Screen array, organized as series of [all #1 digits], [all #2 digits] ... */
@ -19,6 +20,9 @@ typedef struct {
uint32_t rows; /*!< Number of drivers vertically */
} DotMatrix_Init;
// global inst
extern DotMatrix_Cfg *dmtx;
DotMatrix_Cfg* dmtx_init(DotMatrix_Init *init);
@ -26,13 +30,13 @@ DotMatrix_Cfg* dmtx_init(DotMatrix_Init *init);
* @brief Display the whole screen array
* @param dmtx : driver struct
*/
void dmtx_show(DotMatrix_Cfg* dmtx);
void dmtx_show(DotMatrix_Cfg* disp);
/** Set intensity 0-16 */
void dmtx_intensity(DotMatrix_Cfg* dmtx, uint8_t intensity);
void dmtx_intensity(DotMatrix_Cfg* disp, uint8_t intensity);
/** Display on/off */
void dmtx_blank(DotMatrix_Cfg* dmtx, bool blank);
void dmtx_blank(DotMatrix_Cfg* disp, bool blank);
/**
* @brief Send a single bit
@ -41,15 +45,18 @@ void dmtx_blank(DotMatrix_Cfg* dmtx, bool blank);
* @param y : pixel Y
* @param bit : 1 or 0
*/
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);
/** Get a single bit */
bool dmtx_get(DotMatrix_Cfg* dmtx, int32_t x, int32_t y);
bool dmtx_get(DotMatrix_Cfg* disp, int32_t x, int32_t y);
/** Set a block using array of row data */
void dmtx_set_block(DotMatrix_Cfg* disp, int32_t startX, int32_t startY, uint32_t *data_rows, uint32_t width, uint16_t height);
/** Toggle a single bit */
void dmtx_toggle(DotMatrix_Cfg* dmtx, int32_t x, int32_t y);
void dmtx_toggle(DotMatrix_Cfg* disp, int32_t x, int32_t y);
/** Clear the screen (not showing) */
void dmtx_clear(DotMatrix_Cfg* dmtx);
void dmtx_clear(DotMatrix_Cfg* disp);
#endif // MATRIXDSP_H

@ -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

@ -2,7 +2,7 @@
#include "com/iface_usart.h"
#include "com/com_fileio.h"
#include "com/datalink.h"
//#include "com/datalink.h"
#include "utils/debounce.h"
#include "utils/timebase.h"
@ -10,6 +10,8 @@
#include "bus/event_queue.h"
#include "com/debug.h"
#include "dotmatrix.h"
// ---- Private prototypes --------
static void conf_gpio(void);
@ -19,6 +21,7 @@ static void conf_systick(void);
static void conf_subsystems(void);
static void conf_irq_prios(void);
static void conf_adc(void);
static void init_display(void);
// ---- Public functions ----------
/**
@ -39,7 +42,6 @@ void hw_init(void)
// ---- Private functions ---------
static void conf_irq_prios(void)
{
NVIC_SetPriorityGrouping(0); // 0 bits for sub-priority
@ -59,13 +61,31 @@ static void conf_irq_prios(void)
static void conf_subsystems(void)
{
// task scheduler subsystem
timebase_init(15, 15);
timebase_init(20, 20);
// event and task queues
queues_init(30, 30);
queues_init(30, 10);
// initialize SBMP for ESP8266
dlnk_init();
// dlnk_init();
// dot matrix
init_display();
}
static void init_display(void)
{
DotMatrix_Init dmtx_cfg;
dmtx_cfg.CS_GPIOx = GPIOA;
dmtx_cfg.CS_PINx = GPIO_Pin_4;
dmtx_cfg.SPIx = SPI1;
dmtx_cfg.cols = 2;
dmtx_cfg.rows = 2;
dmtx = dmtx_init(&dmtx_cfg);
dmtx_intensity(dmtx, 7);
}
@ -87,12 +107,15 @@ static void conf_gpio(void)
gpio_cnf.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOC, &gpio_cnf);
// UARTs
// [ UARTs ]
// Tx
gpio_cnf.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_9;
gpio_cnf.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_cnf.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &gpio_cnf);
// Rx
gpio_cnf.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_3;
gpio_cnf.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio_cnf);
@ -123,14 +146,17 @@ static void conf_gpio(void)
static void conf_usart(void)
{
// Debug interface, working as stdout/stderr.
debug_iface = usart_iface_init(USART2, 115200, 256, 256);
debug_iface = usart_iface_init(USART1, 115200, 256, 256);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
debug_iface->file = stdout;
// Datalink iface
data_iface = usart_iface_init(USART1, 460800, 256, 256);
gamepad_iface = usart_iface_init(USART2, 115200, 16, 16);
// Datalink iface
//data_iface = usart_iface_init(USART1, 460800, 256, 256);
}
/**
@ -205,46 +231,4 @@ static void conf_adc(void)
TIM_TimeBaseInit(TIM3, &tim_cnf);
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
//TIM3->CR1 |= TIM_CR1_URS; // generate update on overflow
//TIM3->CR2 |= TIM_CR2_MMS_1; // trig on update
}
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);
}
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);
}

@ -3,5 +3,3 @@
#include "main.h"
void hw_init(void);
void start_adc_dma(uint32_t *memory, uint32_t count);

@ -11,137 +11,88 @@
#include "colorled.h"
#include "display.h"
#include <math.h>
#include <sbmp.h>
//#include <sbmp.h>
//#include "matrixdsp.h"
#include "max2719.h"
#include "dotmatrix.h"
#include "arm_math.h"
static volatile bool capture_pending = false;
static volatile bool print_next_fft = false;
#include <arm_math.h>
static float virt_zero_value = 2045.0f;
#include "mode_audio.h"
#include "mode_snake.h"
#include "mode_life.h"
static void poll_subsystems(void);
#include "scrolltext.h"
static DotMatrix_Cfg *dmtx;
/** Functional mode */
typedef enum {
MODE_AUDIO,
MODE_LIFE,
MODE_SNAKE,
MODE_END,
} GameMode;
#define SAMP_BUF_LEN 256
static void poll_subsystems(void);
static void gamepad_rx(ComIface *iface);
static void switch_mode(void *unused); // circle b/w modes
static void activate_mode(void); // activate currently selected mode
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)];
};
static GameMode app_mode;
// sample buffers (static - invalidated when sampling starts anew).
static union samp_buf_union samp_buf;
void audio_capture_done(void* unused)
static void switch_mode(void *unused)
{
(void)unused;
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];
if (++app_mode == MODE_END) {
app_mode = 0;
}
// normalize
float mean;
arm_mean_f32(samp_buf.floats, samp_count, &mean);
virt_zero_value = mean;
activate_mode();
for (int i = 0; i < samp_count; i++) {
samp_buf.floats[i] -= virt_zero_value;
// discard buffer
uint8_t x;
while(com_rx(gamepad_iface, &x));
}
if (print_next_fft) {
printf("--- Raw (adjusted) ---\n");
for(int i = 0; i < samp_count; i++) {
printf("%.2f, ", samp_buf.floats[i]);
}
printf("\n");
}
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;
#define SCROLL_STEP 22
static void activate_mode(void)
{
// --- Audio FFT mode ---
arm_cfft_f32(S, bins, 0, true); // bit reversed FFT
arm_cmplx_mag_f32(bins, bins, bin_count); // get magnitude (extract real values)
if (app_mode == MODE_AUDIO) {
info("MODE: Audio");
scrolltext("AUDIO", SCROLL_STEP);
if (print_next_fft) {
printf("--- Bins ---\n");
for(int i = 0; i < bin_count; i++) {
printf("%.2f, ", bins[i]);
}
printf("\n");
mode_audio_start();
} else {
mode_audio_stop();
}
// normalize
dmtx_clear(dmtx);
float factor = (1.0f/bin_count)*0.2f;
for(int i = 0; i < bin_count-1; i+=2) {
bins[i] *= factor;
bins[i+1] *= factor;
//float avg = i==0 ? bins[1] : (bins[i] + bins[i+1])/2;
float avg = (bins[i] + bins[i+1])/2;
for(int j = 0; j < 1+floorf(avg); j++) {
//dmtx_toggle(dmtx, i/2, j);
dmtx_toggle(dmtx, i/2, j);
//dmtx_toggle(dmtx, j, 15-i/2);
//dmtx_toggle(dmtx, 15- i/2, 15-j);
}
}
// --- Game Of Life ---
dmtx_show(dmtx);
if (app_mode == MODE_LIFE) {
info("MODE: Life");
scrolltext("CONWAY", SCROLL_STEP);
print_next_fft = false;
capture_pending = false;
mode_life_start();
} else {
mode_life_stop();
}
// --- Snake Minigame ---
static void capture_audio(void *unused)
{
(void)unused;
if (capture_pending) return;
capture_pending = true;
start_adc_dma(samp_buf.uints, SAMP_BUF_LEN/2);
}
if (app_mode == MODE_SNAKE) {
info("MODE: Snake");
scrolltext("SNAKE", SCROLL_STEP);
static void rx_char(ComIface *iface)
{
uint8_t ch;
while(com_rx(iface, &ch)) {
if (ch == 'p') {
info("PRINT_NEXT");
print_next_fft = true;
}
mode_snake_start();
} else {
mode_snake_stop();
}
}
static task_pid_t capture_task_id;
int main(void)
{
hw_init();
@ -149,26 +100,14 @@ int main(void)
banner("*** FFT dot matrix display ***");
banner_info("(c) Ondrej Hruska, 2016");
debug_iface->rx_callback = rx_char;
DotMatrix_Init dmtx_cfg;
dmtx_cfg.CS_GPIOx = GPIOA;
dmtx_cfg.CS_PINx = GPIO_Pin_4;
dmtx_cfg.SPIx = SPI1;
dmtx_cfg.cols = 2;
dmtx_cfg.rows = 2;
gamepad_iface->rx_callback = gamepad_rx;
dmtx = dmtx_init(&dmtx_cfg);
mode_audio_init();
mode_life_init();
mode_snake_init();
dmtx_intensity(dmtx, 7);
for(int i = 0; i < 16; i++) {
dmtx_set(dmtx, i, 0, 1);
dmtx_show(dmtx);
delay_ms(25);
}
capture_task_id = add_periodic_task(capture_audio, NULL, 10, false);
app_mode = MODE_AUDIO;
mode_audio_start();
ms_time_t last;
while (1) {
@ -185,14 +124,14 @@ static void poll_subsystems(void)
{
// poll serial buffers (runs callback)
com_poll(debug_iface);
com_poll(data_iface);
//com_poll(data_iface);
com_poll(gamepad_iface);
// run queued tasks
tq_poll();
// handle queued events
Event evt;
until_timeout(2) { // take 2 ms max
if (eq_take(&evt)) {
run_event_handler(&evt);
@ -203,7 +142,40 @@ static void poll_subsystems(void)
}
void dlnk_rx(SBMP_Datagram *dg)
static void gamepad_rx(ComIface *iface)
{
dbg("Rx dg type %d", dg->type);
char ch;
while(com_rx(iface, (uint8_t*)&ch)) {
switch (ch) {
case 'I': // Select pressed
tq_post(switch_mode, NULL);
break;
case 'i': // Select released
break;
default:
switch (app_mode) {
case MODE_AUDIO:
// discard
break;
case MODE_LIFE:
mode_life_btn(ch);
break;
case MODE_SNAKE:
mode_snake_btn(ch);
break;
}
}
}
}
//void dlnk_rx(SBMP_Datagram *dg)
//{
// (void)dg;
// //dbg("Rx dg type %d", dg->type);
//}

@ -15,7 +15,8 @@
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
void audio_capture_done(void* unused);
// screen size
#define SCREEN_W 16
#define SCREEN_H 16
#endif // MAIN_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

@ -189,6 +189,21 @@ bool reset_periodic_task(task_pid_t pid)
}
bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval)
{
if (pid == PID_NONE) return false;
for (size_t i = 0; i < periodic_slot_count; i++) {
periodic_task_t *task = &periodic_tasks[i];
if (task->pid != pid) continue;
task->interval_ms = interval;
return true;
}
return false;
}
/** Remove a periodic task. */
bool remove_periodic_task(task_pid_t pid)
{

@ -67,6 +67,9 @@ bool is_periodic_task_enabled(task_pid_t pid);
/** Reset timer for a task */
bool reset_periodic_task(task_pid_t pid);
/** Set inteval */
bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval);
// --- Future -------------------------------------------------

Loading…
Cancel
Save