Bits and pieces for flashing an Arduino Mega. (#290)

pull/326/head
dannybackx 8 years ago committed by Thorsten von Eicken
parent c0cd698860
commit bc87885a5f
  1. 1167
      esp-link/cgimega.c
  2. 14
      esp-link/cgimega.h
  3. 129
      esp-link/cgioptiboot.c
  4. 9
      esp-link/main.c
  5. 149
      esp-link/pgmshared.c
  6. 54
      esp-link/pgmshared.h
  7. 114
      esp-link/stk500v2.h
  8. 114
      megaflash

File diff suppressed because it is too large Load Diff

@ -0,0 +1,14 @@
// Copyright (c) 2016-2017 by Danny Backx, see LICENSE.txt in the esp-link repo
#ifndef CGIMEGA_H
#define CGIMEGA_H
#include <httpd.h>
int ICACHE_FLASH_ATTR cgiMegaSync(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiMegaData(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiMegaRead(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiMegaFuse(HttpdConnData *connData);
int ICACHE_FLASH_ATTR cgiMegaRebootMCU(HttpdConnData *connData);
#endif

@ -1,5 +1,10 @@
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
// Some code moved to esp-link/pgmshared.c to avoid code duplication.
// Those changes are Copyright (c) 2017 by Danny Backx.
// Protocol used : https://github.com/Optiboot/optiboot/wiki/HowOptibootWorks
#include <esp8266.h>
#include <osapi.h>
#include "cgi.h"
@ -11,6 +16,8 @@
#include "mqtt_cmd.h"
#include "serled.h"
#include "pgmshared.h"
#define INIT_DELAY 150 // wait this many millisecs before sending anything
#define BAUD_INTERVAL 600 // interval after which we change baud rate
#define PGM_TIMEOUT 20000 // timeout after sync is achieved, in milliseconds
@ -43,33 +50,12 @@ static short ackWait; // counter of expected ACKs
static uint16_t optibootVers;
static uint32_t baudRate; // baud rate at which we're programming
#define RESP_SZ 64
static char responseBuf[RESP_SZ]; // buffer to accumulate responses from optiboot
static short responseLen = 0; // amount accumulated so far
#define ERR_MAX 128
static char errMessage[ERR_MAX]; // error message
#define MAX_PAGE_SZ 512 // max flash page size supported
#define MAX_SAVED 512 // max chars in saved buffer
// structure used to remember request details from one callback to the next
// allocated dynamically so we don't burn so much static RAM
static struct optibootData {
char *saved; // buffer for saved incomplete hex records
char *pageBuf; // buffer for received data to be sent to AVR
uint16_t pageLen; // number of bytes in pageBuf
uint16_t pgmSz; // size of flash page to be programmed at a time
uint16_t pgmDone; // number of bytes programmed
uint32_t address; // address to write next page to
uint32_t startTime; // time of program POST request
HttpdConnData *conn; // request doing the programming, so we can cancel it
bool eof; // got EOF record
} *optibootData;
// forward function references
static void optibootTimerCB(void *);
static void optibootUartRecv(char *buffer, short length);
static bool processRecord(char *buf, short len);
static bool programPage(void);
static void armTimer(uint32_t ms);
static void initBaud(void);
@ -176,30 +162,6 @@ int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) {
return HTTPD_CGI_DONE;
}
// verify that N chars are hex characters
static bool ICACHE_FLASH_ATTR checkHex(char *buf, short len) {
while (len--) {
char c = *buf++;
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
continue;
DBG("OB non-hex\n");
os_sprintf(errMessage, "Non hex char in POST record: '%c'/0x%02x", c, c);
return false;
}
return true;
}
// get hex value of some hex characters
static uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len) {
uint32_t v = 0;
while (len--) {
v = (v<<4) | (uint32_t)(*buf & 0xf);
if (*buf > '9') v += 9;
buf++;
}
return v;
}
//===== Cgi to write firmware to Optiboot, requires prior sync call
int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
@ -229,6 +191,7 @@ int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) {
errorResponse(connData, 400, "Out of memory");
return HTTPD_CGI_DONE;
}
optibootData->mega = false;
optibootData->pageBuf = pageBuf;
optibootData->saved = saved;
optibootData->startTime = system_get_time();
@ -333,80 +296,6 @@ int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) {
return HTTPD_CGI_DONE;
}
// verify checksum
static bool ICACHE_FLASH_ATTR verifyChecksum(char *buf, short len) {
uint8_t sum = 0;
while (len >= 2) {
sum += (uint8_t)getHexValue(buf, 2);
buf += 2;
len -= 2;
}
return sum == 0;
}
// Process a hex record -- assumes that the records starts with ':' & hex length
static bool ICACHE_FLASH_ATTR processRecord(char *buf, short len) {
buf++; len--; // skip leading ':'
// check we have all hex chars
if (!checkHex(buf, len)) return false;
// verify checksum
if (!verifyChecksum(buf, len)) {
buf[len] = 0;
os_sprintf(errMessage, "Invalid checksum for record %s", buf);
return false;
}
// dispatch based on record type
uint8_t type = getHexValue(buf+6, 2);
switch (type) {
case 0x00: { // data
//DBG("OB REC data %ld pglen=%d\n", getHexValue(buf, 2), optibootData->pageLen);
uint32_t addr = getHexValue(buf+2, 4);
// check whether we need to program previous record(s)
if (optibootData->pageLen > 0 &&
addr != ((optibootData->address+optibootData->pageLen)&0xffff)) {
//DBG("OB addr chg\n");
if (!programPage()) return false;
}
// set address, unless we're adding to the end (programPage may have changed pageLen)
if (optibootData->pageLen == 0) {
optibootData->address = (optibootData->address & 0xffff0000) | addr;
//DBG("OB set-addr 0x%lx\n", optibootData->address);
}
// append record
uint16_t recLen = getHexValue(buf, 2);
for (uint16_t i=0; i<recLen; i++)
optibootData->pageBuf[optibootData->pageLen++] = getHexValue(buf+8+2*i, 2);
// program page, if we have a full page
if (optibootData->pageLen >= optibootData->pgmSz) {
//DBG("OB full\n");
if (!programPage()) return false;
}
break; }
case 0x01: // EOF
DBG("OB EOF\n");
// program any remaining partial page
if (optibootData->pageLen > 0)
if (!programPage()) return false;
optibootData->eof = true;
break;
case 0x04: // address
DBG("OB address 0x%x\n", getHexValue(buf+8, 4) << 16);
// program any remaining partial page
if (optibootData->pageLen > 0)
if (!programPage()) return false;
optibootData->address = getHexValue(buf+8, 4) << 16;
break;
case 0x05: // start address
// ignore, there's no way to tell optiboot that...
break;
default:
DBG("OB bad record type\n");
os_sprintf(errMessage, "Invalid/unknown record type: 0x%02x", type);
return false;
}
return true;
}
// Poll UART for ACKs, max 50ms
static bool pollAck() {
char recv[16];
@ -427,7 +316,7 @@ static bool pollAck() {
}
// Program a flash page
static bool ICACHE_FLASH_ATTR programPage(void) {
bool ICACHE_FLASH_ATTR optibootProgramPage(void) {
if (optibootData->pageLen == 0) return true;
armTimer(PGM_TIMEOUT); // keep the timerCB out of the picture

@ -19,6 +19,7 @@
#include "cgimqtt.h"
#include "cgiflash.h"
#include "cgioptiboot.h"
#include "cgimega.h"
#include "cgiwebserversetup.h"
#include "auth.h"
#include "espfs.h"
@ -69,8 +70,16 @@ HttpdBuiltInUrl builtInUrls[] = {
{ "/flash/next", cgiGetFirmwareNext, NULL },
{ "/flash/upload", cgiUploadFirmware, NULL },
{ "/flash/reboot", cgiRebootFirmware, NULL },
{ "/pgm/sync", cgiOptibootSync, NULL },
{ "/pgm/upload", cgiOptibootData, NULL },
{ "/pgmmega/sync", cgiMegaSync, NULL }, // Start programming mode
{ "/pgmmega/upload", cgiMegaData, NULL }, // Upload stuff
{ "/pgmmega/read/*", cgiMegaRead, NULL }, // Download stuff (to verify)
{ "/pgmmega/fuse/*", cgiMegaFuse, NULL }, // Read or write fuse
{ "/pgmmega/rebootmcu", cgiMegaRebootMCU, NULL }, // Get out of programming mode
{ "/log/text", ajaxLog, NULL },
{ "/log/dbg", ajaxLogDbg, NULL },
{ "/log/reset", cgiReset, NULL },

@ -0,0 +1,149 @@
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
// Copyright (c) 2016-2017 by Danny Backx
#include <esp8266.h>
#include <osapi.h>
#include "cgi.h"
#include "config.h"
#include "uart.h"
#include "stk500v2.h"
#include "serbridge.h"
#include "serled.h"
#include "pgmshared.h"
struct optibootData *optibootData;
char responseBuf[RESP_SZ]; // buffer to accumulate responses from optiboot
short responseLen = 0; // amount accumulated so far
char errMessage[ERR_MAX]; // error message
// verify that N chars are hex characters
bool ICACHE_FLASH_ATTR checkHex(char *buf, short len) {
while (len--) {
char c = *buf++;
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
continue;
DBG("OB non-hex\n");
os_sprintf(errMessage, "Non hex char in POST record: '%c'/0x%02x", c, c);
return false;
}
return true;
}
// get hex value of some hex characters
uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len) {
uint32_t v = 0;
while (len--) {
v = (v<<4) | (uint32_t)(*buf & 0xf);
if (*buf > '9') v += 9;
buf++;
}
return v;
}
// verify checksum
static bool ICACHE_FLASH_ATTR verifyChecksum(char *buf, short len) {
uint8_t sum = 0;
while (len >= 2) {
sum += (uint8_t)getHexValue(buf, 2);
buf += 2;
len -= 2;
}
return sum == 0;
}
// We've not built one function that works on both, but kept the different functions.
// This calls one or the other
static bool ICACHE_FLASH_ATTR programPage() {
if (optibootData->mega)
return megaProgramPage();
return optibootProgramPage();
}
// Process a hex record -- assumes that the records starts with ':' & hex length
bool ICACHE_FLASH_ATTR processRecord(char *buf, short len) {
buf++; len--; // skip leading ':'
// check we have all hex chars
if (!checkHex(buf, len)) return false;
// verify checksum
if (!verifyChecksum(buf, len)) {
buf[len] = 0;
os_sprintf(errMessage, "Invalid checksum for record %s", buf);
return false;
}
// dispatch based on record type
uint8_t type = getHexValue(buf+6, 2);
switch (type) {
case 0x00: { // Intel HEX data record
//DBG("OB REC data %ld pglen=%d\n", getHexValue(buf, 2), optibootData->pageLen);
uint32_t addr = getHexValue(buf+2, 4);
// check whether we need to program previous record(s)
if (optibootData->pageLen > 0 &&
addr != ((optibootData->address+optibootData->pageLen)&0xffff)) {
//DBG("OB addr chg\n");
//DBG("processRecord addr chg, len %d, addr 0x%04x\n", optibootData->pageLen, addr);
if (!programPage()) return false;
}
// set address, unless we're adding to the end (programPage may have changed pageLen)
if (optibootData->pageLen == 0) {
optibootData->address = (optibootData->address & 0xffff0000) | addr;
//DBG("OB set-addr 0x%lx\n", optibootData->address);
}
// append record
uint16_t recLen = getHexValue(buf, 2);
for (uint16_t i=0; i<recLen; i++)
optibootData->pageBuf[optibootData->pageLen++] = getHexValue(buf+8+2*i, 2);
// program page, if we have a full page
if (optibootData->pageLen >= optibootData->pgmSz) {
//DBG("OB full\n");
DBG("processRecord %d, call programPage() %08x\n", optibootData->pgmSz, optibootData->address + optibootData->segment);
if (!programPage()) return false;
}
break; }
case 0x01: // Intel HEX EOF record
DBG("OB EOF\n");
// program any remaining partial page
#if 1
if (optibootData->pageLen > 0) {
// DBG("processRecord remaining partial page, len %d, addr 0x%04x\n", optibootData->pageLen, optibootData->address + optibootData->segment);
if (!programPage()) return false;
}
optibootData->eof = true;
#else
if (optibootData->pageLen > 0) {
// HACK : fill up with 0xFF
while (optibootData->pageLen < 256) {
optibootData->pageBuf[optibootData->pageLen++] = 0xFF;
}
if (!programPage()) return false;
}
optibootData->eof = true;
#endif
break;
case 0x04: // Intel HEX address record
DBG("OB address 0x%x\n", getHexValue(buf+8, 4) << 16);
// program any remaining partial page
if (optibootData->pageLen > 0) {
DBG("processRecord 0x04 remaining partial page, len %d, addr 0x%04x\n",
optibootData->pageLen, optibootData->address + optibootData->segment);
if (!programPage()) return false;
}
optibootData->address = getHexValue(buf+8, 4) << 16;
break;
case 0x05: // Intel HEX start address (MDK-ARM only)
// ignore, there's no way to tell optiboot that...
break;
case 0x02: // Intel HEX extended segment address record
// Depending on the case, just ignoring this record could solve the problem
// optibootData->segment = getHexValue(buf+8, 4) << 4;
DBG("OB segment 0x%08X\n", optibootData->segment);
return true;
default:
// DBG("OB bad record type\n");
DBG(errMessage, "Invalid/unknown record type: 0x%02x, packet %s", type, buf);
return false;
}
return true;
}

@ -0,0 +1,54 @@
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
// Copyright (c) 2017 by Danny Backx
#ifndef _PGM_SHARED_H_
#define _PGM_SHARED_H_
#include "user_config.h"
#define RESP_SZ 64
#define ERR_MAX 128
extern char responseBuf[RESP_SZ];
extern short responseLen;
extern char errMessage[ERR_MAX];
// structure used to remember request details from one callback to the next
// allocated dynamically so we don't burn so much static RAM
extern struct optibootData {
char *saved; // buffer for saved incomplete hex records
char *pageBuf; // buffer for received data to be sent to AVR
uint16_t pageLen; // number of bytes in pageBuf
uint16_t pgmSz; // size of flash page to be programmed at a time
uint32_t pgmDone; // number of bytes programmed
uint32_t address; // address to write next page to
uint32_t segment; // for extended segment addressing, added to the address field
uint32_t startTime; // time of program POST request
HttpdConnData *conn; // request doing the programming, so we can cancel it
bool eof; // got EOF record
// Whether to use the Mega (STK500v2) protocol
bool mega;
// STK500v2 variables
int hardwareVersion,
firmwareVersionMajor,
firmwareVersionMinor,
vTarget;
uint8_t signature[3];
uint8_t lfuse, hfuse, efuse;
} *optibootData;
bool ICACHE_FLASH_ATTR checkHex(char *buf, short len);
uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len);
bool ICACHE_FLASH_ATTR processRecord(char *buf, short len);
bool megaProgramPage(void);
bool optibootProgramPage(void);
#ifdef OPTIBOOT_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
#else
#define DBG(format, ...) do { } while(0)
#endif
#endif

@ -0,0 +1,114 @@
//**** ATMEL AVR - A P P L I C A T I O N N O T E ************************
//*
//* Title: AVR068 - STK500 Communication Protocol
//* Filename: command.h
//* Version: 1.0
//* Last updated: 31.01.2005
//*
//* Support E-mail: avr@atmel.com
//*
//**************************************************************************
// *****************[ STK message constants ]***************************
#define MESSAGE_START 0x1B //= ESC = 27 decimal
#define TOKEN 0x0E
// *****************[ STK general command constants ]**************************
#define CMD_SIGN_ON 0x01
#define CMD_SET_PARAMETER 0x02
#define CMD_GET_PARAMETER 0x03
#define CMD_SET_DEVICE_PARAMETERS 0x04
#define CMD_OSCCAL 0x05
#define CMD_LOAD_ADDRESS 0x06
#define CMD_FIRMWARE_UPGRADE 0x07
// *****************[ STK ISP command constants ]******************************
#define CMD_ENTER_PROGMODE_ISP 0x10
#define CMD_LEAVE_PROGMODE_ISP 0x11
#define CMD_CHIP_ERASE_ISP 0x12
#define CMD_PROGRAM_FLASH_ISP 0x13
#define CMD_READ_FLASH_ISP 0x14
#define CMD_PROGRAM_EEPROM_ISP 0x15
#define CMD_READ_EEPROM_ISP 0x16
#define CMD_PROGRAM_FUSE_ISP 0x17
#define CMD_READ_FUSE_ISP 0x18
#define CMD_PROGRAM_LOCK_ISP 0x19
#define CMD_READ_LOCK_ISP 0x1A
#define CMD_READ_SIGNATURE_ISP 0x1B
#define CMD_READ_OSCCAL_ISP 0x1C
#define CMD_SPI_MULTI 0x1D
// *****************[ STK PP command constants ]*******************************
#define CMD_ENTER_PROGMODE_PP 0x20
#define CMD_LEAVE_PROGMODE_PP 0x21
#define CMD_CHIP_ERASE_PP 0x22
#define CMD_PROGRAM_FLASH_PP 0x23
#define CMD_READ_FLASH_PP 0x24
#define CMD_PROGRAM_EEPROM_PP 0x25
#define CMD_READ_EEPROM_PP 0x26
#define CMD_PROGRAM_FUSE_PP 0x27
#define CMD_READ_FUSE_PP 0x28
#define CMD_PROGRAM_LOCK_PP 0x29
#define CMD_READ_LOCK_PP 0x2A
#define CMD_READ_SIGNATURE_PP 0x2B
#define CMD_READ_OSCCAL_PP 0x2C
#define CMD_SET_CONTROL_STACK 0x2D
// *****************[ STK HVSP command constants ]*****************************
#define CMD_ENTER_PROGMODE_HVSP 0x30
#define CMD_LEAVE_PROGMODE_HVSP 0x31
#define CMD_CHIP_ERASE_HVSP 0x32
#define CMD_PROGRAM_FLASH_HVSP 0x33
#define CMD_READ_FLASH_HVSP 0x34
#define CMD_PROGRAM_EEPROM_HVSP 0x35
#define CMD_READ_EEPROM_HVSP 0x36
#define CMD_PROGRAM_FUSE_HVSP 0x37
#define CMD_READ_FUSE_HVSP 0x38
#define CMD_PROGRAM_LOCK_HVSP 0x39
#define CMD_READ_LOCK_HVSP 0x3A
#define CMD_READ_SIGNATURE_HVSP 0x3B
#define CMD_READ_OSCCAL_HVSP 0x3C
// *****************[ STK status constants ]***************************
// Success
#define STATUS_CMD_OK 0x00
// Warnings
#define STATUS_CMD_TOUT 0x80
#define STATUS_RDY_BSY_TOUT 0x81
#define STATUS_SET_PARAM_MISSING 0x82
// Errors
#define STATUS_CMD_FAILED 0xC0
#define STATUS_CKSUM_ERROR 0xC1
#define STATUS_CMD_UNKNOWN 0xC9
// *****************[ STK parameter constants ]***************************
#define PARAM_BUILD_NUMBER_LOW 0x80
#define PARAM_BUILD_NUMBER_HIGH 0x81
#define PARAM_HW_VER 0x90
#define PARAM_SW_MAJOR 0x91
#define PARAM_SW_MINOR 0x92
#define PARAM_VTARGET 0x94
#define PARAM_VADJUST 0x95
#define PARAM_OSC_PSCALE 0x96
#define PARAM_OSC_CMATCH 0x97
#define PARAM_SCK_DURATION 0x98
#define PARAM_TOPCARD_DETECT 0x9A
#define PARAM_STATUS 0x9C
#define PARAM_DATA 0x9D
#define PARAM_RESET_POLARITY 0x9E
#define PARAM_CONTROLLER_INIT 0x9F
// *****************[ STK answer constants ]***************************
#define ANSWER_CKSUM_ERROR 0xB0

@ -0,0 +1,114 @@
#! /bin/bash
#
# Flash an Arduino Mega with STK500v2 using the esp-link built-in programmer
# Basically we first reset the AVR and get in sync, and then send the hex file
#
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42):
# Thorsten von Eicken wrote this file. As long as you retain
# this notice you can do whatever you want with this stuff. If we meet some day,
# and you think this stuff is worth it, you can buy me a beer in return.
#
# Danny Backx wrote the changes for Arduino Mega.
# ----------------------------------------------------------------------------
show_help() {
cat <<EOT
Usage: ${0##*/} [-options...] hostname sketch.hex
Flash the Mega running optiboot attached to esp-link with the sketch.
Note : this is for stk500v2 MCUs, use avrflash for Arduino Uno etc instead.
-v Be verbose
-h show this help
Example: ${0##*/} -v esp-link mysketch.hex
${0##*/} 192.168.4.1 mysketch.hex
EOT
}
if ! which curl >/dev/null; then
echo "ERROR: Cannot find curl: it is required for this script." >&2
exit 1
fi
start=`date +%s`
# ===== Parse arguments
verbose=
while getopts "hvx:" opt; do
case "$opt" in
h) show_help; exit 0 ;;
v) verbose=1 ;;
x) foo="$OPTARG" ;;
'?') show_help >&2; exit 1 ;;
esac
done
# Shift off the options and optional --.
shift "$((OPTIND-1))"
# Get the fixed arguments
if [[ $# != 2 ]]; then
show_help >&2
exit 1
fi
hostname=$1
hex=$2
re='[-A-Za-z0-9.]+'
if [[ ! "$hostname" =~ $re ]]; then
echo "ERROR: hostname ${hostname} is not a valid hostname or ip address" >&2
exit 1
fi
if [[ ! -r "$hex" ]]; then
echo "ERROR: cannot read hex file ($hex)" >&2
exit 1
fi
# ===== Get AVR in sync
[[ -n "$verbose" ]] && echo "Resetting AVR with http://$hostname/pgmmega/sync" >&2
v=; [[ -n "$verbose" ]] && v=-v
sync=`curl -m 10 $v -s -w '%{http_code}' -XPOST "http://$hostname/pgmmega/sync"`
if [[ $? != 0 || "$sync" != 204 ]]; then
echo "Error resetting AVR" >&2
exit 1
fi
while true; do
# sync=`curl -m 10 $v -s "http://$hostname/pgmmega/sync"`
sync=`curl $v -s "http://$hostname/pgmmega/sync"`
if [[ $? != 0 ]]; then
echo "Error checking sync" >&2
exit 1
fi
case "$sync" in
SYNC*)
echo "AVR in $sync" >&2
break;;
"NOT READY"*)
[[ -n "$verbose" ]] && echo " Waiting for sync..." >&2
;;
*)
echo "Error checking sync: $sync" >&2
exit 1
;;
esac
sleep 0.1
done
# ===== Send HEX file
[[ -n "$verbose" ]] && echo "Sending HEX file for programming" >&2
sync=`curl -m 20 $v -s -g -d "@$hex" "http://$hostname/pgmmega/upload"`
echo $sync
if [[ $? != 0 || ! "$sync" =~ ^Success ]]; then
echo "Error programming AVR" >&2
exit 1
fi
sec=$(( `date +%s` - $start ))
echo "Success, took $sec seconds" >&2
exit 0
Loading…
Cancel
Save