]> mj.ucw.cz Git - home-hw.git/blob - test-sinclair/main.c
test-sinclair: SS is not used at all
[home-hw.git] / test-sinclair / main.c
1 /*
2  *      Testing Communication with Sinclair Air Conditioner
3  *
4  *      (c) 2023 Martin Mareš <mj@ucw.cz>
5  */
6
7 #include "util.h"
8
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/timer.h>
19 #include <libopencm3/usb/dfu.h>
20 #include <libopencm3/usb/usbd.h>
21
22 #include <string.h>
23
24 /*** Hardware init ***/
25
26 static void clock_init(void)
27 {
28         rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
29
30         rcc_periph_clock_enable(RCC_GPIOA);
31         rcc_periph_clock_enable(RCC_GPIOB);
32         rcc_periph_clock_enable(RCC_GPIOC);
33         rcc_periph_clock_enable(RCC_SPI2);
34         rcc_periph_clock_enable(RCC_USART1);
35         rcc_periph_clock_enable(RCC_USB);
36         rcc_periph_clock_enable(RCC_TIM3);
37
38         rcc_periph_reset_pulse(RST_GPIOA);
39         rcc_periph_reset_pulse(RST_GPIOB);
40         rcc_periph_reset_pulse(RST_GPIOC);
41         rcc_periph_reset_pulse(RST_SPI2);
42         rcc_periph_reset_pulse(RST_USART1);
43         rcc_periph_reset_pulse(RST_USB);
44         rcc_periph_reset_pulse(RST_TIM3);
45 }
46
47 static void gpio_init(void)
48 {
49         // PA9 = TXD1 for debugging console
50         // PA10 = RXD1 for debugging console
51         gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
52         gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
53
54         // PC13 = BluePill LED
55         gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
56         gpio_clear(GPIOC, GPIO13);
57
58         // PB13 = SCK2
59         // PB15 = MOSI2
60         gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO13);
61         gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO15);
62 }
63
64 static void usart_init(void)
65 {
66         usart_set_baudrate(USART1, 115200);
67         usart_set_databits(USART1, 8);
68         usart_set_stopbits(USART1, USART_STOPBITS_1);
69         usart_set_mode(USART1, USART_MODE_TX);
70         usart_set_parity(USART1, USART_PARITY_NONE);
71         usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
72
73         usart_enable(USART1);
74 }
75
76 /*** System ticks ***/
77
78 static volatile u32 ms_ticks;
79
80 void sys_tick_handler(void)
81 {
82         ms_ticks++;
83 }
84
85 static void tick_init(void)
86 {
87         systick_set_frequency(1000, CPU_CLOCK_MHZ * 1000000);
88         systick_counter_enable();
89         systick_interrupt_enable();
90 }
91
92 static void delay_ms(uint ms)
93 {
94         u32 start_ticks = ms_ticks;
95         while (ms_ticks - start_ticks < ms)
96                 ;
97 }
98
99 /*** Emulated TM1618 LED Driver ***/
100
101 /*
102  *  Theory of operation:
103  *
104  *  TM1618 communicates using a bi-directional SPI-like protocol.
105  *  The AC unit is sending a stream of commands like this once per ca. 4 ms:
106  *
107  *      00 - set mode: 4 grids, 8 segments
108  *      44 - will write to display memory, no auto-increment
109  *      Cx - set memory address to x
110  *      yy - data to write, two most-significant bits are always zero
111  *      8B - display ON, duty cycle 10/16
112  *
113  *  No read commands are issued, so we can simulate TM1618 using a pure SPI slave.
114  *
115  *  Commands are delimited using the STB* (strobe) pin, but since our opto-couplers
116  *  are negating, we cannot route this pin to SS (slave select) of our SPI.
117  *  We tried triggering an external interrupt by this pin, but it turned out
118  *  that the latency is too high.
119  *
120  *  Instead, we ignore STB* completely and implement a self-synchronizing receiver:
121  *
122  *    - The only byte which can have top 2 bits both set is the Cx command,
123  *      so we can use this to find memory addresses and data in the stream.
124  *      We can ignore all other commands.
125  *
126  *    - Whenever 1 ms passes since the last byte was received, we reset the SPI.
127  *      This allows us to recover from misaligned bytes.
128  */
129
130 static void tm_init(void)
131 {
132         // Configure SPI2 to receive
133         spi_set_receive_only_mode(SPI2);
134         spi_enable_software_slave_management(SPI2);
135         spi_set_nss_low(SPI2);
136         spi_send_lsb_first(SPI2);
137         spi_set_clock_polarity_0(SPI2);
138         spi_set_clock_phase_1(SPI2);
139         spi_enable_rx_buffer_not_empty_interrupt(SPI2);
140         nvic_enable_irq(NVIC_SPI2_IRQ);
141         spi_enable(SPI2);
142
143         // TIM3 will handle receive timeout
144         timer_set_prescaler(TIM3, CPU_CLOCK_MHZ-1);     // 1 tick = 1 μs
145         timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_DOWN);
146         timer_update_on_overflow(TIM3);
147         timer_disable_preload(TIM3);
148         timer_one_shot_mode(TIM3);
149         timer_enable_irq(TIM3, TIM_DIER_UIE);
150         nvic_enable_irq(NVIC_TIM3_IRQ);
151 }
152
153 static volatile byte tm_data[8];
154 static volatile byte tm_overrun;
155
156 static volatile byte tm_buffer[256];
157 static volatile uint tm_len;
158
159 static volatile uint tm_timeouts;
160
161 void spi2_isr(void)
162 {
163         if (SPI_SR(SPI2) & SPI_SR_OVR)
164                 tm_overrun = 1;
165         if (SPI_SR(SPI2) & SPI_SR_RXNE) {
166                 byte x = SPI_DR(SPI2) ^ 0xff;
167 #if 0
168                 if (tm_len < ARRAY_SIZE(tm_buffer))
169                         tm_buffer[tm_len++] = x;
170 #endif
171                 static byte tm_address;
172                 if (tm_address) {
173                         tm_data[tm_address & 7] = x;
174                         tm_address = 0;
175                 } else if ((x & 0xc0) == 0xc0) {
176                         tm_address = x;
177                 }
178                 timer_set_period(TIM3, 999);
179                 timer_generate_event(TIM3, TIM_EGR_UG);
180                 timer_enable_counter(TIM3);
181         }
182 }
183
184 void tim3_isr(void)
185 {
186         if (TIM_SR(TIM3) & TIM_SR_UIF) {
187                 TIM_SR(TIM3) &= ~TIM_SR_UIF;
188                 tm_timeouts++;
189                 spi_set_nss_high(SPI2);
190                 spi_set_nss_low(SPI2);
191         }
192 }
193
194 static void tm_show(void)
195 {
196         debug_printf("TM:");
197         for (uint i=0; i<8; i++)
198                 debug_printf(" %02x", tm_data[i]);
199         debug_printf(" o=%d t=%d", tm_overrun, tm_timeouts);
200         tm_overrun = 0;
201         debug_putc('\n');
202
203 #if 0
204         static byte tm_dumped;
205         if (!tm_dumped && tm_len == ARRAY_SIZE(tm_buffer)) {
206                 for (uint i=0; i < tm_len; i++)
207                         debug_printf("%02x ", tm_buffer[i]);
208                 debug_putc('\n');
209                 // tm_dumped = 1;
210                 tm_len = 0;
211         }
212 #endif
213 }
214
215 /*** USB ***/
216
217 static usbd_device *usbd_dev;
218
219 enum usb_string {
220         STR_MANUFACTURER = 1,
221         STR_PRODUCT,
222         STR_SERIAL,
223 };
224
225 static char usb_serial_number[13];
226
227 static const char *usb_strings[] = {
228         "United Computer Wizards",
229         "Sinclair Air Conditioner",
230         usb_serial_number,
231 };
232
233 static const struct usb_device_descriptor device = {
234         .bLength = USB_DT_DEVICE_SIZE,
235         .bDescriptorType = USB_DT_DEVICE,
236         .bcdUSB = 0x0200,
237         .bDeviceClass = 0xFF,
238         .bDeviceSubClass = 0,
239         .bDeviceProtocol = 0,
240         .bMaxPacketSize0 = 64,
241         .idVendor = 0x4242,
242         .idProduct = 0x0007,
243         .bcdDevice = 0x0000,
244         .iManufacturer = STR_MANUFACTURER,
245         .iProduct = STR_PRODUCT,
246         .iSerialNumber = STR_SERIAL,
247         .bNumConfigurations = 1,
248 };
249
250 static const struct usb_endpoint_descriptor endpoints[] = {{
251         // Bulk end-point for sending values to the display
252         .bLength = USB_DT_ENDPOINT_SIZE,
253         .bDescriptorType = USB_DT_ENDPOINT,
254         .bEndpointAddress = 0x01,
255         .bmAttributes = USB_ENDPOINT_ATTR_BULK,
256         .wMaxPacketSize = 64,
257         .bInterval = 1,
258 }};
259
260 static const struct usb_interface_descriptor iface = {
261         .bLength = USB_DT_INTERFACE_SIZE,
262         .bDescriptorType = USB_DT_INTERFACE,
263         .bInterfaceNumber = 0,
264         .bAlternateSetting = 0,
265         .bNumEndpoints = 1,
266         .bInterfaceClass = 0xFF,
267         .bInterfaceSubClass = 0,
268         .bInterfaceProtocol = 0,
269         .iInterface = 0,
270         .endpoint = endpoints,
271 };
272
273 static const struct usb_dfu_descriptor dfu_function = {
274         .bLength = sizeof(struct usb_dfu_descriptor),
275         .bDescriptorType = DFU_FUNCTIONAL,
276         .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
277         .wDetachTimeout = 255,
278         .wTransferSize = 1024,
279         .bcdDFUVersion = 0x0100,
280 };
281
282 static const struct usb_interface_descriptor dfu_iface = {
283         .bLength = USB_DT_INTERFACE_SIZE,
284         .bDescriptorType = USB_DT_INTERFACE,
285         .bInterfaceNumber = 1,
286         .bAlternateSetting = 0,
287         .bNumEndpoints = 0,
288         .bInterfaceClass = 0xFE,
289         .bInterfaceSubClass = 1,
290         .bInterfaceProtocol = 1,
291         .iInterface = 0,
292
293         .extra = &dfu_function,
294         .extralen = sizeof(dfu_function),
295 };
296
297 static const struct usb_interface ifaces[] = {{
298         .num_altsetting = 1,
299         .altsetting = &iface,
300 }, {
301         .num_altsetting = 1,
302         .altsetting = &dfu_iface,
303 }};
304
305 static const struct usb_config_descriptor config = {
306         .bLength = USB_DT_CONFIGURATION_SIZE,
307         .bDescriptorType = USB_DT_CONFIGURATION,
308         .wTotalLength = 0,
309         .bNumInterfaces = 2,
310         .bConfigurationValue = 1,
311         .iConfiguration = 0,
312         .bmAttributes = 0x80,
313         .bMaxPower = 50,        // multiplied by 2 mA
314         .interface = ifaces,
315 };
316
317 static byte usb_configured;
318 static uint8_t usbd_control_buffer[64];
319
320 static void dfu_detach_complete(usbd_device *dev UNUSED, struct usb_setup_data *req UNUSED)
321 {
322         // Reset to bootloader, which implements the rest of DFU
323         debug_printf("Switching to DFU\n");
324         debug_flush();
325         scb_reset_core();
326 }
327
328 static enum usbd_request_return_codes dfu_control_cb(usbd_device *dev UNUSED,
329         struct usb_setup_data *req,
330         uint8_t **buf UNUSED,
331         uint16_t *len UNUSED,
332         void (**complete)(usbd_device *dev, struct usb_setup_data *req))
333 {
334         if (req->bmRequestType != 0x21 || req->bRequest != DFU_DETACH)
335                 return USBD_REQ_NOTSUPP;
336
337         *complete = dfu_detach_complete;
338         return USBD_REQ_HANDLED;
339 }
340
341 static void ep01_cb(usbd_device *dev, uint8_t ep UNUSED)
342 {
343         // We received a frame from the USB host
344         byte buf[8];
345         uint len = usbd_ep_read_packet(dev, 0x01, buf, 8);
346         debug_printf("USB: Host sent %u bytes\n", len);
347 }
348
349 static void set_config_cb(usbd_device *dev, uint16_t wValue UNUSED)
350 {
351         usbd_register_control_callback(
352                 dev,
353                 USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
354                 USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
355                 dfu_control_cb);
356         usbd_ep_setup(dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, ep01_cb);
357         usb_configured = 1;
358 }
359
360 static void reset_cb(void)
361 {
362         debug_printf("USB: Reset\n");
363         usb_configured = 0;
364 }
365
366 static volatile bool usb_event_pending;
367
368 void usb_lp_can_rx0_isr(void)
369 {
370         /*
371          *  We handle USB in the main loop to avoid race conditions between
372          *  USB interrupts and other code. However, we need an interrupt to
373          *  up the main loop from sleep.
374          *
375          *  We set up only the low-priority ISR, because high-priority ISR handles
376          *  only double-buffered bulk transfers and isochronous transfers.
377          */
378         nvic_disable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
379         usb_event_pending = 1;
380 }
381
382 static void usb_init(void)
383 {
384         // Simulate USB disconnect
385         gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12);
386         gpio_clear(GPIOA, GPIO11 | GPIO12);
387         delay_ms(100);
388
389         usbd_dev = usbd_init(
390                 &st_usbfs_v1_usb_driver,
391                 &device,
392                 &config,
393                 usb_strings,
394                 ARRAY_SIZE(usb_strings),
395                 usbd_control_buffer,
396                 sizeof(usbd_control_buffer)
397         );
398         usbd_register_reset_callback(usbd_dev, reset_cb);
399         usbd_register_set_config_callback(usbd_dev, set_config_cb);
400         usb_event_pending = 1;
401 }
402
403 /*** Main ***/
404
405 int main(void)
406 {
407         clock_init();
408         gpio_init();
409         usart_init();
410
411         tick_init();
412         desig_get_unique_id_as_dfu(usb_serial_number);
413
414         debug_printf("Hello, world!\n");
415
416         tm_init();
417         usb_init();
418
419         u32 last_blink = 0;
420
421         for (;;) {
422                 if (ms_ticks - last_blink >= 1000) {
423                         debug_led_toggle();
424                         last_blink = ms_ticks;
425                         tm_show();
426                 }
427
428                 if (usb_event_pending) {
429                         usbd_poll(usbd_dev);
430                         usb_event_pending = 0;
431                         nvic_clear_pending_irq(NVIC_USB_LP_CAN_RX0_IRQ);
432                         nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
433                 }
434
435                 wait_for_interrupt();
436         }
437
438         return 0;
439 }