Prusa-Firmware/Firmware/optiboot_w25x20cl.cpp
bubnikv eef6c68c9f 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
2018-06-14 15:13:21 +02:00

309 lines
9.4 KiB
C++

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