/*
- * 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"
#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/exti.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_AFIO);
+ 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_AFIO);
+ rcc_periph_reset_pulse(RST_TIM3);
}
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);
+
+ // PB12 = SS2 (but used as GP input)
+ // PB13 = SCK2
+ // PB15 = MOSI2
+ gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO12);
+ 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)
;
}
+/*** Emulated TM1618 LED Driver ***/
+
+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);
+
+#if 0
+ // Since our optocouplers are negating, we cannot let STM32 to handle slave
+ // select in hardware. Instead, we let SS trigger an interrupt, which changes
+ // SPI state accordingly.
+ nvic_set_priority(NVIC_EXTI15_10_IRQ, 0);
+ nvic_enable_irq(NVIC_EXTI15_10_IRQ);
+ exti_set_trigger(EXTI12, EXTI_TRIGGER_BOTH);
+ exti_select_source(EXTI12, GPIOB);
+ exti_enable_request(EXTI12);
+#endif
+
+#if 1
+ 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);
+ //timer_set_period(TIM3, 9999);
+ //timer_generate_event(TIM3, TIM_EGR_UG);
+ //timer_enable_counter(TIM3);
+#endif
+}
+
+void exti15_10_isr(void)
+{
+ // We require low latency here, so interaction with peripherals is open-coded.
+#if 0
+ if (GPIO_IDR(GPIOB) & (1 << 12))
+ SPI_CR1(SPI2) |= SPI_CR1_SSI;
+ else
+ SPI_CR1(SPI2) &= ~SPI_CR1_SSI;
+#else
+ SPI_CR1(SPI2) &= ~SPI_CR1_SSI;
+#endif
+ EXTI_PR = EXTI12;
+}
+
+static volatile byte tm_data[8];
+static volatile byte tm_overrun;
+
+static volatile byte tm_buffer[256];
+static volatile uint tm_len;
+
+static volatile uint tm_timeouts;
+
+void spi2_isr(void)
+{
+ /*
+ * The AC unit is sending a stream of commands like this:
+ *
+ * 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
+ *
+ * So the only byte which can have top 2 bits both set is the Cx command.
+ * We make use of this to synchronize the stream.
+ */
+ if (SPI_SR(SPI2) & SPI_SR_OVR)
+ tm_overrun = 1;
+ 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);
+ }
+}
+
+#if 0
+static void tm_test(void)
+{
+ u32 start_ticks = ms_ticks;
+ static byte tmbuf[256];
+ uint len = 0;
+
+ while (ms_ticks - start_ticks < 1000 && len < ARRAY_SIZE(tmbuf)) {
+ if (SPI_SR(SPI2) & SPI_SR_RXNE) {
+ tmbuf[len++] = SPI_DR(SPI2) ^ 0xff;
+ }
+ }
+
+ for (uint i=0; i<len; i++) {
+ debug_printf("%02x ", tmbuf[i]);
+ if ((i % 5) == 4)
+ debug_putc('\n');
+ }
+ debug_putc('\n');
+}
+#endif
+
+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_overrun, tm_timeouts);
+ tm_overrun = 0;
+ 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;
debug_printf("Hello, world!\n");
+ tm_init();
usb_init();
u32 last_blink = 0;
if (ms_ticks - last_blink >= 1000) {
debug_led_toggle();
last_blink = ms_ticks;
+ tm_show();
}
if (usb_event_pending) {