mirror of
https://github.com/Optiboot/optiboot.git
synced 2025-08-17 21:41:03 +03:00
Better (?) example of flash write from an Arduino sketch
This commit is contained in:
241
optiboot/examples/demo_flashwrite/demo_flashwrite.ino
Normal file
241
optiboot/examples/demo_flashwrite/demo_flashwrite.ino
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
/*------------- Optiboot flasher example for the MiniCore ------------------|
|
||||||
|
| |
|
||||||
|
| Created May 2016 by MCUdude, https://github.com/MCUdude |
|
||||||
|
| Based on the work done by Marek Wodzinski, https://github.com/majekw |
|
||||||
|
| Released to public domain |
|
||||||
|
| |
|
||||||
|
| This is example how to use optiboot.h together with Optiboot |
|
||||||
|
| bootloader to write to FLASH memory by application code. |
|
||||||
|
| |
|
||||||
|
| IMPORTANT THINGS: |
|
||||||
|
| - All flash content gets erased after each upload cycle |
|
||||||
|
| - Buffer must be page aligned (see declaration of flash_buffer) |
|
||||||
|
| - Interrupts must be disabled during SPM |
|
||||||
|
| - Writing to EEPROM destroys temporary buffer |
|
||||||
|
| - You can write only once into one location of temporary buffer |
|
||||||
|
| - Only safe and always working sequence is erase-fill-write |
|
||||||
|
| - If you want to do fill-erase-write, you must put code in NRWW |
|
||||||
|
| and pass data!=0 for erase. It's not easy, but possible. |
|
||||||
|
| |
|
||||||
|
| WRITE SEQUENCE - OPTION 1 (used in this example) |
|
||||||
|
| 1. Erase page by optiboot_page_erase |
|
||||||
|
| 2. Write contents of page into temporary buffer by optiboot_page_fill |
|
||||||
|
| 3. Write temporary buffer to FLASH by optiboot_page_write |
|
||||||
|
| |
|
||||||
|
| WRITE SEQUENCE - OPTION 2 (works only for code in NRWW) |
|
||||||
|
| 1. Write contents of page into temporary buffer by optiboot_page_fill |
|
||||||
|
| 2. Erase page by optiboot_page_erase (set data to NOT zero) |
|
||||||
|
| 3. Write temporary buffer to FLASH by optiboot_page_write |
|
||||||
|
|-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// optiboot.h contains the functions that lets you read to
|
||||||
|
// and write from the flash memory
|
||||||
|
#include "optiboot.h"
|
||||||
|
#include "simpleParser.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Define the number of pages you want to write to here (limited by flash size)
|
||||||
|
// these are flash pages "internal" to the sketch.
|
||||||
|
#define NUMBER_OF_PAGES 8
|
||||||
|
|
||||||
|
// Define your termination and blank character here
|
||||||
|
const char terminationChar = '@';
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t charBuffer;
|
||||||
|
int8_t menuOption;
|
||||||
|
int16_t pageNumber;
|
||||||
|
char returnToMenu;
|
||||||
|
|
||||||
|
// The temporary data (data that's read or is about to get written) is stored here
|
||||||
|
uint8_t ramBuffer[SPM_PAGESIZE + 1];
|
||||||
|
|
||||||
|
// This array allocates the space you'll be able to write to
|
||||||
|
const uint8_t flashSpace[SPM_PAGESIZE * NUMBER_OF_PAGES] __attribute__ (( aligned(SPM_PAGESIZE) )) PROGMEM = {
|
||||||
|
"This some default content stored on page one"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Initialize serial
|
||||||
|
Serial.begin(115200);
|
||||||
|
while (!Serial)
|
||||||
|
; // possibly wait for USB Serial to enumerate.
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleParser<80> ttycli(Serial);
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
byte clicmd;
|
||||||
|
// Print main menu
|
||||||
|
Serial.println();
|
||||||
|
Serial.print(F("|------------------------------------------------|\n"
|
||||||
|
"| Welcome to the Optiboot flash writer example! |\n"
|
||||||
|
#ifdef USE_NVMCTRL
|
||||||
|
"| Running on mega0/xTiny with NVMCTRL support. |\n"
|
||||||
|
#else
|
||||||
|
"| Running on traditional AVR with SPM support. |\n"
|
||||||
|
#endif
|
||||||
|
"| Each flash page is "));
|
||||||
|
Serial.print(SPM_PAGESIZE);
|
||||||
|
Serial.print ( F(" bytes long. |\n"));
|
||||||
|
Serial.print(F("| There are "));
|
||||||
|
Serial.print(NUMBER_OF_PAGES);
|
||||||
|
Serial.print (F( " pages that can be read/written to. |\n"));
|
||||||
|
Serial.print(F("| Total assigned flash space: "));
|
||||||
|
Serial.print(NUMBER_OF_PAGES * SPM_PAGESIZE);
|
||||||
|
Serial.print(F(" bytes. |\n"
|
||||||
|
"| Change the NUMBER_OF_PAGES constant to |\n"
|
||||||
|
"| increase or decrease this number. |\n"
|
||||||
|
"| |\n"
|
||||||
|
"| What do you want to do? |\n"
|
||||||
|
"| Read - read current flash content |\n"
|
||||||
|
"| Write - Write to flash memory |\n"
|
||||||
|
"|------------------------------------------------|\n\n"));
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t counter = 0;
|
||||||
|
|
||||||
|
static const char PROGMEM cmdStrings[] = "read write ?";
|
||||||
|
enum { CMD_READ = 0, CMD_WRITE, CMD_HELP };
|
||||||
|
Serial.print("Cmd: ");
|
||||||
|
ttycli.reset();
|
||||||
|
ttycli.getLineWait();
|
||||||
|
clicmd = ttycli.keyword(cmdStrings); // look for a command.
|
||||||
|
|
||||||
|
switch (clicmd) {
|
||||||
|
case CMD_READ:
|
||||||
|
// Read flash option selected
|
||||||
|
if (ttycli.eol()) {
|
||||||
|
Serial.print(F("What page number do you want to read? Page: "));
|
||||||
|
ttycli.reset();
|
||||||
|
ttycli.getLineWait();
|
||||||
|
}
|
||||||
|
pageNumber = ttycli.number();
|
||||||
|
|
||||||
|
if (pageNumber < 0 || pageNumber > NUMBER_OF_PAGES) {
|
||||||
|
Serial.print(F("\nPlease enter a valid page between 1 and "));
|
||||||
|
Serial.print(NUMBER_OF_PAGES);
|
||||||
|
Serial.println(F(".\nThe number of pages can be extended by changing NUMBER_OF_PAGES constant"));
|
||||||
|
return; // restart parsing
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
if (pageNumber > _etext && (pageNumber & (SPM_PAGESIZE-1)) == 0) {
|
||||||
|
|
||||||
|
if (pageNumber > 0)
|
||||||
|
Serial.println(pageNumber);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// READ SELECTED PAGE AND STORE THE CONTENT IN THE ramBuffer ARRAY
|
||||||
|
// flash_buffer is where the data is stored (contains the memory addresses)
|
||||||
|
// ramBuffer is where the data gets stored after reading from flash
|
||||||
|
// pageNumber is the page the data is read from
|
||||||
|
// blankChar is the character that gets printed/stored if there are unused space (default '.')
|
||||||
|
// use optiboot_readPage(flashSpace, ramBuffer, pageNumber) if you don't want blank chars
|
||||||
|
|
||||||
|
if (pageNumber == 0) // Read all pages
|
||||||
|
{
|
||||||
|
Serial.println(F("\nAll flash content:"));
|
||||||
|
for (uint16_t page = 1; page < NUMBER_OF_PAGES + 1; page++)
|
||||||
|
{
|
||||||
|
Serial.print(F("Page "));
|
||||||
|
Serial.print(page);
|
||||||
|
Serial.println(F(": "));
|
||||||
|
optiboot_readPage(flashSpace, ramBuffer, page);
|
||||||
|
for (int i = 0; i < SPM_PAGESIZE; i += 16) {
|
||||||
|
DumpHex(&ramBuffer[i], 16);
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Read selected page
|
||||||
|
{
|
||||||
|
// Print page content
|
||||||
|
Serial.print(F("\nContent of page "));
|
||||||
|
Serial.print(pageNumber);
|
||||||
|
Serial.println(F(":"));
|
||||||
|
optiboot_readPage(flashSpace, ramBuffer, pageNumber);
|
||||||
|
for (int i = 0; i < SPM_PAGESIZE; i += 16) {
|
||||||
|
DumpHex(&ramBuffer[i], 16);
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_WRITE:
|
||||||
|
// Write flash option selected
|
||||||
|
if (ttycli.eol()) {
|
||||||
|
Serial.print(F("What page number do you want to Write? Page: "));
|
||||||
|
ttycli.reset();
|
||||||
|
ttycli.getLineWait();
|
||||||
|
}
|
||||||
|
pageNumber = ttycli.number();
|
||||||
|
|
||||||
|
if (pageNumber < 1 || pageNumber > NUMBER_OF_PAGES) {
|
||||||
|
Serial.print(F("\nPlease enter a valid page between 1 and "));
|
||||||
|
Serial.print(NUMBER_OF_PAGES);
|
||||||
|
Serial.println(F(".\nThe number of pages can be extended by changing NUMBER_OF_PAGES constant"));
|
||||||
|
return; // restart parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print prompt to enter some new characters to write to flash
|
||||||
|
Serial.print(F("Please type the characters you want to store (max "));
|
||||||
|
Serial.print(SPM_PAGESIZE);
|
||||||
|
Serial.println(F(" characters)"));
|
||||||
|
Serial.print(F("End with a line containing only '"));
|
||||||
|
Serial.write(terminationChar);
|
||||||
|
Serial.println(F("' character:"));
|
||||||
|
|
||||||
|
// Get all characters from the serial monitor and store it to the ramBuffer
|
||||||
|
memset(ramBuffer, 0, sizeof(ramBuffer));
|
||||||
|
counter = 0;
|
||||||
|
do {
|
||||||
|
ttycli.reset();
|
||||||
|
ttycli.getLineWait();
|
||||||
|
if (ttycli.nextChar() == terminationChar) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char *p = ttycli.restOfLine();
|
||||||
|
byte c;
|
||||||
|
do {
|
||||||
|
c = *p++;
|
||||||
|
if (c == 0)
|
||||||
|
break;
|
||||||
|
ramBuffer[counter++] = c;
|
||||||
|
if (counter >= SPM_PAGESIZE) {
|
||||||
|
Serial.println(F("Page is full"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (1);
|
||||||
|
} while (counter < SPM_PAGESIZE);
|
||||||
|
Serial.println(F("\n\nAll chars received \nWriting to flash..."));
|
||||||
|
|
||||||
|
// WRITE RECEIVED DATA TO THE CURRENT FLASH PAGE
|
||||||
|
// flash_buffer is where the data is stored (contains the memory addresses)
|
||||||
|
// ramBuffer contains the data that's going to be stored in the flash
|
||||||
|
// pageNumber is the page the data is written to
|
||||||
|
optiboot_writePage(flashSpace, ramBuffer, pageNumber);
|
||||||
|
|
||||||
|
Serial.println(F("Writing finished.\nYou can now reset or power cycle the board and check for new contents!"));
|
||||||
|
confirm();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_HELP:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} // End of loop
|
||||||
|
|
||||||
|
void confirm()
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
Serial.println("\n--type newline to continue--");
|
||||||
|
ttycli.reset();
|
||||||
|
ttycli.getLineWait();
|
||||||
|
if (ttycli.eol()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
243
optiboot/examples/demo_flashwrite/optiboot.h
Normal file
243
optiboot/examples/demo_flashwrite/optiboot.h
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
/*------------------------ Optiboot header file ----------------------------|
|
||||||
|
| |
|
||||||
|
| June 2015 by Marek Wodzinski, https://github.com/majekw |
|
||||||
|
| Modified June 2016 by MCUdude, https://github.com/MCUdude |
|
||||||
|
| Modified Sep 2020 for mega0/xTiny (optiboot_x) |
|
||||||
|
| by Bill Westfield https://github.com/westfw |
|
||||||
|
| Modified Nov 2020 change the read() function to not change data |
|
||||||
|
| Released to public domain |
|
||||||
|
| |
|
||||||
|
| This header file gives possibility to use SPM instruction |
|
||||||
|
| from Optiboot bootloader memory. |
|
||||||
|
| |
|
||||||
|
| There are 5 convenient functions available here: |
|
||||||
|
| * optiboot_page_erase - to erase a FLASH page |
|
||||||
|
| * optiboot_page_fill - to put words into temporary buffer |
|
||||||
|
| * optiboot_page_write - to write contents of temporary buffer into FLASH |
|
||||||
|
| * optiboot_readPage - higher level function to read a flash page and |
|
||||||
|
| store it in an array |
|
||||||
|
| * optiboot_writePage - higher level function to write content to |
|
||||||
|
| a flash page |
|
||||||
|
| |
|
||||||
|
| For some hardcore users, you could use 'do_spm' as raw entry to |
|
||||||
|
| bootloader spm function. |
|
||||||
|
|-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _OPTIBOOT_H_
|
||||||
|
#define _OPTIBOOT_H_ 1
|
||||||
|
|
||||||
|
#ifdef SPMCSR
|
||||||
|
// Mega0 does not have SPMCSR
|
||||||
|
#include <avr/boot.h>
|
||||||
|
#endif
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
// figure out whether we have SPM or NVMCTRL
|
||||||
|
#ifdef FUSE_BOOTEND
|
||||||
|
#define USE_NVMCTRL 1
|
||||||
|
#define NVMCTRL_CMD_COPY_gc (NVMCTRL_CMD_gm+1) // one beyond existing commands.
|
||||||
|
#define SPM_PAGESIZE (PROGMEM_PAGE_SIZE)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main 'magic' function - enter to bootloader do_spm function
|
||||||
|
*
|
||||||
|
* address - address to write (in bytes) but must be even number
|
||||||
|
* command - one of __BOOT_PAGE_WRITE, __BOOT_PAGE_ERASE or __BOOT_PAGE_FILL
|
||||||
|
* data - data to write in __BOOT_PAGE_FILL. In __BOOT_PAGE_ERASE or
|
||||||
|
* __BOOT_PAGE_WRITE it control if boot_rww_enable is run
|
||||||
|
* (0 = run, !0 = skip running boot_rww_enable)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 'typedef' (in following line) and 'const' (few lines below)
|
||||||
|
// are a way to define external function at some arbitrary address
|
||||||
|
typedef void (*do_spm_t)(uint16_t address, uint8_t command, uint16_t data);
|
||||||
|
typedef void (*do_nvmctrl_t)(uint16_t address, uint8_t command, uint8_t data);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Devices with more than 64KB of flash:
|
||||||
|
* - have larger bootloader area (1KB) (they are BIGBOOT targets)
|
||||||
|
* - have RAMPZ register :-)
|
||||||
|
* - need larger variable to hold address (pgmspace.h uses uint32_t)
|
||||||
|
*/
|
||||||
|
#ifdef RAMPZ
|
||||||
|
typedef uint32_t optiboot_addr_t;
|
||||||
|
#else
|
||||||
|
typedef uint16_t optiboot_addr_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_NVMCTRL
|
||||||
|
// Mega0/xTiny/etc. Bootloader is in low memory.
|
||||||
|
const do_spm_t do_nvmctrl = (do_spm_t)((PROGMEM_START + 2) >> 1);
|
||||||
|
#else
|
||||||
|
#if FLASHEND > 65534
|
||||||
|
const do_spm_t do_spm = (do_spm_t)((FLASHEND - 1023 + 2) >> 1);
|
||||||
|
#else
|
||||||
|
const do_spm_t do_spm = (do_spm_t)((FLASHEND - 511 + 2) >> 1);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USE_NVMCTRL
|
||||||
|
|
||||||
|
// SPM-based functions
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same as do_spm but with disable/restore interrupts state
|
||||||
|
* required to succesfull SPM execution
|
||||||
|
*
|
||||||
|
* On devices with more than 64kB flash, 16 bit address is not enough,
|
||||||
|
* so there is also RAMPZ used in that case.
|
||||||
|
*/
|
||||||
|
void do_spm_cli(optiboot_addr_t address, uint8_t command, uint16_t data) {
|
||||||
|
uint8_t sreg_save;
|
||||||
|
|
||||||
|
sreg_save = SREG; // save old SREG value
|
||||||
|
asm volatile("cli"); // disable interrupts
|
||||||
|
#ifdef RAMPZ
|
||||||
|
RAMPZ = (address >> 16) & 0xff; // address bits 23-16 goes to RAMPZ
|
||||||
|
#ifdef EIND
|
||||||
|
uint8_t eind = EIND;
|
||||||
|
EIND = FLASHEND / 0x20000;
|
||||||
|
#endif
|
||||||
|
do_spm((address & 0xffff), command, data); // do_spm accepts only lower 16 bits of address
|
||||||
|
#ifdef EIND
|
||||||
|
EIND = eind;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
do_spm(address, command, data); // 16 bit address - no problems to pass directly
|
||||||
|
#endif
|
||||||
|
SREG = sreg_save; // restore last interrupts state
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Erase page in FLASH
|
||||||
|
void optiboot_page_erase(optiboot_addr_t address) {
|
||||||
|
do_spm_cli(address, __BOOT_PAGE_ERASE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Write word into temporary buffer
|
||||||
|
void optiboot_page_fill(optiboot_addr_t address, uint16_t data) {
|
||||||
|
do_spm_cli(address, __BOOT_PAGE_FILL, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Write temporary buffer into FLASH
|
||||||
|
void optiboot_page_write(optiboot_addr_t address) {
|
||||||
|
do_spm_cli(address, __BOOT_PAGE_WRITE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Higher level functions for reading and writing from flash
|
||||||
|
* See the examples for more info on how to use these functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Function to write data to a flash page
|
||||||
|
void optiboot_writePage(const uint8_t allocated_flash_space[],
|
||||||
|
uint8_t data_to_store[], uint16_t page)
|
||||||
|
{
|
||||||
|
uint16_t word_buffer = 0;
|
||||||
|
|
||||||
|
// Erase the flash page
|
||||||
|
optiboot_page_erase((optiboot_addr_t)(void*) &allocated_flash_space[SPM_PAGESIZE * (page - 1)]);
|
||||||
|
|
||||||
|
// Copy ram buffer to temporary flash buffer
|
||||||
|
for (uint16_t i = 0; i < SPM_PAGESIZE; i++)
|
||||||
|
{
|
||||||
|
if (i % 2 == 0) // We must write words
|
||||||
|
word_buffer = data_to_store[i];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
word_buffer += (data_to_store[i] << 8);
|
||||||
|
optiboot_page_fill((optiboot_addr_t)(void*) &allocated_flash_space[i + SPM_PAGESIZE * (page - 1)], word_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing temporary buffer to flash
|
||||||
|
optiboot_page_write((optiboot_addr_t)(void*) &allocated_flash_space[SPM_PAGESIZE * (page - 1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // Newer Mega0/xTiny chips with NVMCTRL
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same as do_nvmctrl but with disable/restore interrupts state
|
||||||
|
* required to succesfull execution
|
||||||
|
*
|
||||||
|
* Currently, there are no mega0/xTint parts with more than 64k, and when there are
|
||||||
|
* they'll need extra effort beyond just RAMPZ :-(
|
||||||
|
*/
|
||||||
|
void do_nvmctrl_cli(optiboot_addr_t address, uint8_t command, uint16_t data)
|
||||||
|
{
|
||||||
|
uint8_t sreg_save;
|
||||||
|
|
||||||
|
sreg_save = SREG; // save old SREG value
|
||||||
|
asm volatile("cli"); // disable interrupts
|
||||||
|
do_nvmctrl(address, command, data); // 16 bit address - no problems to pass directly
|
||||||
|
SREG = sreg_save; // restore last interrupts state
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Erase page in FLASH
|
||||||
|
void optiboot_page_erase(optiboot_addr_t address)
|
||||||
|
{
|
||||||
|
// set page by writing to address.
|
||||||
|
do_nvmctrl(address + MAPPED_PROGMEM_START, NVMCTRL_CMD_COPY_gc, 0xFF);
|
||||||
|
do_nvmctrl_cli(0, NVMCTRL_CMD_PAGEERASE_gc, 0); // do actual erase
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Write word into temporary buffer
|
||||||
|
void optiboot_page_fill(optiboot_addr_t address, uint16_t data)
|
||||||
|
{
|
||||||
|
do_nvmctrl(address + MAPPED_PROGMEM_START, NVMCTRL_CMD_COPY_gc, data & 0xFF);
|
||||||
|
do_nvmctrl(address + MAPPED_PROGMEM_START, NVMCTRL_CMD_COPY_gc, data >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Write temporary buffer into FLASH
|
||||||
|
void optiboot_page_write(optiboot_addr_t address)
|
||||||
|
{
|
||||||
|
do_nvmctrl_cli(address, NVMCTRL_CMD_PAGEWRITE_gc, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Higher level functions for reading and writing from flash
|
||||||
|
* See the examples for more info on how to use these functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Function to write data to a flash page
|
||||||
|
void optiboot_writePage(const uint8_t allocated_flash_space[],
|
||||||
|
uint8_t data_to_store[], uint16_t page)
|
||||||
|
{
|
||||||
|
const uint8_t *adjusted_address;
|
||||||
|
// Copy ram buffer to temporary flash buffer
|
||||||
|
for (uint16_t i = 0; i < SPM_PAGESIZE; i++)
|
||||||
|
{
|
||||||
|
adjusted_address = &allocated_flash_space[i + SPM_PAGESIZE * (page - 1)];
|
||||||
|
adjusted_address += MAPPED_PROGMEM_START;
|
||||||
|
do_nvmctrl((optiboot_addr_t)(void*) adjusted_address,
|
||||||
|
NVMCTRL_CMD_COPY_gc, data_to_store[i]);
|
||||||
|
}
|
||||||
|
do_nvmctrl_cli(0, NVMCTRL_CMD_PAGEERASEWRITE_gc, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_NVMTRL
|
||||||
|
|
||||||
|
// Function to read a flash page and store it in an array (storage_array[])
|
||||||
|
// (these can be shared between old and new AVRs.)
|
||||||
|
void optiboot_readPage(const uint8_t allocated_flash_space[],
|
||||||
|
uint8_t storage_array[], uint16_t page)
|
||||||
|
{
|
||||||
|
uint8_t read_character;
|
||||||
|
for (uint16_t j = 0; j < SPM_PAGESIZE; j++)
|
||||||
|
{
|
||||||
|
read_character = pgm_read_byte(&allocated_flash_space[j + SPM_PAGESIZE * (page - 1)]);
|
||||||
|
storage_array[j] = read_character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* _OPTIBOOT_H_ */
|
279
optiboot/examples/demo_flashwrite/simpleParser.cpp
Normal file
279
optiboot/examples/demo_flashwrite/simpleParser.cpp
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
/*
|
||||||
|
* simpleParser
|
||||||
|
* Implement a command-line parser.
|
||||||
|
* Written 2014 by Bill Westfield (WestfW)
|
||||||
|
* refactored 2020
|
||||||
|
* Released to the public domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "simpleParser.h"
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
//#define DEBUG 1
|
||||||
|
|
||||||
|
#if defined(DEBUG) && DEBUG
|
||||||
|
//extern char *spbuffer;
|
||||||
|
char spbuffer[100];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset the line buffer
|
||||||
|
*/
|
||||||
|
void parserCore::reset ()
|
||||||
|
{
|
||||||
|
memset(buffer, 0, lineLen);
|
||||||
|
inptr = 0;
|
||||||
|
parsePtr = 0;
|
||||||
|
termchar = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getLine
|
||||||
|
* Read a line of text from Serial into the internal line buffer.
|
||||||
|
* With echoing and editing!
|
||||||
|
* Non-blocking. Returns 0 until end-of-line seen.
|
||||||
|
*/
|
||||||
|
uint8_t parserCore::getLine ()
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
|
||||||
|
c = S->read();
|
||||||
|
switch (c) {
|
||||||
|
case 127:
|
||||||
|
case CTRL('H'):
|
||||||
|
/*
|
||||||
|
Destructive backspace: remove last character
|
||||||
|
*/
|
||||||
|
if (inptr > 0) {
|
||||||
|
S->print("\010 \010");
|
||||||
|
buffer[--inptr] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CTRL('R'):
|
||||||
|
/*
|
||||||
|
Ctrl-R retypes the line
|
||||||
|
*/
|
||||||
|
S->print("\r\n");
|
||||||
|
S->print(buffer);
|
||||||
|
break;
|
||||||
|
case CTRL('U'):
|
||||||
|
/*
|
||||||
|
Ctrl-U deletes the entire line and starts over.
|
||||||
|
*/
|
||||||
|
S->println("XXX");
|
||||||
|
reset();
|
||||||
|
break;
|
||||||
|
case CTRL('J'):
|
||||||
|
case CTRL('M'):
|
||||||
|
buffer[inptr++] = '\n';
|
||||||
|
S->println(); /* Echo newline too. */
|
||||||
|
return inptr;
|
||||||
|
case -1:
|
||||||
|
/*
|
||||||
|
No character present; don't do anything.
|
||||||
|
*/
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
Otherwise, echo the character and put it into the buffer
|
||||||
|
*/
|
||||||
|
buffer[inptr++] = c;
|
||||||
|
S->write(c);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getLineWait
|
||||||
|
* like getLine, but block until a complete line is read
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t parserCore::getLineWait (void)
|
||||||
|
{
|
||||||
|
uint8_t status;
|
||||||
|
|
||||||
|
do {
|
||||||
|
status = getLine();
|
||||||
|
} while (status == 0);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool parserCore::IsWhitespace (char c)
|
||||||
|
{
|
||||||
|
return (c == ' ' || c == CTRL('I'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool parserCore::delim(char c)
|
||||||
|
{
|
||||||
|
static const char Delimiters[] PROGMEM = "\r\n ,;:=\t";
|
||||||
|
if (c == 0 || strchr_P(Delimiters, c))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number
|
||||||
|
* Advance the token and parse a number. Accept decimal, hex, octal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int parserCore::number()
|
||||||
|
{
|
||||||
|
char *p = token();
|
||||||
|
if (p) {
|
||||||
|
return strtol(p, 0, 0);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* eol
|
||||||
|
* return true if we're at the end of the line.
|
||||||
|
*/
|
||||||
|
boolean parserCore::eol ()
|
||||||
|
{
|
||||||
|
while (IsWhitespace(buffer[parsePtr])) { /* skip leading whitespace */
|
||||||
|
parsePtr++;
|
||||||
|
}
|
||||||
|
return buffer[parsePtr] == '\n' || buffer[parsePtr] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cliTermChar
|
||||||
|
* return the termination character of the last token
|
||||||
|
*/
|
||||||
|
uint8_t parserCore::termChar ()
|
||||||
|
{
|
||||||
|
return termchar;
|
||||||
|
}
|
||||||
|
|
||||||
|
char parserCore::nextChar () {
|
||||||
|
return buffer[parsePtr];
|
||||||
|
}
|
||||||
|
|
||||||
|
char *parserCore::restOfLine() {
|
||||||
|
return &buffer[parsePtr];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cliCharacter
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* token
|
||||||
|
* A token is a set of non-delimiter characters ending at a delimiter.
|
||||||
|
* As a line is parsed from the internal buffer, parsePtr is advanced, and
|
||||||
|
* the delimiters of parsed tokens are replaced with nulls.
|
||||||
|
* Note that a line always ends with the newline character AND a null.
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *parserCore::token ()
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
if (eol()) { // reached the end of the line?
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
i = parsePtr; // save start position of token
|
||||||
|
while ((!delim(buffer[parsePtr])) && (parsePtr < lineLen)) {
|
||||||
|
parsePtr++; // advance pointer till we hit a delimiter.
|
||||||
|
}
|
||||||
|
termchar = buffer[parsePtr];
|
||||||
|
buffer[parsePtr++] = 0; // replace the delimiter with null
|
||||||
|
return &buffer[i]; // convert position to pointer for retval
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Match the next token with a list of keywords.
|
||||||
|
* The list of keywords is in PROGMEM, separated by spaces.
|
||||||
|
* returns either the position of the found keyword (0..n),
|
||||||
|
* PARSER_NOMATCH, PARSER_AMB, or PARSER_EOL at the end of line
|
||||||
|
*/
|
||||||
|
int8_t parserCore::keyword (const char *keys)
|
||||||
|
{
|
||||||
|
char *p = token();
|
||||||
|
char *thisKey = (char *)keys;
|
||||||
|
int8_t i = 0, match, first = PARSER_NOMATCH;
|
||||||
|
if (!p) {
|
||||||
|
#if defined(DEBUG) && DEBUG
|
||||||
|
S->println("Early EOL");
|
||||||
|
#endif
|
||||||
|
return PARSER_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pgm_read_byte(thisKey)) {
|
||||||
|
match = tokcasecmp(p, thisKey);
|
||||||
|
#if defined(DEBUG) && DEBUG
|
||||||
|
sprintf(spbuffer, "key='%S', p='%s', match = %d\n", thisKey, p, match);
|
||||||
|
S->print(spbuffer);
|
||||||
|
#endif
|
||||||
|
if (match == CMP_MATCH) {
|
||||||
|
#if defined(DEBUG) && DEBUG
|
||||||
|
sprintf(spbuffer, "Exact match %d\n", i);
|
||||||
|
S->print(spbuffer);
|
||||||
|
#endif
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
byte c;
|
||||||
|
do {
|
||||||
|
c = pgm_read_byte(thisKey);
|
||||||
|
if (c == 0)
|
||||||
|
break;
|
||||||
|
thisKey++; // advance to next keyword, but not past end!
|
||||||
|
} while (c > ' ');
|
||||||
|
|
||||||
|
if (match == CMP_PARTIALMATCH) {
|
||||||
|
// There was a partial match; check for another...
|
||||||
|
if (first != PARSER_NOMATCH) { // already another match?
|
||||||
|
return (PARSER_AMB);
|
||||||
|
} else {
|
||||||
|
first = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#if defined(DEBUG) && DEBUG
|
||||||
|
sprintf(spbuffer, "Match %d\n", i);
|
||||||
|
S->print(spbuffer);
|
||||||
|
#endif
|
||||||
|
return i; // match
|
||||||
|
}
|
||||||
|
i++; // next keyword
|
||||||
|
}
|
||||||
|
#if defined(DEBUG) && DEBUG
|
||||||
|
sprintf(spbuffer, "Partial match %d\n", first);
|
||||||
|
S->print(spbuffer);
|
||||||
|
#endif
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tokcasecmp
|
||||||
|
* tokcasecmp is like strcasecmp_P, except that the strings are terminated
|
||||||
|
* by any char < 32. Return value is 0 for match, or pointer to the delimiter
|
||||||
|
* for non-match (to expedite comparing against strings of targets.)
|
||||||
|
*/
|
||||||
|
uint8_t parserCore::tokcasecmp(const char *tok, const char *target)
|
||||||
|
{
|
||||||
|
char tokc, keyc;
|
||||||
|
const char *t = (char *)target;
|
||||||
|
|
||||||
|
do {
|
||||||
|
tokc = toupper(*tok++);
|
||||||
|
keyc = toupper(pgm_read_byte(t++));
|
||||||
|
// tok++; t++;
|
||||||
|
if (tokc == 0) {
|
||||||
|
// End of token; see if end of keyword as well
|
||||||
|
if (keyc <= ' ') {
|
||||||
|
return CMP_MATCH; // both ended - exact match
|
||||||
|
}
|
||||||
|
return CMP_PARTIALMATCH; // keyword is longer - partial
|
||||||
|
}
|
||||||
|
// Not end of token
|
||||||
|
if (keyc <= ' ') {
|
||||||
|
return CMP_NONMATCH; // key ended before tok - non match
|
||||||
|
}
|
||||||
|
} while (tokc == keyc);
|
||||||
|
return CMP_NONMATCH; // neither string ended, but non-match
|
||||||
|
}
|
64
optiboot/examples/demo_flashwrite/simpleParser.h
Normal file
64
optiboot/examples/demo_flashwrite/simpleParser.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Written 2014 by Bill Westfield (WestfW)
|
||||||
|
* Refactored 2020
|
||||||
|
* Released to the public domain.
|
||||||
|
*/
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
|
||||||
|
class parserCore {
|
||||||
|
private:
|
||||||
|
char *buffer;
|
||||||
|
char *lastToken;
|
||||||
|
byte lineLen;
|
||||||
|
Stream *S;
|
||||||
|
|
||||||
|
byte inptr; /* read character into here */
|
||||||
|
byte parsePtr;
|
||||||
|
byte termchar;
|
||||||
|
// Internal functions.
|
||||||
|
bool IsWhitespace(char c);
|
||||||
|
bool delim(char c);
|
||||||
|
char *token(void);
|
||||||
|
// int8_t KeywordPM (const char *keys);
|
||||||
|
uint8_t tokcasecmp(const char *tok, const char * target);
|
||||||
|
|
||||||
|
public:
|
||||||
|
parserCore(char *buf, byte buflen, Stream &io) {
|
||||||
|
buffer = buf;
|
||||||
|
lineLen = buflen;
|
||||||
|
S = &io;
|
||||||
|
}
|
||||||
|
uint8_t getLine(void); /* Non-blocking read line w/editing*/
|
||||||
|
uint8_t getLineWait(void); /* wait for a full line of input */
|
||||||
|
void reset(void); /* reset the parser */
|
||||||
|
int number(); /* parse a number */
|
||||||
|
int lastNumber();
|
||||||
|
boolean eol(); /* check for EOL */
|
||||||
|
char nextChar();
|
||||||
|
char *restOfLine();
|
||||||
|
uint8_t termChar(); /* return the terminating char of last token */
|
||||||
|
int8_t keyword(const char *keys); /* keyword with partial matching */
|
||||||
|
// int8_t keywordExact(const char *keys); /* keyword exact match */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
#define PARSER_NOMATCH -1
|
||||||
|
#define PARSER_EOL -2
|
||||||
|
#define PARSER_AMB -3
|
||||||
|
|
||||||
|
#define CMP_PARTIALMATCH 2
|
||||||
|
#define CMP_NONMATCH 1
|
||||||
|
#define CMP_MATCH 0
|
||||||
|
|
||||||
|
#define CTRL(x) (x-64)
|
||||||
|
|
||||||
|
template <size_t maxline=80>
|
||||||
|
class simpleParser: public parserCore {
|
||||||
|
private:
|
||||||
|
char buf[maxline];
|
||||||
|
public:
|
||||||
|
simpleParser(Stream &io) : parserCore(buf, sizeof(buf), io) {}
|
||||||
|
};
|
31
optiboot/examples/demo_flashwrite/supportfuncs.ino
Normal file
31
optiboot/examples/demo_flashwrite/supportfuncs.ino
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Dump a byte as two hex characters.
|
||||||
|
void hexout(uint8_t b)
|
||||||
|
{
|
||||||
|
uint8_t high, low;
|
||||||
|
|
||||||
|
high = b >> 4;
|
||||||
|
low = b & 0xF;
|
||||||
|
if (high > 9) {
|
||||||
|
high += ('A'-10) - '0';
|
||||||
|
}
|
||||||
|
Serial.write(high + '0');
|
||||||
|
if (low > 9) {
|
||||||
|
low += ('A'-10) - '0';
|
||||||
|
}
|
||||||
|
Serial.write(low + '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpHex(uint8_t *p, uint8_t len) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
Serial.write(' ');
|
||||||
|
hexout(p[i]);
|
||||||
|
}
|
||||||
|
Serial.print(" ");
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (p[i] < ' ') {
|
||||||
|
Serial.write('.');
|
||||||
|
} else {
|
||||||
|
Serial.write(p[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user