aboutsummaryrefslogtreecommitdiffstats
path: root/example_avr_double
diff options
context:
space:
mode:
authorPetteri Aimonen <jpa@git.mail.kapsi.fi>2013-01-29 22:10:37 +0200
committerPetteri Aimonen <jpa@git.mail.kapsi.fi>2013-01-29 22:10:37 +0200
commite4b55179d1f53c42916f5e62fb83789973bf4b01 (patch)
tree4505321c4c14bdca6362b1e1bb1054c3588a4543 /example_avr_double
parent2392d255749715ad337d3f5e23d3de7f2065e3dd (diff)
Add an example of handling doubles on AVR platform.
Diffstat (limited to 'example_avr_double')
-rw-r--r--example_avr_double/Makefile22
-rw-r--r--example_avr_double/README.txt22
-rw-r--r--example_avr_double/decode_double.c33
-rw-r--r--example_avr_double/double_conversion.c123
-rw-r--r--example_avr_double/double_conversion.h26
-rw-r--r--example_avr_double/doubleproto.proto13
-rw-r--r--example_avr_double/encode_double.c25
-rw-r--r--example_avr_double/test_conversions.c56
8 files changed, 320 insertions, 0 deletions
diff --git a/example_avr_double/Makefile b/example_avr_double/Makefile
new file mode 100644
index 00000000..74300fc6
--- /dev/null
+++ b/example_avr_double/Makefile
@@ -0,0 +1,22 @@
+CFLAGS=-Wall -Werror -I .. -g -O0
+DEPS=double_conversion.c ../pb_decode.c ../pb_decode.h ../pb_encode.c ../pb_encode.h ../pb.h
+
+all: run_tests
+
+clean:
+ rm -f test_conversions encode_double decode_double doubleproto.pb.c doubleproto.pb.h
+
+test_conversions: test_conversions.c double_conversion.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+%: %.c $(DEPS) doubleproto.pb.h doubleproto.pb.c
+ $(CC) $(CFLAGS) -o $@ $< double_conversion.c ../pb_decode.c ../pb_encode.c doubleproto.pb.c
+
+doubleproto.pb.c doubleproto.pb.h: doubleproto.proto ../generator/nanopb_generator.py
+ protoc -I. -I../generator -I/usr/include -odoubleproto.pb $<
+ python ../generator/nanopb_generator.py doubleproto.pb
+
+run_tests: test_conversions encode_double decode_double
+ ./test_conversions
+ ./encode_double | ./decode_double
+
diff --git a/example_avr_double/README.txt b/example_avr_double/README.txt
new file mode 100644
index 00000000..0090d729
--- /dev/null
+++ b/example_avr_double/README.txt
@@ -0,0 +1,22 @@
+Some processors/compilers, such as AVR-GCC, do not support the double
+datatype. Instead, they have sizeof(double) == 4. Because protocol
+binary format uses the double encoding directly, this causes trouble
+if the protocol in .proto requires double fields.
+
+This directory contains a solution to this problem. It uses uint64_t
+to store the raw wire values, because its size is correct on all
+platforms. The file double_conversion.c provides functions that
+convert these values to/from floats, without relying on compiler
+support.
+
+To use this method, you need to make two modifications to your code:
+
+1) Change all 'double' fields into 'fixed64' in the .proto.
+
+2) Whenever writing to a 'double' field, use float_to_double().
+
+3) Whenever reading a 'double' field, use double_to_float().
+
+The conversion routines should be as accurate as the float datatype can
+be. Furthermore, they should handle all special values (NaN, inf, denormalized
+numbers) correctly. There are testcases in test_conversions.c.
diff --git a/example_avr_double/decode_double.c b/example_avr_double/decode_double.c
new file mode 100644
index 00000000..5802eca7
--- /dev/null
+++ b/example_avr_double/decode_double.c
@@ -0,0 +1,33 @@
+/* Decodes a double value into a float variable.
+ * Used to read double values with AVR code, which doesn't support double directly.
+ */
+
+#include <stdio.h>
+#include <pb_decode.h>
+#include "double_conversion.h"
+#include "doubleproto.pb.h"
+
+int main()
+{
+ uint8_t buffer[32];
+ size_t count = fread(buffer, 1, sizeof(buffer), stdin);
+ pb_istream_t stream = pb_istream_from_buffer(buffer, count);
+
+ AVRDoubleMessage message;
+ pb_decode(&stream, AVRDoubleMessage_fields, &message);
+
+ float v1 = double_to_float(message.field1);
+ float v2 = double_to_float(message.field2);
+
+ printf("Values: %f %f\n", v1, v2);
+
+ if (v1 == 1234.5678f &&
+ v2 == 0.00001f)
+ {
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+}
diff --git a/example_avr_double/double_conversion.c b/example_avr_double/double_conversion.c
new file mode 100644
index 00000000..cf79b9a0
--- /dev/null
+++ b/example_avr_double/double_conversion.c
@@ -0,0 +1,123 @@
+/* Conversion routines for platforms that do not support 'double' directly. */
+
+#include "double_conversion.h"
+#include <math.h>
+
+typedef union {
+ float f;
+ uint32_t i;
+} conversion_t;
+
+/* Note: IEE 754 standard specifies float formats as follows:
+ * Single precision: sign, 8-bit exp, 23-bit frac.
+ * Double precision: sign, 11-bit exp, 52-bit frac.
+ */
+
+uint64_t float_to_double(float value)
+{
+ conversion_t in;
+ in.f = value;
+ uint8_t sign;
+ int16_t exponent;
+ uint64_t mantissa;
+
+ /* Decompose input value */
+ sign = (in.i >> 31) & 1;
+ exponent = ((in.i >> 23) & 0xFF) - 127;
+ mantissa = in.i & 0x7FFFFF;
+
+ if (exponent == 128)
+ {
+ /* Special value (NaN etc.) */
+ exponent = 1024;
+ }
+ else if (exponent == -127)
+ {
+ if (!mantissa)
+ {
+ /* Zero */
+ exponent = -1023;
+ }
+ else
+ {
+ /* Denormalized */
+ mantissa <<= 1;
+ while (!(mantissa & 0x800000))
+ {
+ mantissa <<= 1;
+ exponent--;
+ }
+ mantissa &= 0x7FFFFF;
+ }
+ }
+
+ /* Combine fields */
+ mantissa <<= 29;
+ mantissa |= (uint64_t)(exponent + 1023) << 52;
+ mantissa |= (uint64_t)sign << 63;
+
+ return mantissa;
+}
+
+float double_to_float(uint64_t value)
+{
+ uint8_t sign;
+ int16_t exponent;
+ uint32_t mantissa;
+ conversion_t out;
+
+ /* Decompose input value */
+ sign = (value >> 63) & 1;
+ exponent = ((value >> 52) & 0x7FF) - 1023;
+ mantissa = (value >> 28) & 0xFFFFFF; /* Highest 24 bits */
+
+ /* Figure if value is in range representable by floats. */
+ if (exponent == 1024)
+ {
+ /* Special value */
+ exponent = 128;
+ }
+ else if (exponent > 127)
+ {
+ /* Too large */
+ if (sign)
+ return -INFINITY;
+ else
+ return INFINITY;
+ }
+ else if (exponent < -150)
+ {
+ /* Too small */
+ if (sign)
+ return -0.0f;
+ else
+ return 0.0f;
+ }
+ else if (exponent < -126)
+ {
+ /* Denormalized */
+ mantissa |= 0x1000000;
+ mantissa >>= (-126 - exponent);
+ exponent = -127;
+ }
+
+ /* Round off mantissa */
+ mantissa = (mantissa + 1) >> 1;
+
+ /* Check if mantissa went over 2.0 */
+ if (mantissa & 0x800000)
+ {
+ exponent += 1;
+ mantissa &= 0x7FFFFF;
+ mantissa >>= 1;
+ }
+
+ /* Combine fields */
+ out.i = mantissa;
+ out.i |= (uint32_t)(exponent + 127) << 23;
+ out.i |= (uint32_t)sign << 31;
+
+ return out.f;
+}
+
+
diff --git a/example_avr_double/double_conversion.h b/example_avr_double/double_conversion.h
new file mode 100644
index 00000000..62b6a8ae
--- /dev/null
+++ b/example_avr_double/double_conversion.h
@@ -0,0 +1,26 @@
+/* AVR-GCC does not have real double datatype. Instead its double
+ * is equal to float, i.e. 32 bit value. If you need to communicate
+ * with other systems that use double in their .proto files, you
+ * need to do some conversion.
+ *
+ * These functions use bitwise operations to mangle floats into doubles
+ * and then store them in uint64_t datatype.
+ */
+
+#ifndef DOUBLE_CONVERSION
+#define DOUBLE_CONVERSION
+
+#include <stdint.h>
+
+/* Convert native 4-byte float into a 8-byte double. */
+extern uint64_t float_to_double(float value);
+
+/* Convert 8-byte double into native 4-byte float.
+ * Values are rounded to nearest, 0.5 away from zero.
+ * Overflowing values are converted to Inf or -Inf.
+ */
+extern float double_to_float(uint64_t value);
+
+
+#endif
+
diff --git a/example_avr_double/doubleproto.proto b/example_avr_double/doubleproto.proto
new file mode 100644
index 00000000..d8b7f2db
--- /dev/null
+++ b/example_avr_double/doubleproto.proto
@@ -0,0 +1,13 @@
+// A message containing doubles, as used by other applications.
+message DoubleMessage {
+ required double field1 = 1;
+ required double field2 = 2;
+}
+
+// A message containing doubles, but redefined using uint64_t.
+// For use in AVR code.
+message AVRDoubleMessage {
+ required fixed64 field1 = 1;
+ required fixed64 field2 = 2;
+}
+
diff --git a/example_avr_double/encode_double.c b/example_avr_double/encode_double.c
new file mode 100644
index 00000000..cd532d46
--- /dev/null
+++ b/example_avr_double/encode_double.c
@@ -0,0 +1,25 @@
+/* Encodes a float value into a double on the wire.
+ * Used to emit doubles from AVR code, which doesn't support double directly.
+ */
+
+#include <stdio.h>
+#include <pb_encode.h>
+#include "double_conversion.h"
+#include "doubleproto.pb.h"
+
+int main()
+{
+ AVRDoubleMessage message = {
+ float_to_double(1234.5678f),
+ float_to_double(0.00001f)
+ };
+
+ uint8_t buffer[32];
+ pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
+
+ pb_encode(&stream, AVRDoubleMessage_fields, &message);
+ fwrite(buffer, 1, stream.bytes_written, stdout);
+
+ return 0;
+}
+
diff --git a/example_avr_double/test_conversions.c b/example_avr_double/test_conversions.c
new file mode 100644
index 00000000..22620a6a
--- /dev/null
+++ b/example_avr_double/test_conversions.c
@@ -0,0 +1,56 @@
+#include "double_conversion.h"
+#include <math.h>
+#include <stdio.h>
+
+static const double testvalues[] = {
+ 0.0, -0.0, 0.1, -0.1,
+ M_PI, -M_PI, 123456.789, -123456.789,
+ INFINITY, -INFINITY, NAN, INFINITY - INFINITY,
+ 1e38, -1e38, 1e39, -1e39,
+ 1e-38, -1e-38, 1e-39, -1e-39,
+ 3.14159e-37,-3.14159e-37, 3.14159e-43, -3.14159e-43,
+ 1e-60, -1e-60, 1e-45, -1e-45,
+ 0.99999999999999, -0.99999999999999, 127.999999999999, -127.999999999999
+};
+
+#define TESTVALUES_COUNT (sizeof(testvalues)/sizeof(testvalues[0]))
+
+int main()
+{
+ int status = 0;
+ int i;
+ for (i = 0; i < TESTVALUES_COUNT; i++)
+ {
+ double orig = testvalues[i];
+ float expected_float = (float)orig;
+ double expected_double = (double)expected_float;
+
+ float got_float = double_to_float(*(uint64_t*)&orig);
+ uint64_t got_double = float_to_double(got_float);
+
+ uint32_t e1 = *(uint32_t*)&expected_float;
+ uint32_t g1 = *(uint32_t*)&got_float;
+ uint64_t e2 = *(uint64_t*)&expected_double;
+ uint64_t g2 = got_double;
+
+ if (g1 != e1)
+ {
+ printf("%3d double_to_float fail: %08x != %08x\n", i, g1, e1);
+ status = 1;
+ }
+
+ if (g2 != e2)
+ {
+ printf("%3d float_to_double fail: %016llx != %016llx\n", i,
+ (unsigned long long)g2,
+ (unsigned long long)e2);
+ status = 1;
+ }
+ }
+
+ return status;
+}
+
+
+
+