diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index 2de07c70b..cfd73292b 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -99,6 +99,7 @@ #ifdef W25X20CL #include "w25x20cl.h" +#include "optiboot_w25x20cl.h" #endif //W25X20CL #ifdef BLINKM @@ -1138,6 +1139,10 @@ void list_sec_lang_from_external_flash() // are initialized by the main() routine provided by the Arduino framework. void setup() { +#ifdef W25X20CL + // Enter an STK500 compatible Optiboot boot loader waiting for flashing the languages to an external flash memory. + optiboot_w25x20cl_enter(); +#endif lcd_init(); fdev_setup_stream(lcdout, lcd_putchar, NULL, _FDEV_SETUP_WRITE); //setup lcdout stream diff --git a/Firmware/optiboot_w25x20cl.cpp b/Firmware/optiboot_w25x20cl.cpp new file mode 100644 index 000000000..ce50323f9 --- /dev/null +++ b/Firmware/optiboot_w25x20cl.cpp @@ -0,0 +1,308 @@ +// Based on the OptiBoot project +// https://github.com/Optiboot/optiboot +// Licence GLP 2 or later. + +#include "Marlin.h" +#include "w25x20cl.h" +#include "stk500.h" + +#define OPTIBOOT_MAJVER 6 +#define OPTIBOOT_CUSTOMVER 0 +#define OPTIBOOT_MINVER 2 +static unsigned const int __attribute__((section(".version"))) + optiboot_version = 256*(OPTIBOOT_MAJVER + OPTIBOOT_CUSTOMVER) + OPTIBOOT_MINVER; + +/* Watchdog settings */ +#define WATCHDOG_OFF (0) +#define WATCHDOG_16MS (_BV(WDE)) +#define WATCHDOG_32MS (_BV(WDP0) | _BV(WDE)) +#define WATCHDOG_64MS (_BV(WDP1) | _BV(WDE)) +#define WATCHDOG_125MS (_BV(WDP1) | _BV(WDP0) | _BV(WDE)) +#define WATCHDOG_250MS (_BV(WDP2) | _BV(WDE)) +#define WATCHDOG_500MS (_BV(WDP2) | _BV(WDP0) | _BV(WDE)) +#define WATCHDOG_1S (_BV(WDP2) | _BV(WDP1) | _BV(WDE)) +#define WATCHDOG_2S (_BV(WDP2) | _BV(WDP1) | _BV(WDP0) | _BV(WDE)) +#define WATCHDOG_4S (_BV(WDP3) | _BV(WDE)) +#define WATCHDOG_8S (_BV(WDP3) | _BV(WDP0) | _BV(WDE)) + +#if 0 +#define W25X20CL_SIGNATURE_0 9 +#define W25X20CL_SIGNATURE_1 8 +#define W25X20CL_SIGNATURE_2 7 +#else +//FIXME this is a signature of ATmega2560! +#define W25X20CL_SIGNATURE_0 0x1E +#define W25X20CL_SIGNATURE_1 0x98 +#define W25X20CL_SIGNATURE_2 0x01 +#endif + +static void watchdogConfig(uint8_t x) { + WDTCSR = _BV(WDCE) | _BV(WDE); + WDTCSR = x; +} + +static void watchdogReset() { + __asm__ __volatile__ ( + "wdr\n" + ); +} + +#define RECV_READY ((UCSR0A & _BV(RXC0)) != 0) + +static uint8_t getch(void) { + uint8_t ch; + while(! RECV_READY) ; + if (!(UCSR0A & _BV(FE0))) { + /* + * A Framing Error indicates (probably) that something is talking + * to us at the wrong bit rate. Assume that this is because it + * expects to be talking to the application, and DON'T reset the + * watchdog. This should cause the bootloader to abort and run + * the application "soon", if it keeps happening. (Note that we + * don't care that an invalid char is returned...) + */ + watchdogReset(); + } + ch = UDR0; + return ch; +} + +static void putch(char ch) { + while (!(UCSR0A & _BV(UDRE0))); + UDR0 = ch; +} + +static void verifySpace() { + if (getch() != CRC_EOP) { + putch(STK_FAILED); + watchdogConfig(WATCHDOG_16MS); // shorten WD timeout + while (1) // and busy-loop so that WD causes + ; // a reset and app start. + } + putch(STK_INSYNC); +} + +static void getNch(uint8_t count) { + do getch(); while (--count); + verifySpace(); +} + +typedef uint16_t pagelen_t; + +static const char entry_magic_send [] PROGMEM = "start\n"; +static const char entry_magic_receive[] PROGMEM = "w25x20cl_enter\n"; +static const char entry_magic_cfm [] PROGMEM = "w25x20cl_cfm\n"; + +struct block_t; +extern struct block_t *block_buffer; + +void optiboot_w25x20cl_enter() +{ + uint8_t ch; + uint8_t rampz = 0; + register uint16_t address = 0; + register pagelen_t length; + // Use the planner's queue for the receive / transmit buffers. +// uint8_t *buff = (uint8_t*)block_buffer; + uint8_t buff[260]; + // bitmap of pages to be written. Bit is set to 1 if the page has already been erased. + uint8_t pages_erased = 0; + + // Handshake sequence: Initialize the serial line, flush serial line, send magic, receive magic. + // If the magic is not received on time, or it is not received correctly, continue to the application. + { + watchdogReset(); + unsigned long boot_timeout = 2000000; + unsigned long boot_timer = 0; + const char *ptr = entry_magic_send; + const char *end = strlen_P(entry_magic_send) + ptr; + // Initialize the serial line. + UCSR0A |= (1 << U2X0); + UBRR0L = (((float)(F_CPU))/(((float)(115200))*8.0)-1.0+0.5); + UCSR0B = (1 << RXEN0) | (1 << TXEN0); + // Flush the serial line. + while (RECV_READY) { + watchdogReset(); + // Dummy register read (discard) + (void)(*(char *)UDR0); + } + // Send the initial magic string. + while (ptr != end) + putch(pgm_read_byte_far(ptr ++)); + watchdogReset(); + // Wait for one second until a magic string (constant entry_magic) is received + // from the serial line. + ptr = entry_magic_receive; + end = strlen_P(entry_magic_receive) + ptr; + while (ptr != end) { + while (! RECV_READY) { + watchdogReset(); + delayMicroseconds(1); + if (++ boot_timer > boot_timeout) + // Timeout expired, continue with the application. + return; + } + ch = UDR0; + if (pgm_read_byte_far(ptr ++) != ch) + // Magic was not received correctly, continue with the application + return; + watchdogReset(); + } + // Send the cfm magic string. + ptr = entry_magic_cfm; + while (ptr != end) + putch(pgm_read_byte_far(ptr ++)); + } + + spi_init(); + w25x20cl_init(); + watchdogConfig(WATCHDOG_OFF); + + /* Forever loop: exits by causing WDT reset */ + for (;;) { + /* get character from UART */ + ch = getch(); + + if(ch == STK_GET_PARAMETER) { + unsigned char which = getch(); + verifySpace(); + /* + * Send optiboot version as "SW version" + * Note that the references to memory are optimized away. + */ + if (which == STK_SW_MINOR) { + putch(optiboot_version & 0xFF); + } else if (which == STK_SW_MAJOR) { + putch(optiboot_version >> 8); + } else { + /* + * GET PARAMETER returns a generic 0x03 reply for + * other parameters - enough to keep Avrdude happy + */ + putch(0x03); + } + } + else if(ch == STK_SET_DEVICE) { + // SET DEVICE is ignored + getNch(20); + } + else if(ch == STK_SET_DEVICE_EXT) { + // SET DEVICE EXT is ignored + getNch(5); + } + else if(ch == STK_LOAD_ADDRESS) { + // LOAD ADDRESS + uint16_t newAddress; + // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter. + // Send the binary data by nibbles to avoid transmitting the ';' character. + newAddress = getch(); + newAddress |= getch(); + newAddress |= (((uint16_t)getch()) << 8); + newAddress |= (((uint16_t)getch()) << 8); + // Transfer top bit to LSB in rampz + if (newAddress & 0x8000) + rampz |= 0x01; + else + rampz &= 0xFE; + newAddress += newAddress; // Convert from word address to byte address + address = newAddress; + verifySpace(); + } + else if(ch == STK_UNIVERSAL) { + // LOAD_EXTENDED_ADDRESS is needed in STK_UNIVERSAL for addressing more than 128kB + if ( AVR_OP_LOAD_EXT_ADDR == getch() ) { + // get address + getch(); // get '0' + rampz = (rampz & 0x01) | ((getch() << 1) & 0xff); // get address and put it in rampz + getNch(1); // get last '0' + // response + putch(0x00); + } + else { + // everything else is ignored + getNch(3); + putch(0x00); + } + } + /* Write memory, length is big endian and is in bytes */ + else if(ch == STK_PROG_PAGE) { + // PROGRAM PAGE - we support flash programming only, not EEPROM + uint8_t desttype; + uint8_t *bufPtr; + pagelen_t savelength; + // Read the page length, with the length transferred each nibble separately to work around + // the Prusa's USB to serial infamous semicolon issue. + length = ((pagelen_t)getch()) << 8; + length |= ((pagelen_t)getch()) << 8; + length |= getch(); + length |= getch(); + + savelength = length; + // Read the destination type. It should always be 'F' as flash. + desttype = getch(); + + // read a page worth of contents + bufPtr = buff; + do *bufPtr++ = getch(); + while (--length); + + // Read command terminator, start reply + verifySpace(); + if (desttype == 'E') { + while (1) ; // Error: wait for WDT + } else { + uint32_t addr = (((uint32_t)rampz) << 16) | address; + // During a single bootloader run, only erase a 64kB block once. + // An 8bit bitmask 'pages_erased' covers 512kB of FLASH memory. + if (address == 0 && (pages_erased & (1 << addr)) == 0) { + w25x20cl_wait_busy(); + w25x20cl_enable_wr(); + w25x20cl_block64_erase(addr); + pages_erased |= (1 << addr); + } + w25x20cl_wait_busy(); + w25x20cl_enable_wr(); + w25x20cl_page_program(addr, buff, savelength); + w25x20cl_wait_busy(); + w25x20cl_disable_wr(); + } + } + /* Read memory block mode, length is big endian. */ + else if(ch == STK_READ_PAGE) { + uint32_t addr = (((uint32_t)rampz) << 16) | address; + uint8_t desttype; + register pagelen_t i; + // Read the page length, with the length transferred each nibble separately to work around + // the Prusa's USB to serial infamous semicolon issue. + length = ((pagelen_t)getch()) << 8; + length |= ((pagelen_t)getch()) << 8; + length |= getch(); + length |= getch(); + // Read the destination type. It should always be 'F' as flash. + desttype = getch(); + verifySpace(); + w25x20cl_wait_busy(); + w25x20cl_rd_data(addr, buff, length); + for (i = 0; i < length; ++ i) + putch(buff[i]); + } + /* Get device signature bytes */ + else if(ch == STK_READ_SIGN) { + // READ SIGN - return what Avrdude wants to hear + verifySpace(); + putch(W25X20CL_SIGNATURE_0); + putch(W25X20CL_SIGNATURE_1); + putch(W25X20CL_SIGNATURE_2); + } + else if (ch == STK_LEAVE_PROGMODE) { /* 'Q' */ + // Adaboot no-wait mod + watchdogConfig(WATCHDOG_16MS); + verifySpace(); + } + else { + // This covers the response to commands like STK_ENTER_PROGMODE + verifySpace(); + } + putch(STK_OK); + } +} diff --git a/Firmware/optiboot_w25x20cl.h b/Firmware/optiboot_w25x20cl.h new file mode 100644 index 000000000..dc7d4395c --- /dev/null +++ b/Firmware/optiboot_w25x20cl.h @@ -0,0 +1,6 @@ +#ifndef OPTIBOOT_W25X20CL_H +#define OPTIBOOT_W25X20CL_H + +extern void optiboot_w25x20cl_enter(); + +#endif /* OPTIBOOT_W25X20CL_H */ diff --git a/Firmware/stk500.h b/Firmware/stk500.h new file mode 100644 index 000000000..ee1a17fd5 --- /dev/null +++ b/Firmware/stk500.h @@ -0,0 +1,49 @@ +/* STK500 constants list, from AVRDUDE + * + * Trivial set of constants derived from Atmel App Note AVR061 + * Not copyrighted. Released to the public domain. + */ + +#define STK_OK 0x10 +#define STK_FAILED 0x11 // Not used +#define STK_UNKNOWN 0x12 // Not used +#define STK_NODEVICE 0x13 // Not used +#define STK_INSYNC 0x14 // ' ' +#define STK_NOSYNC 0x15 // Not used +#define ADC_CHANNEL_ERROR 0x16 // Not used +#define ADC_MEASURE_OK 0x17 // Not used +#define PWM_CHANNEL_ERROR 0x18 // Not used +#define PWM_ADJUST_OK 0x19 // Not used +#define CRC_EOP 0x20 // 'SPACE' +#define STK_GET_SYNC 0x30 // '0' +#define STK_GET_SIGN_ON 0x31 // '1' +#define STK_SET_PARAMETER 0x40 // '@' +#define STK_GET_PARAMETER 0x41 // 'A' +#define STK_SET_DEVICE 0x42 // 'B' +#define STK_SET_DEVICE_EXT 0x45 // 'E' +#define STK_ENTER_PROGMODE 0x50 // 'P' +#define STK_LEAVE_PROGMODE 0x51 // 'Q' +#define STK_CHIP_ERASE 0x52 // 'R' +#define STK_CHECK_AUTOINC 0x53 // 'S' +#define STK_LOAD_ADDRESS 0x55 // 'U' +#define STK_UNIVERSAL 0x56 // 'V' +#define STK_PROG_FLASH 0x60 // '`' +#define STK_PROG_DATA 0x61 // 'a' +#define STK_PROG_FUSE 0x62 // 'b' +#define STK_PROG_LOCK 0x63 // 'c' +#define STK_PROG_PAGE 0x64 // 'd' +#define STK_PROG_FUSE_EXT 0x65 // 'e' +#define STK_READ_FLASH 0x70 // 'p' +#define STK_READ_DATA 0x71 // 'q' +#define STK_READ_FUSE 0x72 // 'r' +#define STK_READ_LOCK 0x73 // 's' +#define STK_READ_PAGE 0x74 // 't' +#define STK_READ_SIGN 0x75 // 'u' +#define STK_READ_OSCCAL 0x76 // 'v' +#define STK_READ_FUSE_EXT 0x77 // 'w' +#define STK_READ_OSCCAL_EXT 0x78 // 'x' +#define STK_SW_MAJOR 0x81 // ' ' +#define STK_SW_MINOR 0x82 // ' ' + +/* AVR raw commands sent via STK_UNIVERSAL */ +#define AVR_OP_LOAD_EXT_ADDR 0x4d diff --git a/Firmware/w25x20cl.c b/Firmware/w25x20cl.c index 611897fe4..79467cb6d 100644 --- a/Firmware/w25x20cl.c +++ b/Firmware/w25x20cl.c @@ -122,7 +122,7 @@ void w25x20cl_page_program_P(uint32_t addr, uint8_t* data, uint16_t cnt) void w25x20cl_erase(uint8_t cmd, uint32_t addr) { _CS_LOW(); - _SPI_TX(_CMD_SECTOR_ERASE); // send command 0x20 + _SPI_TX(cmd); // send command 0x20 _SPI_TX(((uint8_t*)&addr)[2]); // send addr bits 16..23 _SPI_TX(((uint8_t*)&addr)[1]); // send addr bits 8..15 _SPI_TX(((uint8_t*)&addr)[0]); // send addr bits 0..7 @@ -177,3 +177,8 @@ int w25x20cl_mfrid_devid(void) _CS_HIGH(); return ((w25x20cl_mfrid == _MFRID) && (w25x20cl_devid == _DEVID)); } + +void w25x20cl_wait_busy(void) +{ + while (w25x20cl_rd_status_reg() & W25X20CL_STATUS_BUSY) ; +} diff --git a/Firmware/w25x20cl.h b/Firmware/w25x20cl.h index 8a0ed0acf..10aaaf2c7 100644 --- a/Firmware/w25x20cl.h +++ b/Firmware/w25x20cl.h @@ -34,8 +34,9 @@ extern void w25x20cl_sector_erase(uint32_t addr); extern void w25x20cl_block32_erase(uint32_t addr); extern void w25x20cl_block64_erase(uint32_t addr); extern void w25x20cl_chip_erase(void); +extern void w25x20cl_page_program(uint32_t addr, uint8_t* data, uint16_t cnt); extern void w25x20cl_rd_uid(uint8_t* uid); - +extern void w25x20cl_wait_busy(void); #if defined(__cplusplus) }