Rename MCU

This commit is contained in:
fruchti 2021-04-04 20:33:45 +02:00
parent ed2925a961
commit 64a01bef05
75 changed files with 4 additions and 4 deletions

View file

@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
#define BUILD_VERSION_MAJOR 0
#define BUILD_VERSION_MINOR 2
#define BUILD_VERSION_PATCH 0
#define BUILD_DATE ((uint32_t)&__BUILD_DATE)
#define BUILD_NUMBER ((uint32_t)&__BUILD_NUMBER)
extern char __BUILD_DATE;
extern char __BUILD_NUMBER;

View file

@ -0,0 +1,59 @@
#pragma once
// Legend:
// -> is a transmission via endpoint 2 (out) from the host to the MCU
// <- is a transmission via endpoint 1 (in) from the MCU to the host
typedef enum
{
// Does nothing
CMD_NOP = 0x00,
// Basic bootloader info. Returns 19 to 64 bytes (cf. BootloaderInfo_t):
// <- Build date: u32 (YYYYMMDD as an unsigned integer)
// <- Build number: u32
// <- Flash application base address: u32
// <- Maximum application size: u32
// <- Major version: u8
// <- Minor version: u8
// <- Patch version: u8
// <- Identifier string (variable length, not zero-terminated)
CMD_BOOTLOADER_INFO = 0x01,
// Calculate a CRC32 of a memory region:
// -> Start address: u32
// -> Length: u32
// <- CRC32: u32
CMD_READ_CRC = 0x02,
// Read memory contents
// -> Start address: u32
// -> Length n: u32
// <- Data: n bytes
CMD_READ_MEMORY = 0x03,
// Erase a single 1 KiB flash page
// -> Page number: u8
// <- Return code (0 for success): u8
CMD_ERASE_PAGE = 0x04,
// Program flash
// -> Start adress: u32
// -> Data: n bytes (n must be even)
CMD_PROGRAM = 0x05,
// Exit bootloader and start application software
CMD_EXIT = 0xff
} Command_t;
typedef struct
{
uint32_t build_date;
uint32_t build_number;
uint32_t flash_application_start;
uint32_t flash_application_size;
uint8_t version_major;
uint8_t version_minor;
uint8_t version_patch;
char identifier[];
} __attribute__((packed, aligned(1))) BootloaderInfo_t;

View file

@ -0,0 +1,66 @@
#include "flash.h"
#include "usb.h"
#include "ownership.h"
MODULE_OWNS_PERIPHERAL(FLASH);
static inline void Flash_Unlock(void)
{
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
static inline void Flash_Lock(void)
{
FLASH->CR = FLASH_CR_LOCK;
}
Flash_Status_t Flash_ErasePage(unsigned int page)
{
if(page >= FLASH_PAGES)
{
return FLASH_PROHIBITED;
}
uint32_t page_start = FLASH_BASE + page * FLASH_PAGE_BYTES;
Flash_Unlock();
while(FLASH->SR & FLASH_SR_BSY);
FLASH->CR = FLASH_CR_PER;
FLASH->AR = page_start;
FLASH->CR = FLASH_CR_STRT | FLASH_CR_PER;
while(FLASH->SR & FLASH_SR_BSY);
// Flash_Lock() clears the PER bit, so we don't have to reset that
Flash_Lock();
// Verify
for(uint32_t i = page_start; i < page_start + FLASH_PAGE_BYTES; i += 4)
{
if(*(uint32_t*)i != 0xffffffff)
{
return FLASH_VERIFY_FAILED;
}
}
return FLASH_SUCCESS;
}
void Flash_ProgramFromPMA(uint32_t flash_adress, uint16_t pma_offset,
uint32_t length)
{
Flash_Unlock();
uint16_t *pma = (uint16_t*)(USB_PMA_ADDR + 2 * pma_offset);
volatile uint16_t *flash = (uint16_t*)flash_adress;
FLASH->CR = FLASH_CR_PG;
for(unsigned int i = 0; i < (length + 1) / 2; i++)
{
*flash++ = *pma++;
pma++;
while(FLASH->SR & FLASH_SR_BSY);
}
// Flash_Lock() clears the PG bit, so we don't have to reset that
Flash_Lock();
}

View file

@ -0,0 +1,22 @@
#pragma once
#include "stm32f103x6.h"
#define FLASH_PAGE_BYTES 1024U
#define FLASH_BOOTLOADER_PAGES 2U
#define FLASH_PAGES 64U
typedef enum
{
FLASH_SUCCESS = 0,
FLASH_PROHIBITED = 1,
FLASH_VERIFY_FAILED = 2
} Flash_Status_t;
#define FLASH_APPLICATION_BASE (FLASH_BASE \
+ FLASH_BOOTLOADER_PAGES * FLASH_PAGE_BYTES)
Flash_Status_t Flash_ErasePage(unsigned int page);
void Flash_ProgramFromPMA(uint32_t flash_adress, uint16_t pma_offset,
uint32_t length);

View file

@ -0,0 +1,119 @@
#include "main.h"
// Configure clocks based on a 8 MHz external crystal for:
// SYSCLK, AHB, APB2 72 Mhz
// APB1, ADC 36 MHz
static inline void Clock_Init(void)
{
// Activate HSE and wait for it to be ready
RCC->CR = RCC_CR_HSEON | RCC_CR_HSION;
while(!(RCC->CR & RCC_CR_HSERDY));
RCC->CFGR = RCC_CFGR_SW_0;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_0);
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_1;
// Set PLL to x9 (-> 72MHz system clock)
RCC->CFGR = RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLSRC | RCC_CFGR_PPRE1_2
| RCC_CFGR_SW_0;
// Activate PLL and wait
RCC->CR = RCC_CR_PLLON | RCC_CR_HSEON | RCC_CR_HSION;
while(!(RCC->CR & RCC_CR_PLLRDY));
// Select PLL as clock source
RCC->CFGR = RCC_CFGR_PLLMULL9 | RCC_CFGR_PLLSRC | RCC_CFGR_PPRE1_2
| RCC_CFGR_SW_1;
// Disable all interrupts
RCC->CIR = 0x00000000;
// Enable peripheral clocks
RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN;
RCC->APB1ENR = RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN | RCC_APB1ENR_USBEN;
RCC->AHBENR = RCC_AHBENR_CRCEN;
}
static inline bool Bootloader_EntryCondition(void)
{
// Activate pull-up for test point
GPIOA->CRH = (0x44444444
& ~(0x0f << (PIN_TEST_POINT * 4 - 32)))
| (0x08<< (PIN_TEST_POINT * 4 - 32))
;
GPIOA->BSRR = (1 << PIN_TEST_POINT);
// The first word in the application frame is the stack end pointer
const uint32_t *application_start = (uint32_t*)FLASH_APPLICATION_BASE;
// If it is not a valid RAM address, we can assume there's no application
// present in flash and thus enter the bootloader
if((*application_start & 0xffff8000) != 0x20000000)
{
return true;
}
// Check RTC backup register 1 for magic value
PWR->CR = PWR_CR_DBP;
if(BKP->DR1 == 0xb007)
{
return true;
}
// Check if test point is held low externally
if(~GPIOA->IDR & (1 << PIN_TEST_POINT))
{
return true;
}
return false;
}
static inline void Bootloader_Exit(void)
{
// Reset RTC backup register
BKP->DR1 = 0x0000;
// Reset peripherals
RCC->APB1RSTR = RCC_APB1RSTR_USBRST;
RCC->APB2RSTR = RCC_APB2RSTR_IOPARST | RCC_APB2RSTR_AFIORST;
RCC->APB1RSTR = 0x00000000;
RCC->APB2RSTR = 0x00000000;
// Reset peripheral clock enable registers to their reset values
RCC->AHBENR = 0x00000014; // Disable CRC
RCC->APB2ENR = 0x00000000; // Disable GPIOA, GPIOB, AFIO
RCC->APB1ENR = 0x00000000; // Disable USB, PWR, BKP
// Set vector table location
SCB->VTOR = FLASH_APPLICATION_BASE - FLASH_BASE;
// Set stack pointer and jump into application
uint32_t application_stack_pointer = *(uint32_t*)FLASH_APPLICATION_BASE;
uint32_t application_reset_handler = *(uint32_t*)(FLASH_APPLICATION_BASE
+ 4);
__asm__ volatile(".syntax unified \n"
"msr msp, %[stack_pointer] \n"
"bx %[reset_handler] \n"
:
: [stack_pointer] "r" (application_stack_pointer),
[reset_handler] "r" (application_reset_handler));
}
int main(void)
{
Clock_Init();
if(Bootloader_EntryCondition())
{
USB_Init();
while(USB_Poll());
// Delay to allow answer token to be fetched by host
Util_Delay(100000);
}
Bootloader_Exit();
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <stdbool.h>
#include "stm32f103x6.h"
#include "pinning.h"
#include "usb.h"
#include "flash.h"
#include "util.h"
int main(void);

View file

@ -0,0 +1,12 @@
#pragma once
#define _PASTE(x, y) x ## y
#define PASTE(x, y) _PASTE(x, y)
#define MODULE_OWNS_PERIPHERAL(peripheral) \
void *_PERIPHERAL_OWNERSHIP_ ## peripheral \
= (void*)(peripheral)
#define MODULE_OWNS_PIN(gpio, pin) \
void *PASTE(_PIN_OWNERSHIP_ ## gpio ## _, pin) \
= (void*)(gpio + pin)

View file

@ -0,0 +1,6 @@
#pragma once
// Port A
#define PIN_TEST_POINT 10 // PA10
#define PIN_USB_DM 11 // PA11 - USB_DM
#define PIN_USB_DP 12 // PA12 - USB_DP

View file

@ -0,0 +1,88 @@
.syntax unified
.thumb
.fpu softvfp
.global Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
init_data:
ldr r2, =_start_init_data
ldr r3, =_start_data
ldr r1, =_end_data
init_data_loop:
cmp r3, r1
bhs zero_bss
ldr r0, [r2], #4
str r0, [r3], #4
b init_data_loop
zero_bss:
ldr r3, =_start_bss
ldr r1, =_end_bss
movs r0, #0
zero_bss_loop:
cmp r3, r1
bhs init_finished
str r0, [r3], #4
b zero_bss_loop
init_finished:
bl main
infinite_loop:
b infinite_loop
.size Reset_Handler, .-Reset_Handler
// Only the core interrupt vectors, since the bootloader code does not use
// interrupts. Saves a few bytes to omit the interrupt vector table.
.section .cortex_vectors, "a"
.word _end_stack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0x00000000
.word 0x00000000
.word 0x00000000
.word 0x00000000
.word SVC_Handler
.word DebugMon_Handler
.word 0x00000000
.word PendSV_Handler
.word SysTick_Handler
.type Dummy_Handler, %function
Dummy_Handler:
b Dummy_Handler
.weak NMI_Handler
.thumb_set NMI_Handler, Dummy_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler, Dummy_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler, Dummy_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler, Dummy_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler, Dummy_Handler
.weak SVC_Handler
.thumb_set SVC_Handler, Dummy_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler, Dummy_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler, Dummy_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler, Dummy_Handler

View file

@ -0,0 +1,257 @@
#include "usb.h"
#include "usb_descriptors.h"
#include "usb_util.h"
#include "usb_com.h"
#include "util.h"
#include "ownership.h"
MODULE_OWNS_PERIPHERAL(USB);
MODULE_OWNS_PIN(GPIOA, PIN_USB_DM);
MODULE_OWNS_PIN(GPIOA, PIN_USB_DP);
uint8_t USB_DeviceStatus[2] = {0x00, 0x00};
volatile unsigned int USB_Address = 0;
void USB_Init(void)
{
// Initialise USB GPIOs to AF mode
// Note: This will reset the test point pin to an input without pullup
GPIOA->CRH = (0x44444444
& ~(0x0f << (PIN_USB_DM * 4 - 32))
& ~(0x0f << (PIN_USB_DP * 4 - 32)))
| (0x0b << (PIN_USB_DM * 4 - 32)) // AF mode, 50 MHz
| (0x0b << (PIN_USB_DP * 4 - 32)) // AF mode, 50 MHz
;
Util_Delay(100000);
// Analog power up
USB->CNTR = (uint16_t)USB_CNTR_FRES;
// Minimum delay: 1 µs
Util_Delay(3000);
USB->CNTR = (uint16_t)0;
USB->ISTR = (uint16_t)0;
USB->CNTR = (uint16_t)(USB_CNTR_RESETM | USB_CNTR_CTRM);
}
static inline void USB_HandleReset(void)
{
// Remove reset flag
USB->ISTR = (uint16_t)~(USB_ISTR_RESET);
// Set buffer table origin
USB->BTABLE = USB_BTABLE_OFFSET;
// Control endpoint 0 (64 bytes size)
USB_BTABLE_ENTRIES[0].COUNT_RX = USB_EP_RXCOUNT_BL_SIZE | (1 << 10);
USB_BTABLE_ENTRIES[0].ADDR_RX = 0x40;
USB_BTABLE_ENTRIES[0].COUNT_TX = 0;
USB_BTABLE_ENTRIES[0].ADDR_TX = 0x80;
USB_SetEPR(&(USB->EP0R), USB_EPR_EP_TYPE_CONTROL
| USB_EPR_STAT_TX_NAK | USB_EPR_STAT_RX_VALID);
// Endpoint 1: In (buffer size 64)
USB_BTABLE_ENTRIES[1].COUNT_TX = 0;
USB_BTABLE_ENTRIES[1].ADDR_TX = 0xc0;
USB_BTABLE_ENTRIES[1].COUNT_RX = 0;
USB_BTABLE_ENTRIES[1].ADDR_RX = 0;
USB_SetEPR(&(USB->EP1R), USB_EPR_EP_TYPE_BULK
| USB_EPR_STAT_TX_NAK | USB_EPR_STAT_RX_DISABLED
| (1 << USB_EP1R_EA_Pos));
// Endpoint 2: Out (buffer size 64)
USB_BTABLE_ENTRIES[2].COUNT_RX = USB_EP_RXCOUNT_BL_SIZE | (1 << 10);
USB_BTABLE_ENTRIES[2].ADDR_RX = 0x100;
USB_BTABLE_ENTRIES[2].COUNT_TX = 0;
USB_BTABLE_ENTRIES[2].ADDR_TX = 0;
USB_SetEPR(&(USB->EP2R), USB_EPR_EP_TYPE_BULK
| USB_EPR_STAT_TX_DISABLED | USB_EPR_STAT_RX_VALID
| (2 << USB_EP2R_EA_Pos));
// Enable
USB->DADDR = USB_DADDR_EF;
}
static inline void USB_HandleIn(void)
{
if((USB->DADDR & USB_DADDR_ADD) != USB_Address)
{
USB->DADDR = USB_Address | USB_DADDR_EF;
}
}
static inline bool USB_HandleSetup(void)
{
bool exit_bootloader = false;
USB_SetupPacket_t sp;
USB_PMAToMemory(&sp, USB_BTABLE_ENTRIES[0].ADDR_RX,
sizeof(USB_SetupPacket_t));
const void *reply_data = NULL;
int reply_length = 0;
uint8_t reply_response = USB_EP_TX_STALL;
if((sp.bmRequestType & (USB_REQUEST_TYPE | USB_REQUEST_RECIPIENT))
== (USB_REQUEST_TYPE_STANDARD | USB_REQUEST_RECIPIENT_DEVICE))
{
switch(sp.bRequest)
{
case USB_REQUEST_GET_STATUS:
if(sp.wValue == 0 && sp.wIndex == 0 && sp.wLength == 2)
{
reply_length = 2;
reply_data = USB_DeviceStatus;
}
break;
case USB_REQUEST_GET_DESCRIPTOR:;
USB_DescriptorType_t descriptor_type = sp.wValue >> 8;
int descriptor_index = sp.wValue & 0xff;
reply_length = sp.wLength;
USB_HandleGetDescriptor(descriptor_type, descriptor_index,
&reply_data, &reply_length, &reply_response);
break;
case USB_REQUEST_SET_ADDRESS:
USB_Address = sp.wValue & USB_DADDR_ADD;
reply_response = USB_EP_TX_VALID;
break;
case USB_REQUEST_SET_CONFIGURATION:
// There is only one configuration, so this request is ignored
// (but still accepted)
reply_response = USB_EP_TX_VALID;
break;
default:
reply_response = USB_EP_TX_STALL;
break;
}
}
else if((sp.bmRequestType & USB_REQUEST_TYPE) == USB_REQUEST_TYPE_VENDOR)
{
reply_response = USB_EP_TX_VALID;
if(!USB_HandleCommand(&sp))
{
exit_bootloader = true;
}
}
else
{
// Unknown request
reply_response = USB_EP_TX_STALL;
}
if(reply_data)
{
// Reply with data
USB_MemoryToPMA(USB_BTABLE_ENTRIES[0].ADDR_TX, reply_data,
reply_length);
USB_BTABLE_ENTRIES[0].COUNT_TX = reply_length;
USB->EP0R = (USB_EP_TX_NAK ^ USB_EP_TX_VALID)
| USB_EP_CTR_RX | USB_EP_CTR_TX | USB_EPR_EP_TYPE_CONTROL | 0;
}
else
{
// Send response
USB_BTABLE_ENTRIES[0].COUNT_TX = 0;
USB->EP0R = (USB_EP_TX_NAK ^ reply_response)
| USB_EP_CTR_RX | USB_EP_CTR_TX | USB_EPR_EP_TYPE_CONTROL | 0;
}
return !exit_bootloader;
}
bool USB_Poll(void)
{
if(USB->ISTR & USB_ISTR_RESET)
{
// Reset happened
USB_HandleReset();
return true;
}
uint16_t istr;
while((istr = USB->ISTR) & (USB_ISTR_CTR))
{
if(istr & USB_ISTR_CTR)
{
// Correct transfer
int ep = istr & USB_ISTR_EP_ID;
switch(ep)
{
case 0:
// Determine transfer direction
if(istr & USB_ISTR_DIR)
{
// Out transfer
if(USB->EP0R & USB_EP0R_SETUP)
{
// Clear CTR_RX and set RX status to VALID (from
// NAK)
USB->EP0R = USB_EP_CTR_TX
| (USB_EP_RX_NAK ^ USB_EP_RX_VALID)
| USB_EPR_EP_TYPE_CONTROL | 0;
// Setup packed received and check if a command to
// exit the bootloader was received
if(!USB_HandleSetup())
{
return false;
}
}
else
{
// Only setup packets are supported, so other out
// transfers are just ignored
// Clear CTR_RX and set RX status to VALID (from
// NAK)
USB->EP0R = USB_EP_CTR_TX
| (USB_EP_RX_NAK ^ USB_EP_RX_VALID)
| USB_EPR_EP_TYPE_CONTROL | 0;
}
}
else
{
// In transfer
// Clear CTR_TX
USB->EP0R = USB_EP_CTR_RX | USB_EPR_EP_TYPE_CONTROL | 0;
USB_HandleIn();
}
break;
case 1:
// Data in endpoint
// In transfer finished. STAT_TX gets set to NAK
// automatically.
// Clear CTR_TX
USB->EP1R = USB_EPR_EP_TYPE_BULK | 1;
break;
case 2:
// Data out endpoint
// Clear CTR_RX and set RX status to VALID (which is NAK
// after a correct transfer)
USB->EP2R = (USB_EP_RX_VALID ^ USB_EP_RX_NAK)
| USB_EPR_EP_TYPE_BULK | 2;
// Out transfer finished
USB_HandleEP2Out();
break;
}
}
}
return true;
}

View file

@ -0,0 +1,130 @@
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include "stm32f103x6.h"
#include "pinning.h"
#define USB_TOKEN_OUT 0b0001
#define USB_TOKEN_IN 0x1001
#define USB_TOKEN_SOF 0b0101
#define USB_TOKEN_SETUP 0b1101
#define USB_TOKEN_DATA0 0b0011
#define USB_TOKEN_DATA1 0b1011
#define USB_TOKEN_DATA2 0b0111
#define USB_TOKEN_ACK 0b0010
#define USB_TOKEN_NAK 0b1010
#define USB_TOKEN_STALL 0b1110
#define USB_TOKEN_NYET 0b0110
#define USB_TOKEN_PRE 0b1100
#define USB_TOKEN_ERR 0b1100
#define USB_TOKEN_SPLIT 0b1000
#define USB_TOKEN_PING 0b0100
#define USB_REQUEST_DIRECTION (1 << 7)
#define USB_REQUEST_DIRECTION_OUT 0
#define USB_REQUEST_DIRECTION_IN (1 << 7)
#define USB_REQUEST_TYPE (0x3 << 5)
#define USB_REQUEST_TYPE_STANDARD 0
#define USB_REQUEST_TYPE_CLASS (1 << 5)
#define USB_REQUEST_TYPE_VENDOR (2 << 5)
#define USB_REQUEST_TYPE_RESERVED (3 << 5)
#define USB_REQUEST_RECIPIENT 0x1f
#define USB_REQUEST_RECIPIENT_DEVICE 0
#define USB_REQUEST_RECIPIENT_INTERFACE 1
#define USB_REQUEST_RECIPIENT_ENDPOINT 2
#define USB_REQUEST_RECIPIENT_OTHER 3
#define USB_REQUEST_CLEAR_FEATURE 1
// wValue = <feature>, wIndex = 0|<interface>|<endpoint>, wLength = 0
#define USB_REQUEST_GET_CONFIGURATION 8
// wValue = 0, wIndex = 0, wLength = 1
#define USB_REQUEST_GET_DESCRIPTOR 6
// wValue = <descriptor type>:<descriptor index>, wIndex = 0|<language>, wLength = <descriptor length>
#define USB_REQUEST_GET_INTERFACE 10
// wValue = 0, wIndex = <interface>, wLength = 1
#define USB_REQUEST_GET_STATUS 0
// wValue = 0, wIndex = 0|<interface>|<endpoint>, wLength = 2
#define USB_REQUEST_SET_ADDRESS 5
// wValue = <address>, wIndex = 0, wLength = 0
#define USB_REQUEST_SET_CONFIGURATION 9
// wValue = <configuration value>, wIndex = 0, wLength = 0
#define USB_REQUEST_SET_DESCRIPTOR 7
// wValue = <descriptor type>:<descriptor index>, wIndex = 0|<language>, wLength = <descriptor length>
#define USB_REQUEST_SET_FEATURE 3
// wValue = <feature selector>, wIndex = 0|<interface>|<endpoint>, wLength = 0
#define USB_REQUEST_SET_INTERFACE 11
// wValue = <alternate setting>, wIndex = <interface>, wLength = 0
#define USB_REQUEST_SYNCH_FRAME 12
// wValue = 0, wIndex = <endpoint>, wLength = 2
#define USB_EPR_STAT_TX_DISABLED 0x00
#define USB_EPR_STAT_TX_STALL USB_EP0R_STAT_TX_0
#define USB_EPR_STAT_TX_NAK USB_EP0R_STAT_TX_1
#define USB_EPR_STAT_TX_VALID (USB_EP0R_STAT_TX_0 \
| USB_EP0R_STAT_TX_1)
#define USB_EPR_STAT_RX_DISABLED 0x00
#define USB_EPR_STAT_RX_STALL USB_EP0R_STAT_RX_0
#define USB_EPR_STAT_RX_NAK USB_EP0R_STAT_RX_1
#define USB_EPR_STAT_RX_VALID (USB_EP0R_STAT_RX_0 \
| USB_EP0R_STAT_RX_1)
#define USB_EPR_EP_TYPE_BULK 0x00
#define USB_EPR_EP_TYPE_CONTROL USB_EP0R_EP_TYPE_0
#define USB_EPR_EP_TYPE_ISO USB_EP0R_EP_TYPE_1
#define USB_EPR_EP_TYPE_INTERRUPT (USB_EP0R_EP_TYPE_0 \
| USB_EP0R_EP_TYPE_1)
#define USB_PMA_ADDR 0x40006000UL
#define USB_BTABLE_OFFSET 0x00
#define USB_EP_RXCOUNT_BL_SIZE (1 << 15)
typedef struct
{
volatile union
{
volatile uint16_t ADDR_TX;
volatile uint16_t ADDR_RX_0;
volatile uint16_t ADDR_TX_0;
} __attribute__((aligned(4)));
volatile union
{
volatile uint16_t COUNT_TX;
volatile uint16_t COUNT_RX_0;
volatile uint16_t COUNT_TX_0;
} __attribute__((aligned(4)));
volatile union
{
volatile uint16_t ADDR_RX;
volatile uint16_t ADDR_RX_1;
volatile uint16_t ADDR_TX_1;
} __attribute__((aligned(4)));
volatile union
{
volatile uint16_t COUNT_RX;
volatile uint16_t COUNT_RX_1;
volatile uint16_t COUNT_TX_1;
} __attribute__((aligned(4)));
} __attribute__((aligned(8))) USB_BufferTableEntry_t;
typedef struct
{
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} __attribute__((packed, aligned(2))) USB_SetupPacket_t;
#define USB_BTABLE_ENTRIES \
((volatile USB_BufferTableEntry_t*)(USB_PMA_ADDR + USB_BTABLE_OFFSET))
void USB_Init(void);
// Because no interrupts are used, the interrupt flags have to be polled in a
// loop. This functions checks the flags and handles the interrupt requests. It
// has thus to be called in a loop. It returns false if the bootloader should
// exit and the application be started.
bool USB_Poll(void);

View file

@ -0,0 +1,189 @@
#include "usb_com.h"
#include "usb_util.h"
#include "commands.h"
#include "buildinfo.h"
#include "flash.h"
static const BootloaderInfo_t BootloaderInfo =
{
.build_date = BUILD_DATE,
.build_number = BUILD_NUMBER,
.flash_application_start = FLASH_APPLICATION_BASE,
.flash_application_size = (FLASH_PAGES - FLASH_BOOTLOADER_PAGES)
* FLASH_PAGE_BYTES,
.version_major = BUILD_VERSION_MAJOR,
.version_minor = BUILD_VERSION_MINOR,
.version_patch = BUILD_VERSION_PATCH,
.identifier = "STM32F103T8U6"
};
static Command_t USB_PendingCommand = CMD_NOP;
static void USB_EP1Transmit(const void *data, uint16_t length)
{
USB_MemoryToPMA(USB_BTABLE_ENTRIES[1].ADDR_TX, data, length);
USB_BTABLE_ENTRIES[1].COUNT_TX = length;
// Assume that STAT_TX is currently NAK (which is the case after a reset or
// after a correct transfer, just not if the function is called multiple
// times without an actual transfer in between)
USB->EP1R = (USB_EP_TX_NAK ^ USB_EP_TX_VALID)
| USB_EP_CTR_RX | USB_EP_CTR_TX | USB_EPR_EP_TYPE_BULK | 1;
}
bool USB_HandleCommand(const USB_SetupPacket_t *sp)
{
// The command is stored in the second byte (bRequest field) of the setup
// packet
Command_t command = sp->bRequest;
const void *reply_data = NULL;
int reply_length = 0;
switch(command)
{
case CMD_BOOTLOADER_INFO:
reply_data = &BootloaderInfo;
reply_length = sizeof(BootloaderInfo)
+ strlen(BootloaderInfo.identifier);
break;
case CMD_READ_CRC:
// The command will be executed as soon as the start address and
// length are transferred via EP2
USB_PendingCommand = CMD_READ_CRC;
break;
case CMD_READ_MEMORY:
// The command will be executed as soon as the start address and
// length are transferred via EP2
USB_PendingCommand = CMD_READ_MEMORY;
break;
case CMD_ERASE_PAGE:
// The command will be executed as soon as the page number is
// transferred via EP2. Since only a single byte is needed for the
// page index, this would also be technically be possible with just
// one setup packet. Since this is the only command, this minor
// USB bandwith saving is not worth the extra special case.
USB_PendingCommand = CMD_ERASE_PAGE;
break;
case CMD_PROGRAM:
USB_PendingCommand = CMD_PROGRAM;
break;
case CMD_EXIT:
return false;
default:
// Invalid commands get ignored
break;
}
if(reply_length > 0)
{
USB_EP1Transmit(reply_data, reply_length);
}
return true;
}
void USB_HandleEP2Out(void)
{
// Read how many bytes have been received by EP2
int packet_length = USB_BTABLE_ENTRIES[2].COUNT_RX & 0x3ff;
// The meaning of the received data depends on the command transmitted via
// a setup packet before it
switch(USB_PendingCommand)
{
case CMD_READ_CRC:
if(packet_length == 8)
{
uint32_t buff[2];
USB_PMAToMemory(buff, USB_BTABLE_ENTRIES[2].ADDR_RX,
sizeof(buff));
uint32_t *addr = (uint32_t*)(buff[0]);
uint32_t length = buff[1];
CRC->CR = CRC_CR_RESET;
// TODO: Add basic sanity checks so it isn't possible to crash
// the bootloader with this command (or at least not as easy)
while(length > 4)
{
CRC->DR = *addr++;
length -= 4;
}
CRC->DR = *addr & (0xffffffffU >> (32 - 8 * length));
buff[0] = CRC->DR;
USB_EP1Transmit(buff, 4);
}
break;
case CMD_READ_MEMORY:
if(packet_length == 8)
{
uint32_t buff[2];
USB_PMAToMemory(buff, USB_BTABLE_ENTRIES[2].ADDR_RX,
sizeof(buff));
uint8_t *start = (uint8_t*)(buff[0]);
uint32_t length = buff[1];
if(length > 64)
{
length = 64;
}
USB_EP1Transmit(start, length);
}
break;
case CMD_ERASE_PAGE:
if(packet_length == 1)
{
// Not that only one byte has been received but since the PMA
// can only be accessed word-wise, we'll have to copy two
uint8_t buff[2];
USB_PMAToMemory(buff, USB_BTABLE_ENTRIES[2].ADDR_RX,
sizeof(buff));
unsigned int page_index = buff[0];
if(page_index < FLASH_BOOTLOADER_PAGES)
{
// Do not allow erasing the bootloader
buff[0] = FLASH_PROHIBITED;
}
else
{
buff[0] = Flash_ErasePage(page_index);
}
// Reply with status byte
USB_EP1Transmit(buff, 1);
}
break;
case CMD_PROGRAM:;
uint32_t start;
USB_PMAToMemory(&start, USB_BTABLE_ENTRIES[2].ADDR_RX, 4);
uint32_t length = packet_length - 4;
if(start >= FLASH_APPLICATION_BASE && start + length
<= FLASH_BASE + FLASH_PAGE_BYTES * FLASH_PAGES)
{
// Program directly from PMA without an intermediate buffer in
// RAM
Flash_ProgramFromPMA(start, USB_BTABLE_ENTRIES[2].ADDR_RX + 4,
length);
}
break;
default:
break;
}
USB_PendingCommand = CMD_NOP;
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include <string.h>
#include "stm32f103x6.h"
#include "usb.h"
void USB_HandleEP2Out(void);
// Parses a setup packet with a vendor request type and handle the command it
// contains. Returns false if the bootloader should exit and start the
// application, true if the bootloader should continue execution.
bool USB_HandleCommand(const USB_SetupPacket_t *sp);

View file

@ -0,0 +1,170 @@
#include "usb_descriptors.h"
const USB_DeviceDescriptor_t USB_DeviceDescriptor =
{
.bLength = 18,
.bDescriptorType = USB_DEVICE_DESCRIPTOR,
.bcdUSB = 0x0200,
.bDeviceClass = 0xff,
.bDeviceSubClass = 0xff,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 64,
.idVendor = 0x16c0,
.idProduct = 0x05dc,
.bcdDevice = 0x0200,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1
};
const USB_WholeDescriptor_t USB_ConfigurationInterfaceDescriptor =
{
.configuration = (USB_ConfigurationDescriptor_t)
{
.bLength = 9,
.bDescriptorType = USB_CONFIGURATION_DESCRIPTOR,
.wTotalLength = sizeof(USB_WholeDescriptor_t),
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = 0x80,
.bMaxPower = 100
},
.main_interface = (USB_InterfaceDescriptor_t)
{
.bLength = 9,
.bDescriptorType = USB_INTERFACE_DESCRIPTOR,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = 0x00,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.iInterface = 0
},
// Endpoint 1: in
.data_in_endpoint = (USB_EndpointDescriptor_t)
{
.bLength = sizeof(USB_EndpointDescriptor_t),
.bDescriptorType = USB_ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_ENDPOINT_IN | 1,
.bmAttributes = USB_ENDPOINT_BULK | USB_ENDPOINT_NO_SYNCHRONIZATION
| USB_ENDPOINT_DATA,
.wMaxPacketSize = 64,
.bInterval = 0x01
},
// Endpoint 2: out
.data_out_endpoint = (USB_EndpointDescriptor_t)
{
.bLength = sizeof(USB_EndpointDescriptor_t),
.bDescriptorType = USB_ENDPOINT_DESCRIPTOR,
.bEndpointAddress = USB_ENDPOINT_OUT | 2,
.bmAttributes = USB_ENDPOINT_BULK | USB_ENDPOINT_NO_SYNCHRONIZATION
| USB_ENDPOINT_DATA,
.wMaxPacketSize = 64,
.bInterval = 0x01
},
};
#define USB_STRING_LANGID 0x0409
#define USB_STRING_VENDOR '2', '5', '1', '2', '0'
#define USB_STRING_PRODUCT 'p', 'u', 'n', 't'
const uint16_t USB_StringDescriptor_LangID[] =
USB_BUILD_STRING_DESCRIPTOR(USB_STRING_LANGID);
const uint16_t USB_StringDescriptor_Vendor[] =
USB_BUILD_STRING_DESCRIPTOR(USB_STRING_VENDOR);
const uint16_t USB_StringDescriptor_Product[] =
USB_BUILD_STRING_DESCRIPTOR(USB_STRING_PRODUCT);
void USB_HandleGetDescriptor(USB_DescriptorType_t descriptor_type,
int descriptor_index, const void **reply_data, int *reply_length,
uint8_t *reply_response)
{
switch(descriptor_type)
{
case USB_DEVICE_DESCRIPTOR:
*reply_data = &USB_DeviceDescriptor;
*reply_length = USB_DeviceDescriptor.bLength;
break;
case USB_CONFIGURATION_DESCRIPTOR:
*reply_data = &USB_ConfigurationInterfaceDescriptor;
if(*reply_length < USB_ConfigurationInterfaceDescriptor
.configuration.wTotalLength)
{
*reply_length = USB_ConfigurationInterfaceDescriptor
.configuration.bLength;
}
else
{
*reply_length = USB_ConfigurationInterfaceDescriptor
.configuration.wTotalLength;
}
break;
case USB_STRING_DESCRIPTOR:
switch(descriptor_index)
{
case 0:
*reply_data = (uint8_t*)USB_StringDescriptor_LangID;
*reply_length = (uint8_t)*USB_StringDescriptor_LangID;
break;
case 1:
*reply_data = (uint8_t*)USB_StringDescriptor_Vendor;
*reply_length = (uint8_t)*USB_StringDescriptor_Vendor;
break;
case 2:
*reply_data = (uint8_t*)USB_StringDescriptor_Product;
*reply_length = (uint8_t)*USB_StringDescriptor_Product;
break;
case 3:;
// String descriptors are 16 bits per char
static uint16_t buff[25];
// The first byte is the total length in bytes, the second
// byte is the descriptor type (3)
buff[0] = (0x03 << 8) | sizeof(buff);
// The unique device ID is 96 bits = 12 bytes long
for(int i = 0; i < 12; i++)
{
uint8_t uid_byte = *((uint8_t*)UID_BASE + i);
// The representation does not matter for the serial, so
// we're using one of the first 16 letters of the
// alphabet for each nibble
buff[1 + 2 * i] = 'A' + (uid_byte & 0x0f);
buff[2 + 2 * i] = 'A' + (uid_byte >> 4);
}
*reply_data = (uint8_t*)buff;
*reply_length = (uint8_t)*buff;
break;
}
break;
case USB_INTERFACE_DESCRIPTOR:
*reply_data = &USB_ConfigurationInterfaceDescriptor
.main_interface;
*reply_length = USB_ConfigurationInterfaceDescriptor
.main_interface.bLength;
break;
case USB_DEVICE_QUALIFIER_DESCRIPTOR:
// Device is full-speed only, so it must return a request error
*reply_response = USB_EP_TX_STALL;
*reply_data = NULL;
break;
case USB_ENDPOINT_DESCRIPTOR:
case USB_OTHER_DESCRIPTOR:
case USB_INTERFACE_POWER_DESCRIPTOR:
case USB_INTERFACE_ASSOCIATION_DESCRIPTOR:
case USB_CLASS_SPECIFIC_INTERFACE_DESCRIPTOR:
case USB_CLASS_SPECIFIC_ENDPOINT_DESCRIPTOR:
// Not implemented
break;
}
}

View file

@ -0,0 +1,113 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "stm32f103x6.h"
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} __attribute__((packed, aligned(1))) USB_DeviceDescriptor_t;
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
} __attribute__((packed, aligned(1))) USB_ConfigurationDescriptor_t;
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} __attribute__((packed, aligned(1))) USB_InterfaceDescriptor_t;
// Endpoint direction for the bEndpointAddress field
#define USB_ENDPOINT_OUT 0x00
#define USB_ENDPOINT_IN 0x80
// Flags in bmAttributes
#define USB_ENDPOINT_CONTROL 0x00
#define USB_ENDPOINT_ISOCHRONOUS 0x01
#define USB_ENDPOINT_BULK 0x02
#define USB_ENDPOINT_INTERRUPT 0x03
#define USB_ENDPOINT_NO_SYNCHRONIZATION 0x00
#define USB_ENDPOINT_ASYNCHRONOUS 0x04
#define USB_ENDPOINT_ADAPTIVE 0x08
#define USB_ENDPOINT_SYNCHRONOUS 0x0c
#define USB_ENDPOINT_DATA 0x00
#define USB_ENDPOINT_FEEDBACK 0x10
#define USB_ENDPOINT_IMPLICIT_FEEDBACK_DATA 0x20
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} __attribute__((packed, aligned(1))) USB_EndpointDescriptor_t;
typedef struct
{
USB_ConfigurationDescriptor_t configuration;
USB_InterfaceDescriptor_t main_interface;
USB_EndpointDescriptor_t data_in_endpoint;
USB_EndpointDescriptor_t data_out_endpoint;
} __attribute__((packed, aligned(1))) USB_WholeDescriptor_t;
typedef enum
{
USB_DEVICE_DESCRIPTOR = 0x01,
USB_CONFIGURATION_DESCRIPTOR = 0x02,
USB_STRING_DESCRIPTOR = 0x03,
USB_INTERFACE_DESCRIPTOR = 0x04,
USB_ENDPOINT_DESCRIPTOR = 0x05,
USB_DEVICE_QUALIFIER_DESCRIPTOR = 0x06,
USB_OTHER_DESCRIPTOR = 0x07,
USB_INTERFACE_POWER_DESCRIPTOR = 0x08,
USB_INTERFACE_ASSOCIATION_DESCRIPTOR = 0x0b,
USB_CLASS_SPECIFIC_INTERFACE_DESCRIPTOR = 0x24,
USB_CLASS_SPECIFIC_ENDPOINT_DESCRIPTOR = 0x25
} __attribute__((packed)) USB_DescriptorType_t;
#define USB_STRING_DESCRIPTOR_LENGTH(...) \
(sizeof((uint16_t[]){__VA_ARGS__}) + 2)
#define USB_BUILD_STRING_DESCRIPTOR(...) \
{USB_STRING_DESCRIPTOR_LENGTH(__VA_ARGS__) \
| (USB_STRING_DESCRIPTOR << 8), __VA_ARGS__}
void USB_HandleGetDescriptor(USB_DescriptorType_t descriptor_type,
int descriptor_index, const void **reply_data, int *reply_length,
uint8_t *reply_response);

View file

@ -0,0 +1,55 @@
#include "usb_util.h"
void USB_PMAToMemory(void *mem, uint16_t offset, size_t length)
{
// Only words can be copied. Thus, if the length is not even, it has to be
// incremented to ensure that the last byte is copied. This of course means
// that the target memory area must be of even length!
if(length & 1)
{
length++;
}
uint8_t *dst = (uint8_t*)mem;
uint8_t *pma = (uint8_t*)(USB_PMA_ADDR + 2 * offset);
for(unsigned int i = 0; i < length / 2; i++)
{
dst[2 * i] = *pma++;
dst[2 * i + 1] = *pma++;
pma += 2;
}
}
void USB_MemoryToPMA(uint16_t offset, const void *mem, size_t length)
{
// Only words can be copied. Thus, if the length is not even, it has to be
// incremented to ensure that the last byte is copied. Since the PMA buffer
// always has even size, this is not a problem.
if(length & 1)
{
length++;
}
const uint8_t *src = (const uint8_t*)mem;
uint16_t *pma = (uint16_t*)(USB_PMA_ADDR + 2 * offset);
for(unsigned int i = 0; i < length / 2; i++)
{
uint16_t tmp = src[2 * i] | (src[2 * i + 1] << 8);
*pma++ = tmp;
pma++;
}
}
void USB_SetEPR(volatile uint16_t *EPR, uint16_t status)
{
// Caution: This function does a read-modify-write and is prone to
// unexpected behaviour when there are transactions going one, because the
// register contents might change during the function's execution. Thus,
// only use this function in initialisation code!
volatile uint16_t v = *EPR;
status ^= v & (USB_EP0R_DTOG_RX | USB_EP0R_STAT_RX |\
USB_EP0R_DTOG_TX | USB_EP0R_STAT_TX);
*EPR = status;
}

View file

@ -0,0 +1,7 @@
#pragma once
#include "usb.h"
void USB_PMAToMemory(void *mem, uint16_t offset, size_t length);
void USB_MemoryToPMA(uint16_t offset, const void *mem, size_t length);
void USB_SetEPR(volatile uint16_t *EPR, uint16_t status);

View file

@ -0,0 +1,10 @@
#include "util.h"
void Util_Delay(unsigned int delay)
{
SysTick->LOAD = delay;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
while(!((SysTick->CTRL) & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL = 0;
}

View file

@ -0,0 +1,6 @@
#pragma once
#include "stm32f103x6.h"
// Delay in AHB clock cycles
void Util_Delay(unsigned int delay);