Last active
September 26, 2017 05:38
-
-
Save ImpulseAdventure/374192f340dd7d432c40b7a5602b5a01 to your computer and use it in GitHub Desktop.
Lightweight printf() for Arduino
This file contains 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
// ======================================================================== | |
// 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