Browse Source

Added support for a secondary boot loader, based on the OptiBoot project,
modified to update the external flash memory on Einsy boards.
Due to a bug in the USB to serial converter firmware on the Prusa Einsy
boards, the STK500 protocol has been modified to never send semicolon
characters towards the main processor.

This firmware updater is compatible with a modified avrdude using
the "arduino" protocol, see the following commit.
https://github.com/prusa3d/Slic3r/tree/fwupdater_languages

bubnikv 5 years ago
parent
commit
eef6c68c9f

+ 5 - 0
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
 

+ 308 - 0
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);
+  }
+}

+ 6 - 0
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 */

+ 49 - 0
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

+ 6 - 1
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) ;
+}

+ 2 - 1
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)
 }