Why cant't Arduino deal with high precision numbers? - arduino

In my project I need to make some calculations with high-precision numbers. But I noticed that the answers are not correct. The code bellow shows what I mean.
void setup() {
Serial.begin(9600);
double l = 32.48750458;
double n = 32.48751068;
double im = l - n;
double im1 = im * 0.605;
double cs = 32.48751068 + im;
Serial.println(l, 8);
Serial.println(n, 8);
Serial.println(im1, 8);
Serial.println(cs, 8);
}
void loop() {
}
The output of this code on the serial monitor is:
32.48750305
32.48751068
-0.00000462
32.48750305
So how can I deal with this precision in Arduino?

On most Arduino the double data type doesn't exist. They all end up really being just regular float. The arduino just doesn't have the muscle to be playing with double precision numbers. The code generated wouldn't leave room for the rest of your program.
Floating point numbers just aren't that precise. They get about 6 or 7 digits of precision. Quite often it is better to use unsigned long and stick with fixed point math so you can get at least 9 digits and there aren't any approximations.

Related

Serial communication printing issue

I am working on serial communication between two MCU's particularly teensy(similar to Arduino) for generating fake GPS data. I have been able to write GPS data and read from the other MCU fine but if u look closely, the data that is printed has some ambiguity. The last values are changed somehow and I don't understand why is this because of sprintf command or conversion of float to string or what?
Some help will be appreciated.
Below are the working code and snippet of the serial terminal.
Thank you
float lat = 37.4980608;
char str1[21];
void setup()
{
Serial3.begin(115200);
Serial.begin(115200); // Config serial port (USB)
while(!Serial);
while(!Serial3);
Serial.println("Sending gps data");
}
void loop()
{
sprintf(str1, "%.7f%.7f", lon, lat);
Serial.println(str1);
Serial3.write(str1);
Serial3.flush();
delay(500);
}
What you are seeing is the compiler's approximation of your floats because their values are exceeding the precision possible with a float (4-bytes). Using a double won't help unless your MCU supports 8-byte doubles; I've not used a teensy but I highly doubt it supports 8-byte doubles.
This is not a clever solution but it should get you pointed in the right direction.
Define a struct that can represent large real number values
typedef struct {
int whole;
unsigned long fraction;
} BigNumber;
The you may declare/initialize latitude and longitude l
BigNumber latitude { 126, 9653503 };
BigNumber longitude { 37, 4980608 };
Then printing is easy:
sprintf(strbuf, "%i.%lu %i.%lu",
latitude.whole, latitude.fraction,
longitude.whole, longitude.fraction
);
However if mathematical operations are necessary - add, subtract, etc. - this won't cut it; find an arbitrary big number library like Nick Gammon's
Lastly, have a care: in your code str1 is too small - there is no accounting for the null terminator appended by sprintf, so you're getting plain lucky your program is not crashing.

Power monitoring on a three phase system with Arduino Uno

Hello
I am currently working on a project, where I want to measure the voltage and current in a 3-phase system with an Arduino Uno.
This is a small schoolproject and I've had the necessary course on AC-systems to know about safety around higher voltages. I've also have a little bit experience with microcontroller but I've never used ADC.
I have a problem when reading from the analog pins of the Arduino Uno. It seems like the analog pins are mixed which i believe is called ghosting. I've been searching the internet for some answers to this matter, but the proposed solutions didn't work for me. I tried to make a dummy measurement and also to make a small time delay between measurements but since it's about power monitoring timing is critical. I need at minimum 20 readings which needs to be done in 20ms
To test the code I used two function generators. Is this even possible or allowed? Is it best to have at minimum a resistance in between and maybe a capacitor to remove noise?
Is there something in the circuit when transforming the voltage/current to be between 0V-5V there can be done to prevent this ghosting-effect?
I am using a voltagetransformer for the voltage and a Hall-effect sensor for the current. Both circuits need offset.
This is the code that makes the measurements.
void measure(char pin_volt, char pin_curr, int *volt_rms, int *curr_rms, float *theta){
int i;
long squared_v, squared_c, sum_squared_v = 0, sum_squared_c = 0, inst_v, inst_c, mean_squared_v, mean_squared_c;
unsigned long time_v, time_c;
for(i = 0; i < samples; i++){
inst_v = analogRead(pin_volt) - volt_offset;
if(inst_v > -volt_varying && inst_v < volt_varying) {
time_v = micros();
}
inst_c = analogRead(pin_curr) - curr_offset;
if(inst_c >= -curr_varying && inst_c <= curr_varying) {
time_c = micros();
}
squared_v = inst_v * inst_v;
squared_c = inst_c * inst_c;
sum_squared_v += squared_v;
sum_squared_c += squared_c;
delayMicroseconds(80);
}
mean_squared_v = sum_squared_v / samples;
mean_squared_c = sum_squared_c / samples;
*volt_rms = sqrt(mean_squared_v);
*curr_rms = sqrt(mean_squared_c);
*theta = calculate_phase_difference(time_v,time_c);
}
Adding a capacitor can lower the problem.
Try to do the following:
No current or tension on the circuit, so the arduino should measure 0 values.
Run a sketch that reads values and prints max and min values to serial monitor; you will see that values will not be zero as expected, those are interferences.
Try and find a capacitor that can lower those values but don't exagerate.

Arduino multiplication error

Arduino not is able to multiply numbers from 40 onwards by 1000 for example
void setup() {
Serial.begin(9600);
}
void loop() {
float a = 60 * 1000;
Serial.print(a);
}
the result is -5536 .-. ??? what ??
I need to convert seconds to milliseconds, but I do not know alternatives to multiplication by 1000
The problem is that you are
taking a (signed) int and setting it to 60
taking a (signed) int and setting it to 1000
multiplying them, obtaining a signed int. This generates an overflow, so the result is -5536
converting this number in a float; -5536 -> -5536.0
The solution? Since you want to deal with floats... Operate with floats!
float a = ((float)60) * 1000;
float a = 60.0 * 1000;
The two solutions are the same; the first converts (int)60 in a float, then multiplies it by (int)1000, which gives you (float)60000.
The second tells the compiler that 60.0 is a float.
In both cases a float multiplied by an int gives you a float, so... No overflow!
The problem is that Serial.print converts a to signed integer. Try this:
Serial.print((float)a);
or this:
#include "floatToString.h"
char buffer[25];
Serial.print(floatToString(buffer, a, 5));

Microcontroller, How to display decimal on LCD?

I have a microcontroller and I am sampling the values of an LM335 temperature sensor.
The LCD library that I have allows me to display the hexadecimal value sampled by the 10-bit ADC.
10bit ADC gives me values from 0x0000 to 0x03FF.
What I am having trouble is trying to convert the hexadecimal value to a format that can be understood by regular humans.
Any leads would be greatly appreciated, since I am completely lost on the issue.
You could create a "string" into which you construct the decimal number like this (constants depend on what size the value actually, I presume 0-255, whether You want it to be null-terminated, etc.):
char result[4];
char i = 3;
do {
result[i] = '0' + value % 10;
value /= 10;
i--;
}
while (value > 0);
Basically, your problem is how to split a number into decimal digits so you can use your LCD library and send one digit to each cell.
If your LCD is based on 7-segment cells, then you need to output a value from 0 to 9 for each digit, not an ASCII code. The solution by #Roman Hocke is fine for this, provided that you don't add '0' to value % 10
Another way to split a number into digits is to convert it into BCD. For that, there is an algorithm named "double dabble" which allows you to convert your number into BCD without using divisions nor module operations, which can be nice if your microcontroller has no provision for division operation, or this is slower than you need.
"Double dable" algorithm sounds perfect for microcontrollers without provision for the division operation. However, a quick oversight of such algorithm in the Wikipedia shows that it uses dynamic memory, which seems to be worst than a routine for division. Of course, there must be an implementation out there that are not using calls to malloc() and friends.
Just to point out that Roman Hocke's snippet code has a little mistake. This version works ok for decimals in the range 0-255. It can be easily expand it to any range:
void dec2str(uint8_t val, char * res)
{
uint8_t i = 2;
do {
res[i] = '0' + val % 10;
val /= 10;
i--;
} while (val > 0);
res[3] = 0;
}

how to convert double between host and network byte order?

Could somebody tell me how to convert double precision into network byte ordering.
I tried
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
functions and they worked well but none of them does double (float) conversion because these types are different on every architecture. And through the XDR i found double-float precision format representations (http://en.wikipedia.org/wiki/Double_precision) but no byte ordering there.
So, I would much appreciate if somebody helps me out on this (C code would be great!).
NOTE: OS is Linux kernel (2.6.29), ARMv7 CPU architecture.
You could look at IEEE 754 at the interchanging formats of floating points.
But the key should be to define a network order, ex. 1. byte exponent and sign, bytes 2 to n as mantissa in msb order.
Then you can declare your functions
uint64_t htond(double hostdouble);
double ntohd(uint64_t netdouble);
The implementation only depends of your compiler/plattform.
The best should be to use some natural definition,
so you could use at the ARM-platform simple transformations.
EDIT:
From the comment
static void htond (double &x)
{
int *Double_Overlay;
int Holding_Buffer;
Double_Overlay = (int *) &x;
Holding_Buffer = Double_Overlay [0];
Double_Overlay [0] = htonl (Double_Overlay [1]);
Double_Overlay [1] = htonl (Holding_Buffer);
}
This could work, but obviously only if both platforms use the same coding schema for double and if int has the same size of long.
Btw. The way of returning the value is a bit odd.
But you could write a more stable version, like this (pseudo code)
void htond (const double hostDouble, uint8_t result[8])
{
result[0] = signOf(hostDouble);
result[1] = exponentOf(hostDouble);
result[2..7] = mantissaOf(hostDouble);
}
This might be hacky (the char* hack), but it works for me:
double Buffer::get8AsDouble(){
double little_endian = *(double*)this->cursor;
double big_endian;
int x = 0;
char *little_pointer = (char*)&little_endian;
char *big_pointer = (char*)&big_endian;
while( x < 8 ){
big_pointer[x] = little_pointer[7 - x];
++x;
}
return big_endian;
}
For brevity, I've not include the range guards. Though, you should include range guards when working at this level.

Resources