pinballatmel/CommandLine.h

592 lines
23 KiB
C

/*****************************************************************************
2023-01-18 Tim Gopaul add command BusyFaultCount to report how many Busy interrupts were generated since last reboot
2023-01-11/12 Tim Gopaul Removed getHexLineFromSerialPort.. use the main parsing code get command
2023-01-11 Tim Gopaul added call to make commands case insensitive.
2023-01-08 Tim Gopaul - add load command to read in Hex format string and place in RAM memory
2023-01-07 Tim Gopaul - Intel Hex format saveMemory to console
- have serial run at 1Mhz
2022-12-30 Tim Gopaul changed the readNumber to read inputs in base 16
return atoi(numTextPtr); //K&R string.h pg. 251
return int(strtol(numTextPtr, NULL, 0)); //https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int
//strlol string to long will accept 0x for Hex, leading zero for octal, 0b for binary
// strtol returns a long integer so shorten to int ..or byte.
How to Use CommandLine:
Create a sketch. Look below for a sample setup and main loop code and copy and paste it in into the new sketch.
Create a new tab. (Use the drop down menu (little triangle) on the far right of the Arduino Editor.
Name the tab CommandLine.h
Paste this file into it.
Test:
Download the sketch you just created to your Arduino as usual and open the Serial Window. Type these commands followed by return:
add 5, 10
subtract 10, 5
Look at the add and subtract commands included and then write your own!
*****************************************************************************
Here's what's going on under the covers
*****************************************************************************
Simple and Clear Command Line Interpreter
This file will allow you to type commands into the Serial Window like,
add 23,599
blink 5
playSong Yesterday
to your sketch running on the Arduino and execute them.
Implementation note: This will use C strings as opposed to String Objects based on the assumption that if you need a commandLine interpreter,
you are probably short on space too and the String object tends to be space inefficient.
1) Simple Protocol
Commands are words and numbers either space or comma spearated
The first word is the command, each additional word is an argument
"\n" terminates each command
2) Using the C library routine strtok:
A command is a word separated by spaces or commas. A word separated by certain characters (like space or comma) is called a token.
To get tokens one by one, I use the C lib routing strtok (part of C stdlib.h see below how to include it).
It's part of C language library <string.h> which you can look up online. Basically you:
1) pass it a string (and the delimeters you use, i.e. space and comman) and it will return the first token from the string
2) on subsequent calls, pass it NULL (instead of the string ptr) and it will continue where it left off with the initial string.
I've written a couple of basic helper routines:
readNumber: uses strtok and atoi (atoi: ascii to int, again part of C stdlib.h) to return an integer.
Note that atoi returns an int and if you are using 1 byte ints like uint8_t you'll have to get the lowByte().
readWord: returns a ptr to a text word
2022-12-28 Tim Gopaul - return atoi(numTextPtr);//K&R string.h pg. 251, ia replaced with strtol string to long that allows selection of base 16
- return int(strtol(numTextPtr, NULL, 16));
4) DoMyCommand: A list of if-then-elses for each command. You could make this a case statement if all commands were a single char.
Using a word is more readable.
For the purposes of this example we have:
Add
Subtract
nullCommand
2022-10-18 added commands
read
write
dump
dumpBuffer
fill
*/
/******************sample main loop code ************************************
#include "CommandLine.h"
void
setup() {
Serial.begin(115200);
}
void
loop() {
bool received = getCommandLineFromSerialPort(CommandLine); //global CommandLine is defined in CommandLine.h
if (received) DoMyCommand(CommandLine);
}
**********************************************************************************/
//Name this tab: CommandLine.h
#include <string.h>
#include <stdlib.h>
#include <errno.h> // https://stackoverflow.com/questions/26080829/detecting-strtol-failure when strtol fails it returns zero and an errno
#include <limits.h> // used to find LONG_MIN and LONG_MAX
extern volatile unsigned int BusyFaultCount;
extern volatile unsigned int ShadowFaultCount;
//Function Prototypes from .ino file
void writeAddress(unsigned int address, byte dataByte);
byte readAddress(unsigned int address);
void fillRange(unsigned int addrStart, unsigned int addrCount, byte dataByte);
void fillRandomRange(unsigned int addrStart, unsigned int addrCount);
void dumpRange(unsigned int addrStart, unsigned int addrCount);
void gameDumpRange(unsigned int addrStart, unsigned int addrCount);
void dumpBuffRange(unsigned int addrStart, unsigned int addrCount);
void saveMemory(unsigned int addrStart, unsigned int addrCount);
void gameSaveMemory(unsigned int addrStart, unsigned int addrCount);
void testMemory(unsigned int addrStart, unsigned int addrCount);
void loadMemory();
void gameLoadMemory();
int helpText();
void testMemory(unsigned int addrStart, unsigned int addrCount, int testLoops);
void gameWriteAddress(unsigned int address, byte dataByte);
byte gameReadAddress(unsigned int address);
void gameDumpRange(unsigned int addrStart, unsigned int addrCount);
//this following macro is good for debugging, e.g. print2("myVar= ", myVar);
#define print2(x,y) (Serial.print(x), Serial.println(y))
#define CR '\r'
#define LF '\n'
#define BS '\b'
#define NULLCHAR '\0'
#define SPACE ' '
#define ESC 'Q'
#define COMMAND_BUFFER_LENGTH 60 //length of Serial buffer for incoming commands
char CommandLine[COMMAND_BUFFER_LENGTH + 1]; //Read commands into this buffer from Serial. +1 in length for a termination char
const char *delimiters = ", \n"; //commands can be separated by return, space or comma
/*************************************************************************************************************
your Command Names Here
*/
const char *addCommandToken = "add"; //Modify here
const char *subtractCommandToken = "sub"; //Modify here
const char *readCommandToken = "read"; // read address ignore
const char *writeCommandToken = "write"; // write address byte
const char *dumpCommandToken = "dump"; // Dumps memory from starting address with byte count
const char *dumpBuffCommandToken = "dumpbuffer"; // Dumps memory held in the buffer
const char *fillCommandToken = "fill"; // Fills the RAM starting at address with byte
const char *fillRandomCommandToken = "fillrandom"; // Fills with random byte the RAM starting at address with byte
const char *saveMemoryCommandToken = "save"; // creates Intel Hex output from ram range.
const char *helpCommandToken = "help";
const char *loadMemoryCommandToken = "load"; // takes an Intel Hex formatted line and writes it to RAM
const char *testMemoryCommandToken = "testmemory"; // destructive test read from memory rotate bits and write then compare
const char *gameReadCommandToken = "gameread"; // read address ignore
const char *gameWriteCommandToken = "gamewrite"; // write address byte
const char *gameDumpCommandToken = "gameDump"; // Dumps game memory from starting address with byte count
const char *gameSaveMemoryCommandToken = "gamesave"; // creates Intel Hex output from ram range.
const char *gameLoadMemoryCommandToken = "gameload"; // takes an Intel Hex formatted line and writes it to RAM
const char *BusyFaultCountToken = "busyfaultcount"; // displays the accumulative count of the busy interrupts
const char *ShadowFaultCountToken = "shadowfaultcount"; // displays the accumulative count of the busy interrupts
/*************************************************************************************************************
getCommandLineFromSerialPort()
Return the string of the next command. Commands are delimited by return"
Handle BackSpace character
Make all chars lowercase
*************************************************************************************************************/
bool
getCommandLineFromSerialPort(char * commandLine)
{
static uint8_t charsRead = 0; //note: COMAND_BUFFER_LENGTH must be less than 255 chars long
//read asynchronously until full command input
while (Serial.available()) {
char c = Serial.read();
switch (c) {
case CR: //likely have full command in buffer now, commands are terminated by CR and/or LF
case LF:
commandLine[charsRead] = NULLCHAR; //null terminate our command char array
if (charsRead > 0) {
charsRead = 0; //charsRead is static, so have to reset
// Serial.println(commandLine);
return true;
}
break;
case BS: // handle backspace in input: put a space in last char
if (charsRead > 0) { //and adjust commandLine and charsRead
commandLine[--charsRead] = NULLCHAR;
Serial.print(" \b");
}
break;
case ESC: // ESC escape should clear the command line and start over without execiting commad
if (charsRead > 0) {
charsRead = 0;
commandLine[charsRead] = NULLCHAR;
Serial.print(" ESC\n");
inputMode = CommandMode;
while(Serial.available() > 0) Serial.read(); //eat what's left comming in.
}
break;
default:
//c = tolower(c); //switches all characters to lower case.( not needed switched to strcasecmp()
if (charsRead < COMMAND_BUFFER_LENGTH) { //if the buffer is not full add the c charcter read
commandLine[charsRead++] = c; //add the character and increment the buffer count charsRead
}
commandLine[charsRead] = NULLCHAR; //the buffer has a NULLCHAR waiting to be overwritten.
break;
}
}
return false;
}
/* ****************************
readNumber: return a 16bit (for Arduino Uno) signed integer from the command line
readWord: get a text word from the command line
*/
int readNumber () {
char *numTextPtr = strtok(NULL, delimiters); //K&R string.h pg. 250 Continue parsing the first call of strtok
char *endptr = NULL;
long int number = 0;
// reset errno to 0 before call
errno = 0;
// call to strtol assigning return to number, strtol returns a long integer so shorten to int ..or byte.
number = strtol(numTextPtr, &endptr, 0); //strlol string to long will accept 0x for Hex, leading zero for octal, 0b for binary
// {
// Serial.printf("errno: %d\n", errno);
//
// /* test return to number and errno values */
// if (numTextPtr == endptr)
// Serial.printf (" number : %lu invalid (no digits found, 0 returned)\n", number);
// else if (errno == ERANGE && number == LONG_MIN)
// Serial.printf (" number : %lu invalid (underflow occurred)\n", number);
// else if (errno == ERANGE && number == LONG_MAX)
// Serial.printf (" number : %lu invalid (overflow occurred)\n", number);
// else if (errno == EINVAL) /* not in all c99 implementations - gcc OK */
// printf (" number : %lu invalid (base contains unsupported value)\n", number);
// else if (errno != 0 && number == 0)
// Serial.printf (" number : %lu invalid (unspecified error occurred)\n", number);
// else if (errno == 0 && numTextPtr && !*endptr)
// Serial.printf (" number : %lu valid (and represents all characters read)\n", number);
// else if (errno == 0 && numTextPtr && *endptr != 0)
// Serial.printf (" number : %lu valid (but additional characters remain)\n", number);
// }
return int(number);
}
// ***** readWord *****
char * readWord() {
char * word = strtok(NULL, delimiters); //K&R string.h pg. 250
return word;
}
void
nullCommand(char * ptrToCommandName) {
print2("Command not found: ", ptrToCommandName); //see above for macro print2
}
/****************************************************
Add your commands here
*/
int addCommand() { //Modify here
int firstOperand = readNumber();
int secondOperand = readNumber();
return firstOperand + secondOperand;
}
int subtractCommand() { //Modify here
int firstOperand = readNumber();
int secondOperand = readNumber();
return firstOperand - secondOperand;
}
int readCommand() { //read a byte from RAM
int address = readNumber();
byte dataByte = readAddress(address);
Serial.printf("0x%04X: 0x%02X\n", address, dataByte);
return dataByte; //return the byte but printing is done here
}
int writeCommand() { //write a byte to RAM
int address = readNumber();
int dataByte = readNumber();
//read before writing
Serial.printf("0x%04X: 0x%02X\n", address, readAddress(address));
writeAddress(address, dataByte);
Serial.printf("0x%04X: 0x%02X\n", address, dataByte);
return dataByte; //return the byte but printing is done here
}
int dumpCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
dumpRange(addrStart, addrCount);
return addrStart;
}
int dumpBuffCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
dumpBuffRange(addrStart, addrCount);
return addrStart;
}
int fillCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
byte dataByte = readNumber();
fillRange(addrStart, addrCount, dataByte);
return dataByte;
}
int fillRandomCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
fillRandomRange(addrStart, addrCount); //dataByte is recreated for each address of range
return 0;
}
// ***** saveMemoryCommand *****
int saveMemoryCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
saveMemory(addrStart, addrCount);
return 0;
}
// ***** loadMemoryCommand *****
int loadMemoryCommand() {
loadMemory();
return 0;
}
// ***** gameSaveMemoryCommand *****
int gameSaveMemoryCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
gameSaveMemory(addrStart, addrCount);
return 0;
}
// ***** gameLoadMemoryCommand *****
int gameLoadMemoryCommand() {
gameLoadMemory();
return 0;
}
// ***** Help Text *****
int helpCommand() {
helpText();
return 0;
}
int gameReadCommand() { //read a byte from RAM
int address = readNumber();
byte dataByte = gameReadAddress(address);
Serial.printf("0x%04X: 0x%02X\n", address, dataByte);
return dataByte; //return the byte but printing is done here
}
int gameWriteCommand() { //write a byte to RAM
int address = readNumber();
int dataByte = readNumber();
//read before writing
Serial.printf("0x%04X: 0x%02X\n", address, readAddress(address));
gameWriteAddress(address, dataByte);
Serial.printf("0x%04X: 0x%02X\n", address, dataByte);
return dataByte; //return the byte but printing is done here
}
int gameDumpCommand() {
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
gameDumpRange(addrStart, addrCount);
return addrStart;
}
void busyFaultCountCommand(){
Serial.printf("> Cumlative fault count since last Atmega1284 reboot: %d\n", BusyFaultCount );
}
void shadowFaultCountCommand(){
Serial.printf("> Cumlative fault count since last Atmega1284 reboot: %d\n", ShadowFaultCount );
}
// ***** testMemmory *****
int testMemoryCommand(){
unsigned int addrStart = readNumber();
unsigned int addrCount = readNumber();
unsigned int testLoops = readNumber();
Serial.println(addrCount);
testMemory(addrStart, addrCount, testLoops);
return 0;
}
/****************************************************
DoMyHexLine
*/
bool
DoMyHexLine(char * HexLine, int writeMode) {
// char * endOfFile = ":00000001FF"; //For Intel Hex file transfer a the final line must match.. to switch to command inputMode
Serial.printf("%s\n", HexLine);
int HexLineLength = strlen(HexLine);
// Serial.printf("> HexLineLength: 0x%02X\n", HexLineLength);
if (HexLineLength < 11){
Serial.println("> HexLine minimum valid line length is 11 characters");
}
if (HexLine[0]!=':') {
Serial.println("> HexLine must start with : character");
}
if (HexLineLength % 2 == 0) {
Serial.println("> HexLine must be odd when including colon start character");
}
#define HEXLINEBYTESSIZE MAXHEXLINE *2 + 5
byte HexLineBytes[HEXLINEBYTESSIZE]; //Buffer will hold the hex values for input record
int HLBIndex = 0;
int checkSum = 0;
for (int i= 1; i< HexLineLength; i+=2){
char inByteText[3] = { HexLine[i], HexLine[i+1] , NULLCHAR };
byte inByte = (byte)strtol(inByteText,NULL,16);
HexLineBytes[HLBIndex++] = inByte; //Fill the HexLineBytes buffer with Byte values of HEX input
// Serial.printf( "%s 0x%02X ", inByteText, inByte);
checkSum += inByte;
checkSum &= 0xFF;
// Serial.printf( " 0x%02X 0x%02X 0x%02X \n", HexLine[i], HexLine[i+1], checkSum);
}
if (checkSum != 0 ) Serial.println("> Bad CheckSum");
//Write the bytes to RAM
//HexLineBytes holds all the byte values from the input HEX ASCII pull off addrCount and addrStart
//And save them to the dual port RAM afer checks
//HEX record format :llaaaatt[dd...]cc
unsigned int hexCount = HexLineBytes[0]; //if there is junk in these buffers the checkSum will prevent use
unsigned int addrStart = ((HexLineBytes[1]<<8) + HexLineBytes[2]);
int recordType = HexLineBytes[3]; //Record Type 00 for data 01 for end of file
// HLBIndex =4; //Data starts at index 4 in HexLineBytes
if ((checkSum == 0) && (HexLineBytes[0] > 0) &&
(HexLineBytes[0] < HEXLINEBYTESSIZE) && (recordType == 0)){
int address = addrStart;
HLBIndex =4; //Data starts at index 4 in HexLineBytes
for(unsigned int i = 0; i < hexCount; i++){
switch(writeMode){
case DataMode:
writeAddress( address++, HexLineBytes[HLBIndex++]);
break;
case gameDataMode:
gameWriteAddress( address++, HexLineBytes[HLBIndex++]);
break;
default:
break;
}
}
}
Serial.printf("\n> HexLine %s\n", HexLine);
if (strcasecmp(HexLine, ":00000001FF") == 0){ //use strcasecmp for case insensitive compare
inputMode = CommandMode;
Serial.println("> Enter Command");
}
else{
Serial.println("> Send next Hex record. To terminate: :00000001FF");
}
return(true);
}
/****************************************************
DoMyCommand
*/
bool
DoMyCommand(char * commandLine) {
// print2("\nCommand: ", commandLine);
int result;
char * ptrToCommandName = strtok(commandLine, delimiters); //on first call to strtok pass it a pointer to a string, on subsequent calls it NULL to continue parsing
// print2("commandName= ", ptrToCommandName);
if (strcasecmp(ptrToCommandName, addCommandToken) == 0) { //Modify here
result = addCommand();
Serial.printf("> The sum is = %d 0x%04X\n", result, result);
}
else if (strcasecmp(ptrToCommandName, subtractCommandToken) == 0) { //Modify here
result = subtractCommand();
Serial.printf("> The difference is = %d 0x%04X\n", result, result);
}
else if (strcasecmp(ptrToCommandName, readCommandToken) == 0) { //Modify here
result = readCommand();
}
else if (strcasecmp(ptrToCommandName, writeCommandToken) == 0) { //Modify here
result = writeCommand();
}
else if (strcasecmp(ptrToCommandName, dumpCommandToken) == 0) { //Modify here
result = dumpCommand();
// Serial.println();
}
else if (strcasecmp(ptrToCommandName, gameReadCommandToken) == 0) { //Modify here
result = gameReadCommand();
}
else if (strcasecmp(ptrToCommandName, gameWriteCommandToken) == 0) { //Modify here
result = gameWriteCommand();
}
else if (strcasecmp(ptrToCommandName, gameDumpCommandToken) == 0) { //Modify here
result = gameDumpCommand();
// Serial.println();
}
else if (strcasecmp(ptrToCommandName, dumpBuffCommandToken) == 0) { //Modify here
result = dumpBuffCommand();
Serial.println();
}
else if (strcasecmp(ptrToCommandName, fillCommandToken) == 0) { //Modify here
result = fillCommand();
Serial.println();
}
else if (strcasecmp(ptrToCommandName, fillRandomCommandToken) == 0) { //Modify here
result = fillRandomCommand();
Serial.println();
}
else if (strcasecmp(ptrToCommandName, saveMemoryCommandToken) == 0) { //Modify here
result = saveMemoryCommand();
Serial.println();
}
else if (strcasecmp(ptrToCommandName, loadMemoryCommandToken) == 0) { //Modify here
result = loadMemoryCommand();
}
else if (strcasecmp(ptrToCommandName, gameSaveMemoryCommandToken) == 0) { //Modify here
result = saveMemoryCommand();
Serial.println();
}
else if (strcasecmp(ptrToCommandName, gameLoadMemoryCommandToken) == 0) { //Modify here
result = loadMemoryCommand();
}
else if (strcasecmp(ptrToCommandName, helpCommandToken) == 0) { //Modify here
result = helpText();
}
else if (strcasecmp(ptrToCommandName, testMemoryCommandToken) == 0) { //Modify here
result = testMemoryCommand();
}
else if (strcasecmp(ptrToCommandName, BusyFaultCountToken) == 0) { //Modify here
busyFaultCountCommand();
}
else if (strcasecmp(ptrToCommandName, ShadowFaultCountToken) == 0) { //Modify here
shadowFaultCountCommand();
}
else {
nullCommand(ptrToCommandName);
}
return true;
}