]> mj.ucw.cz Git - home-hw.git/commitdiff
Merge branch 'master' of ssh://git.ucw.cz/home/mj/GIT/home-hw
authorMartin Mares <mj@ucw.cz>
Thu, 13 Jul 2023 14:30:30 +0000 (16:30 +0200)
committerMartin Mares <mj@ucw.cz>
Thu, 13 Jul 2023 14:30:30 +0000 (16:30 +0200)
test-sinclair/README
test-sinclair/main.c

index a62505be9f46cdff4878f35634ee2b2cc0c7a55e..cdb49b88c433b255843ec3fdced7e635c6be22f6 100644 (file)
@@ -1,19 +1,19 @@
 Assignment of peripherals and pins
 ==================================
 
-I2C1   display
+SPI2   emulated TM1618 LED driver
 USART1 debugging
 
 
                           Blue Pill pinout
                        +--------------------+
                        | VBATT         3.3V |
-BluePill LED           | PC13           GND |  display power
-                       | PC14            5V |  display power (white side of connector)
+BluePill LED           | PC13           GND |
+                       | PC14            5V |
                        | PC15           PB9 |
-                       | PA0            PB8 |  SFH5110 output (white side of connector)
-                       | PA1            PB7 |  SDA1 display (white side of connector)
-                       | PA2            PB6 |  SCL1 display
+                       | PA0            PB8 |
+                       | PA1            PB7 |
+                       | PA2            PB6 |
                        | PA3            PB5 |
                        | PA4            PB4 |
                        | PA5            PB3 |
@@ -23,8 +23,8 @@ BluePill LED          | PC13           GND |  display power
                        | PB1           PA10 |  RXD1 - debugging console
                        | PB10           PA9 |  TXD1 - debugging console
                        | PB11           PA8 |
-                       | RESET         PB15 |
-                       | 3.3 V         PB14 |
-                       | GND           PB13 |
-                       | GND           PB12 |
+                       | RESET         PB15 |  MOSI2 - LED driver data input
+                       | 3.3 V         PB14 |  MISO2 - unused
+                       | GND           PB13 |  SCK2 - LED driver clock
+                       | GND           PB12 |  SS2 - unused
                        +--------------------+
index 7e8659b4c23c74a8bef74b2f5b07395feb57a4c9..753bcfd9ffe951f94b4a9b2152792ec6eb69e9dd 100644 (file)
@@ -1,7 +1,7 @@
 /*
- *     Workshop Clock
+ *     Testing Communication with Sinclair Air Conditioner
  *
- *     (c) 2020 Martin Mareš <mj@ucw.cz>
+ *     (c) 2023 Martin Mareš <mj@ucw.cz>
  */
 
 #include "util.h"
@@ -14,7 +14,8 @@
 #include <libopencm3/stm32/desig.h>
 #include <libopencm3/stm32/gpio.h>
 #include <libopencm3/stm32/usart.h>
-#include <libopencm3/stm32/i2c.h>
+#include <libopencm3/stm32/spi.h>
+#include <libopencm3/stm32/timer.h>
 #include <libopencm3/usb/dfu.h>
 #include <libopencm3/usb/usbd.h>
 
 
 static void clock_init(void)
 {
-       rcc_clock_setup_in_hse_8mhz_out_72mhz();
+       rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
 
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_GPIOB);
        rcc_periph_clock_enable(RCC_GPIOC);
-       rcc_periph_clock_enable(RCC_I2C1);
+       rcc_periph_clock_enable(RCC_SPI2);
        rcc_periph_clock_enable(RCC_USART1);
        rcc_periph_clock_enable(RCC_USB);
+       rcc_periph_clock_enable(RCC_TIM3);
 
        rcc_periph_reset_pulse(RST_GPIOA);
        rcc_periph_reset_pulse(RST_GPIOB);
        rcc_periph_reset_pulse(RST_GPIOC);
-       rcc_periph_reset_pulse(RST_I2C1);
+       rcc_periph_reset_pulse(RST_SPI2);
        rcc_periph_reset_pulse(RST_USART1);
        rcc_periph_reset_pulse(RST_USB);
+       rcc_periph_reset_pulse(RST_TIM3);
 }
 
 static void gpio_init(void)
@@ -51,6 +54,11 @@ static void gpio_init(void)
        // PC13 = BluePill LED
        gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
        gpio_clear(GPIOC, GPIO13);
+
+       // PB13 = SCK2
+       // PB15 = MOSI2
+       gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO13);
+       gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO15);
 }
 
 static void usart_init(void)
@@ -88,6 +96,204 @@ static void delay_ms(uint ms)
                ;
 }
 
+/*** Emulated TM1618 LED Driver ***/
+
+/*
+ *  Theory of operation:
+ *
+ *  TM1618 communicates using a bi-directional SPI-like protocol.
+ *  The AC unit is sending a stream of commands like this once per ca. 4 ms:
+ *
+ *      00 - set mode: 4 grids, 8 segments
+ *      44 - will write to display memory, no auto-increment
+ *      Cx - set memory address to x
+ *      yy - data to write, two most-significant bits are always zero
+ *      8B - display ON, duty cycle 10/16
+ *
+ *  No read commands are issued, so we can simulate TM1618 using a pure SPI slave.
+ *
+ *  Commands are delimited using the STB* (strobe) pin, but since our opto-couplers
+ *  are negating, we cannot route this pin to SS (slave select) of our SPI.
+ *  We tried triggering an external interrupt by this pin, but it turned out
+ *  that the latency is too high.
+ *
+ *  Instead, we ignore STB* completely and implement a self-synchronizing receiver:
+ *
+ *    - The only byte which can have top 2 bits both set is the Cx command,
+ *      so we can use this to find memory addresses and data in the stream.
+ *      We can ignore all other commands.
+ *
+ *    - Whenever 1 ms passes since the last byte was received, we reset the SPI.
+ *      This allows us to recover from misaligned bytes.
+ */
+
+static void tm_init(void)
+{
+       // Configure SPI2 to receive
+       spi_set_receive_only_mode(SPI2);
+       spi_enable_software_slave_management(SPI2);
+       spi_set_nss_low(SPI2);
+       spi_send_lsb_first(SPI2);
+       spi_set_clock_polarity_0(SPI2);
+       spi_set_clock_phase_1(SPI2);
+       spi_enable_rx_buffer_not_empty_interrupt(SPI2);
+       nvic_enable_irq(NVIC_SPI2_IRQ);
+       spi_enable(SPI2);
+
+       // TIM3 will handle receive timeout
+       timer_set_prescaler(TIM3, CPU_CLOCK_MHZ-1);     // 1 tick = 1 μs
+       timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_DOWN);
+       timer_update_on_overflow(TIM3);
+        timer_disable_preload(TIM3);
+       timer_one_shot_mode(TIM3);
+       timer_enable_irq(TIM3, TIM_DIER_UIE);
+       nvic_enable_irq(NVIC_TIM3_IRQ);
+}
+
+/*
+ *  Data memory of TM1618:
+ *
+ *  [0]    .    .    .    -    -    -    -    -
+ *  [1]    .    .    -    -    HEAT .    .    .
+ *  [2]    .    .    .    DRY  -    SLP  MED  LOW
+ *  [3]    .    .    HIGH AUTO COOL .    .    .
+ *  [4]    .    .    .    B2   -    G2   D2   C2
+ *  [5]    .    .    E2   F2   A2   .    .    .
+ *  [6]    .    .    .    B1   -    G1   D1   C1
+ *  [7]    .    .    E1   F1   A1   .    .    .
+ *
+ *  "." is an always-zero bit not defined by TM1618, "-" is defined, but not used by AC.
+ */
+static volatile byte tm_data[8];
+static volatile uint tm_overruns;
+
+static volatile byte tm_buffer[256];
+static volatile uint tm_len;
+
+/*
+ *
+ *  Display segments:
+ *
+ *      +--A--+
+ *      |     |
+ *      F     B
+ *      |     |
+ *      +--G--+
+ *      |     |
+ *      E     C
+ *      |     |
+ *      +--D--+
+ */
+
+enum tm_seg {
+       SEGa = 0x0800,
+       SEGb = 0x0010,
+       SEGc = 0x0001,
+       SEGd = 0x0002,
+       SEGe = 0x2000,
+       SEGf = 0x1000,
+       SEGg = 0x0004,
+};
+
+static const u16 tm_digits[10] = {
+       [0] = SEGa | SEGb | SEGc | SEGd | SEGe | SEGf,
+       [1] = SEGb | SEGc,
+       [2] = SEGa | SEGb | SEGd | SEGe | SEGg,
+       [3] = SEGa | SEGb | SEGc | SEGd | SEGg,
+       [4] = SEGb | SEGc | SEGf | SEGg,
+       [5] = SEGa | SEGc | SEGd | SEGf | SEGg,
+       [6] = SEGa | SEGc | SEGd | SEGe | SEGf | SEGg,
+       [7] = SEGa | SEGb | SEGc,
+       [8] = SEGa | SEGb | SEGc | SEGd | SEGe | SEGf | SEGg,
+       [9] = SEGa | SEGb | SEGc | SEGd | SEGf | SEGg,
+};
+
+static volatile uint tm_timeouts;
+
+void spi2_isr(void)
+{
+       if (SPI_SR(SPI2) & SPI_SR_OVR)
+               tm_overruns++;
+       if (SPI_SR(SPI2) & SPI_SR_RXNE) {
+               byte x = SPI_DR(SPI2) ^ 0xff;
+#if 0
+               if (tm_len < ARRAY_SIZE(tm_buffer))
+                       tm_buffer[tm_len++] = x;
+#endif
+               static byte tm_address;
+               if (tm_address) {
+                       tm_data[tm_address & 7] = x;
+                       tm_address = 0;
+               } else if ((x & 0xc0) == 0xc0) {
+                       tm_address = x;
+               }
+               timer_set_period(TIM3, 999);
+               timer_generate_event(TIM3, TIM_EGR_UG);
+               timer_enable_counter(TIM3);
+       }
+}
+
+void tim3_isr(void)
+{
+       if (TIM_SR(TIM3) & TIM_SR_UIF) {
+               TIM_SR(TIM3) &= ~TIM_SR_UIF;
+               tm_timeouts++;
+               spi_set_nss_high(SPI2);
+               spi_set_nss_low(SPI2);
+       }
+}
+
+static void tm_show(void)
+{
+       debug_printf("TM:");
+       for (uint i=0; i<8; i++)
+               debug_printf(" %02x", tm_data[i]);
+       debug_printf(" o=%d t=%d", tm_overruns, tm_timeouts);
+
+       debug_printf(" =>");
+       if (tm_data[1] & 0x08)
+               debug_printf(" HEAT");
+       if (tm_data[2] & 0x10)
+               debug_printf(" DRY");
+       if (tm_data[2] & 0x04)
+               debug_printf(" SLEEP");
+       if (tm_data[2] & 0x02)
+               debug_printf(" MED");
+       if (tm_data[2] & 0x01)
+               debug_printf(" LOW");
+       if (tm_data[3] & 0x20)
+               debug_printf(" HIGH");
+       if (tm_data[3] & 0x10)
+               debug_printf(" AUTO");
+       if (tm_data[3] & 0x08)
+               debug_printf(" COOL");
+
+       debug_putc(' ');
+       for (int i=0; i<2; i++) {
+               uint x = (tm_data[7-2*i] << 8) | tm_data[6-2*i];
+               uint j = 0;
+               while (j < 10 && tm_digits[j] != x)
+                       j++;
+               if (j == 10)
+                       debug_putc('?');
+               else
+                       debug_putc('0' + j);
+       }
+
+       debug_putc('\n');
+
+#if 0
+       static byte tm_dumped;
+       if (!tm_dumped && tm_len == ARRAY_SIZE(tm_buffer)) {
+               for (uint i=0; i < tm_len; i++)
+                       debug_printf("%02x ", tm_buffer[i]);
+               debug_putc('\n');
+               // tm_dumped = 1;
+               tm_len = 0;
+       }
+#endif
+}
+
 /*** USB ***/
 
 static usbd_device *usbd_dev;
@@ -289,6 +495,7 @@ int main(void)
 
        debug_printf("Hello, world!\n");
 
+       tm_init();
        usb_init();
 
        u32 last_blink = 0;
@@ -297,6 +504,7 @@ int main(void)
                if (ms_ticks - last_blink >= 1000) {
                        debug_led_toggle();
                        last_blink = ms_ticks;
+                       tm_show();
                }
 
                if (usb_event_pending) {