--- /dev/null
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+
+#include <string.h>
+
+typedef unsigned char byte;
+
+static void wait(void)
+{
+ for (int i=0; i<7200; i++)
+ asm volatile ("");
+}
+
+static void set_reg(int reg, int data)
+{
+ data |= reg << 8;
+
+ gpio_clear(GPIOB, GPIO13);
+ wait();
+
+ for (int mask=0x8000; mask; mask >>= 1) {
+ if (data & mask)
+ gpio_set(GPIOB, GPIO12);
+ else
+ gpio_clear(GPIOB, GPIO12);
+ wait();
+ gpio_set(GPIOB, GPIO14);
+ wait();
+ gpio_clear(GPIOB, GPIO14);
+ wait();
+ }
+
+ gpio_set(GPIOB, GPIO13);
+ wait();
+}
+
+byte display[8];
+
+#if 0
+static unsigned char smile[8] = {
+ 0x66, /* .##..##. */
+ 0x66, /* .##..##. */
+ 0x00, /* ........ */
+ 0x18, /* ...##... */
+ 0x00, /* ........ */
+ 0xc3, /* ##....## */
+ 0x66, /* .##..##. */
+ 0x3c, /* ..####.. */
+};
+#endif
+
+static void refresh_display(void)
+{
+ for (int i=1; i<=8; i++)
+ set_reg(i, display[8-i]);
+}
+
+static signed char dirs[4][2] = {{1,0}, {0,-1}, {-1,0}, {0,1}};
+
+static byte snake_dir;
+#define SNAKE_MAX 65
+static byte snake[SNAKE_MAX][2];
+static byte snake_tail, snake_head;
+static byte food[2];
+static unsigned int rng_state = 0xdeadbeef;
+
+static byte buttons[2];
+
+static void gen_food(void)
+{
+ food[0] = (rng_state >> 28) & 7;
+ food[1] = (rng_state >> 20) & 7;
+ rng_state *= 0x3771cadb;
+}
+
+static void snake_init(void)
+{
+ snake[0][0] = 0;
+ snake[0][1] = 0;
+ snake_dir = 3;
+ snake_tail = 0;
+ snake_head = 1;
+ gen_food();
+}
+
+static void snake_show(void)
+{
+ memset(display, 0, 8);
+ for (int i=snake_tail; i != snake_head; i = (i+1) % SNAKE_MAX)
+ display[snake[i][0]] |= 1 << snake[i][1];
+ display[food[0]] |= 1 << food[1];
+ refresh_display();
+}
+
+static void snake_step(void)
+{
+ if (buttons[0])
+ snake_dir = (snake_dir+3) % 4;
+ else if (buttons[1])
+ snake_dir = (snake_dir+1) % 4;
+
+ int hi = (snake_head + SNAKE_MAX - 1) % SNAKE_MAX;
+ int x = snake[hi][0] + dirs[snake_dir][0];
+ int y = snake[hi][1] + dirs[snake_dir][1];
+
+ if (x < 0 || x > 7 || y < 0 || y > 7)
+ return;
+
+ snake[snake_head][0] = x;
+ snake[snake_head][1] = y;
+ snake_head = (snake_head + 1) % SNAKE_MAX;
+
+ if (x == food[0] && y == food[1])
+ gen_food();
+ else
+ snake_tail = (snake_tail + 1) % SNAKE_MAX;
+
+ snake_show();
+}
+
+int main(void)
+{
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
+ rcc_periph_clock_enable(RCC_GPIOA);
+ rcc_periph_clock_enable(RCC_GPIOB);
+ rcc_periph_clock_enable(RCC_GPIOC);
+
+ // PC13 = BluePill LED
+ gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
+
+ // PB12 = display DIN
+ // PB13 = display CS*
+ // PB14 = display CLK
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO12 | GPIO13 | GPIO14);
+ gpio_clear(GPIOB, GPIO12);
+ gpio_set(GPIOB, GPIO13);
+ gpio_clear(GPIOB, GPIO14);
+
+ // PA0 = left button
+ // PA1 = right button
+ gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO0 | GPIO1);
+ gpio_set(GPIOA, GPIO0 | GPIO1);
+
+ refresh_display();
+ set_reg(9, 0);
+ set_reg(10, 0); // intensity
+ set_reg(11, 7);
+ set_reg(13, 0); // test
+ set_reg(12, 1); // shutdown
+
+ snake_init();
+ snake_show();
+
+ for (;;) {
+ buttons[0] = buttons[1] = 0;
+ for (int i=0; i<1000000; i++) {
+ if (!gpio_get(GPIOA, GPIO0))
+ buttons[0] = 1;
+ if (!gpio_get(GPIOA, GPIO1))
+ buttons[1] = 1;
+ asm volatile ("");
+ }
+
+ gpio_toggle(GPIOC, GPIO13);
+ snake_step();
+ }
+}