*/
#include "util.h"
+#include "interface.h"
#include <libopencm3/cm3/cortex.h>
#include <libopencm3/cm3/nvic.h>
// 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);
}
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);
;
}
+/*** 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)
{
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<len; i++) {
+ bsb.tx_buf[i] = buf[i];
+ crc = bsb_crc_update(crc, buf[i]);
+ }
+ bsb.tx_buf[len++] = crc >> 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)
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);
};
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,
};
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(
// 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;
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;
}
{
debug_printf("USB: Reset\n");
usb_configured = 0;
- usb_rx_pending = 0;
+ usb_ep82_pending = 0;
}
static volatile bool usb_event_pending;
// 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,
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) {
}
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();
}