studio
studio copied to clipboard
LVGL flow: measurement specific widget
A very common use case is to display and/or set measurement values (e.g. voltage, current, power, temperature,...) These have a specific way of displaying. I'm thinking of the following properties:
- support for adding Units (V/A/W/....)
- scaling with SI scaling, usually in steps of 1000 (G/M/k/m/u) or not.
- scaling binary (^2) instead of 10. Either with ki or k prefixes.
- a specific number of digits (including trailing zero's) either in total or behind the decimal point.
- a specific resolution (digits below the resolution threshold are never shown)
- set the timeout to wait before changing to a smaller prefix (e.g. from mili to micro). This makes a display more relaxed to view.
- for input/editting:
- input of a float, int, maybe even a string?
- specify valid range: min, max, stepsize(?)
- hooks for keyboard and encoder editing in place
Nice to have: support styles for the different parts of the widget (digits before decimal point, digits behind decimal point, scale part of unit, unit, cursor)
I currently have the code below. It implements bullets 1,2 (it always shows prefix now) ,3 (excl. trailing zero's) and 4. It depends on sprintf and math functions like log10, pow, min, max.
static const char* SIScalePrefixes[] = { "p", "n","\u03bc", "m", " ", "k", "M", "G", "T" };
static const int SIScalePrefixExponents[] = {-12, -9, -6, -3, 0, 3 , 6, 9, 12 };
static const int SIScalePrefixExponentsBase = 4; // Index of 0 exponent
static const int SIScaleprefixExpLast = sizeof(SIScalePrefixExponents)/sizeof(SIScalePrefixExponents[0]) - 1;
void value2str(char* str, float value, int accuracy_exp, int total_digits, int after_point,
bool show_prefix, const char* unit)
{
// 0 is special case
if (value == 0.0f)
{
sprintf(str, " 0 %s", unit);
return;
}
// Determine sign
char sign = value < 0.0f ? '-' : ' ';
// Find exponent
int exponentRaw = floor(log10(abs(value)));
int exponent = std::max(exponentRaw, SIScalePrefixExponents[0]); // Lower limit of exponent
exponent = std::min(exponent, SIScalePrefixExponents[SIScaleprefixExpLast]); // Upper limit of exponent
exponent = std::max(exponent, accuracy_exp); // Don't go below accuracy
// Find prefix, exponent and mantissa to nearest enginering scale.
int exponentIndex = floor((float)exponent * 3.0f)/9 + SIScalePrefixExponentsBase;
exponentIndex = std::max(exponentIndex, (int)ceil(accuracy_exp / 3) + SIScalePrefixExponentsBase);
const char* prefix = SIScalePrefixes[exponentIndex];
float mantissa = abs(value) / pow(10, SIScalePrefixExponents[exponentIndex]); // raw mantissa
// Determine the number of digits after .
int fraction_digits = std::min(after_point, total_digits - (exponentRaw - exponent +1 ));
fraction_digits = std::min(fraction_digits, SIScalePrefixExponents[exponentIndex] - accuracy_exp);
// Format the string.
sprintf(str, "%c%.*f%s%s", sign, fraction_digits, mantissa, SIScalePrefixes[exponentIndex], unit);
} // value2str