2 * Testing Communication with Sinclair Air Conditioner
4 * (c) 2023 Martin Mareš <mj@ucw.cz>
9 #include <libopencm3/cm3/cortex.h>
10 #include <libopencm3/cm3/nvic.h>
11 #include <libopencm3/cm3/systick.h>
12 #include <libopencm3/cm3/scb.h>
13 #include <libopencm3/stm32/rcc.h>
14 #include <libopencm3/stm32/desig.h>
15 #include <libopencm3/stm32/gpio.h>
16 #include <libopencm3/stm32/usart.h>
17 #include <libopencm3/stm32/spi.h>
18 #include <libopencm3/stm32/exti.h>
19 #include <libopencm3/stm32/timer.h>
20 #include <libopencm3/usb/dfu.h>
21 #include <libopencm3/usb/usbd.h>
25 /*** Hardware init ***/
27 static void clock_init(void)
29 rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
31 rcc_periph_clock_enable(RCC_GPIOA);
32 rcc_periph_clock_enable(RCC_GPIOB);
33 rcc_periph_clock_enable(RCC_GPIOC);
34 rcc_periph_clock_enable(RCC_SPI2);
35 rcc_periph_clock_enable(RCC_USART1);
36 rcc_periph_clock_enable(RCC_USB);
37 rcc_periph_clock_enable(RCC_AFIO);
38 rcc_periph_clock_enable(RCC_TIM3);
40 rcc_periph_reset_pulse(RST_GPIOA);
41 rcc_periph_reset_pulse(RST_GPIOB);
42 rcc_periph_reset_pulse(RST_GPIOC);
43 rcc_periph_reset_pulse(RST_SPI2);
44 rcc_periph_reset_pulse(RST_USART1);
45 rcc_periph_reset_pulse(RST_USB);
46 rcc_periph_reset_pulse(RST_AFIO);
47 rcc_periph_reset_pulse(RST_TIM3);
50 static void gpio_init(void)
52 // PA9 = TXD1 for debugging console
53 // PA10 = RXD1 for debugging console
54 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
55 gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
57 // PC13 = BluePill LED
58 gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
59 gpio_clear(GPIOC, GPIO13);
61 // PB12 = SS2 (but used as GP input)
64 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO12);
65 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO13);
66 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO15);
69 static void usart_init(void)
71 usart_set_baudrate(USART1, 115200);
72 usart_set_databits(USART1, 8);
73 usart_set_stopbits(USART1, USART_STOPBITS_1);
74 usart_set_mode(USART1, USART_MODE_TX);
75 usart_set_parity(USART1, USART_PARITY_NONE);
76 usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
81 /*** System ticks ***/
83 static volatile u32 ms_ticks;
85 void sys_tick_handler(void)
90 static void tick_init(void)
92 systick_set_frequency(1000, CPU_CLOCK_MHZ * 1000000);
93 systick_counter_enable();
94 systick_interrupt_enable();
97 static void delay_ms(uint ms)
99 u32 start_ticks = ms_ticks;
100 while (ms_ticks - start_ticks < ms)
104 /*** Emulated TM1618 LED Driver ***/
106 static void tm_init(void)
108 // Configure SPI2 to receive
109 spi_set_receive_only_mode(SPI2);
110 spi_enable_software_slave_management(SPI2);
111 spi_set_nss_low(SPI2);
112 spi_send_lsb_first(SPI2);
113 spi_set_clock_polarity_0(SPI2);
114 spi_set_clock_phase_1(SPI2);
115 spi_enable_rx_buffer_not_empty_interrupt(SPI2);
116 nvic_enable_irq(NVIC_SPI2_IRQ);
120 // Since our optocouplers are negating, we cannot let STM32 to handle slave
121 // select in hardware. Instead, we let SS trigger an interrupt, which changes
122 // SPI state accordingly.
123 nvic_set_priority(NVIC_EXTI15_10_IRQ, 0);
124 nvic_enable_irq(NVIC_EXTI15_10_IRQ);
125 exti_set_trigger(EXTI12, EXTI_TRIGGER_BOTH);
126 exti_select_source(EXTI12, GPIOB);
127 exti_enable_request(EXTI12);
131 timer_set_prescaler(TIM3, CPU_CLOCK_MHZ-1); // 1 tick = 1 μs
132 timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_DOWN);
133 timer_update_on_overflow(TIM3);
134 timer_disable_preload(TIM3);
135 timer_one_shot_mode(TIM3);
136 timer_enable_irq(TIM3, TIM_DIER_UIE);
137 nvic_enable_irq(NVIC_TIM3_IRQ);
138 //timer_set_period(TIM3, 9999);
139 //timer_generate_event(TIM3, TIM_EGR_UG);
140 //timer_enable_counter(TIM3);
144 void exti15_10_isr(void)
146 // We require low latency here, so interaction with peripherals is open-coded.
148 if (GPIO_IDR(GPIOB) & (1 << 12))
149 SPI_CR1(SPI2) |= SPI_CR1_SSI;
151 SPI_CR1(SPI2) &= ~SPI_CR1_SSI;
153 SPI_CR1(SPI2) &= ~SPI_CR1_SSI;
158 static volatile byte tm_data[8];
159 static volatile byte tm_overrun;
161 static volatile byte tm_buffer[256];
162 static volatile uint tm_len;
164 static volatile uint tm_timeouts;
169 * The AC unit is sending a stream of commands like this:
171 * 00 - set mode: 4 grids, 8 segments
172 * 44 - will write to display memory, no auto-increment
173 * Cx - set memory address to x
174 * yy - data to write, two most-significant bits are always zero
175 * 8B - display ON, duty cycle 10/16
177 * So the only byte which can have top 2 bits both set is the Cx command.
178 * We make use of this to synchronize the stream.
180 if (SPI_SR(SPI2) & SPI_SR_OVR)
182 if (SPI_SR(SPI2) & SPI_SR_RXNE) {
183 byte x = SPI_DR(SPI2) ^ 0xff;
185 if (tm_len < ARRAY_SIZE(tm_buffer))
186 tm_buffer[tm_len++] = x;
188 static byte tm_address;
190 tm_data[tm_address & 7] = x;
192 } else if ((x & 0xc0) == 0xc0) {
195 timer_set_period(TIM3, 999);
196 timer_generate_event(TIM3, TIM_EGR_UG);
197 timer_enable_counter(TIM3);
203 if (TIM_SR(TIM3) & TIM_SR_UIF) {
204 TIM_SR(TIM3) &= ~TIM_SR_UIF;
206 spi_set_nss_high(SPI2);
207 spi_set_nss_low(SPI2);
212 static void tm_test(void)
214 u32 start_ticks = ms_ticks;
215 static byte tmbuf[256];
218 while (ms_ticks - start_ticks < 1000 && len < ARRAY_SIZE(tmbuf)) {
219 if (SPI_SR(SPI2) & SPI_SR_RXNE) {
220 tmbuf[len++] = SPI_DR(SPI2) ^ 0xff;
224 for (uint i=0; i<len; i++) {
225 debug_printf("%02x ", tmbuf[i]);
233 static void tm_show(void)
236 for (uint i=0; i<8; i++)
237 debug_printf(" %02x", tm_data[i]);
238 debug_printf(" o=%d t=%d", tm_overrun, tm_timeouts);
243 static byte tm_dumped;
244 if (!tm_dumped && tm_len == ARRAY_SIZE(tm_buffer)) {
245 for (uint i=0; i < tm_len; i++)
246 debug_printf("%02x ", tm_buffer[i]);
256 static usbd_device *usbd_dev;
259 STR_MANUFACTURER = 1,
264 static char usb_serial_number[13];
266 static const char *usb_strings[] = {
267 "United Computer Wizards",
268 "Sinclair Air Conditioner",
272 static const struct usb_device_descriptor device = {
273 .bLength = USB_DT_DEVICE_SIZE,
274 .bDescriptorType = USB_DT_DEVICE,
276 .bDeviceClass = 0xFF,
277 .bDeviceSubClass = 0,
278 .bDeviceProtocol = 0,
279 .bMaxPacketSize0 = 64,
283 .iManufacturer = STR_MANUFACTURER,
284 .iProduct = STR_PRODUCT,
285 .iSerialNumber = STR_SERIAL,
286 .bNumConfigurations = 1,
289 static const struct usb_endpoint_descriptor endpoints[] = {{
290 // Bulk end-point for sending values to the display
291 .bLength = USB_DT_ENDPOINT_SIZE,
292 .bDescriptorType = USB_DT_ENDPOINT,
293 .bEndpointAddress = 0x01,
294 .bmAttributes = USB_ENDPOINT_ATTR_BULK,
295 .wMaxPacketSize = 64,
299 static const struct usb_interface_descriptor iface = {
300 .bLength = USB_DT_INTERFACE_SIZE,
301 .bDescriptorType = USB_DT_INTERFACE,
302 .bInterfaceNumber = 0,
303 .bAlternateSetting = 0,
305 .bInterfaceClass = 0xFF,
306 .bInterfaceSubClass = 0,
307 .bInterfaceProtocol = 0,
309 .endpoint = endpoints,
312 static const struct usb_dfu_descriptor dfu_function = {
313 .bLength = sizeof(struct usb_dfu_descriptor),
314 .bDescriptorType = DFU_FUNCTIONAL,
315 .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
316 .wDetachTimeout = 255,
317 .wTransferSize = 1024,
318 .bcdDFUVersion = 0x0100,
321 static const struct usb_interface_descriptor dfu_iface = {
322 .bLength = USB_DT_INTERFACE_SIZE,
323 .bDescriptorType = USB_DT_INTERFACE,
324 .bInterfaceNumber = 1,
325 .bAlternateSetting = 0,
327 .bInterfaceClass = 0xFE,
328 .bInterfaceSubClass = 1,
329 .bInterfaceProtocol = 1,
332 .extra = &dfu_function,
333 .extralen = sizeof(dfu_function),
336 static const struct usb_interface ifaces[] = {{
338 .altsetting = &iface,
341 .altsetting = &dfu_iface,
344 static const struct usb_config_descriptor config = {
345 .bLength = USB_DT_CONFIGURATION_SIZE,
346 .bDescriptorType = USB_DT_CONFIGURATION,
349 .bConfigurationValue = 1,
351 .bmAttributes = 0x80,
352 .bMaxPower = 50, // multiplied by 2 mA
356 static byte usb_configured;
357 static uint8_t usbd_control_buffer[64];
359 static void dfu_detach_complete(usbd_device *dev UNUSED, struct usb_setup_data *req UNUSED)
361 // Reset to bootloader, which implements the rest of DFU
362 debug_printf("Switching to DFU\n");
367 static enum usbd_request_return_codes dfu_control_cb(usbd_device *dev UNUSED,
368 struct usb_setup_data *req,
369 uint8_t **buf UNUSED,
370 uint16_t *len UNUSED,
371 void (**complete)(usbd_device *dev, struct usb_setup_data *req))
373 if (req->bmRequestType != 0x21 || req->bRequest != DFU_DETACH)
374 return USBD_REQ_NOTSUPP;
376 *complete = dfu_detach_complete;
377 return USBD_REQ_HANDLED;
380 static void ep01_cb(usbd_device *dev, uint8_t ep UNUSED)
382 // We received a frame from the USB host
384 uint len = usbd_ep_read_packet(dev, 0x01, buf, 8);
385 debug_printf("USB: Host sent %u bytes\n", len);
388 static void set_config_cb(usbd_device *dev, uint16_t wValue UNUSED)
390 usbd_register_control_callback(
392 USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
393 USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
395 usbd_ep_setup(dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, ep01_cb);
399 static void reset_cb(void)
401 debug_printf("USB: Reset\n");
405 static volatile bool usb_event_pending;
407 void usb_lp_can_rx0_isr(void)
410 * We handle USB in the main loop to avoid race conditions between
411 * USB interrupts and other code. However, we need an interrupt to
412 * up the main loop from sleep.
414 * We set up only the low-priority ISR, because high-priority ISR handles
415 * only double-buffered bulk transfers and isochronous transfers.
417 nvic_disable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
418 usb_event_pending = 1;
421 static void usb_init(void)
423 // Simulate USB disconnect
424 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12);
425 gpio_clear(GPIOA, GPIO11 | GPIO12);
428 usbd_dev = usbd_init(
429 &st_usbfs_v1_usb_driver,
433 ARRAY_SIZE(usb_strings),
435 sizeof(usbd_control_buffer)
437 usbd_register_reset_callback(usbd_dev, reset_cb);
438 usbd_register_set_config_callback(usbd_dev, set_config_cb);
439 usb_event_pending = 1;
451 desig_get_unique_id_as_dfu(usb_serial_number);
453 debug_printf("Hello, world!\n");
461 if (ms_ticks - last_blink >= 1000) {
463 last_blink = ms_ticks;
467 if (usb_event_pending) {
469 usb_event_pending = 0;
470 nvic_clear_pending_irq(NVIC_USB_LP_CAN_RX0_IRQ);
471 nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
474 wait_for_interrupt();