Skip to content

Instantly share code, notes, and snippets.

@ImpulseAdventure
Last active September 26, 2017 05:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ImpulseAdventure/374192f340dd7d432c40b7a5602b5a01 to your computer and use it in GitHub Desktop.
Save ImpulseAdventure/374192f340dd7d432c40b7a5602b5a01 to your computer and use it in GitHub Desktop.
Lightweight printf() for Arduino
// ========================================================================
// Very lightweight printf() for Arduino
// by Calvin Hass
// http://www.impulseadventure.com/elec/
// ========================================================================
//
// printf() feature support:
// - %u (16-bit unsigned int)
// - %d (16-bit signed int)
// - %s (null-terminated string)
//
// Can also leveage format string storage in Flash (PROGMEM)
// via #define GSLC_USE_PROGMEM
//
// ------------------------------------------------------------------------
//
// Permission is granted to use this code freely for
// personal or commercial use per MIT license.
//
// The following code was written for use in the GUIslice GUI library
// available at https://github.com/ImpulseAdventure/GUIslice
//
// ------------------------------------------------------------------------
// USAGE
// ------------------------------------------------------------------------
//
// User definitions:
// // Define a character-output function that gslc_DebugPrintf() will use
// // - On Arduino, this would typically be a call to Serial.write()
// static int16_t DebugOut(char ch) { Serial.write(ch); return 0; }
//
// Initialization:
// // Assign the global character-output function
// g_pfDebugOut = &DebugOut;
//
// Calling convention examples:
// gslc_DebugPrintf("Value is %d\n",-28);
// gslc_DebugPrintf("String is %s, value is %u\n","TEST",1327);
//
// ------------------------------------------------------------------------
// Definitions
// Uncomment the following if Flash memory is used for format string storage
//#define GSLC_USE_PROGMEM
// printf() state definitions
typedef enum {
GSLC_DEBUG_PRINT_NORM,
GSLC_DEBUG_PRINT_TOKEN,
GSLC_DEBUG_PRINT_UINT16,
GSLC_DEBUG_PRINT_STR
} gslc_teDebugPrintState;
// Type for user-provided debug output character function
typedef int16_t (*GSLC_CB_DEBUG_OUT)(char ch);
/// Global debug output function
/// - The user assigns this function during init
extern GSLC_CB_DEBUG_OUT g_pfDebugOut;
// A lightweight printf() routine that calls user function for
// character output (enabling redirection to Serial). Only
// supports the following tokens:
// - %u (16-bit unsigned int) [see NOTE]
// - %d (16-bit signed int)
// - %s (null-terminated string)
// This routine also supports format strings located in Flash
// NOTE:
// - Due to the way variadic arguments are passed, we can't pass uint16_t on Arduino
// as the parameters are promoted to "int" (ie. int16_t). Passing a value over 32767
// appears to be promoted to int32_t which involves pushing two more bytes onto
// the stack, causing the remainder of the va_args() to be offset.
// PRE:
// - g_pfDebugOut defined
void gslc_DebugPrintf(const char* pFmt, ...)
{
if (g_pfDebugOut) {
char* pStr;
unsigned nMaxDivisor;
unsigned nNumRemain;
bool bNumStart,bNumNeg;
unsigned nNumDivisor;
uint16_t nFmtInd=0;
char cFmt,cOut;
va_list vlist;
va_start(vlist,pFmt);
gslc_teDebugPrintState nState = GSLC_DEBUG_PRINT_NORM;
// Determine maximum number digit size
#if defined(__AVR__)
nMaxDivisor = 10000; // ~2^16
#else
nMaxDivisor = 1000000000; // ~2^32
#endif
#if (GSLC_USE_PROGMEM)
cFmt = pgm_read_byte(&pFmt[nFmtInd]);
#else
cFmt = pFmt[nFmtInd];
#endif
while (cFmt != 0) {
if (nState == GSLC_DEBUG_PRINT_NORM) {
if (cFmt == '%') {
nState = GSLC_DEBUG_PRINT_TOKEN;
} else {
// Normal char
(g_pfDebugOut)(cFmt);
}
nFmtInd++; // Advance format index
} else if (nState == GSLC_DEBUG_PRINT_TOKEN) {
// Get token
if (cFmt == 'd') {
nState = GSLC_DEBUG_PRINT_UINT16;
// Detect negative value and convert to unsigned value
// with negation flag. This enables us to reuse the same
// decoding logic.
int nNumInt = va_arg(vlist,int);
if (nNumInt < 0) {
bNumNeg = true;
nNumRemain = -nNumInt;
} else {
bNumNeg = false;
nNumRemain = nNumInt;
}
bNumStart = false;
nNumDivisor = nMaxDivisor;
} else if (cFmt == 'u') {
nState = GSLC_DEBUG_PRINT_UINT16;
nNumRemain = va_arg(vlist,unsigned);
bNumNeg = false;
bNumStart = false;
nNumDivisor = nMaxDivisor;
} else if (cFmt == 's') {
nState = GSLC_DEBUG_PRINT_STR;
pStr = va_arg(vlist,char*);
} else {
// ERROR
}
nFmtInd++; // Advance format index
} else if (nState == GSLC_DEBUG_PRINT_STR) {
while (*pStr != 0) {
cOut = *pStr;
(g_pfDebugOut)(cOut);
pStr++;
}
nState = GSLC_DEBUG_PRINT_NORM;
// Don't advance format string index
} else if (nState == GSLC_DEBUG_PRINT_UINT16) {
// Handle the negation flag if required
if (bNumNeg) {
cOut = '-';
(g_pfDebugOut)(cOut);
bNumNeg = false; // Clear the negation flag
}
// We remain in this state until we have consumed all of the digits
// in the original number (starting with the most significant).
// Each time we process a digit, the parser doesn't advance its input.
if (nNumRemain < nNumDivisor) {
if (bNumStart) {
cOut = '0';
(g_pfDebugOut)(cOut);
} else {
// We haven't started outputting a number yet
// Check for special case of zero
if (nNumRemain == 0) {
cOut = '0';
(g_pfDebugOut)(cOut);
// Now fall through to done state
nNumDivisor = 1;
}
}
} else {
bNumStart = true;
unsigned nValDigit = nNumRemain / nNumDivisor;
cOut = nValDigit+'0';
nNumRemain -= nNumDivisor*nValDigit;
(g_pfDebugOut)(cOut);
}
// Detect end of digit decode (ie. 1's)
if (nNumDivisor == 1) {
// Done
nState = GSLC_DEBUG_PRINT_NORM;
} else {
// Shift the divisor by an order of magnitude
nNumDivisor /= 10;
}
// Don't advance format string index
}
// Read the format string (usually the next character)
#if (GSLC_USE_PROGMEM)
cFmt = pgm_read_byte(&pFmt[nFmtInd]);
#else
cFmt = pFmt[nFmtInd];
#endif
}
va_end(vlist);
} // g_pfDebugOut
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment