+++ /dev/null
-/*
- * Workshop Clock
- *
- * (c) 2020-2023 Martin Mareš <mj@ucw.cz>
- */
-
-#include "util.h"
-
-#include <libopencm3/cm3/cortex.h>
-#include <libopencm3/cm3/nvic.h>
-#include <libopencm3/cm3/systick.h>
-#include <libopencm3/cm3/scb.h>
-#include <libopencm3/stm32/rcc.h>
-#include <libopencm3/stm32/desig.h>
-#include <libopencm3/stm32/gpio.h>
-#include <libopencm3/stm32/usart.h>
-#include <libopencm3/stm32/i2c.h>
-#include <libopencm3/stm32/timer.h>
-#include <libopencm3/usb/dfu.h>
-#include <libopencm3/usb/usbd.h>
-
-#include <string.h>
-
-static void ep82_send(u32 key_code);
-
-/*** Hardware init ***/
-
-static void clock_init(void)
-{
- 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_USART1);
- rcc_periph_clock_enable(RCC_USB);
- rcc_periph_clock_enable(RCC_TIM1);
-
- 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_USART1);
- rcc_periph_reset_pulse(RST_USB);
- rcc_periph_reset_pulse(RST_TIM1);
-}
-
-static void gpio_init(void)
-{
- // PA9 = TXD1 for debugging console
- // PA10 = RXD1 for debugging console
- gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
- gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
-
- // PC13 = BluePill LED
- gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
- gpio_clear(GPIOC, GPIO13);
-
- // PB7 = SDA for display controller
- // PB6 = SCL for display controller
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO6 | GPIO7);
-
- // PA8 = SFH5110 output (5V tolerant) connected to TIM1_CH1
- gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO8);
-}
-
-static void usart_init(void)
-{
- usart_set_baudrate(USART1, 115200);
- usart_set_databits(USART1, 8);
- usart_set_stopbits(USART1, USART_STOPBITS_1);
- usart_set_mode(USART1, USART_MODE_TX);
- usart_set_parity(USART1, USART_PARITY_NONE);
- usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
-
- usart_enable(USART1);
-}
-
-/*** System ticks ***/
-
-static volatile u32 ms_ticks;
-
-void sys_tick_handler(void)
-{
- ms_ticks++;
-}
-
-static void tick_init(void)
-{
- systick_set_frequency(1000, CPU_CLOCK_MHZ * 1000000);
- systick_counter_enable();
- systick_interrupt_enable();
-}
-
-static void delay_ms(uint ms)
-{
- u32 start_ticks = ms_ticks;
- while (ms_ticks - start_ticks < ms)
- ;
-}
-
-/*** Display ***/
-
-/*
- * Display digits:
- *
- * ---- 40 ----
- * | |
- * | |
- * 20 80
- * | |
- * | |
- * ---- 10 ----
- * | |
- * | |
- * 08 02
- * | |
- * | |
- * ---- 04 ----
- * (01)
- */
-
-static byte disp[4];
-static byte ctrl = 0x56;
-
-static const byte disp_font[] = {
- [0] = 0xee,
- [1] = 0x82,
- [2] = 0xdc,
- [3] = 0xd6,
- [4] = 0xb2,
- [5] = 0x76,
- [6] = 0x7e,
- [7] = 0xc2,
- [8] = 0xfe,
- [9] = 0xf6,
- [10] = 0xea,
- [11] = 0x3e,
- [12] = 0x6c,
- [13] = 0x9e,
- [14] = 0x7c,
- [15] = 0x78,
-};
-
-static void display_update(void)
-{
- // debug_puts("Display update\n");
-
- byte cmds[4];
- cmds[0] = 0;
- cmds[1] = ctrl;
- cmds[2] = ((disp[1] & 0x88) >> 3) | ((disp[1] & 0x44) >> 1) | ((disp[1] & 0x22) << 1) | ((disp[1] & 0x11) << 3);
- cmds[3] = disp[0];
- i2c_transfer7(I2C1, 0x76/2, (byte *) cmds, sizeof(cmds), NULL, 0);
-
- cmds[2] = ((disp[3] & 0x88) >> 3) | ((disp[3] & 0x44) >> 1) | ((disp[3] & 0x22) << 1) | ((disp[3] & 0x11) << 3);
- cmds[3] = disp[2];
- i2c_transfer7(I2C1, 0x70/2, (byte *) cmds, sizeof(cmds), NULL, 0);
-
- // debug_puts("Update done\n");
-}
-
-static void display_init(void)
-{
- debug_puts("I2C init\n");
- i2c_peripheral_disable(I2C1);
- i2c_set_speed(I2C1, i2c_speed_sm_100k, rcc_apb1_frequency / 1000000);
- i2c_peripheral_enable(I2C1);
-
- disp[0] = 0x10;
- disp[1] = 0x10;
- disp[2] = 0x10;
- disp[3] = 0x10;
- display_update();
-}
-
-/*** Infrared Remote Control ***/
-
-static void ir_init(void)
-{
- debug_puts("IR init\n");
-
- // TIM1 will measure pulses and spaces between them with 1μs resolution
- timer_set_prescaler(TIM1, 71); // 72 MHz / 72 = 1 MHz
- timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
- timer_set_period(TIM1, 65535);
- timer_update_on_overflow(TIM1);
-
- // IC1 will trigger on TI1 (TIM1_CH1) falling edge
- timer_ic_set_input(TIM1, TIM_IC1, TIM_IC_IN_TI1);
- // timer_ic_set_filter(TIM1, TIM_IC1, TIM_IC_OFF);
- timer_set_oc_polarity_low(TIM1, TIM_OC1); // OC functions affect IC, too
-
- // IC2 will trigger on TI1 (TIM1_CH1) rising edge
- timer_ic_set_input(TIM1, TIM_IC2, TIM_IC_IN_TI1);
- timer_set_oc_polarity_high(TIM1, TIM_OC2);
-
- // OC3 will trigger on a break longer than 50 ms
- timer_set_oc_mode(TIM1, TIM_OC3, TIM_OCM_ACTIVE);
- timer_set_oc_value(TIM1, TIM_OC3, 30000);
-
- // Program slave controller to reset the timer on IC1
- timer_slave_set_trigger(TIM1, TIM_SMCR_TS_TI1FP1);
- timer_slave_set_mode(TIM1, TIM_SMCR_SMS_RM);
-
- // Request interrupts
- timer_enable_irq(TIM1, TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE);
- nvic_enable_irq(NVIC_TIM1_CC_IRQ);
-
- // Enable ICs and OCs
- timer_enable_oc_output(TIM1, TIM_OC1);
- timer_enable_oc_output(TIM1, TIM_OC2);
- timer_enable_oc_output(TIM1, TIM_OC3);
-
- timer_enable_counter(TIM1);
-}
-
-// Circular queue of pulse durations
-#define IR_MAX_PULSES 32
-static u32 ir_pulses[IR_MAX_PULSES]; // Top 16 bits = mark, bottom 16 bits = space
-static u16 ir_last_pulse;
-static uint ir_pulses_rx, ir_pulses_tx;
-
-#define IR_INF 0xffff
-
-#define IR_MARK(x) (uint)((x) >> 16)
-#define IR_SPACE(x) (uint)((x) & 0xffff)
-
-static inline bool between(uint x, uint min, uint max)
-{
- return x >= min && x <= max;
-}
-
-static void ir_record_pulse(uint mark, uint space)
-{
- uint i = ir_pulses_tx;
- ir_pulses_tx = (i + 1) % IR_MAX_PULSES;
- if (ir_pulses_tx != ir_pulses_rx) {
- ir_pulses[i] = (mark << 16) | space;
- } else {
- // Overflow detected
- ir_pulses[i] = (IR_INF << 16) | IR_INF;
- }
-}
-
-void tim1_cc_isr(void)
-{
- if (TIM_SR(TIM1) & TIM_SR_CC1IF) {
- TIM_SR(TIM1) &= ~TIM_SR_CC1IF;
- u16 now = TIM_CCR1(TIM1);
- if (ir_last_pulse) {
- ir_record_pulse(ir_last_pulse, now - ir_last_pulse);
- ir_last_pulse = 0;
- }
- }
- if (TIM_SR(TIM1) & TIM_SR_CC2IF) {
- TIM_SR(TIM1) &= ~TIM_SR_CC2IF;
- ir_last_pulse = TIM_CCR2(TIM1);
- }
- if (TIM_SR(TIM1) & TIM_SR_CC3IF) {
- TIM_SR(TIM1) &= ~TIM_SR_CC3IF;
- if (ir_last_pulse) {
- ir_record_pulse(ir_last_pulse, IR_INF);
- ir_last_pulse = 0;
- }
- }
-}
-
-static u32 ir_get_pulse(void)
-{
- u32 out = 0;
-
- cm_disable_interrupts();
- if (ir_pulses_rx != ir_pulses_tx) {
- out = ir_pulses[ir_pulses_rx];
- ir_pulses_rx = (ir_pulses_rx + 1) % IR_MAX_PULSES;
- }
- cm_enable_interrupts();
- return out;
-}
-
-// Decoder for Onkyo RC-748S
-
-static u32 ir_blink_start;
-
-static void ir_decode(void)
-{
- u32 pulse = ir_get_pulse();
- if (!pulse)
- return;
-
- uint mark = IR_MARK(pulse);
- uint space = IR_SPACE(pulse);
-
-#ifdef IR_TEST
- debug_printf("IR: %d %d\n", mark, space);
- return;
-#endif
-
- static u16 ir_bits;
- static u32 ir_code;
-#define IR_ERR 0xff
-
- // debug_printf("IR(%d): %d %d\n", ir_bits, mark, space);
-
- if (space == IR_INF) {
- ir_bits = 0;
- } else if (ir_bits == IR_ERR) {
- // Error state
- } else if (ir_bits == 0) {
- // Start?
- if (between(mark, 8900, 9200)) {
- if (between(space, 4200, 4600)) {
- ir_bits = 1;
- ir_code = 0;
- } else if (between(space, 2000, 2300)) {
- debug_printf("IR: => REP\n");
- ir_bits = IR_ERR;
- }
- }
- } else {
- if (between(mark, 500, 700)) {
- ir_bits++;
- if (between(space, 400, 700)) {
- // 0
- } else if (between(space, 1500, 1800)) {
- // 1
- ir_code |= 1U << (33 - ir_bits);
- } else {
- ir_bits = IR_ERR;
- }
- if (ir_bits == 33) {
- debug_printf("IR: => %08x\n", (uint)ir_code);
- disp[3] |= 0x01;
- ir_blink_start = ms_ticks;
- display_update();
- ir_bits = IR_ERR;
- ep82_send(ir_code);
- }
- } else {
- ir_bits = IR_ERR;
- }
- }
-}
-
-/*** USB ***/
-
-static usbd_device *usbd_dev;
-
-enum usb_string {
- STR_MANUFACTURER = 1,
- STR_PRODUCT,
- STR_SERIAL,
-};
-
-static char usb_serial_number[13];
-
-static const char *usb_strings[] = {
- "United Computer Wizards",
- "Workshop Clock",
- usb_serial_number,
-};
-
-static const struct usb_device_descriptor device = {
- .bLength = USB_DT_DEVICE_SIZE,
- .bDescriptorType = USB_DT_DEVICE,
- .bcdUSB = 0x0200,
- .bDeviceClass = 0xFF,
- .bDeviceSubClass = 0,
- .bDeviceProtocol = 0,
- .bMaxPacketSize0 = 64,
- .idVendor = 0x4242,
- .idProduct = 0x0007,
- .bcdDevice = 0x0000,
- .iManufacturer = STR_MANUFACTURER,
- .iProduct = STR_PRODUCT,
- .iSerialNumber = STR_SERIAL,
- .bNumConfigurations = 1,
-};
-
-static const struct usb_endpoint_descriptor endpoints[] = {{
- // Bulk end-point for sending values to the display
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = 0x01,
- .bmAttributes = USB_ENDPOINT_ATTR_BULK,
- .wMaxPacketSize = 64,
- .bInterval = 1,
-}, {
- // Bulk end-point for receiving remote control keys
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = 0x82,
- .bmAttributes = USB_ENDPOINT_ATTR_BULK,
- .wMaxPacketSize = 4,
- .bInterval = 1,
-}};
-
-static const struct usb_interface_descriptor iface = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bInterfaceNumber = 0,
- .bAlternateSetting = 0,
- .bNumEndpoints = 2,
- .bInterfaceClass = 0xFF,
- .bInterfaceSubClass = 0,
- .bInterfaceProtocol = 0,
- .iInterface = 0,
- .endpoint = endpoints,
-};
-
-static const struct usb_dfu_descriptor dfu_function = {
- .bLength = sizeof(struct usb_dfu_descriptor),
- .bDescriptorType = DFU_FUNCTIONAL,
- .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
- .wDetachTimeout = 255,
- .wTransferSize = 1024,
- .bcdDFUVersion = 0x0100,
-};
-
-static const struct usb_interface_descriptor dfu_iface = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bInterfaceNumber = 1,
- .bAlternateSetting = 0,
- .bNumEndpoints = 0,
- .bInterfaceClass = 0xFE,
- .bInterfaceSubClass = 1,
- .bInterfaceProtocol = 1,
- .iInterface = 0,
-
- .extra = &dfu_function,
- .extralen = sizeof(dfu_function),
-};
-
-static const struct usb_interface ifaces[] = {{
- .num_altsetting = 1,
- .altsetting = &iface,
-}, {
- .num_altsetting = 1,
- .altsetting = &dfu_iface,
-}};
-
-static const struct usb_config_descriptor config = {
- .bLength = USB_DT_CONFIGURATION_SIZE,
- .bDescriptorType = USB_DT_CONFIGURATION,
- .wTotalLength = 0,
- .bNumInterfaces = 2,
- .bConfigurationValue = 1,
- .iConfiguration = 0,
- .bmAttributes = 0x80,
- .bMaxPower = 50, // multiplied by 2 mA
- .interface = ifaces,
-};
-
-static bool usb_configured;
-static uint8_t usbd_control_buffer[64];
-
-static bool usb_tx_in_flight;
-static byte ep82_tx_buffer[4];
-
-static byte disp_alive;
-
-static void dfu_detach_complete(usbd_device *dev UNUSED, struct usb_setup_data *req UNUSED)
-{
- // Reset to bootloader, which implements the rest of DFU
- debug_printf("Switching to DFU\n");
- debug_flush();
- scb_reset_core();
-}
-
-static enum usbd_request_return_codes dfu_control_cb(usbd_device *dev UNUSED,
- struct usb_setup_data *req,
- uint8_t **buf UNUSED,
- uint16_t *len UNUSED,
- void (**complete)(usbd_device *dev, struct usb_setup_data *req))
-{
- if (req->bmRequestType != 0x21 || req->bRequest != DFU_DETACH)
- return USBD_REQ_NOTSUPP;
-
- *complete = dfu_detach_complete;
- return USBD_REQ_HANDLED;
-}
-
-static void ep01_cb(usbd_device *dev, uint8_t ep UNUSED)
-{
- // We received a frame from the USB host
- byte buf[8];
- uint len = usbd_ep_read_packet(dev, 0x01, buf, 8);
- debug_printf("USB: Host sent %u bytes\n", len);
- if (len >= 5) {
- for (uint i=0; i<4; i++) {
- disp[i] &= 0x01;
- if (buf[i] < 16)
- disp[i] |= disp_font[buf[i]];
- }
- disp[1] &= 0xfe;
- if (buf[4])
- disp[1] |= 0x01;
- display_update();
- disp_alive = 10;
- }
-}
-
-static void ep82_send(u32 key_code)
-{
- if (usb_tx_in_flight) {
- debug_printf("USB: Send overrun!\n");
- return;
- }
-
- debug_printf("USB: Sending key to host\n");
- put_u32_be(ep82_tx_buffer, key_code);
- usbd_ep_write_packet(usbd_dev, 0x82, ep82_tx_buffer, 4);
- usb_tx_in_flight = true;
-}
-
-static void ep82_cb(usbd_device *dev UNUSED, uint8_t ep UNUSED)
-{
- // We completed sending a frame to the USB host
- usb_tx_in_flight = false;
- debug_printf("USB: Key sending complete\n");
-}
-
-static void set_config_cb(usbd_device *dev, uint16_t wValue UNUSED)
-{
- usbd_register_control_callback(
- dev,
- USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
- USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
- dfu_control_cb);
- usbd_ep_setup(dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, ep01_cb);
- usbd_ep_setup(dev, 0x82, USB_ENDPOINT_ATTR_BULK, 4, ep82_cb);
- usb_configured = true;
-}
-
-static void reset_cb(void)
-{
- debug_printf("USB: Reset\n");
- usb_configured = false;
-}
-
-static volatile bool usb_event_pending;
-
-void usb_lp_can_rx0_isr(void)
-{
- /*
- * We handle USB in the main loop to avoid race conditions between
- * USB interrupts and other code. However, we need an interrupt to
- * up the main loop from sleep.
- *
- * We set up only the low-priority ISR, because high-priority ISR handles
- * only double-buffered bulk transfers and isochronous transfers.
- */
- nvic_disable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
- usb_event_pending = 1;
-}
-
-static void usb_init(void)
-{
- // Simulate USB disconnect
- gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12);
- gpio_clear(GPIOA, GPIO11 | GPIO12);
- delay_ms(100);
-
- usbd_dev = usbd_init(
- &st_usbfs_v1_usb_driver,
- &device,
- &config,
- usb_strings,
- ARRAY_SIZE(usb_strings),
- usbd_control_buffer,
- sizeof(usbd_control_buffer)
- );
- usbd_register_reset_callback(usbd_dev, reset_cb);
- usbd_register_set_config_callback(usbd_dev, set_config_cb);
- usb_event_pending = 1;
-}
-
-/*** Main ***/
-
-int main(void)
-{
- clock_init();
- gpio_init();
- usart_init();
-
- tick_init();
- desig_get_unique_id_as_dfu(usb_serial_number);
-
- debug_printf("Hello, world!\n");
-
- usb_init();
- display_init();
- ir_init();
-
- u32 last_blink = 0;
-
- for (;;) {
- if (ms_ticks - last_blink >= 500) {
- debug_led_toggle();
- last_blink = ms_ticks;
- if (disp_alive) {
- if (!--disp_alive) {
- disp[0] = (disp[0] & 0x01) | 0x10;
- disp[1] = (disp[1] & 0x01) | 0x10;
- disp[2] = (disp[2] & 0x01) | 0x10;
- disp[3] = (disp[3] & 0x01) | 0x10;
- }
- }
- display_update();
- }
-
- if ((disp[3] & 0x01) && ms_ticks - ir_blink_start >= 100) {
- disp[3] &= 0xfe;
- display_update();
- }
-
- ir_decode();
-
- if (usb_event_pending) {
- usbd_poll(usbd_dev);
- usb_event_pending = 0;
- nvic_clear_pending_irq(NVIC_USB_LP_CAN_RX0_IRQ);
- nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
- }
-
- wait_for_interrupt();
- }
-
- return 0;
-}