Merge pull request #111 from dhalbert/spurious-writes

fix SAMD51 spurious writes; allow gcc9 compilation; improve delay() calibration
This commit is contained in:
Scott Shawcroft 2020-04-02 10:21:57 -07:00 committed by GitHub
commit b3e13dfbe0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 209 additions and 90 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
build/
.vs/
uf2-bootloader.elf
tmp
*.sw?
Makefile.user
node_modules
scripts/clean-ifaces/*.plist
.vscode/c_cpp_properties.json
TAGS

View file

@ -15,12 +15,12 @@ node_js:
cache:
directories:
- $HOME/gcc-arm-none-eabi-6-2017-q1-update
- $HOME/gcc-arm-none-eabi-9-2019-q4-major
install:
- export GCC_DIR=$HOME/gcc-arm-none-eabi-6-2017-q1-update
- export GCC_ARCHIVE=$HOME/gcc-arm-none-eabi-6-2017-q1-update-linux.tar.bz2
- export GCC_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/6_1-2017q1/gcc-arm-none-eabi-6-2017-q1-update-linux.tar.bz2?product=GNU%20ARM%20Embedded%20Toolchain,64-bit,,Linux,6-2017-q1-update
- export GCC_DIR=$HOME/gcc-arm-none-eabi-9-2019-q4-major
- export GCC_ARCHIVE=$HOME/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2
- export GCC_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2
- if [ ! -e $GCC_DIR/bin/arm-none-eabi-g++ ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME; fi
- export PATH=$PATH:$GCC_DIR/bin

View file

@ -207,8 +207,11 @@ drop-board: all
mkdir -p build/drop/$(BOARD)
cp $(SELF_EXECUTABLE) build/drop/$(BOARD)/
cp $(EXECUTABLE) build/drop/$(BOARD)/
# .ino works only for SAMD21 right now; suppress for SAMD51
ifeq ($(CHIP_FAMILY),samd21)
cp $(SELF_EXECUTABLE_INO) build/drop/$(BOARD)/
cp boards/$(BOARD)/board_config.h build/drop/$(BOARD)/
endif
drop-pkg:
mv build/drop build/uf2-samd21-$(UF2_VERSION_BASE)

View file

@ -260,6 +260,8 @@ void padded_memcpy(char *dst, const char *src, int len);
void resetIntoApp(void);
void resetIntoBootloader(void);
extern uint32_t current_cpu_frequency_MHz;
extern volatile bool led_tick_on;
void system_init(void);
#define LED_TICK led_tick
@ -288,6 +290,12 @@ void delay(uint32_t ms);
void hidHandoverLoop(int ep);
void handoverPrep(void);
// Useful for debugging.
#ifdef BLINK_DEBUG
void blink_n(uint32_t pin, uint32_t n, uint32_t interval);
void blink_n_forever(uint32_t pin, uint32_t n, uint32_t interval);
#endif
#define CONCAT_1(a, b) a##b
#define CONCAT_0(a, b) CONCAT_1(a, b)
#define STATIC_ASSERT(e) enum { CONCAT_0(_static_assert_, __LINE__) = 1 / ((e) ? 1 : 0) }

View file

@ -453,7 +453,8 @@ __attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __get_MSP(void)
*/
__attribute__( ( always_inline ) ) __STATIC_INLINE void __set_MSP(uint32_t topOfMainStack)
{
__ASM volatile ("MSR msp, %0\n" : : "r" (topOfMainStack) : "sp");
// : "sp" removed from clobber list to avoid gcc 9 warning.
__ASM volatile ("MSR msp, %0\n" : : "r" (topOfMainStack));
}

View file

@ -28,7 +28,7 @@ selfdata_c_path = os.path.join(os.path.dirname(bin_name), "selfdata.c")
with open(selfdata_c_path, "w") as output:
output.write("#include <stdint.h>\n")
output.write("const uint8_t bootloader[{}] ".format(bootloader_size) +
"__attribute__ ((aligned (4))) = {")
"__attribute__ ((aligned (4))) = {\n")
crcs = []
crc = 0
for row in range(bootloader_size / 16):

View file

@ -7,6 +7,9 @@
volatile bool g_interrupt_enabled = true;
// SAMD21 starts at 1MHz by default.
uint32_t current_cpu_frequency_MHz = 1;
static void gclk_sync(void) {
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
;
@ -125,6 +128,9 @@ void system_init(void) {
// PORT->Group[0].PINCFG[30].bit.PMUXEN = 1;
// Set the port mux mask for odd processor pin numbers, PA30 = 30 is even number, PMUXE = PMUX Even
// PORT->Group[0].PMUX[30 / 2].reg |= PORT_PMUX_PMUXE_H;
current_cpu_frequency_MHz = 48;
}
void SysTick_Handler(void) { LED_TICK(); }

View file

@ -1,12 +1,15 @@
#include "uf2.h"
// SAMD51 starts at 48MHz by default.
uint32_t current_cpu_frequency_MHz = 48;
void system_init(void) {
/* Set 1 Flash Wait State for 48MHz */
NVMCTRL->CTRLA.reg |= NVMCTRL_CTRLA_RWS(0);
// Automatic wait states.
NVMCTRL->CTRLA.bit.AUTOWS = 1;
// Output GCLK0 to Metro M4 D5. This way we can see if/when we mess it up.
//PORT->Group[1].PINCFG[14].bit.PMUXEN = true;
//PORT->Group[1].PMUX[7].bit.PMUXE = 12;
// PORT->Group[1].PINCFG[14].bit.PMUXEN = true;
// PORT->Group[1].PMUX[7].bit.PMUXE = 12;
/* Software reset the module to ensure it is re-initialized correctly */
/* Note: Due to synchronization, there is a delay from writing CTRL.SWRST until the reset is complete.
@ -79,6 +82,8 @@ void system_init(void) {
MCLK->CPUDIV.reg = MCLK_CPUDIV_DIV_DIV1;
SysTick_Config(1000);
// No change from initial frequency.
// current_cpu_frequency_MHz = 48;
}
void SysTick_Handler(void) { LED_TICK(); }

View file

@ -29,11 +29,11 @@
/**
* --------------------
* SAM-BA Implementation on SAMD21
* SAM-BA Implementation on SAMD21 and SAMD51
* --------------------
* Requirements to use SAM-BA :
*
* Supported communication interfaces :
* Supported communication interfaces (SAMD21):
* --------------------
*
* SERCOM5 : RX:PB23 TX:PB22
@ -68,8 +68,6 @@
*
* Applications compiled to be executed along with the bootloader will start at
* 0x2000 (samd21) or 0x4000 (samd51)
* The bootloader doesn't changes the VTOR register, application code is
* taking care of this.
*
*/
@ -110,7 +108,7 @@ static void check_start_application(void) {
if (RESET_CONTROLLER->RCAUSE.bit.POR || *DBL_TAP_PTR != DBL_TAP_MAGIC_QUICK_BOOT) {
// the second tap on reset will go into app
*DBL_TAP_PTR = DBL_TAP_MAGIC_QUICK_BOOT;
// this will be cleared after succesful USB enumeration
// this will be cleared after successful USB enumeration
// this is around 1.5s
resetHorizon = timerHigh + 50;
return;
@ -150,7 +148,7 @@ extern char _etext;
extern char _end;
/**
* \brief SAMD21 SAM-BA Main loop.
* \brief SAM-BA Main loop.
* \return Unused (ANSI-C compatibility).
*/
int main(void) {
@ -166,6 +164,42 @@ int main(void) {
#elif defined(SAMD51)
WDT->CTRLA.reg = 0;
while(WDT->SYNCBUSY.reg) {}
// Enable 2.7V brownout detection. The default fuse value is 1.7
// Set brownout detection to ~2.7V. Default from factory is 1.7V,
// which is too low for proper operation of external SPI flash chips (they are 2.7-3.6V).
// Also without this higher level, the SAMD51 will write zeros to flash intermittently.
// Disable while changing level.
SUPC->BOD33.bit.ENABLE = 0;
while (!SUPC->STATUS.bit.B33SRDY) {} // Wait for BOD33 to synchronize.
SUPC->BOD33.bit.LEVEL = 200; // 2.7V: 1.5V + LEVEL * 6mV.
// Don't reset right now.
SUPC->BOD33.bit.ACTION = SUPC_BOD33_ACTION_NONE_Val;
SUPC->BOD33.bit.ENABLE = 1; // enable brown-out detection
// Wait for BOD33 peripheral to be ready.
while (!SUPC->STATUS.bit.BOD33RDY) {}
// Wait for voltage to rise above BOD33 value.
while (SUPC->STATUS.bit.BOD33DET) {}
// If we are starting from a power-on or a brownout,
// wait for the voltage to stabilize. Don't do this on an
// external reset because it interferes with the timing of double-click.
// "BODVDD" means BOD33.
if (RSTC->RCAUSE.bit.POR || RSTC->RCAUSE.bit.BODVDD) {
do {
// Check again in 100ms.
delay(100);
} while (SUPC->STATUS.bit.BOD33DET);
}
// Now enable reset if voltage falls below minimum.
SUPC->BOD33.bit.ENABLE = 0;
while (!SUPC->STATUS.bit.B33SRDY) {} // Wait for BOD33 to synchronize.
SUPC->BOD33.bit.ACTION = SUPC_BOD33_ACTION_RESET_Val;
SUPC->BOD33.bit.ENABLE = 1;
#endif
#if USB_VID == 0x239a && USB_PID == 0x0013 // Adafruit Metro M0

View file

@ -37,7 +37,7 @@ uint8_t pageBuf[FLASH_ROW_SIZE];
void setBootProt(int v) {
#if defined(SAMD21)
uint32_t fuses[2],
uint32_t fuses[2],
newfuses[2];
while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)) {
}

View file

@ -1,5 +1,16 @@
#if defined(__SAMD51__)
#error "update_bootloader*.ino is not available for SAMD51 boards"
#endif
#define BOOTLOADER_K 8
// Error indications:
// 2 quick flashes repeated forever: Flash page size wrong
// 3: checksum error
// 4: write verify failed
//
// Success: 5 slower flashes, then switch to BOOT drive
static uint16_t crcCache[256];
#define CRC16POLY 0x1021
@ -23,8 +34,6 @@ uint16_t add_crc(uint8_t ch, unsigned short crc0) {
return ((crc0 << 8) ^ crcCache[((crc0 >> 8) ^ ch) & 0xff]) & 0xffff;
}
uint8_t pageBuf[FLASH_ROW_SIZE];
#define NVM_USER_MEMORY ((volatile uint16_t *)NVMCTRL_USER)
static inline void wait_ready(void) {
@ -32,37 +41,35 @@ static inline void wait_ready(void) {
}
}
void flash_erase_row(uint32_t *dst) {
wait_ready();
NVMCTRL->STATUS.reg = NVMCTRL_STATUS_MASK;
// Execute "ER" Erase Row
NVMCTRL->ADDR.reg = (uint32_t)dst / 2;
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER;
void exec_cmd(uint32_t cmd, const uint32_t *addr) {
NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK;
NVMCTRL->ADDR.reg = (uint32_t)addr / 2;
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | cmd;
wait_ready();
}
void flash_write_words(uint32_t *dst, uint32_t *src, uint32_t n_words) {
// Set automatic page write
NVMCTRL->CTRLB.bit.MANW = 0;
void flash_erase_row(uint32_t *dst) {
wait_ready();
// Execute "ER" Erase Row
exec_cmd(NVMCTRL_CTRLA_CMD_ER, dst);
}
void flash_write_words(uint32_t *dst, uint32_t *src, uint32_t n_words) {
while (n_words > 0) {
uint32_t len = min(FLASH_PAGE_SIZE >> 2, n_words);
n_words -= len;
// Execute "PBC" Page Buffer Clear
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC;
wait_ready();
const uint32_t* dst_start = dst;
exec_cmd(NVMCTRL_CTRLA_CMD_PBC, dst);
// make sure there are no other memory writes here
// otherwise we get lock-ups
while (len--)
// Write data to page buffer.
while (len--) {
*dst++ = *src++;
}
// Execute "WP" Write Page
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP;
wait_ready();
exec_cmd(NVMCTRL_CTRLA_CMD_WP, dst_start);
}
}
@ -71,100 +78,115 @@ void flash_write_row(uint32_t *dst, uint32_t *src) {
flash_write_words(dst, src, FLASH_ROW_SIZE / 4);
}
#define exec_cmd(cmd) \
do { \
NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; \
NVMCTRL->ADDR.reg = (uint32_t)NVMCTRL_USER / 2; \
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | cmd; \
while (NVMCTRL->INTFLAG.bit.READY == 0) \
; \
} while (0)
void setBootProt(int v) {
uint32_t *NVM_FUSES = (uint32_t *)NVMCTRL_AUX0_ADDRESS;
uint32_t fuses[2];
while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY))
;
fuses[0] = *((uint32_t *)NVMCTRL_AUX0_ADDRESS);
fuses[1] = *(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1);
fuses[0] = NVM_FUSES[0];
fuses[1] = NVM_FUSES[1];
uint32_t bootprot = (fuses[0] & NVMCTRL_FUSES_BOOTPROT_Msk) >> NVMCTRL_FUSES_BOOTPROT_Pos;
if (bootprot == v)
if (bootprot == v) {
return;
}
fuses[0] = (fuses[0] & ~NVMCTRL_FUSES_BOOTPROT_Msk) | (v << NVMCTRL_FUSES_BOOTPROT_Pos);
NVMCTRL->CTRLB.reg = NVMCTRL->CTRLB.reg | NVMCTRL_CTRLB_CACHEDIS | NVMCTRL_CTRLB_MANW;
wait_ready();
exec_cmd(NVMCTRL_CTRLA_CMD_EAR, (uint32_t *)NVMCTRL_USER);
exec_cmd(NVMCTRL_CTRLA_CMD_PBC, (uint32_t *)NVMCTRL_USER);
exec_cmd(NVMCTRL_CTRLA_CMD_EAR);
exec_cmd(NVMCTRL_CTRLA_CMD_PBC);
NVM_FUSES[0] = fuses[0];
NVM_FUSES[1] = fuses[1];
*((uint32_t *)NVMCTRL_AUX0_ADDRESS) = fuses[0];
*(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1) = fuses[1];
exec_cmd(NVMCTRL_CTRLA_CMD_WAP);
exec_cmd(NVMCTRL_CTRLA_CMD_WAP, (uint32_t *)NVMCTRL_USER);
NVIC_SystemReset();
}
void mydelay(int ms) {
// We can't use regular loop_delay() because it uses interrupts, which we turn off.
void loop_delay(int ms) {
ms <<= 13;
while (ms--) {
asm("nop");
}
}
void blink_n(int n, int interval) {
// Start out off.
digitalWrite(LED_BUILTIN, LOW);
loop_delay(interval);
for (int i = 0; i < n; ++i) {
digitalWrite(LED_BUILTIN, HIGH);
loop_delay(interval);
digitalWrite(LED_BUILTIN, LOW);
loop_delay(interval);
}
}
void blink_n_forever(int n, int interval) {
while(1) {
blink_n(n, interval);
loop_delay(interval*5);
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
if (8 << NVMCTRL->PARAM.bit.PSZ != FLASH_PAGE_SIZE)
while (1) {
}
if (8 << NVMCTRL->PARAM.bit.PSZ != FLASH_PAGE_SIZE) {
blink_n_forever(2, 200);
}
NVMCTRL->CTRLB.bit.MANW = 1;
NVMCTRL->CTRLB.bit.CACHEDIS = 1;
__disable_irq();
setBootProt(7); // 0k
// This will cause a reset and re-enter the program if a change was necessary.
// If no change was necessary we'll fall through.
setBootProt(7); // 0kB; disable BOOTPROT while writing.
const uint8_t *ptr = bootloader;
int i;
for (i = 0; i < BOOTLOADER_K; ++i) {
for (int i = 0; i < BOOTLOADER_K; ++i) {
int crc = 0;
for (int j = 0; j < 1024; ++j) {
crc = add_crc(*ptr++, crc);
}
if (bootloader_crcs[i] != crc) {
while (1) {
}
blink_n_forever(3, 200);
}
}
for (i = 0; i < BOOTLOADER_K * 1024; i += FLASH_ROW_SIZE) {
memcpy(pageBuf, &bootloader[i], FLASH_ROW_SIZE);
flash_write_row((uint32_t *)(void *)i, (uint32_t *)(void *)pageBuf);
// Writing starts at 0x0, so flash_addr can be used as an index into bootloader[].
for (int flash_addr = 0; flash_addr < BOOTLOADER_K * 1024; flash_addr += FLASH_ROW_SIZE) {
flash_write_row((uint32_t *)flash_addr, (uint32_t *)&bootloader[flash_addr]);
if (memcmp((const void *)flash_addr, &bootloader[flash_addr], FLASH_ROW_SIZE) != 0) {
// Write verify failed.
blink_n_forever(4, 200);
}
}
// re-base int vector back to bootloader, so that the flash erase below doesn't write over the
// vectors
SCB->VTOR = 0;
// erase first row of this updater app, so the bootloader doesn't start us again
flash_erase_row((uint32_t *)(void *)(BOOTLOADER_K * 1024));
blink_n(5, 750);
for (i = 0; i < 5; ++i) {
digitalWrite(LED_BUILTIN, HIGH);
mydelay(100);
digitalWrite(LED_BUILTIN, LOW);
mydelay(200);
}
// Write zeros to the stack location and reset handler location so the
// bootloader doesn't run us a second time. We don't need to erase to write
// zeros. The remainder of the write unit will be set to 1s which should
// preserve the existing values but it's not critical.
uint32_t zeros[2] = {0, 0};
flash_write_words((uint32_t *)(BOOTLOADER_K * 1024), zeros, 2);
setBootProt(2); // 8k
setBootProt(2); // 8kB
while (1) {
}
}
void loop() {}
void loop() {
}

View file

@ -4,20 +4,48 @@
static uint32_t timerLow;
uint32_t timerHigh, resetHorizon;
void delay(uint32_t ms) {
// SAMD21 starts up at 1mhz by default.
void __attribute__ ((noinline)) delay(uint32_t ms) {
// These multipliers were determined empirically and are only approximate.
// After the pulsing LED is enabled (led_tick_on), the multipliers need to change
// due to the interrupt overhead of the pulsing.
// SAMD21 starts up at 1MHz by default.
#ifdef SAMD21
ms <<= 8;
uint32_t count = ms * (current_cpu_frequency_MHz) * (led_tick_on ? 149: 167);
#endif
// SAMD51 starts up at 48mhz by default.
#ifdef SAMD51
ms <<= 12;
// SAMD51 starts up at 48MHz by default, and we set the clock to
// 48MHz, so we don't need to adjust for current_cpu_frequency_MHz.
uint32_t count = ms * (led_tick_on ? 6353 : 6826);
#endif
for (int i = 1; i < ms; ++i) {
asm("nop");
for (uint32_t i = 1; i < count; ++i) {
asm volatile("");
}
}
// Useful for debugging.
// PIN_PA19 is D12 on Metro M0, D11 on Metro M4
#ifdef BLINK_DEBUG
void blink_n(uint32_t pin, uint32_t n, uint32_t interval) {
// Start out off.
PINOP(pin, DIRSET);
PINOP(pin, OUTCLR);
delay(interval);
for (int i = 0; i < n; ++i) {
PINOP(pin, OUTSET);
delay(interval);
PINOP(pin, OUTCLR);
delay(interval);
}
}
void blink_n_forever(uint32_t pin, uint32_t n, uint32_t interval) {
while(1) {
blink_n(pin, n, interval);
delay(interval*5);
}
}
#endif
void timerTick(void) {
if (timerLow-- == 0) {
timerLow = TIMER_STEP;
@ -108,9 +136,11 @@ void logval(const char *lbl, uint32_t v) {
static uint32_t now;
static uint32_t signal_end;
int8_t led_tick_step = 1;
volatile bool led_tick_on = false;
static uint8_t limit = 200;
void led_tick() {
led_tick_on = true;
now++;
if (signal_end) {
if (now == signal_end - 1000) {