From 2c558a3f7d86dd75754541f15f2ec6e44f628746 Mon Sep 17 00:00:00 2001 From: WestfW Date: Thu, 5 Nov 2020 00:20:24 -0800 Subject: [PATCH] Better (?) example of flash write from an Arduino sketch --- .../demo_flashwrite/demo_flashwrite.ino | 241 +++++++++++++++ optiboot/examples/demo_flashwrite/optiboot.h | 243 +++++++++++++++ .../examples/demo_flashwrite/simpleParser.cpp | 279 ++++++++++++++++++ .../examples/demo_flashwrite/simpleParser.h | 64 ++++ .../examples/demo_flashwrite/supportfuncs.ino | 31 ++ 5 files changed, 858 insertions(+) create mode 100644 optiboot/examples/demo_flashwrite/demo_flashwrite.ino create mode 100644 optiboot/examples/demo_flashwrite/optiboot.h create mode 100644 optiboot/examples/demo_flashwrite/simpleParser.cpp create mode 100644 optiboot/examples/demo_flashwrite/simpleParser.h create mode 100644 optiboot/examples/demo_flashwrite/supportfuncs.ino diff --git a/optiboot/examples/demo_flashwrite/demo_flashwrite.ino b/optiboot/examples/demo_flashwrite/demo_flashwrite.ino new file mode 100644 index 0000000..6f22e1e --- /dev/null +++ b/optiboot/examples/demo_flashwrite/demo_flashwrite.ino @@ -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; + } + } +} diff --git a/optiboot/examples/demo_flashwrite/optiboot.h b/optiboot/examples/demo_flashwrite/optiboot.h new file mode 100644 index 0000000..ca76a71 --- /dev/null +++ b/optiboot/examples/demo_flashwrite/optiboot.h @@ -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 +#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_ */ diff --git a/optiboot/examples/demo_flashwrite/simpleParser.cpp b/optiboot/examples/demo_flashwrite/simpleParser.cpp new file mode 100644 index 0000000..350ffb2 --- /dev/null +++ b/optiboot/examples/demo_flashwrite/simpleParser.cpp @@ -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 + +//#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 +} diff --git a/optiboot/examples/demo_flashwrite/simpleParser.h b/optiboot/examples/demo_flashwrite/simpleParser.h new file mode 100644 index 0000000..c136704 --- /dev/null +++ b/optiboot/examples/demo_flashwrite/simpleParser.h @@ -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 +class simpleParser: public parserCore { +private: + char buf[maxline]; +public: + simpleParser(Stream &io) : parserCore(buf, sizeof(buf), io) {} +}; diff --git a/optiboot/examples/demo_flashwrite/supportfuncs.ino b/optiboot/examples/demo_flashwrite/supportfuncs.ino new file mode 100644 index 0000000..5d12eb6 --- /dev/null +++ b/optiboot/examples/demo_flashwrite/supportfuncs.ino @@ -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]); + } + } +}