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
This commit is contained in:
bubnikv 2018-06-14 15:13:21 +02:00
parent 122d1348ce
commit eef6c68c9f
6 changed files with 376 additions and 2 deletions

View file

@ -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

View file

@ -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);
}
}

View file

@ -0,0 +1,6 @@
#ifndef OPTIBOOT_W25X20CL_H
#define OPTIBOOT_W25X20CL_H
extern void optiboot_w25x20cl_enter();
#endif /* OPTIBOOT_W25X20CL_H */

49
Firmware/stk500.h Normal file
View file

@ -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

View file

@ -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) ;
}

View file

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