aboutsummaryrefslogtreecommitdiff
path: root/src/stm32
diff options
context:
space:
mode:
authorGareth McMullin2011-02-04 20:23:52 +1300
committerGareth McMullin2011-02-04 20:23:52 +1300
commit406617a2a470021d9412e9280feda0d28bdb653b (patch)
tree43b2cb9b562fde0bf5187c31dea77d72318cfc29 /src/stm32
Import of working source tree.
Diffstat (limited to 'src/stm32')
-rw-r--r--src/stm32/Makefile.inc25
-rw-r--r--src/stm32/blackmagic.ld29
-rw-r--r--src/stm32/cdcacm.c304
-rw-r--r--src/stm32/gdb_if.c81
-rw-r--r--src/stm32/jtagtap.c90
-rw-r--r--src/stm32/platform.c179
-rw-r--r--src/stm32/platform.h121
-rw-r--r--src/stm32/swdptap.c156
-rw-r--r--src/stm32/usbdfu.c308
9 files changed, 1293 insertions, 0 deletions
diff --git a/src/stm32/Makefile.inc b/src/stm32/Makefile.inc
new file mode 100644
index 0000000..d9bc876
--- /dev/null
+++ b/src/stm32/Makefile.inc
@@ -0,0 +1,25 @@
+CC = arm-cortexm3-eabi-gcc
+OBJCOPY = arm-cortexm3-eabi-objcopy
+
+CFLAGS += -Istm32/include
+LDFLAGS_BOOT = -lopencm3_stm32 -Wl,--defsym,_stack=0x20005000 \
+ -Wl,-T,stm32/blackmagic.ld -nostartfiles -lc -lnosys -Wl,-Map=mapfile
+LDFLAGS = $(LDFLAGS_BOOT) -Wl,-Ttext=0x8002000
+
+SRC += cdcacm.c \
+ platform.c \
+
+all: blackmagic.bin blackmagic_dfu.bin
+
+blackmagic.bin: blackmagic
+ $(OBJCOPY) -O binary $^ $@
+
+blackmagic_dfu: usbdfu.c
+ $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS_BOOT)
+
+blackmagic_dfu.bin: blackmagic_dfu
+ $(OBJCOPY) -O binary $^ $@
+
+host_clean:
+ -rm blackmagic.bin blackmagic_dfu blackmagic_dfu.bin
+
diff --git a/src/stm32/blackmagic.ld b/src/stm32/blackmagic.ld
new file mode 100644
index 0000000..52314c5
--- /dev/null
+++ b/src/stm32/blackmagic.ld
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the libopenstm32 project.
+ *
+ * Copyright (C) 2010 Thomas Otto <tommi@viadmin.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Define memory regions. */
+MEMORY
+{
+ rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K
+ ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
+}
+
+/* Include the common ld script from libopenstm32. */
+INCLUDE libopencm3_stm32.ld
+
diff --git a/src/stm32/cdcacm.c b/src/stm32/cdcacm.c
new file mode 100644
index 0000000..b2fe1d1
--- /dev/null
+++ b/src/stm32/cdcacm.c
@@ -0,0 +1,304 @@
+/*
+ * This file is part of the Black Magic Debug project.
+ *
+ * Copyright (C) 2011 Black Sphere Technologies Ltd.
+ * Written by Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements a the USB Communications Device Class - Abstract
+ * Control Model (CDC-ACM) as defined in CDC PSTN subclass 1.2.
+ * A Device Firmware Upgrade (DFU 1.1) class interface is provided for
+ * field firmware upgrade.
+ *
+ * The device's unique id is used as the USB serial number string.
+ */
+
+#include <libopencm3/stm32/nvic.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/usb/usbd.h>
+#include <libopencm3/usb/cdc.h>
+#include <libopencm3/stm32/scb.h>
+#include <libopencm3/usb/dfu.h>
+#include <stdlib.h>
+
+#include "platform.h"
+
+static char *get_dev_unique_id(char *serial_no);
+
+static int configured;
+
+static const struct usb_device_descriptor dev = {
+ .bLength = USB_DT_DEVICE_SIZE,
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = USB_CLASS_CDC,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .bMaxPacketSize0 = 64,
+ .idVendor = 0x0483,
+ .idProduct = 0x5740,
+ .bcdDevice = 0x0200,
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .iSerialNumber = 3,
+ .bNumConfigurations = 1,
+};
+
+/* This notification endpoint isn't implemented. According to CDC spec its
+ * optional, but its absence causes a NULL pointer dereference in Linux cdc_acm
+ * driver. */
+static const struct usb_endpoint_descriptor comm_endp[] = {{
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0x83,
+ .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
+ .wMaxPacketSize = 16,
+ .bInterval = 255,
+}};
+
+static const struct usb_endpoint_descriptor data_endp[] = {{
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0x01,
+ .bmAttributes = USB_ENDPOINT_ATTR_BULK,
+ .wMaxPacketSize = 64,
+ .bInterval = 1,
+}, {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0x82,
+ .bmAttributes = USB_ENDPOINT_ATTR_BULK,
+ .wMaxPacketSize = 64,
+ .bInterval = 1,
+}};
+
+static const struct {
+ struct usb_cdc_header_descriptor header;
+ struct usb_cdc_call_management_descriptor call_mgmt;
+ struct usb_cdc_acm_descriptor acm;
+ struct usb_cdc_union_descriptor cdc_union;
+} __attribute__((packed)) cdcacm_functional_descriptors = {
+ .header = {
+ .bFunctionLength = sizeof(struct usb_cdc_header_descriptor),
+ .bDescriptorType = CS_INTERFACE,
+ .bDescriptorSubtype = USB_CDC_TYPE_HEADER,
+ .bcdCDC = 0x0110,
+ },
+ .call_mgmt = {
+ .bFunctionLength =
+ sizeof(struct usb_cdc_call_management_descriptor),
+ .bDescriptorType = CS_INTERFACE,
+ .bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
+ .bmCapabilities = 0,
+ .bDataInterface = 1,
+ },
+ .acm = {
+ .bFunctionLength = sizeof(struct usb_cdc_acm_descriptor),
+ .bDescriptorType = CS_INTERFACE,
+ .bDescriptorSubtype = USB_CDC_TYPE_ACM,
+ .bmCapabilities = 0,
+ },
+ .cdc_union = {
+ .bFunctionLength = sizeof(struct usb_cdc_union_descriptor),
+ .bDescriptorType = CS_INTERFACE,
+ .bDescriptorSubtype = USB_CDC_TYPE_UNION,
+ .bControlInterface = 0,
+ .bSubordinateInterface0 = 1,
+ }
+};
+
+static const struct usb_interface_descriptor comm_iface[] = {{
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_CDC,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
+ .bInterfaceProtocol = USB_CDC_PROTOCOL_AT,
+ .iInterface = 0,
+
+ .endpoint = comm_endp,
+
+ .extra = &cdcacm_functional_descriptors,
+ .extralen = sizeof(cdcacm_functional_descriptors)
+}};
+
+static const struct usb_interface_descriptor data_iface[] = {{
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_DATA,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .iInterface = 0,
+
+ .endpoint = data_endp,
+}};
+
+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 = 0x011A,
+};
+
+const struct usb_interface_descriptor dfu_iface = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 2,
+ .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 = comm_iface,
+}, {
+ .num_altsetting = 1,
+ .altsetting = data_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 = 3,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = 0x80,
+ .bMaxPower = 0x32,
+
+ .interface = ifaces,
+};
+
+static char serial_no[25];
+
+static const char *usb_strings[] = {
+ "x",
+ "Black Sphere Technologies",
+ "Black Magic Probe",
+ serial_no,
+};
+
+static void dfu_detach_complete(struct usb_setup_data *req)
+{
+ (void)req;
+
+ /* Disconnect USB cable */
+ gpio_set_mode(USB_PU_PORT, GPIO_MODE_INPUT, 0, USB_PU_PIN);
+
+ /* Assert boot-request pin */
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL, GPIO12);
+ gpio_clear(GPIOB, GPIO12);
+
+ /* Reset core to enter bootloader */
+ scb_reset_core();
+}
+
+static int cdcacm_control_request(struct usb_setup_data *req, uint8_t **buf,
+ uint16_t *len, void (**complete)(struct usb_setup_data *req))
+{
+ (void)complete;
+ (void)buf;
+ (void)len;
+
+ switch(req->bRequest) {
+ case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+ /* This Linux cdc_acm driver requires this to be implemented
+ * even though it's optional in the CDC spec, and we don't
+ * advertise it in the ACM functional descriptor. */
+ return 1;
+ case DFU_DETACH:
+ if(req->wIndex == 2) {
+ *complete = dfu_detach_complete;
+ return 1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+int cdcacm_get_config(void)
+{
+ return configured;
+}
+
+static void cdcacm_set_config(u16 wValue)
+{
+ configured = wValue;
+
+ usbd_ep_setup(0x01, USB_ENDPOINT_ATTR_BULK, 64, NULL);
+ usbd_ep_setup(0x82, USB_ENDPOINT_ATTR_BULK, 64, NULL);
+ usbd_ep_setup(0x83, USB_ENDPOINT_ATTR_INTERRUPT, 16, NULL);
+
+ usbd_register_control_callback(
+ USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
+ USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
+ cdcacm_control_request);
+}
+
+void cdcacm_init(void)
+{
+ get_dev_unique_id(serial_no);
+
+ usbd_init(&stm32f103_usb_driver, &dev, &config, usb_strings);
+ usbd_register_set_config_callback(cdcacm_set_config);
+
+ nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+
+ gpio_set(USB_PU_PORT, USB_PU_PIN);
+ gpio_set_mode(USB_PU_PORT, GPIO_MODE_OUTPUT_10_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL, USB_PU_PIN);
+}
+
+void usb_lp_can_rx0_isr(void)
+{
+ usbd_poll();
+}
+
+static char *get_dev_unique_id(char *s)
+{
+ volatile uint8_t *unique_id = (volatile uint8_t *)0x1FFFF7E8;
+ int i;
+
+ /* Fetch serial number from chip's unique ID */
+ for(i = 0; i < 24; i+=2) {
+ s[i] = ((*unique_id >> 4) & 0xF) + '0';
+ s[i+1] = (*unique_id++ & 0xF) + '0';
+ }
+ for(i = 0; i < 24; i++)
+ if(s[i] > '9')
+ s[i] += 'A' - '9' - 1;
+
+ return s;
+}
+
diff --git a/src/stm32/gdb_if.c b/src/stm32/gdb_if.c
new file mode 100644
index 0000000..ca0b520
--- /dev/null
+++ b/src/stm32/gdb_if.c
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the Black Magic Debug project.
+ *
+ * Copyright (C) 2011 Black Sphere Technologies Ltd.
+ * Written by Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements a transparent channel over which the GDB Remote
+ * Serial Debugging protocol is implemented. This implementation for STM32
+ * uses the USB CDC-ACM device bulk endpoints to implement the channel.
+ */
+#include "platform.h"
+#include <usbd.h>
+
+#include "gdb_if.h"
+
+#define VIRTUAL_COM_PORT_DATA_SIZE 64
+
+static uint32_t count_out;
+static uint32_t count_in;
+static uint32_t out_ptr;
+static uint8_t buffer_out[VIRTUAL_COM_PORT_DATA_SIZE];
+static uint8_t buffer_in[VIRTUAL_COM_PORT_DATA_SIZE];
+
+int gdb_if_init(void)
+{
+ cdcacm_init();
+
+ return 0;
+}
+
+void gdb_if_putchar(unsigned char c, int flush)
+{
+ buffer_in[count_in++] = c;
+ if(flush || (count_in == VIRTUAL_COM_PORT_DATA_SIZE)) {
+ while(usbd_ep_write_packet(2, buffer_in, count_in) <= 0);
+ count_in = 0;
+ }
+}
+
+unsigned char gdb_if_getchar(void)
+{
+ while(!(out_ptr < count_out)) {
+ while(cdcacm_get_config() != 1);
+ count_out = usbd_ep_read_packet(1, buffer_out,
+ VIRTUAL_COM_PORT_DATA_SIZE);
+ out_ptr = 0;
+ }
+
+ return buffer_out[out_ptr++];
+}
+
+unsigned char gdb_if_getchar_to(int timeout)
+{
+ timeout_counter = timeout/100;
+
+ if(!(out_ptr < count_out)) do {
+ count_out = usbd_ep_read_packet(1, buffer_out,
+ VIRTUAL_COM_PORT_DATA_SIZE);
+ out_ptr = 0;
+ } while(timeout_counter && !(out_ptr < count_out));
+
+ if(out_ptr < count_out)
+ return gdb_if_getchar();
+
+ return -1;
+}
+
diff --git a/src/stm32/jtagtap.c b/src/stm32/jtagtap.c
new file mode 100644
index 0000000..ca5576d
--- /dev/null
+++ b/src/stm32/jtagtap.c
@@ -0,0 +1,90 @@
+/*
+ * This file is part of the Black Magic Debug project.
+ *
+ * Copyright (C) 2011 Black Sphere Technologies Ltd.
+ * Written by Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements the low-level JTAG TAP interface. */
+
+#include <libopencm3/stm32/gpio.h>
+#include <stdio.h>
+
+#include "general.h"
+
+#include "jtagtap.h"
+
+int jtagtap_init(void)
+{
+ /* This needs some fixing... */
+ /* Toggle required to sort out line drivers... */
+ gpio_port_write(GPIOA, 0x8100);
+ gpio_port_write(GPIOB, 0x0000);
+
+ gpio_port_write(GPIOA, 0x8180);
+ gpio_port_write(GPIOB, 0x0002);
+
+ gpio_set_mode(JTAG_PORT, GPIO_MODE_OUTPUT_2_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL, TMS_PIN);
+
+ /* Go to JTAG mode for SWJ-DP */
+ for(int i = 0; i <= 50; i++) jtagtap_next(1, 0); /* Reset SW-DP */
+ jtagtap_tms_seq(0xE73C, 16); /* SWD to JTAG sequence */
+ jtagtap_soft_reset();
+
+ return 0;
+}
+
+void jtagtap_reset(void)
+{
+ volatile int i;
+ gpio_clear(GPIOB, GPIO1);
+ for(i = 0; i < 10000; i++) asm("nop");
+ gpio_set(GPIOB, GPIO1);
+ jtagtap_soft_reset();
+}
+
+void jtagtap_srst(void)
+{
+ volatile int i;
+ gpio_set(GPIOA, GPIO2);
+ for(i = 0; i < 10000; i++) asm("nop");
+ gpio_clear(GPIOA, GPIO2);
+}
+
+inline uint8_t jtagtap_next(uint8_t dTMS, uint8_t dTDO)
+{
+ uint8_t ret;
+
+ gpio_set_val(JTAG_PORT, TMS_PIN, dTMS);
+ gpio_set_val(JTAG_PORT, TDI_PIN, dTDO);
+ gpio_set(JTAG_PORT, TCK_PIN);
+ ret = gpio_get(JTAG_PORT, TDO_PIN);
+ gpio_clear(JTAG_PORT, TCK_PIN);
+
+ DEBUG("jtagtap_next(TMS = %d, TDO = %d) = %d\n", dTMS, dTDO, ret);
+
+ return ret;
+}
+
+
+
+#define PROVIDE_GENERIC_JTAGTAP_TMS_SEQ
+#define PROVIDE_GENERIC_JTAGTAP_TDI_TDO_SEQ
+#define PROVIDE_GENERIC_JTAGTAP_TDI_SEQ
+
+#include "jtagtap_generic.c"
+
diff --git a/src/stm32/platform.c b/src/stm32/platform.c
new file mode 100644
index 0000000..bdf2b33
--- /dev/null
+++ b/src/stm32/platform.c
@@ -0,0 +1,179 @@
+/*
+ * This file is part of the Black Magic Debug project.
+ *
+ * Copyright (C) 2011 Black Sphere Technologies Ltd.
+ * Written by Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements the platform specific functions for the STM32
+ * implementation.
+ */
+
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/systick.h>
+#include <libopencm3/stm32/scb.h>
+#include <libopencm3/stm32/nvic.h>
+
+#include "platform.h"
+
+#include <ctype.h>
+
+uint8_t running_status;
+volatile uint32_t timeout_counter;
+
+jmp_buf fatal_error_jmpbuf;
+
+void morse(const char *msg, char repeat);
+static void morse_update(void);
+
+int
+platform_init(void)
+{
+#ifndef LIGHT
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
+#else
+ rcc_clock_setup_in_hsi_out_48mhz();
+#endif
+
+ /* Enable peripherals */
+ rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USBEN);
+ rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_TIM2EN);
+ rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPAEN);
+ rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPBEN);
+ rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPDEN);
+
+ /* Setup GPIO ports */
+#ifdef LIGHT
+ rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_AFIOEN);
+ AFIO_MAPR |= AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON;
+#endif
+ gpio_clear(USB_PU_PORT, USB_PU_PIN);
+ gpio_set_mode(USB_PU_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT,
+ USB_PU_PIN);
+
+ gpio_set_mode(JTAG_PORT, GPIO_MODE_OUTPUT_10_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL,
+ TMS_PIN | TCK_PIN | TDO_PIN);
+
+ gpio_set_mode(LED_PORT, GPIO_MODE_OUTPUT_2_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL,
+ LED_RUN | LED_IDLE | LED_ERROR);
+
+ /* Setup heartbeat timer */
+ systick_set_clocksource(STK_CTRL_CLKSOURCE_AHB_DIV8);
+ systick_set_reload(900000); /* Interrupt us at 10 Hz */
+ systick_interrupt_enable();
+ systick_counter_enable();
+
+#ifndef LIGHT
+ SCB_VTOR = 0x2000; // Relocate interrupt vector table here
+#endif
+ /* Enable IRQs */
+ nvic_enable_irq(NVIC_TIM2_IRQ);
+
+ return 0;
+}
+
+void sys_tick_handler(void)
+{
+ if(running_status)
+ gpio_toggle(LED_PORT, LED_RUN);
+ else
+ gpio_clear(LED_PORT, LED_RUN);
+
+ if(timeout_counter)
+ timeout_counter--;
+
+ morse_update();
+}
+
+
+/* Morse code patterns and lengths */
+static const struct {
+ uint16_t code;
+ uint8_t bits;
+} morse_letter[] = {
+ { 0b00011101, 8}, // 'A' .-
+ { 0b000101010111, 12}, // 'B' -...
+ { 0b00010111010111, 14}, // 'C' -.-.
+ { 0b0001010111, 10}, // 'D' -..
+ { 0b0001, 4}, // 'E' .
+ { 0b000101110101, 12}, // 'F' ..-.
+ { 0b000101110111, 12}, // 'G' --.
+ { 0b0001010101, 10}, // 'H' ....
+ { 0b000101, 6}, // 'I' ..
+ {0b0001110111011101, 16}, // 'J' .---
+ { 0b000111010111, 12}, // 'K' -.-
+ { 0b000101011101, 12}, // 'L' .-..
+ { 0b0001110111, 10}, // 'M' --
+ { 0b00010111, 8}, // 'N' -.
+ { 0b00011101110111, 14}, // 'O' ---
+ { 0b00010111011101, 14}, // 'P' .--.
+ {0b0001110101110111, 16}, // 'Q' --.-
+ { 0b0001011101, 10}, // 'R' .-.
+ { 0b00010101, 8}, // 'S' ...
+ { 0b000111, 6}, // 'T' -
+ { 0b0001110101, 10}, // 'U' ..-
+ { 0b000111010101, 12}, // 'V' ...-
+ { 0b000111011101, 12}, // 'W' .--
+ { 0b00011101010111, 14}, // 'X' -..-
+ {0b0001110111010111, 16}, // 'Y' -.--
+ { 0b00010101110111, 14}, // 'Z' --..
+};
+
+
+const char *morse_msg;
+static const char * volatile morse_ptr;
+static char morse_repeat;
+
+void morse(const char *msg, char repeat)
+{
+ morse_msg = morse_ptr = msg;
+ morse_repeat = repeat;
+ SET_ERROR_STATE(0);
+}
+
+static void morse_update(void)
+{
+ static uint16_t code;
+ static uint8_t bits;
+
+ if(!morse_ptr) return;
+
+ if(!bits) {
+ char c = *morse_ptr++;
+ if(!c) {
+ if(morse_repeat) {
+ morse_ptr = morse_msg;
+ c = *morse_ptr++;
+ } else {
+ morse_ptr = 0;
+ return;
+ }
+ }
+ if((c >= 'A') && (c <= 'Z')) {
+ c -= 'A';
+ code = morse_letter[c].code;
+ bits = morse_letter[c].bits;
+ } else {
+ code = 0; bits = 4;
+ }
+ }
+ SET_ERROR_STATE(code & 1);
+ code >>= 1; bits--;
+}
+
diff --git a/src/stm32/platform.h b/src/stm32/platform.h
new file mode 100644
index 0000000..daeafa8
--- /dev/null
+++ b/src/stm32/platform.h
@@ -0,0 +1,121 @@
+/*
+ * This file is part of the Black Magic Debug project.
+ *
+ * Copyright (C) 2011 Black Sphere Technologies Ltd.
+ * Written by Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements the platform specific functions for the STM32
+ * implementation.
+ */
+#ifndef __PLATFORM_H
+#define __PLATFORM_H
+
+#include <libopencm3/stm32/gpio.h>
+
+#include <setjmp.h>
+
+#include "gdb_packet.h"
+
+/* Important pin mappings for STM32 implementation:
+ *
+ * LED0 = PB2 (Yellow LED : Running)
+ * LED1 = PB10 (Yellow LED : Idle)
+ * LED2 = PB11 (Red LED : Error)
+ *
+ * TPWR = RB0 (input)
+ * nTRST = PB1
+ * SRST_OUT = PA2
+ * TDI = PA3
+ * TMS = PA4 (input for SWDP)
+ * TCK = PA5
+ * TDO = PA6 (input)
+ * nSRST = PA7 (input)
+ *
+ * USB cable pull-up: PA8 // was PA10 on prototype
+ * Force DFU mode button: PB12
+ */
+
+/* Hardware definitions... */
+#ifndef LIGHT
+# define JTAG_PORT GPIOA
+# define TDI_PIN GPIO3
+# define TMS_PIN GPIO4
+# define TCK_PIN GPIO5
+# define TDO_PIN GPIO6
+
+# define SWDP_PORT JTAG_PORT
+# define SWDIO_PIN TMS_PIN
+# define SWCLK_PIN TCK_PIN
+
+# define USB_PU_PORT GPIOA
+# define USB_PU_PIN GPIO8
+
+# define LED_PORT GPIOB
+# define LED_RUN GPIO2
+# define LED_IDLE GPIO10
+# define LED_ERROR GPIO11
+#else
+# define JTAG_PORT GPIOA
+# define TDI_PIN GPIO3
+# define TMS_PIN GPIO2
+# define TCK_PIN GPIO7
+# define TDO_PIN GPIO6
+
+# define SWDP_PORT JTAG_PORT
+# define SWDIO_PIN TMS_PIN
+# define SWCLK_PIN TCK_PIN
+
+# define USB_PU_PORT GPIOA
+# define USB_PU_PIN GPIO15
+#endif
+
+#define DEBUG(...)
+
+extern uint8_t running_status;
+extern volatile uint32_t timeout_counter;
+
+extern jmp_buf fatal_error_jmpbuf;
+
+extern const char *morse_msg;
+
+#define gpio_set_val(port, pin, val) do { \
+ if(val) \
+ gpio_set((port), (pin)); \
+ else \
+ gpio_clear((port), (pin)); \
+} while(0)
+
+#define SET_RUN_STATE(state) {running_status = (state);}
+#define SET_IDLE_STATE(state) {gpio_set_val(LED_PORT, LED_IDLE, state);}
+#define SET_ERROR_STATE(state) {gpio_set_val(LED_PORT, LED_ERROR, state);}
+
+#define PLATFORM_SET_FATAL_ERROR_RECOVERY() {setjmp(fatal_error_jmpbuf);}
+#define PLATFORM_FATAL_ERROR(error) { \
+ if(running_status) gdb_putpacketz("X1D"); \
+ else gdb_putpacketz("EFF"); \
+ running_status = 0; \
+ TARGET_LIST_FREE(); \
+ cur_target = last_target = NULL; \
+ morse("TARGET LOST.", 1); \
+ longjmp(fatal_error_jmpbuf, (error)); \
+}
+
+int platform_init(void);
+void morse(const char *msg, char repeat);
+
+#endif
+
diff --git a/src/stm32/swdptap.c b/src/stm32/swdptap.c
new file mode 100644
index 0000000..e6799eb
--- /dev/null
+++ b/src/stm32/swdptap.c
@@ -0,0 +1,156 @@
+/*
+ * This file is part of the Black Magic Debug project.
+ *
+ * Copyright (C) 2011 Black Sphere Technologies Ltd.
+ * Written by Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This file implements the low-level SW-DP interface. */
+
+#include <libopencm3/stm32/gpio.h>
+#include <stdio.h>
+
+#include "general.h"
+
+#include "swdptap.h"
+
+#include "gdb_packet.h"
+
+void swdptap_turnaround(uint8_t dir)
+{
+ static uint8_t olddir = 0;
+
+ DEBUG("%s", dir ? "\n-> ":"\n<- ");
+
+ /* Don't turnaround if direction not changing */
+ if(dir == olddir) return;
+ olddir = dir;
+
+ if(dir)
+ gpio_set_mode(SWDP_PORT, GPIO_MODE_INPUT,
+ GPIO_CNF_INPUT_FLOAT, SWDIO_PIN);
+ gpio_set(SWDP_PORT, SWCLK_PIN);
+ gpio_clear(SWDP_PORT, SWCLK_PIN);
+ if(!dir)
+ gpio_set_mode(SWDP_PORT, GPIO_MODE_OUTPUT_10_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL, SWDIO_PIN);
+}
+
+uint8_t swdptap_bit_in(void)
+{
+ uint8_t ret;
+
+ ret = gpio_get(SWDP_PORT, SWDIO_PIN);
+ gpio_set(SWDP_PORT, SWCLK_PIN);
+ gpio_clear(SWDP_PORT, SWCLK_PIN);
+
+ DEBUG("%d", ret?1:0);
+
+ return ret;
+}
+
+void swdptap_bit_out(uint8_t val)
+{
+ DEBUG("%d", val);
+
+ gpio_set_val(SWDP_PORT, SWDIO_PIN, val);
+ gpio_set(SWDP_PORT, SWCLK_PIN);
+ gpio_clear(SWDP_PORT, SWCLK_PIN);
+}
+
+int swdptap_init(void)
+{
+ /* This must be investigated in more detail.
+ * As described in STM32 Reference Manual... */
+ swdptap_reset();
+ swdptap_seq_out(0xE79E, 16); /* 0b0111100111100111 */
+ swdptap_reset();
+ swdptap_seq_out(0, 16);
+
+ return 0;
+}
+
+
+void swdptap_reset(void)
+{
+ swdptap_turnaround(0);
+ /* 50 clocks with TMS high */
+ for(int i = 0; i < 50; i++) swdptap_bit_out(1);
+}
+
+
+uint32_t swdptap_seq_in(int ticks)
+{
+ uint32_t index = 1;
+ uint32_t ret = 0;
+
+ swdptap_turnaround(1);
+
+ while(ticks--) {
+ if(swdptap_bit_in()) ret |= index;
+ index <<= 1;
+ }
+
+ return ret;
+}
+
+
+uint8_t swdptap_seq_in_parity(uint32_t *ret, int ticks)
+{
+ uint32_t index = 1;
+ uint8_t parity = 0;
+ *ret = 0;
+
+ swdptap_turnaround(1);
+
+ while(ticks--) {
+ if(swdptap_bit_in()) {
+ *ret |= index;
+ parity ^= 1;
+ }
+ index <<= 1;
+ }
+ if(swdptap_bit_in()) parity ^= 1;
+
+ return parity;
+}
+
+
+void swdptap_seq_out(uint32_t MS, int ticks)
+{
+ swdptap_turnaround(0);
+
+ while(ticks--) {
+ swdptap_bit_out(MS & 1);
+ MS >>= 1;
+ }
+}
+
+
+void swdptap_seq_out_parity(uint32_t MS, int ticks)
+{
+ uint8_t parity = 0;
+
+ swdptap_turnaround(0);
+
+ while(ticks--) {
+ swdptap_bit_out(MS & 1);
+ parity ^= MS;
+ MS >>= 1;
+ }
+ swdptap_bit_out(parity & 1);
+}
+
diff --git a/src/stm32/usbdfu.c b/src/stm32/usbdfu.c
new file mode 100644
index 0000000..a6194ee
--- /dev/null
+++ b/src/stm32/usbdfu.c
@@ -0,0 +1,308 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <libopencm3/stm32/systick.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/flash.h>
+#include <libopencm3/stm32/scb.h>
+#include <libopencm3/usb/usbd.h>
+#include <libopencm3/usb/dfu.h>
+
+#define APP_ADDRESS 0x08002000
+
+/* Commands sent with wBlockNum == 0 as per ST implementation. */
+#define CMD_SETADDR 0x21
+#define CMD_ERASE 0x41
+
+/* We need a special large control buffer for this device: */
+u8 usbd_control_buffer[1024];
+
+static enum dfu_state usbdfu_state = STATE_DFU_IDLE;
+
+static char *get_dev_unique_id(char *serial_no);
+
+static struct {
+ u8 buf[sizeof(usbd_control_buffer)];
+ u16 len;
+ u32 addr;
+ u16 blocknum;
+} prog;
+
+const struct usb_device_descriptor dev = {
+ .bLength = USB_DT_DEVICE_SIZE,
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = 0,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .bMaxPacketSize0 = 64,
+ .idVendor = 0x0483,
+ .idProduct = 0xDF11,
+ .bcdDevice = 0x0200,
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .iSerialNumber = 3,
+ .bNumConfigurations = 1,
+};
+
+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 = 0x011A,
+};
+
+const struct usb_interface_descriptor iface = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = 0xFE, /* Device Firmware Upgrade */
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 2,
+
+ /* The ST Microelectronics DfuSe application needs this string.
+ * The format isn't documented... */
+ .iInterface = 4,
+
+ .extra = &dfu_function,
+ .extralen = sizeof(dfu_function),
+};
+
+const struct usb_interface ifaces[] = {{
+ .num_altsetting = 1,
+ .altsetting = &iface,
+}};
+
+const struct usb_config_descriptor config = {
+ .bLength = USB_DT_CONFIGURATION_SIZE,
+ .bDescriptorType = USB_DT_CONFIGURATION,
+ .wTotalLength = 0,
+ .bNumInterfaces = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = 0xC0,
+ .bMaxPower = 0x32,
+
+ .interface = ifaces,
+};
+
+static char serial_no[25];
+
+static const char *usb_strings[] = {
+ "x",
+ "Black Sphere Technologies",
+ "Black Magic Probe (Upgrade)",
+ serial_no,
+ /* This string is used by ST Microelectronics' DfuSe utility */
+ "@Internal Flash /0x08000000/8*001Ka,120*001Kg"
+};
+
+static u8 usbdfu_getstatus(u32 *bwPollTimeout)
+{
+ switch(usbdfu_state) {
+ case STATE_DFU_DNLOAD_SYNC:
+ usbdfu_state = STATE_DFU_DNBUSY;
+ *bwPollTimeout = 100;
+ return DFU_STATUS_OK;
+
+ case STATE_DFU_MANIFEST_SYNC:
+ /* Device will reset when read is complete */
+ usbdfu_state = STATE_DFU_MANIFEST;
+ return DFU_STATUS_OK;
+
+ default:
+ return DFU_STATUS_OK;
+ }
+}
+
+static void usbdfu_getstatus_complete(struct usb_setup_data *req)
+{
+ int i;
+ (void)req;
+
+ switch(usbdfu_state) {
+ case STATE_DFU_DNBUSY:
+
+ flash_unlock();
+ if(prog.blocknum == 0) {
+ switch(prog.buf[0]) {
+ case CMD_ERASE:
+ flash_erase_page(*(u32*)(prog.buf+1));
+ case CMD_SETADDR:
+ prog.addr = *(u32*)(prog.buf+1);
+ }
+ } else {
+ u32 baseaddr = prog.addr +
+ ((prog.blocknum - 2) *
+ dfu_function.wTransferSize);
+ for(i = 0; i < prog.len; i += 2)
+ flash_program_half_word(baseaddr + i,
+ *(u16*)(prog.buf+i));
+ }
+ flash_lock();
+
+ /* We jump straight to dfuDNLOAD-IDLE,
+ * skipping dfuDNLOAD-SYNC
+ */
+ usbdfu_state = STATE_DFU_DNLOAD_IDLE;
+ return;
+
+ case STATE_DFU_MANIFEST:
+ /* USB device must detach, we just reset... */
+ scb_reset_system();
+ return; /* Will never return */
+ default:
+ return;
+ }
+}
+
+static int usbdfu_control_request(struct usb_setup_data *req, u8 **buf,
+ u16 *len, void (**complete)(struct usb_setup_data *req))
+{
+
+ if((req->bmRequestType & 0x7F) != 0x21)
+ return 0; /* Only accept class request */
+
+ switch(req->bRequest) {
+ case DFU_DNLOAD:
+ if((len == NULL) || (*len == 0)) {
+ usbdfu_state = STATE_DFU_MANIFEST_SYNC;
+ return 1;
+ } else {
+ /* Copy download data for use on GET_STATUS */
+ prog.blocknum = req->wValue;
+ prog.len = *len;
+ memcpy(prog.buf, *buf, *len);
+ usbdfu_state = STATE_DFU_DNLOAD_SYNC;
+ return 1;
+ }
+ case DFU_CLRSTATUS:
+ /* Clear error and return to dfuIDLE */
+ if(usbdfu_state == STATE_DFU_ERROR)
+ usbdfu_state = STATE_DFU_IDLE;
+ return 1;
+ case DFU_ABORT:
+ /* Abort returns to dfuIDLE state */
+ usbdfu_state = STATE_DFU_IDLE;
+ return 1;
+ case DFU_UPLOAD:
+ /* Upload not supported for now */
+ return 0;
+ case DFU_GETSTATUS: {
+ u32 bwPollTimeout = 0; /* 24-bit integer in DFU class spec */
+
+ (*buf)[0] = usbdfu_getstatus(&bwPollTimeout);
+ (*buf)[1] = bwPollTimeout & 0xFF;
+ (*buf)[2] = (bwPollTimeout >> 8) & 0xFF;
+ (*buf)[3] = (bwPollTimeout >> 16) & 0xFF;
+ (*buf)[4] = usbdfu_state;
+ (*buf)[5] = 0; /* iString not used here */
+ *len = 6;
+
+ *complete = usbdfu_getstatus_complete;
+
+ return 1;
+ }
+ case DFU_GETSTATE:
+ /* Return state with no state transision */
+ *buf[0] = usbdfu_state;
+ *len = 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(void)
+{
+ rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPBEN);
+ if(gpio_get(GPIOB, GPIO12)) {
+ /* Boot the application if it's valid */
+ if((*(volatile u32*)APP_ADDRESS & 0x2FFE0000) == 0x20000000) {
+ /* Set vector table base address */
+ SCB_VTOR = APP_ADDRESS & 0xFFFF;
+ /* Initialise master stack pointer */
+ asm volatile ("msr msp, %0"::"g"
+ (*(volatile u32*)APP_ADDRESS));
+ /* Jump to application */
+ (*(void(**)())(APP_ADDRESS + 4))();
+ }
+ }
+
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
+
+ rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USBEN);
+ rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPAEN);
+
+ gpio_set_mode(GPIOA, GPIO_MODE_INPUT, 0, GPIO8);
+
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL, GPIO11);
+ systick_set_clocksource(STK_CTRL_CLKSOURCE_AHB_DIV8);
+ systick_set_reload(900000);
+ systick_interrupt_enable();
+ systick_counter_enable();
+ gpio_set_mode(GPIOB, GPIO_MODE_INPUT,
+ GPIO_CNF_INPUT_FLOAT, GPIO2 | GPIO10);
+
+ get_dev_unique_id(serial_no);
+
+ usbd_init(&stm32f103_usb_driver, &dev, &config, usb_strings);
+ usbd_set_control_buffer_size(sizeof(usbd_control_buffer));
+ usbd_register_control_callback(
+ USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
+ USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
+ usbdfu_control_request);
+
+ gpio_set(GPIOA, GPIO8);
+ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ,
+ GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
+
+ while (1)
+ usbd_poll();
+}
+
+static char *get_dev_unique_id(char *s)
+{
+ volatile uint8_t *unique_id = (volatile uint8_t *)0x1FFFF7E8;
+ int i;
+
+ /* Fetch serial number from chip's unique ID */
+ for(i = 0; i < 24; i+=2) {
+ s[i] = ((*unique_id >> 4) & 0xF) + '0';
+ s[i+1] = (*unique_id++ & 0xF) + '0';
+ }
+ for(i = 0; i < 24; i++)
+ if(s[i] > '9')
+ s[i] += 'A' - '9' - 1;
+
+ return s;
+}
+
+void sys_tick_handler()
+{
+ gpio_toggle(GPIOB, GPIO11); /* LED2 on/off */
+}
+