Code:
//******************************************************************************
// FlasherX -- firmware "OTA" update via Intel Hex file over serial byte stream
//******************************************************************************
//
// WARNING: You can brick your Teensy with incorrect flash erase/write, such as
// incorrect flash config (0x400-40F). This code may or may not prevent that.
//
// Based on Flasher3 (Teensy 3.x) and Flasher4 (Teensy 4.x) by Jon Zeeff
//
// Jon Zeeff 2016, 2019, 2020
// This code is in the public domain. Please retain my name and
// in distributed copies, and let me know about any bugs
//
// I, Jon Zeeff, give no warranty, expressed or implied for this software
// and/or documentation provided, including, without limitation, warranty of
// merchantability and fitness for a particular purpose.
// 7th Modifications 01/07/22 updates by Joe Pasquariello (v2.1)
// - FlashTxx.cpp delete local T4x wait/write/erase to use functions in TD 1.56
// - FlashTxx.h update FLASH_SIZE for Teensy Micromod from 8 to 16 MB
// - FlasherX.ino update to print "FlasherX OTA firmware update v2.1" on bootup
// - add option to artificially increase code size via const array (in flash)
// 6th Modifications 11/18/21 bug fix in file FlashTXX.cpp by Joe Pasquariello
// - fix logic in while loop in flash_block_write() in FlashTXX
// 5th Modifications 10/27/21 add support for Teensy Micromod by Joe Pasquariello
// - define macros for TEENSY_MICROMOD w/ same values as for TEENSY40
// - update FLASH_SIZE for T4.1 and TMM from 2MB to 8MB
// 4th Modifications merge of Flasher3/4 and new features by Joe Pasquariello
// - FLASH buffer dynamically sized from top of existing code to FLASH_RESERVE
// - optional RAM buffer option for T4.x via macro RAM_BUFFER_SIZE > 0
// - Stream* (USB or UART) and buffer addr/size set at run-time
// - incorporate Frank Boesing's FlashKinetis routines for T3.x
// - add support for Teensy 4.1 and Teensy LC
// This code is released into the public domain.
// 3rd Modifications for T3.5 and T3.6 in Dec 2020 by Joe Pasquariello
// This code is released into the public domain.
// 2nd Modifications for teensy 3.5/3/6 by Deb Hollenback at GiftCoder
// This code is released into the public domain.
// see https://forum.pjrc.com/threads/43165-Over-the-Air-firmware-updates-changes-for-flashing-Teensy-3-5-amp-3-6
// 1st Modifications by Jon Zeeff
// see https://forum.pjrc.com/threads/29607-Over-the-air-updates
// Original by Niels A. Moseley, 2015.
// This code is released into the public domain.
// https://namoseley.wordpress.com/2015/02/04/freescale-kinetis-mk20dx-series-flash-erasing/
#include <Arduino.h>
#include "FlashTxx.h" // TLC/T3x/T4x/TMM flash primitives
#include <i2c_t3.h>
// Function prototypes
void receiveEvent(size_t count);
// Memory
#define MEM_LEN 256
char databuf[MEM_LEN];
volatile uint8_t received;
const int ledPin = 13; // LED
Stream *serial = &Serial
; // Serial (USB) or Serial1, Serial2, etc. (UART)
#define LARGE_ARRAY_TEST (0) // 1 = define large array for large code download
#if (LARGE_ARRAY_TEST)
// nested arrays of integers to add code size for testing
#define A0 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15} // 16 elements 64
#define A1 {A0,A0,A0,A0,A0,A0,A0,A0,A0,A0,A0,A0,A0,A0,A0,A0} // 256 elements 1KB
#define A2 {A1,A1,A1,A1,A1,A1,A1,A1,A1,A1,A1,A1,A1,A1,A1,A1} // 4K elements 16KB
#define A3 {A2,A2,A2,A2,A2,A2,A2,A2,A2,A2,A2,A2,A2,A2,A2,A2} // 64K elements 256KB
#define A4 {A3,A3,A3,A3,A3,A3,A3,A3,A3,A3,A3,A3,A3,A3,A3,A3} // 1M elements 4MB
// const variables reside in flash and get optimized out if never accessed
// use uint8_t -> 1MB, uint16_t -> 2MB, uint32_t -> 4MB, uint64_t -> 8MB)
PROGMEM const uint8_t a[16][16][16][16][16] = A4;
#endif
//******************************************************************************
// hex_info_t struct for hex record and hex file info
//******************************************************************************
typedef struct { //
char *data; // pointer to array allocated elsewhere
unsigned int addr; // address in intel hex record
unsigned int code; // intel hex record type (0=data, etc.)
unsigned int num; // number of data bytes in intel hex record
uint32_t base; // base address to be added to intel hex 16-bit addr
uint32_t min; // min address in hex file
uint32_t max; // max address in hex file
int eof; // set true on intel hex EOF (code = 1)
int lines; // number of hex records received
} hex_info_t;
void read_ascii_line( Stream *serial, char *line, int maxbytes );
int parse_hex_line( const char *theline, char *bytes,
unsigned int *addr, unsigned int *num, unsigned int *code );
int process_hex_record( hex_info_t *hex );
void update_firmware( Stream *serial, uint32_t buffer_addr, uint32_t buffer_size );
// variables for serial input
String inputString = ""; // a string to hold incoming data
boolean stringComplete = false; // whether the string is complete
int feature = 0;
uint32_t buffer_addr, buffer_size;
void setup ()
{
Wire.begin(I2C_SLAVE, 0x66, I2C_PINS_18_19, I2C_PULLUP_EXT, 400000);
// Data init
received = 0;
memset(databuf, 0, sizeof(databuf));
pinMode(7, OUTPUT);
digitalWrite(7, LOW);
if (serial == (Stream*)&Serial) {
Serial1.begin(115200);
Serial.begin(115200);
while (!Serial1) {}
}
else {
((HardwareSerial*)serial)->begin( 115200 );
}
serial->printf( "\nFlasherX v2.1 -- OTA firmware update -- %s %s\n", __DATE__, __TIME__ );
serial->printf( "WARNING: this can ruin your device\n" );
serial->printf( "target = %s (%dK flash in %dK sectors)\n",
FLASH_ID, FLASH_SIZE / 1024, FLASH_SECTOR_SIZE / 1024);
#if (LARGE_ARRAY_TEST) // if true, access array so it doesn't get optimized out
serial->printf( "Large Array Test -- %08lX\n", (uint32_t)&a[15][15][15][15][15] );
#endif
pinMode(ledPin, OUTPUT); // assign output
digitalWrite(ledPin, HIGH); // set the LED on
delay(200); // delay
digitalWrite(ledPin, LOW); // set the LED off
serial->printf( "Begining Firmware Upgrade...\n");
if (firmware_buffer_init( &buffer_addr, &buffer_size ) == 0) {
serial->printf( "unable to create buffer\n" );
serial->flush();
for (;;) {}
}
serial->printf( "buffer = %1luK %s (%08lX - %08lX)\n",
buffer_size / 1024, IN_FLASH(buffer_addr) ? "FLASH" : "RAM",
buffer_addr, buffer_addr + buffer_size );
// register events
Wire.onReceive(receiveEvent);
}
void receiveEvent(size_t count)
{
Wire.read(databuf, count); // copy Rx data to databuf
received = count; // set received flag to count, this triggers print in main loop
}
void loop ()
{
if (received)
{
// digitalWrite(LED_BUILTIN,HIGH);
// Serial.printf("Slave received: '%s'\n", databuf);
received = 0;
// digitalWrite(LED_BUILTIN,LOW);
processFirmwareFile();
}
}
void processFirmwareFile() {
static char data[32] __attribute__ ((aligned (8))); // buffer for hex data
hex_info_t hex = { // intel hex info struct
data, 0, 0, 0, // data,addr,num,code
0, 0xFFFFFFFF, 0, // base,min,max,
0, 0 // eof,lines
};
while (!hex.eof) {
if (parse_hex_line( (const char*)databuf, hex.data, &hex.addr, &hex.num, &hex.code ) == 0) {
serial->printf( "abort - bad hex line %s\n", databuf );
return;
}
else if (process_hex_record( &hex ) != 0) { // error on bad hex code
serial->printf( "abort - invalid hex code %d\n", hex.code );
return;
}
else if (hex.code == 0) { // if data record
uint32_t addr = buffer_addr + hex.base + hex.addr - FLASH_BASE_ADDR;
if (hex.max > (FLASH_BASE_ADDR + buffer_size)) {
serial->printf( "abort - max address %08lX too large\n", hex.max );
return;
}
else if (!IN_FLASH(buffer_addr)) {
memcpy( (void*)addr, (void*)hex.data, hex.num );
}
else if (IN_FLASH(buffer_addr)) {
int error = flash_write_block( addr, hex.data, hex.num );
if (error) {
serial->printf( "abort - error %02X in flash_write_block()\n", error );
Serial.println(addr, HEX);
return;
}
}
}
hex.lines++;
}
}
//******************************************************************************
// process_hex_record() process record and return okay (0) or error (1)
//******************************************************************************
int process_hex_record( hex_info_t *hex ) {
if (hex->code == 0) { // data -- update min/max address so far
if (hex->base + hex->addr + hex->num > hex->max)
hex->max = hex->base + hex->addr + hex->num;
if (hex->base + hex->addr < hex->min)
hex->min = hex->base + hex->addr;
}
else if (hex->code == 1) { // EOF (:flash command not received yet)
hex->eof = 1;
}
else if (hex->code == 2) { // extended segment address (top 16 of 24-bit addr)
hex->base = ((hex->data[0] << 8) | hex->data[1]) << 4;
}
else if (hex->code == 3) { // start segment address (80x86 real mode only)
return 1;
}
else if (hex->code == 4) { // extended linear address (top 16 of 32-bit addr)
hex->base = ((hex->data[0] << 8) | hex->data[1]) << 16;
}
else if (hex->code == 5) { // start linear address (32-bit big endian addr)
hex->base = (hex->data[0] << 24) | (hex->data[1] << 16)
| (hex->data[2] << 8) | (hex->data[3] << 0);
}
else {
return 1;
}
return 0;
}
//******************************************************************************
// Intel Hex record foramt:
//
// Start code: one character, ASCII colon ':'.
// Byte count: two hex digits, number of bytes (hex digit pairs) in data field.
// Address: four hex digits
// Record type: two hex digits, 00 to 05, defining the meaning of the data field.
// Data: n bytes of data represented by 2n hex digits.
// Checksum: two hex digits, computed value used to verify record has no errors.
//
// Examples:
// :10 9D30 00 711F0000AD38000005390000F5460000 35
// :04 9D40 00 01480000 D6
// :00 0000 01 FF
//******************************************************************************
/* Intel HEX read/write functions, Paul Stoffregen, paul@ece.orst.edu */
/* This code is in the public domain. Please retain my name and */
/* email address in distributed copies, and let me know about any bugs */
/* I, Paul Stoffregen, give no warranty, expressed or implied for */
/* this software and/or documentation provided, including, without */
/* limitation, warranty of merchantability and fitness for a */
/* particular purpose. */
// type modifications by Jon Zeeff
/* parses a line of intel hex code, stores the data in bytes[] */
/* and the beginning address in addr, and returns a 1 if the */
/* line was valid, or a 0 if an error occured. The variable */
/* num gets the number of bytes that were stored into bytes[] */
#include <stdio.h> // sscanf(), etc.
#include <string.h> // strlen(), etc.
int parse_hex_line( const char *theline, char *bytes,
unsigned int *addr, unsigned int *num, unsigned int *code )
{
unsigned sum, len, cksum;
const char *ptr;
int temp;
*num = 0;
if (theline[0] != ':')
return 0;
if (strlen (theline) < 11)
return 0;
ptr = theline + 1;
if (!sscanf (ptr, "%02x", &len))
return 0;
ptr += 2;
if (strlen (theline) < (11 + (len * 2)))
return 0;
if (!sscanf (ptr, "%04x", (unsigned int *)addr))
return 0;
ptr += 4;
/* Serial.printf("Line: length=%d Addr=%d\n", len, *addr); */
if (!sscanf (ptr, "%02x", code))
return 0;
ptr += 2;
sum = (len & 255) + ((*addr >> 8) & 255) + (*addr & 255) + (*code & 255);
while (*num != len)
{
if (!sscanf (ptr, "%02x", &temp))
return 0;
bytes[*num] = temp;
ptr += 2;
sum += bytes[*num] & 255;
(*num)++;
if (*num >= 256)
return 0;
}
if (!sscanf (ptr, "%02x", &cksum))
return 0;
if (((sum & 255) + (cksum & 255)) & 255)
return 0; /* checksum error */
return 1;
}