Created
March 25, 2023 08:18
-
-
Save bitbank2/33f334ac7b8f2ebef2eedaf1d3d86101 to your computer and use it in GitHub Desktop.
An interactive serial terminal program to control GPIO, SPI, etc on a USB-connected Microcontroller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Interactive SPI & GPIO tester | |
// written by Larry Bank | |
// August 14, 2022 | |
// | |
// Purpose: Use the serial terminal to interactively test SPI & GPIO devices on ESP32 MCUs | |
// | |
// Copyright 2022 BitBank Software, Inc. All Rights Reserved. | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
//=========================================================================== | |
// The command set below is accepted in upper or lower case, and parameters are assumed to | |
// be decimal values unless preceded by 0x | |
// | |
// INFO - display current pin and SPI settings | |
// DC - set the DC pin number (e.g. "DC 25") | |
// MOSI, CLK, RST, CS - same as above | |
// SPI - initialize SPI with the given speed and mode (e.g. "SPI mode speed" -> "SPI 0 4000000") | |
// DW - set as output and write a value to a GPIO pin (e.g. "GW pin value") | |
// DR - set as input and read a value from a GPIO pin (e.g. "GR pin") | |
// AW - analog write | |
// AR - analog read | |
// RESET - toggle the reset pin low then high | |
// CMD - send one or more hex bytes in command mode (e.g. "CMD 0xf1 0x03 0x5a,...") | |
// CMDP - send one command byte followed by data bytes (e.g. "CMDP 0x05 0x00 0x00 0x00") | |
// DATA - send one or more data bytes (e.g. "DATA 0x00 0xf0 0xd1 0x39") | |
// DATAR - send a repeated data byte (e.g. "DATAR 0xff 0x1000") | |
// BLINK - toggle a GPIO | |
// HELP - display the command list | |
// All commands will print a response in the serial terminal indicating success or an error | |
// | |
#include <stdio.h> | |
#include <string.h> | |
#include <SPI.h> | |
// GPIO pins | |
static uint8_t ucDC, ucMOSI, ucCLK, ucRST, ucCS; | |
// SPI info | |
static uint32_t u32Mode = 0, u32Speed = 0; | |
const char *szCMDs[] = {"HELP", "INFO", "DC", "MOSI", "CLK", "RST", "CS", "SPI", "DW", "DR", "AW", "AR", "RESET", "CMD", "CMDP", | |
"DATA", "DATAR", "BLINK", 0}; | |
enum { | |
CMD_HELP = 0, | |
CMD_INFO, | |
CMD_DC, | |
CMD_MOSI, | |
CMD_CLK, | |
CMD_RST, | |
CMD_CS, | |
CMD_SPI, | |
CMD_DW, | |
CMD_DR, | |
CMD_AW, | |
CMD_AR, | |
CMD_RESET, | |
CMD_CMD, | |
CMD_CMDP, | |
CMD_DATA, | |
CMD_DATAR, | |
CMD_BLINK, | |
CMD_COUNT | |
}; | |
// 125 seconds of no input will send a msg | |
#define MAX_WAIT 500 | |
bool getCmd(char *szString) | |
{ | |
bool bFim = false; | |
char c, *d = szString; | |
int iTimeout = 0; | |
d[0] = 0; // start with a null string in case we timeout | |
while (!bFim && iTimeout < MAX_WAIT) { | |
while (!Serial.available() && iTimeout < MAX_WAIT) { | |
delay(250); // wait for characters to arrive | |
iTimeout++; | |
} | |
if (Serial.available()) { | |
c = Serial.read(); | |
if (c == 0xa || c == 0xd) { // end of line | |
*d++ = 0; // terminate the string | |
return false; // break out of loop | |
} | |
*d++ = c; | |
} // if character in buffer | |
} // while waiting for complete response | |
return (iTimeout >= MAX_WAIT); | |
} /* getCmd() */ | |
// Return the length and value of the parsed substring | |
int delimitedValue(char *szText, int *pValue, int iLen) | |
{ | |
int i, j, val; | |
// skip leading spaces | |
i = 0; | |
while (i < iLen && szText[i] == ' ') i++; | |
j = i; | |
while (j < iLen && szText[j] != ' ') j++; | |
if (szText[i] == '0' && (szText[i+1] == 'x' || szText[i+1] == 'X')) { // interpret as HEX | |
sscanf(&szText[i+2], "%x", &val); | |
} else { // interpret as decimal | |
val = atoi(&szText[i]); | |
} | |
*pValue = val; | |
return j; | |
} /* delimiatedValue() */ | |
int tokenizeCMD(char *szText, int iLen) | |
{ | |
int i = 0; | |
char szTemp[16]; | |
memcpy(szTemp, szText, iLen); | |
szTemp[iLen] = 0; | |
while (szCMDs[i]) { | |
if (strcasecmp(szCMDs[i], szTemp) == 0) { // found it | |
return i; | |
} | |
i++; | |
} | |
return -1; // not found | |
} /* tokenizeCMD() */ | |
int parseCmd(char *szText, int *pData) | |
{ | |
int iLen, i, iCount, iValue; | |
int iStart; | |
iStart = 0; // starting offset to parse string | |
iLen = strlen(szText); | |
i = delimitedValue(szText, &iValue, iLen-iStart); // first one must be the command | |
pData[0] = tokenizeCMD(szText, i); // command is stored at data index 0 | |
if (pData[0] < 0 || pData[0] >= CMD_COUNT) {// invalid command | |
Serial.print("Invalid command encountered: "); | |
szText[i] = 0; | |
Serial.println(szText); | |
Serial.println("Type HELP for a list of commands"); | |
return 0; | |
} | |
iCount = 1; // output data index | |
iStart += i; | |
while (iStart < iLen) { | |
i = delimitedValue(&szText[iStart], &iValue, iLen-iStart); | |
pData[iCount++] = iValue; | |
iStart += i; | |
} | |
return iCount; // number of numerical values parsed | |
} /* parseCmd() */ | |
// Check that values are in range | |
// returns true if a value is invalid | |
bool checkByteValues(int *pData, int iCount) | |
{ | |
for (int i=0; i<iCount; i++) | |
{ | |
if (pData[i] < 0 || pData[i] > 255) | |
return true; | |
} | |
return false; | |
} /* checkByteValues() */ | |
void showHelp() | |
{ | |
Serial.println("Interactive SPI & GPIO tester command list"); | |
Serial.println("(case insensitive, decimal assumed, hex preceded with '0x'"); | |
Serial.println("INFO - display current pin and SPI settings"); | |
Serial.println("DC - set the DC pin number (e.g. \"DC 25\")"); | |
Serial.println("MOSI, CLK, RST, CS - same as above"); | |
Serial.println("SPI - initialize SPI with the given speed and mode (e.g. \"SPI mode speed\" -> \"SPI 0 4000000\")"); | |
Serial.println("DW - digitalWrite; set as output and write a value to a GPIO pin (e.g. \"GW pin value\")"); | |
Serial.println("DR - digitalRead; set as input and read a value from a GPIO pin (e.g. \"GR pin\")"); | |
Serial.println("RESET - toggle the reset pin low then high"); | |
Serial.println("CMD - send one or more hex bytes in command mode (e.g. \"CMD 0xf1 0x03 0x5a ...\")"); | |
Serial.println("CMDP - send one command byte followed by data bytes (e.g. \"CMDP 0x05 0x00 0x00 0x00\")"); | |
Serial.println("DATA - send one or more data bytes (e.g. \"DATA 0x00 0xf0 0xd1 0x39\")"); | |
Serial.println("DATAR - send a repeated data byte (e.g. \"DATAR 0xff 0x1000\")"); | |
Serial.println("BLINK - toggle a GPIO (e.g. \"BLINK 32 100 1000\" - toggle GPIO 32 100 times, 1000ms delay)"); | |
Serial.println("HELP - show this list"); | |
} /* showHelp() */ | |
void showInfo() | |
{ | |
Serial.println("Current SPI info:"); | |
Serial.printf("DC = %d, MOSI = %d, CLK = %d, RST = %d, CS = %d\n", ucDC, ucMOSI, ucCLK, ucRST, ucCS); | |
Serial.printf("SPI: mode = %d, speed = %d\n", u32Mode, u32Speed); | |
} /* showInfo() */ | |
void executeCmd(int *pData, int iCount) | |
{ | |
switch (pData[0]) { | |
case CMD_HELP: | |
showHelp(); | |
break; | |
case CMD_INFO: | |
showInfo(); | |
break; | |
case CMD_DC: | |
if (!checkByteValues(&pData[1], 1)) { | |
ucDC = (uint8_t)pData[1]; | |
pinMode(ucDC, OUTPUT); | |
Serial.printf("DC pin set to %d\n", pData[1]); | |
} | |
break; | |
case CMD_MOSI: | |
if (!checkByteValues(&pData[1], 1)) { | |
ucMOSI = (uint8_t)pData[1]; | |
Serial.printf("MOSI pin set to %d\n", pData[1]); | |
} | |
break; | |
case CMD_CLK: | |
if (!checkByteValues(&pData[1], 1)) { | |
ucCLK = (uint8_t)pData[1]; | |
Serial.printf("CLK pin set to %d\n", pData[1]); | |
} | |
break; | |
case CMD_RST: | |
if (!checkByteValues(&pData[1], 1)) { | |
ucRST = (uint8_t)pData[1]; | |
pinMode(ucRST, OUTPUT); | |
Serial.printf("RST pin set to %d\n", pData[1]); | |
} | |
break; | |
case CMD_CS: | |
if (!checkByteValues(&pData[1], 1)) { | |
ucCS = (uint8_t)pData[1]; | |
Serial.printf("CS pin set to %d\n", pData[1]); | |
} | |
break; | |
case CMD_SPI: | |
if (iCount != 3) { | |
Serial.println("SPI command invalid parameters; format: SPI mode speed"); | |
return; | |
} | |
u32Mode = pData[1]; | |
u32Speed = pData[2]; | |
SPI.begin(ucCLK, -1, ucMOSI, ucCS); | |
Serial.printf("SPI set to mode=%d, speed=%d with pins: MOSI=%d CLK=%d CS=%d\n", u32Mode, u32Speed, ucMOSI, ucCLK, ucCS); | |
break; | |
case CMD_DW: | |
if (iCount != 3 || checkByteValues(&pData[1], 2)) { | |
Serial.println("DW - invalid parameter(s)"); | |
return; | |
} | |
pinMode(pData[1], OUTPUT); | |
digitalWrite(pData[1], pData[2]); | |
Serial.printf("%d written to GPIO %d\n", pData[2], pData[1]); | |
break; | |
case CMD_DR: | |
if (iCount != 2 || checkByteValues(&pData[1], 1)) { | |
Serial.println("DR - invalid parameter(s)"); | |
return; | |
} | |
pinMode(pData[1], INPUT); | |
Serial.printf("GPIO %d = %d\n", pData[1], digitalRead(pData[1])); | |
break; | |
case CMD_AW: | |
if (iCount != 3 || checkByteValues(&pData[1], 2)) { | |
Serial.println("AW - invalid parameter(s)"); | |
return; | |
} | |
// pinMode(pData[1], OUTPUT); | |
analogWrite(pData[1], pData[2]); | |
Serial.printf("%d written to GPIO %d\n", pData[2], pData[1]); | |
break; | |
case CMD_AR: | |
if (iCount != 2 || checkByteValues(&pData[1], 1)) { | |
Serial.println("AR - invalid parameter(s)"); | |
return; | |
} | |
// pinMode(pData[1], INPUT); | |
Serial.printf("GPIO %d = %d\n", pData[1], analogRead(pData[1])); | |
break; | |
case CMD_RESET: | |
digitalWrite(ucRST, LOW); | |
delay(100); | |
digitalWrite(ucRST, HIGH); | |
Serial.println("RST pin toggled"); | |
break; | |
case CMD_CMD: | |
if (iCount < 2 || checkByteValues(&pData[1], iCount-1)) { | |
Serial.println("CMD - missing or invalid parameter(s)"); | |
return; | |
} | |
digitalWrite(ucDC, LOW); // set command mode | |
SPI.beginTransaction(SPISettings(u32Speed, MSBFIRST, u32Mode)); | |
for (int i=1; i<iCount; i++) { | |
SPI.transfer((uint8_t)pData[i]); | |
} | |
SPI.endTransaction(); | |
Serial.printf("%d CMD bytes sent\n", iCount-1); | |
break; | |
case CMD_CMDP: | |
if (iCount < 3 || checkByteValues(&pData[1], iCount-1)) { | |
Serial.println("CMDP - missing or invalid parameter(s)"); | |
return; | |
} | |
digitalWrite(ucDC, LOW); // set command mode | |
SPI.beginTransaction(SPISettings(u32Speed, MSBFIRST, u32Mode)); | |
SPI.transfer((uint8_t)pData[1]); | |
digitalWrite(ucDC, HIGH); // the rest is data | |
for (int i=2; i<iCount; i++) { | |
SPI.transfer((uint8_t)pData[i]); | |
} | |
SPI.endTransaction(); | |
Serial.printf("1 CMD byte & %d parameter bytes sent\n", iCount-2); | |
break; | |
case CMD_DATA: | |
if (iCount < 2 || checkByteValues(&pData[1], iCount-1)) { | |
Serial.println("DATA - missing or invalid parameter(s)"); | |
return; | |
} | |
digitalWrite(ucDC, HIGH); // set data mode | |
SPI.beginTransaction(SPISettings(u32Speed, MSBFIRST, u32Mode)); | |
for (int i=1; i<iCount; i++) { | |
SPI.transfer((uint8_t)pData[i]); | |
} | |
SPI.endTransaction(); | |
Serial.printf("%d DATA bytes sent\n", iCount-1); | |
break; | |
case CMD_DATAR: | |
if (iCount != 3 || checkByteValues(&pData[1], 1)) { | |
Serial.println("DATAR - missing or invalid parameter(s)"); | |
return; | |
} | |
digitalWrite(ucDC, HIGH); // set data mode | |
SPI.beginTransaction(SPISettings(u32Speed, MSBFIRST, u32Mode)); | |
for (int i=0; i<pData[2]; i++) { | |
SPI.transfer((uint8_t)pData[1]); | |
} | |
SPI.endTransaction(); | |
Serial.printf("DATAR: 0x%02x sent %d times\n", pData[1], pData[2]); | |
break; | |
case CMD_BLINK: | |
if (iCount != 4 || checkByteValues(&pData[1], 1)) { | |
Serial.println("DATAR - missing or invalid parameter(s)"); | |
return; | |
} | |
Serial.printf("BLINK GPIO:%d, %d times, delay=%dms\n", pData[1], pData[2], pData[3]); | |
pinMode(pData[1], OUTPUT); | |
for (int i=0; i<pData[2]; i++) { | |
digitalWrite(pData[1], HIGH); | |
delay(pData[3]); | |
digitalWrite(pData[1], LOW); | |
delay(pData[3]); | |
} | |
break; | |
} | |
} /* executeCmd() */ | |
void setup() { | |
Serial.begin(115200); | |
delay(3000); // allow time for CDC serial to start | |
Serial.println("Interactive SPI & GPIO tester. Type HELP to see the command list"); | |
} | |
void loop() { | |
char szText[256]; | |
int iData[32], iCount; | |
// do this forever | |
if (getCmd(szText)) { | |
// timed out | |
Serial.println("Enter a command or HELP"); | |
return; | |
} | |
iCount = parseCmd(szText, iData); | |
if (iCount > 0) { | |
executeCmd(iData, iCount); | |
} | |
} /* loop() */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment