studio icon indicating copy to clipboard operation
studio copied to clipboard

LVGL flow: measurement specific widget

Open fietser28 opened this issue 2 years ago • 1 comments

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)

fietser28 avatar Dec 26 '22 13:12 fietser28

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

fietser28 avatar May 07 '23 12:05 fietser28