Why are you using float on an 8-bit microcontroller without hardware float support? Using e.g. int32_t to describe the values in thousandths (so 1 displays as 0.001 and -1 displays as -.001) would be just as precise, but much faster.
Here's what I'd use:
#include <stdint.h>
#define display_none 0x00
#define display_dot 0x80
#define display_neg 0x40
#define display_over 0x5C
static const uint8_t display_digit[10] = {
0x3F, /* "0": 0011 1111 */
0x06, /* "1": 0000 0110 */
0x5B, /* "2": 0101 1011 */
0x4F, /* "3": 0100 1111 */
0x66, /* "4", 0110 0110 */
0x6D, /* "5", 0110 1101 */
0x7D, /* "6", 0111 1101 */
0x07, /* "7", 0000 0111 */
0x7F, /* "8", 0111 1111 */
0x6F, /* "9", 0110 1111 */
};
/* [0] is rightmost, [3] leftmost */
uint8_t display_pattern[4];
void display_thousandths(int32_t value)
{
uint_fast32_t u, v;
int_fast8_t i, s, d;
display_pattern[0] = display_none;
display_pattern[1] = display_none;
display_pattern[2] = display_none;
display_pattern[3] = display_none;
if (value >= 9999500) {
/* Too large to display. */
display_pattern[2] = display_over;
return;
} else
if (value >= 1000000) {
u = (value + 500) / 1000;
s = 0;
d = 0;
} else
if (value >= 100000) {
u = (value + 50) / 100;
s = 0;
d = 1;
} else
if (value >= 10000) {
u = (value + 5) / 10;
s = 0;
d = 2;
} else
if (value > 0) {
u = value;
s = 0;
d = 3;
} else
if (value == 0) {
display_pattern[0] = display_digit[0];
return;
} else
if (value <= -999500) {
/* Too large to display */
display_pattern[3] = display_neg;
display_pattern[2] = display_over;
return;
} else
if (value <= -100000) {
u = (500 - value) / 1000;
s = 1;
d = 0;
} else
if (value <= -10000) {
u = (50 - value) / 100;
s = 1;
d = 1;
} else
if (value <= -1000) {
u = (5 - value) / 10;
s = 1;
d = 2;
} else {
u = (-value);
s = 1;
d = 3;
}
/* Optional: Remove trailing fractional zeros. */
while (d > 1 && !(u % 10)) {
u /= 10;
d--;
}
/* Save the value. We'll need it later. */
v = u;
/* Convert to digit patterns. */
i = 0;
do {
display_pattern[i++] = display_digit[u % 10];
u /= 10;
} while (u > 0);
/* Zeros up to decimal point. */
while (i < d) {
display_pattern[i++] = display_digit[0];
}
/* Room for an unit zero? Need one? */
if (d > 0 && s + i < 4) {
if ((d == 1 && v < 10) ||
(d == 2 && v < 100) ||
(d == 3 && v < 1000)) {
display_pattern[i++] = display_digit[0];
}
}
/* Negative? */
if (s && i < 4) {
display_pattern[i] = display_neg;
}
/* Decimal point? */
if (d > 0) {
display_pattern[d] |= display_dot;
}
/*
* TODO: Update display based on display_pattern[].
*/
}and in your case, you can use the following to display floats via using the above:
void display_float(float value)
{
if (value > 9999.999f)
display_thousandths(9999999);
else
if (value >= 0.0f)
display_thousandths((int32_t)(1000.0f * value + 0.5f));
else
if (value < -999.999f)
display_thousandths(-999999);
else
display_thousandths((int32_t)(1000.0f * value - 0.5f));
}
Note that the 0.5f above are used for correct rounding.
If you want to explore what that looks like, add
#include <stdlib.h>
#include <stdio.h>
static const unsigned char lcd_mask[7][8] = {
{ 0x21, 0x01, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00 },
{ 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 },
{ 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 },
{ 0x70, 0x40, 0x40, 0x40, 0x46, 0x00, 0x00, 0x00 },
{ 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 },
{ 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 },
{ 0x18, 0x08, 0x08, 0x08, 0x0C, 0x00, 0x80, 0x00 },
};
int main(int argc, char *argv[])
{
float val;
int arg, r, c, d;
char dummy;
for (arg = 1; arg < argc; arg++) {
if (sscanf(argv[arg], " %f %c", &val, &dummy) != 1) {
fprintf(stderr, "%s: Invalid value.\n", argv[arg]);
return EXIT_FAILURE;
} else {
printf("%s:\n", argv[arg]);
display_float(val);
for (r = 0; r < 7; r++) {
for (d = 3; d >= 0; d--) {
for (c = 0; c < 8; c++) {
if (display_pattern[d] & lcd_mask[r][c]) {
fputc('X', stdout);
} else {
fputc(' ', stdout);
}
}
}
fputc('\n', stdout);
}
fflush(stdout);
}
}
return EXIT_SUCCESS;
}
so you can compile it on any C compiler, to be run on your development system. Supply one or more numbers as a command-line parameter, and it'll display a crude ASCII version of the expected seven-segment display. For example, for -99.25 it will print
-99.25:
XXXXX XXXXX XXXXX
X X X X X
X X X X X
XXXXX XXXXX XXXXX XXXXX
X X X
X X X
XXXXX XXXXX X XXXXX
and for 92.127
XXXXX XXXXX X XXXXX
X X X X X
X X X X X
XXXXX XXXXX X XXXXX
X X X X
X X X X
XXXXX XXXXX X X XXXXX
The displayable range is -999 to 9999. The code is smart enough to format -0.001 as -.001, but will use a leading zero if one fits.