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