From: Martin Mares Date: Wed, 26 Feb 2020 17:08:29 +0000 (+0100) Subject: BSB: Transmit implemented X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=abca287bfe8ba1d1ef3240f4e4e4cff724a8bfd2;p=home-hw.git BSB: Transmit implemented --- diff --git a/bsb/firmware/interface.h b/bsb/firmware/interface.h new file mode 100644 index 0000000..0168d20 --- /dev/null +++ b/bsb/firmware/interface.h @@ -0,0 +1,32 @@ +/* + * Boiler System Bus Gateway -- Interface Definitions + * + * (c) 2020 Martin Mareš + */ + +enum bsb_tx_result { + TX_RESULT_NONE, + TX_RESULT_OK, + TX_RESULT_OVERRUN, + TX_RESULT_MALFORMED, + TX_RESULT_FORBIDDEN, + TX_RESULT_TIMEOUT, + TX_RESULT_TOO_MANY_RETRIES, +}; + +// Sent via USB in little-endian format +struct bsb_stats { + u32 rx_noise; + u32 rx_errors; + u32 rx_invalid; + u32 rx_overruns; + u32 rx_timeouts; + u32 rx_bad_crc; + u32 rx_ok; + u32 tx_overruns; + u32 tx_rejects; + u32 tx_timeouts; + u32 tx_collisions; + u32 tx_ok; +}; + diff --git a/bsb/firmware/main.c b/bsb/firmware/main.c index 26b3def..c2b5397 100644 --- a/bsb/firmware/main.c +++ b/bsb/firmware/main.c @@ -5,6 +5,7 @@ */ #include "util.h" +#include "interface.h" #include #include @@ -58,8 +59,7 @@ static void gpio_init(void) // PB10 = TXD3 for BSB // PB11 = RXD3 for BSB - // FIXME: TX turned off temporarily - // gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO10); + gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO10); gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11); } @@ -68,7 +68,7 @@ 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_RX); + usart_set_mode(USART1, USART_MODE_TX); usart_set_parity(USART1, USART_PARITY_NONE); usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE); @@ -98,39 +98,77 @@ static void delay_ms(uint ms) ; } +/*** Random generator ***/ + +static u32 rng_state; + +static u32 random_u32(void) +{ + rng_state *= 2654289733; + return rng_state; +} + +static void random_init(void) +{ + u32 desig[3]; + desig_get_unique_id(desig); + rng_state = desig[0] ^ desig[1] ^ desig[2]; +} + /*** BSB ***/ #define BSB_MAX_SIZE 32 -#define BSB_RX_TIMEOUT 10 // ms +#define BSB_RX_TIMEOUT 10 // ms +#define BSB_TX_IDLE_LINE 100 // ms +#define BSB_TX_MAX_ATTEMPTS 3 +#define BSB_TX_WAIT_LOG 10 // base 2 log of timeout in ms +#define BSB_TX_TIMEOUT 10000 // ms -#undef DEBUG_BSB +#define DEBUG_BSB #ifdef DEBUG_BSB -#define BSB_DEBUG(x...) debug_printf(x) +#define BSB_DEBUG(x) debug_putc(x) #else -#define BSB_DEBUG(x...) do { } while (0) +#define BSB_DEBUG(x) do { } while (0) #endif -static byte bsb_rx_buf[BSB_MAX_SIZE]; -static byte bsb_rx_len; -static u16 bsb_rx_crc; -static volatile u32 bsb_rx_timestamp; +struct bsb_state { + byte rx_len; // Bytes received so far + byte rx_led; // Countdown to turning RX LED off + u16 rx_crc; + volatile u32 rx_timestamp; // Last byte received + + byte tx_state; // TX_STATE_xxx + byte tx_len; + byte tx_send_index; + byte tx_check_index; + byte tx_attempts; + byte tx_result; // TX_RESULT_xxx + u32 tx_start_timestamp; + u32 tx_ticks_to_send; + + byte rx_buf[BSB_MAX_SIZE]; + byte tx_buf[BSB_MAX_SIZE]; +}; + +static struct bsb_state bsb; -// Passing received frames to the main loop +// Passing received frames and status codes to the main loop static byte bsb_rx_frame[BSB_MAX_SIZE]; static volatile byte bsb_rx_frame_len; -static byte bsb_rx_frame_led; - -struct bsb_stat { - u32 rx_noise; - u32 rx_errors; - u32 rx_invalid; - u32 rx_overruns; - u32 rx_timeouts; - u32 rx_bad_crc; - u32 rx_ok; +static byte bsb_tx_result; + +enum bsb_tx_state { + TX_STATE_OFF, + TX_STATE_WAITING_FOR_IDLE_LINE, + TX_STATE_PRE_PACKET_DELAY, + TX_STATE_SENDING, + TX_STATE_DONE, + TX_STATE_COLLISION, }; -static struct bsb_stat bsb_stat; +static struct bsb_stats bsb_stats; + +static void bsb_attempt_tx(void); static u16 bsb_crc_update(u16 crc, byte data) { @@ -150,57 +188,220 @@ void usart3_isr(void) if (status & USART_SR_RXNE) { uint ch = ~usart_recv(USART3) & 0xff; - bsb_rx_timestamp = ms_ticks; - BSB_DEBUG(" %02x", ch); - - if (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) { - bsb_stat.rx_errors++; - bsb_rx_len = 0; - } else if (!bsb_rx_len) { - // Start of frame - if (ch == 0xdc) { - bsb_rx_buf[bsb_rx_len++] = ch; - bsb_rx_crc = bsb_crc_update(0, ch); - BSB_DEBUG("<"); + bsb.rx_timestamp = ms_ticks; +#ifdef DEBUG_BSB + debug_printf(" %02x", ch); +#endif + + if (bsb.tx_state == TX_STATE_SENDING) { + // We are hearing the echo of a transmitted packet, check it for collisions + if (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) { + BSB_DEBUG('e'); + bsb.tx_state = TX_STATE_COLLISION; + } else if (ch == bsb.tx_buf[bsb.tx_check_index++]) { + if (bsb.tx_check_index >= bsb.tx_len) { + BSB_DEBUG('d'); + bsb.tx_state = TX_STATE_DONE; + } } else { - bsb_stat.rx_noise++; - BSB_DEBUG("?"); + BSB_DEBUG('c'); + bsb.tx_state = TX_STATE_COLLISION; } } else { - bsb_rx_buf[bsb_rx_len++] = ch; - bsb_rx_crc = bsb_crc_update(bsb_rx_crc, ch); - if (bsb_rx_len < 4) { - // First three bytes: SOF, source addr, destination addr - } else if (bsb_rx_len == 4) { - // Received length byte - if (bsb_rx_buf[3] < 7 || bsb_rx_buf[3] > BSB_MAX_SIZE) { - bsb_stat.rx_invalid++; - bsb_rx_len = 0; - BSB_DEBUG("!L"); - } - } else if (bsb_rx_len == bsb_rx_buf[3]) { - // Received a complete frame: check CRC and pass it to the main loop - if (bsb_rx_crc) { - bsb_stat.rx_bad_crc++; - BSB_DEBUG("!C"); + // Regular receive + if (status & (USART_SR_FE | USART_SR_ORE | USART_SR_NE)) { + bsb_stats.rx_errors++; + bsb.rx_len = 0; + } else if (!bsb.rx_len) { + // Start of frame + if (ch == 0xdc) { + bsb.rx_buf[bsb.rx_len++] = ch; + bsb.rx_crc = bsb_crc_update(0, ch); + BSB_DEBUG('<'); } else { - if (bsb_rx_frame_len) { - // The previous one was not sent yet - bsb_stat.rx_overruns++; - BSB_DEBUG("!O"); + bsb_stats.rx_noise++; + BSB_DEBUG('?'); + } + } else { + bsb.rx_buf[bsb.rx_len++] = ch; + bsb.rx_crc = bsb_crc_update(bsb.rx_crc, ch); + if (bsb.rx_len < 4) { + // First three bytes: SOF, source addr, destination addr + } else if (bsb.rx_len == 4) { + // Received length byte + if (bsb.rx_buf[3] < 7 || bsb.rx_buf[3] > BSB_MAX_SIZE) { + bsb_stats.rx_invalid++; + bsb.rx_len = 0; + BSB_DEBUG('L'); + } + } else if (bsb.rx_len == bsb.rx_buf[3]) { + // Received a complete frame: check CRC and pass it to the main loop + if (bsb.rx_crc) { + bsb_stats.rx_bad_crc++; + BSB_DEBUG('C'); } else { - memcpy(bsb_rx_frame, bsb_rx_buf, bsb_rx_buf[3]); - bsb_rx_frame_len = bsb_rx_buf[3] - 2; - bsb_stat.rx_ok++; - BSB_DEBUG("."); + if (bsb_rx_frame_len) { + // The previous one was not sent yet + bsb_stats.rx_overruns++; + BSB_DEBUG('O'); + } else { + memcpy(bsb_rx_frame, bsb.rx_buf, bsb.rx_buf[3]); + bsb_rx_frame_len = bsb.rx_buf[3] - 2; + bsb_stats.rx_ok++; + BSB_DEBUG('.'); + } + gpio_clear(GPIOB, GPIO1); + bsb.rx_led = 100; } - gpio_clear(GPIOB, GPIO1); - bsb_rx_frame_led = 100; + bsb.rx_len = 0; } - bsb_rx_len = 0; } } } + + if (status & USART_SR_TXE) { + if (bsb.tx_state == TX_STATE_SENDING && bsb.tx_send_index < bsb.tx_len) { + BSB_DEBUG('>'); + usart_send(USART3, bsb.tx_buf[bsb.tx_send_index++] ^ 0xff); + } else { + usart_disable_tx_interrupt(USART3); + // Main loop will be notified by receiving the echo of our frame, + // so we do not need to wait for the "transmit complete" flag. + } + } +} + +static void bsb_rx_step(void) +{ + cm_disable_interrupts(); + barrier(); + if (bsb.rx_len && ms_ticks - bsb.rx_timestamp >= BSB_RX_TIMEOUT) { + bsb.rx_len = 0; + bsb_stats.rx_timeouts++; + BSB_DEBUG('T'); + } + if (bsb.rx_led) { + if (!--bsb.rx_led) + gpio_set(GPIOB, GPIO1); + } + cm_enable_interrupts(); +} + +static void bsb_tx_step(void) +{ + switch (bsb.tx_state) { + case TX_STATE_WAITING_FOR_IDLE_LINE: + if (ms_ticks - bsb.rx_timestamp < BSB_TX_IDLE_LINE) + return; + bsb.tx_state = TX_STATE_PRE_PACKET_DELAY; + BSB_DEBUG('i'); + // Fall thru + case TX_STATE_PRE_PACKET_DELAY: + if (ms_ticks - bsb.tx_start_timestamp > BSB_TX_TIMEOUT) { + bsb.tx_state = TX_STATE_OFF; + bsb_tx_result = TX_RESULT_TIMEOUT; + bsb_stats.tx_timeouts++; + return; + } + if (ms_ticks - bsb.rx_timestamp < BSB_TX_IDLE_LINE) { + bsb.tx_state = TX_STATE_WAITING_FOR_IDLE_LINE; + BSB_DEBUG('n'); + return; + } + if (--bsb.tx_ticks_to_send) + return; + BSB_DEBUG('s'); + bsb.rx_timestamp = ms_ticks; + barrier(); + bsb.tx_state = TX_STATE_SENDING; + barrier(); + usart_enable_tx_interrupt(USART3); + return; + case TX_STATE_SENDING: + // In this state, we can race with the USART interrupt handler + cm_disable_interrupts(); + if (bsb.tx_state == TX_STATE_SENDING && ms_ticks - bsb.rx_timestamp >= BSB_RX_TIMEOUT) { + // The echo of our packet was truncated due to collision + bsb.tx_state = TX_STATE_COLLISION; + BSB_DEBUG('t'); + } + cm_enable_interrupts(); + return; + case TX_STATE_COLLISION: + bsb_stats.tx_collisions++; + bsb_attempt_tx(); + return; + case TX_STATE_DONE: + bsb_stats.tx_ok++; + bsb.tx_state = TX_STATE_OFF; + bsb_tx_result = TX_RESULT_OK; + return; + default: + ; + } +} + +static void bsb_attempt_tx(void) +{ + if (bsb.tx_attempts++ >= BSB_TX_MAX_ATTEMPTS) { + bsb_tx_result = TX_RESULT_TOO_MANY_RETRIES; + bsb.tx_state = TX_STATE_OFF; + return; + } + + bsb.tx_ticks_to_send = (random_u32() | 0x80000000) >> (32 - BSB_TX_WAIT_LOG - (bsb.tx_attempts-1)); + + bsb.tx_state = TX_STATE_WAITING_FOR_IDLE_LINE; + bsb.tx_send_index = 0; + bsb.tx_check_index = 0; + debug_printf("TX: Attempt #%u, wait for %u ms\n", bsb.tx_attempts, (uint) bsb.tx_ticks_to_send); +} + +static void bsb_send(byte *buf, uint len) +{ + // If a transmit is in progress, declare overrrun + if (bsb_tx_result != TX_RESULT_NONE) { + bsb_stats.tx_overruns++; + return; + } + if (bsb.tx_state != TX_STATE_OFF) { + bsb_stats.tx_overruns++; + bsb_tx_result = TX_RESULT_OVERRUN; + return; + } + + // Check frame validity + if (len < 5 || len > BSB_MAX_SIZE - 2 || buf[0] != 0xdc || buf[3] != len + 2) { + bsb_stats.tx_rejects++; + bsb_tx_result = TX_RESULT_MALFORMED; + return; + } + + // So far, we allow only QUERY frames from address 42 + if (buf[1] != 0x80 + 42 && buf[4] != 0x06) { + bsb_stats.tx_rejects++; + bsb_tx_result = TX_RESULT_FORBIDDEN; + return; + } + + // Copy frame and calculate CRC + u16 crc = 0; + for (uint i=0; i> 8; + bsb.tx_buf[len++] = crc; + debug_printf("TX: Prepared frame of %u bytes with CRC %04x\n", len, crc); + + // Queue frame for transmission + bsb.tx_len = len; + bsb.tx_start_timestamp = ms_ticks; + bsb.tx_attempts = 0; + bsb_attempt_tx(); + + // Light the TX LED + gpio_clear(GPIOB, GPIO0); } static void bsb_init(void) @@ -208,7 +409,7 @@ static void bsb_init(void) usart_set_baudrate(USART3, 4800); usart_set_databits(USART3, 9); usart_set_stopbits(USART3, USART_STOPBITS_1); - usart_set_mode(USART3, USART_MODE_RX); + usart_set_mode(USART3, USART_MODE_TX_RX); usart_set_parity(USART3, USART_PARITY_ODD); usart_set_flow_control(USART3, USART_FLOWCONTROL_NONE); @@ -239,13 +440,15 @@ static const struct usb_device_descriptor device = { }; static const struct usb_endpoint_descriptor endpoints[] = {{ + // Bulk end-point for sending frames to BSB .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = 0x81, + .bEndpointAddress = 0x01, .bmAttributes = USB_ENDPOINT_ATTR_BULK, .wMaxPacketSize = 64, .bInterval = 1, },{ + // Interrupt end-point for receiving frames from BSB .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x82, @@ -320,7 +523,7 @@ static const char *usb_strings[] = { }; static byte usb_configured; -static byte usb_rx_pending; +static byte usb_ep82_pending; static uint8_t usbd_control_buffer[64]; static enum usbd_request_return_codes control_cb( @@ -335,8 +538,8 @@ static enum usbd_request_return_codes control_cb( // We support reading of statistics via control requests debug_printf("USB: Reading statistics\n"); - uint n = MIN(*len, sizeof(bsb_stat)); - memcpy(*buf, (char *) &bsb_stat, n); + uint n = MIN(*len, sizeof(bsb_stats)); + memcpy(*buf, (char *) &bsb_stats, n); *len = n; return USBD_REQ_HANDLED; @@ -363,34 +566,36 @@ static enum usbd_request_return_codes dfu_control_cb(usbd_device *dev UNUSED, return USBD_REQ_HANDLED; } -static void ep81_cb(usbd_device *dev, uint8_t ep UNUSED) +static void ep01_cb(usbd_device *dev, uint8_t ep UNUSED) { - byte buf[4]; - put_u32_be(buf, ms_ticks); - usbd_ep_write_packet(dev, 0x81, buf, sizeof(buf)); - debug_printf("USB: Bulk write\n"); + // We received a frame from the USB host + byte buf[64]; + uint len = usbd_ep_read_packet(dev, 0x01, buf, sizeof(buf)); + debug_printf("USB: Host sent %u bytes\n", len); + bsb_send(buf, len); } -static void rx_cb(usbd_device *dev UNUSED, uint8_t ep UNUSED) +static void ep82_cb(usbd_device *dev UNUSED, uint8_t ep UNUSED) { - // Received packet passed through interrupt end-point - debug_printf("USB: RX EP done\n"); - usb_rx_pending = 0; + // The frame was accepted by the USB host + debug_printf("USB: Interrupt done\n"); + usb_ep82_pending = 0; } static void set_config_cb(usbd_device *dev, uint16_t wValue UNUSED) { - usbd_register_control_callback(dev, + usbd_register_control_callback( + dev, USB_REQ_TYPE_VENDOR, USB_REQ_TYPE_TYPE, control_cb); - usbd_register_control_callback(dev, + 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, 0x81, USB_ENDPOINT_ATTR_BULK, 64, ep81_cb); - usbd_ep_setup(dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, rx_cb); - ep81_cb(dev, 0); + usbd_ep_setup(dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, ep01_cb); + usbd_ep_setup(dev, 0x82, USB_ENDPOINT_ATTR_INTERRUPT, 64, ep82_cb); usb_configured = 1; } @@ -398,7 +603,7 @@ static void reset_cb(void) { debug_printf("USB: Reset\n"); usb_configured = 0; - usb_rx_pending = 0; + usb_ep82_pending = 0; } static volatile bool usb_event_pending; @@ -422,7 +627,7 @@ 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(1000); + delay_ms(100); usbd_dev = usbd_init( &st_usbfs_v1_usb_driver, @@ -447,19 +652,20 @@ int main(void) tick_init(); usart_init(); desig_get_unique_id_as_dfu(usb_serial_number); + random_init(); - debug_printf("Hello, kitty!\n"); + debug_printf("Hail, Lord Damian! Thy BSB interface is ready.\n"); bsb_init(); usb_init(); - u32 last_ds_step = 0; + u32 last_blink = 0; + u32 last_step = 0; for (;;) { - if (ms_ticks - last_ds_step >= 100) { + if (ms_ticks - last_blink >= 100) { debug_led_toggle(); - gpio_toggle(GPIOB, GPIO0); - last_ds_step = ms_ticks; + last_blink = ms_ticks; } if (usb_event_pending) { @@ -470,32 +676,31 @@ int main(void) } if (usb_configured) { - // Passing of received packets to USB - if (!usb_rx_pending) { + // Passing of received frames to USB + if (!usb_ep82_pending) { if (bsb_rx_frame_len) { barrier(); usbd_ep_write_packet(usbd_dev, 0x82, bsb_rx_frame, bsb_rx_frame_len); - usb_rx_pending = 1; + usb_ep82_pending = 1; barrier(); bsb_rx_frame_len = 0; - debug_printf("USB: RX started\n"); + debug_printf("USB: Sending frame to interrupt EP\n"); + } + if (bsb_tx_result != TX_RESULT_NONE) { + usbd_ep_write_packet(usbd_dev, 0x82, &bsb_tx_result, 1); + usb_ep82_pending = 1; + gpio_set(GPIOB, GPIO0); // Turn the TX LED off + debug_printf("USB: Sending status %u to interrupt EP\n", bsb_tx_result); + bsb_tx_result = TX_RESULT_NONE; } } } - // Packet timeouts - cm_disable_interrupts(); - barrier(); - if (bsb_rx_len && ms_ticks - bsb_rx_timestamp >= BSB_RX_TIMEOUT) { - bsb_rx_len = 0; - bsb_stat.rx_timeouts++; - BSB_DEBUG("!T"); - } - if (bsb_rx_frame_led) { - if (!--bsb_rx_frame_led) - gpio_set(GPIOB, GPIO1); + if (ms_ticks != last_step) { + last_step = ms_ticks; + bsb_rx_step(); + bsb_tx_step(); } - cm_enable_interrupts(); wait_for_interrupt(); }