rushabhcodes/desk-fidget-counter

A desk knob assembly combining a circuit board with PETG-printed mechanical parts and vendor CAD-modeled hardware.

Version
5.6.1
License
MIT
Stars
0

firmware/lcd.c

// SPDX-License-Identifier: MIT
#include "lcd.h"
#include "stm32f0xx.h"
// Mode 0, SPI2 12MHz; one 480-byte scanline per task call. No full framebuffer.
static uint8_t phase, init_index;
static unsigned scanline;
static uint32_t deadline;
static bool healthy = true;
static LcdView frame;
static uint16_t row[240];
static const struct {
  uint8_t cmd, n, data[12];
} regs[] = {
#include "gc9a01-registers.inc"
};
static void pin(unsigned p, bool high) {
  if (p == 12)
    GPIOA->BSRR = 1u << (8 + (high ? 0 : 16));
  else
    GPIOB->BSRR = 1u << (p + (high ? 0 : 16));
}
static bool byte(uint8_t b) {
  unsigned budget = 4800;
  while (!(SPI2->SR & SPI_SR_TXE))
    if (!--budget)
      return false;
  *(volatile uint8_t *)&SPI2->DR = b;
  return true;
}
static bool finish(void) {
  unsigned budget = 48000;
  while (SPI2->SR & SPI_SR_BSY)
    if (!--budget)
      return false;
  return true;
}
static bool command(uint8_t cmd, const uint8_t *data, unsigned n) {
  pin(12, false);
  pin(14, false);
  bool ok = byte(cmd) && finish();
  pin(14, true);
  for (unsigned i = 0; ok && i < n; i++)
    ok = byte(data[i]);
  ok = ok && finish();
  pin(12, true);
  return ok;
}
void lcd_init(void) {
  pin(10, false);
  pin(11, false);
  pin(12, true);
  pin(14, false);
  GPIOA->MODER = (GPIOA->MODER & ~(3u << 16)) | (1u << 16); // PA8 CS
  for (unsigned p = 10; p <= 15; p++) {
    if (p == 12)
      continue; // PB12 unused; software CS is PA8
    unsigned mode = (p == 13 || p == 15) ? 2 : 1;
    GPIOB->MODER = (GPIOB->MODER & ~(3u << (p * 2))) | (mode << (p * 2));
    GPIOB->OSPEEDR |= 3u << (p * 2);
  }
  GPIOB->AFR[1] &= ~((15u << 20) | (15u << 28)); // AF0 SPI2 PB13/PB15
  SPI2->CR2 = (7u << 8) | SPI_CR2_FRXTH;
  SPI2->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_0 |
              SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SPE;
  phase = 0;
  init_index=0; scanline=0;
  healthy = true;
  deadline = 20;
}
void lcd_power(bool enabled, uint32_t now) {
  if (enabled) { lcd_init(); deadline=now+20; return; }
  pin(11,false); pin(10,false); pin(12,false); pin(14,false);
  SPI2->CR1 &= ~SPI_CR1_SPE;
  // No high signals may back-power the unpowered module through its inputs.
  for (unsigned p=10;p<=15;p++) {
    if (p==12) continue;
    GPIOB->BSRR=1u<<(p+16);
    GPIOB->MODER=(GPIOB->MODER&~(3u<<(p*2)))|(1u<<(p*2));
  }
}
bool lcd_ok(void) { return healthy; }
void lcd_task(uint32_t now, const LcdView *view) {
  if (!healthy || (int32_t)(now - deadline) < 0)
    return;
  bool ok = true;
  switch (phase) {
  case 0:
    pin(10, true);
    deadline = now + 120;
    phase = 1;
    break;
  case 1:
    if (init_index < sizeof(regs) / sizeof(regs[0])) {
      const unsigned i = init_index++;
      ok = command(regs[i].cmd, regs[i].data, regs[i].n);
    } else {
      ok = command(0x11, 0, 0);
      deadline = now + 120;
      phase = 2;
    }
    break;
  case 2:
    ok = command(0x29, 0, 0);
    deadline = now + 20;
    phase = 3;
    break;
  case 3:
    if (!scanline)
      frame = *view; // Coherent UI for the full refresh
    lcd_render_row(&frame, scanline, row);
    {
      uint8_t x[] = {0, 0, 0, 239},
              y[] = {0, (uint8_t)scanline, 0, (uint8_t)scanline};
      ok = command(0x2a, x, 4) && command(0x2b, y, 4) && command(0x2c, 0, 0);
      pin(12, false);
      pin(14, true);
      for (unsigned i = 0; ok && i < 240; i++)
        ok = byte(row[i] >> 8) && byte(row[i] & 255);
      ok = ok && finish();
      pin(12, true);
    }
    if (++scanline == 240) {
      scanline = 0;
      pin(11, true);
      deadline = now + 100;
    }
    break;
  }
  if (!ok) {
    healthy = false;
    pin(11, false);
    pin(12, true);
  } // USB and counting continue
}