Jelajahi Sumber

Merge pull request #841 from bubnikv/optiboot_w25x20cl

Added support for a secondary boot loader, based on the OptiBoot project
XPila 6 tahun lalu
induk
melakukan
f5f945fdf8

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