Skip to content

Instantly share code, notes, and snippets.

@bitbank2
Created March 25, 2023 08:18
Show Gist options
  • Save bitbank2/33f334ac7b8f2ebef2eedaf1d3d86101 to your computer and use it in GitHub Desktop.
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
//
// 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