Read ONCE from unstable data - hint for the best approach

Hello everyone, I have a bit of headache trying to figure out the best approach for an issue I recently encountered. Turn out I even have hard time to explain, better place a few forewords.

I'm building an antenna rotator, grade precision with feeback of actual aiming. The construction obviously have cables coming down, thus I cannot rotate indefinitely. Say, I can rotate one full turn, and overlap 90° after each turn, to allow some more freedom.
Plan is to keep track of the 'overlapping' status and write to EEPROM, so that if power goes down, this crucial information can retrieved and the 'do no go any further, please come back' command to be issued.

Now, I successfully have all this data, write to registry, direction of rotation etc etc.

Problem: Have a data stream coming in from magnetometer. This is not steady value by any means, and even though range from -say- +-1degree which is perfectly fine for my application, during the transition between the 'zero' point it returns an abundance of positive and negative values, forcing the program to rewrite EEPROM, at each passage, a dozen times. That's no good.

I have 'current position', 'heading position', 'turning direction', but I can't make up my mind the best way to approach this issue and mark the transit only one time each direction. Also consider, for safety assurance, the 'write on EEPROM' must to done precisely at the time the rotator transit over the 'zero', not at the end of operation, just in case power goes down in the process.

Perhaps I'm lost in a mental loop, but please press the break button and show me the way out :slight_smile:

Thanks to everyone for any hint.

I had a similar problem with a model radar system I built for a local museum
i.e. it could not rotate continuously due to cables running up to a ultrasonic sensor in the "radar" antenna
I placed a magnet at the end of clockwise rotation and used a Hall Effect Sensor Proximity Switch to detect the magnet
on power up I rotated clockwise until the magnet was detected

Edit: be careful EEPROMs have a very limited lifetime for writes
use an FRAM

FRAM! My new love! Thank you!
About the proximity switch(es) I prefer to keep the project as simple as possible. Being outdoor, the less cable the better. Thank you for the hint, I'll probably work around it and use all its capabilities of 10thousandsBillions rewrites, perhaps out of curiosity, with a long for the number of writes.

Are you saying the data varies more at this angle than at other angles? Why would that be? Is something interfering with the magnetometer at that angle?

But if the variation is similar at 'zero' to other angles, then your problem sounds like a maths or coding thing. If you post your code we can review it for you. Please read the forum guide to find out how to do that correctly, and please auto-format the code.

Thanks for your intervention.
Probably i didn't express myself well enough: noise on sensors (I believe) to be unavoidable, and those fluctuations into the decimal world, rounded up to the unit makes this to happen:

11:03:41.524 -> Aiming: 000###
11:03:41.621 -> Aiming: 360###
11:03:41.684 -> Aiming: 360###
11:03:41.748 -> Aiming: 360###
11:03:41.812 -> Aiming: 000###
11:03:41.876 -> Aiming: 360###
11:03:41.943 -> Aiming: 000###

In the span of a second, 3 readings '000' and 4 readings '360', but that's true (double checked) for every reading across the angle (any angle) change:

11:07:09.037 -> Aiming: 052###
11:07:09.102 -> Aiming: 052###
11:07:09.167 -> Aiming: 052###
11:07:09.268 -> Aiming: 053###
11:07:09.332 -> Aiming: 053###
11:07:09.396 -> Aiming: 053###
11:07:09.461 -> Aiming: 052###
11:07:09.526 -> Aiming: 052###
11:07:09.591 -> Aiming: 053###
11:07:09.653 -> Aiming: 053###
11:07:09.750 -> Aiming: 052###
11:07:09.815 -> Aiming: 052###
11:07:09.886 -> Aiming: 053###
11:07:09.944 -> Aiming: 052###

All this while standing nicely within the 1° angle the issue doesn't rise. Now, assuming the sensors works just fine and code also (courtesy of HMC5883L Pinout, Interfacing with Arduino, Applications, Datasheet -> down to Example code), the glitch is just on the inherent noise of the reading, and I see no easy way out to this.

Why is your code reporting 360? That is the same angle as 000. So your first example output actually shows no variation in angle at all.

If rounding to nearest degree, you need to check if the result is 360 and update it to 000 before comparing it to the previous value.

Indeed, but in your first post you implied that something different was happening at the 'zero' angle compared to other angles. I don't believe that is the case, you just have a bug in your code.

am i write in saying this "dithering" results in an excessive number of EEPROM writes?

what about applying a little filtering or only writing to EEPROM when there is a change of > 1

a simple filter

avg += (samp - avg) / N; 

Cleaned up the code and got the original, working one:

#include <Arduino.h>
#include <Wire.h>
#include <HMC5883L_Simple.h>

// Create a compass
HMC5883L_Simple Compass;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  Compass.SetDeclination(23, 35, 'E');  
  Compass.SetSamplingMode(COMPASS_SINGLE);
  Compass.SetScale(COMPASS_SCALE_130);
  Compass.SetOrientation(COMPASS_HORIZONTAL_X_NORTH);
}

void loop()
{
   float heading = Compass.GetHeadingDegrees();
   Serial.print("Heading: \t");
   Serial.println( heading );   
   delay(1000);
}

with HMC5883L_simple library, courtesy of same website (full credit to them, HMC5883L Pinout, Interfacing with Arduino, Applications, Datasheet make sure I can understand, let write, a tiny fraction of it)

#include <Arduino.h>

// PLEASE NOTE!
// The Arduino IDE is a bit braindead, even though we include Wire.h here, it does nothing
// you must include Wire.h in your main sketch, the Arduino IDE will not include Wire
// in the build process otherwise.
#include <Wire.h>
#include "HMC5883L_Simple.h"

HMC5883L_Simple::HMC5883L_Simple()
{
  declination_offset_radians = 0;
  mode = COMPASS_SINGLE | COMPASS_SCALE_130 | COMPASS_HORIZONTAL_X_NORTH;  
  i2c_address = COMPASS_I2C_ADDRESS;  // NB: The HMC5883L does not appear to be able to have any different address.
                                      //     so this is a bit moot.                                      
}

/** Set declination in degrees, minutes and direction (E/W)
 *   See http://www.magnetic-declination.com/
 */

void HMC5883L_Simple::SetDeclination( int declination_degs , int declination_mins, char declination_dir )
{    
  // Convert declination to decimal degrees
  switch(declination_dir)
  {
    // North and East are positive   
    case 'E': 
      declination_offset_radians = ( declination_degs + (1/60 * declination_mins)) * (M_PI / 180);
      break;
      
    // South and West are negative    
    case 'W':
      declination_offset_radians =  0 - (( declination_degs + (1/60 * declination_mins) ) * (M_PI / 180));
      break;
  } 
}

/** Set the sampling mode to one of COMPASS_CONTINUOUS or COMPASS_SINGLE
 */

void HMC5883L_Simple::SetSamplingMode( uint16_t sampling_mode )
{  
  // Mode is the bits marked M in mode
  //    xxxxxxxxxxxSSSMM
  mode = (mode & ~0x03) | (sampling_mode & 0x03);

  Write(COMPASS_MODE_REGISTER, mode & 0x03);  
}

/** Set the scale to one of COMPASS_SCALE_088 through COMPASS_SCALE_810
 * Higher scales are less sensitive and less noisy
 * Lower scales are more sensitive and more noisy
 */

void HMC5883L_Simple::SetScale( uint16_t scale )
{
  // Scale is the bits marked S in mode
  //    xxxxxxxxxxxSSSMM  
  mode = (mode & ~0x1C) | (scale & 0x1C);

  Write(COMPASS_CONFIG_REGISTER_B, (( mode >> 2 ) & 0x07) << 5);
}

/** Set the orientation to one of COMPASS_HORIZONTAL_X_NORTH 
 * through COMPASS_VERTICAL_Y_WEST
 *  
 */

void HMC5883L_Simple::SetOrientation( uint16_t orientation )
{
  // Orientation is the bits marked XXXYYYZZZ in mode
  //    xxXXXYYYZZZxxxxx
  mode = (mode & ~0x3FE0) | (orientation & 0x3FE0);    
}

/** Get the heading of the compass in degrees. */
float HMC5883L_Simple::GetHeadingDegrees()
{     
  // Obtain a sample of the magnetic axes
  MagnetometerSample sample = ReadAxes();
  
  float heading;    
  
  // Determine which of the Axes to use for North and West (when compass is "pointing" north)
  float mag_north, mag_west;
   
  // Z = bits 0-2
  switch((mode >> 5) & 0x07 )
  {
    case COMPASS_NORTH: mag_north = sample.Z; break;
    case COMPASS_SOUTH: mag_north = 0-sample.Z; break;
    case COMPASS_WEST:  mag_west  = sample.Z; break;
    case COMPASS_EAST:  mag_west  = 0-sample.Z; break;
      
    // Don't care
    case COMPASS_UP:
    case COMPASS_DOWN:
     break;
  }
  
  // Y = bits 3 - 5
  switch(((mode >> 5) >> 3) & 0x07 )
  {
    case COMPASS_NORTH: mag_north = sample.Y;  break;
    case COMPASS_SOUTH: mag_north = 0-sample.Y; ;  break;
    case COMPASS_WEST:  mag_west  = sample.Y;  break;
    case COMPASS_EAST:  mag_west  = 0-sample.Y;  break;
      
    // Don't care
    case COMPASS_UP:
    case COMPASS_DOWN:
     break;
  }
  
  // X = bits 6 - 8
  switch(((mode >> 5) >> 6) & 0x07 )
  {
    case COMPASS_NORTH: mag_north = sample.X; break;
    case COMPASS_SOUTH: mag_north = 0-sample.X; break;
    case COMPASS_WEST:  mag_west  = sample.X; break;
    case COMPASS_EAST:  mag_west  = 0-sample.X; break;
      
    // Don't care
    case COMPASS_UP:
    case COMPASS_DOWN:
     break;
  }
    
  // calculate heading from the north and west magnetic axes
  heading = atan2(mag_west, mag_north);
  
  // Adjust the heading by the declination
  heading += declination_offset_radians;
  
  // Correct for when signs are reversed.
  if(heading < 0)
    heading += 2*M_PI;
    
  // Check for wrap due to addition of declination.
  if(heading > 2*M_PI)
    heading -= 2*M_PI;
   
  // Convert radians to degrees for readability.
  return heading * 180/M_PI; 
}


/** Read the axes from the magnetometer.
 * In SINGLE mode we take a sample.  In CONTINUOUS mode we 
 * just grab the most recent result in the registers.
 */

HMC5883L_Simple::MagnetometerSample HMC5883L_Simple::ReadAxes()
{
  if(mode & COMPASS_SINGLE) 
  {    
    Write(COMPASS_MODE_REGISTER, (uint8_t)( mode & 0x03 ));  
    delay(66); // We could listen to the data ready pin instead of waiting.
  }
  
  uint8_t buffer[6];
  Read(COMPASS_DATA_REGISTER, buffer, 6);

  MagnetometerSample sample;
  
  // NOTE:
  // The registers are in the order X Z Y  (page 11 of datasheet)
  // the datasheet when it describes the registers details then in order X Y Z (page 15)
  // stupid datasheet writers
  sample.X = (buffer[0] << 8) | buffer[1];  
  sample.Z = (buffer[2] << 8) | buffer[3];
  sample.Y = (buffer[4] << 8) | buffer[5];
  
  return sample;
}

/** Write data to the compass by I2C */
void HMC5883L_Simple::Write(uint8_t register_address, uint8_t data)
{
  Wire.beginTransmission(i2c_address);
  Wire.write(register_address);
  Wire.write(data);
  Wire.endTransmission();
}

/** Read data from the compass by I2C  
 */
uint8_t HMC5883L_Simple::Read(uint8_t register_address, uint8_t buffer[], uint8_t length)
{
  // Write the register address that we will begin the read from, this
  // has the effect of "seeking" to that register
  Wire.beginTransmission(i2c_address);
  Wire.write(register_address);
  Wire.endTransmission();
  
  // Read the data starting at that register we seeked
  Wire.requestFrom(i2c_address, length);

  if(Wire.available() == length)
  {
    for(uint8_t i = 0; i < length; i++)
    {
      buffer[i] = Wire.read();
    }
    
    return length;
  }

  return 0;
}

Now, this all said, my intention is to make possible to set //any// point be the threshold for the 'overlap beginning' - therefore you're right when you mention that 0 and 360 are the same position, thus there's a glitch on the software. Good catch. Luckily I can cope with it.
Less with the fact that if I say 'my threshold is [put here a number]' every time I transit over, there's a moment with 10~15 alternative readings (as shown above 52-53 degrees) that makes the mark of being past or not the threshold a boolean nightmare to me.

Also, to answer this other gentleman gcjr (thanks for your contribution too), average the previous value seems to me to retain the same erratic behavious, just introduce it smootly and likely have it to happen less time (say, when antenna have to 'stay' near the -relative- zero other than transit at full speed over it).

Ok, let me check my understanding of the problem.

Let's imagine the "home" direction of the antenna is 045M. This is when the cables are not twisted. From here it could rotate clockwise as far as 315M, but no further without damaging the cables. It could rotate anticlockwise as far as 135M but no further.

Starting from the home position, the antenna is commanded to move to 180M. It will choose to move clockwise because that is the shorter angle to turn.

It is then commanded to move to 230M. Again, it will choose clockwise. As soon as the bearing reads 225M, it will update the EEPROM to record the fact it is now twisted at or beyond 180 degrees from home. But the next reading is only 224M because of the random variations, and we don't want to update the EEPROM again. I think this can be avoided by knowing the the movement direction is clockwise, and that the EEPROM is already set, can't it? Only if the movement direction was anticlockwise and the bearing was 224M and the EEPROM was already set would it then clear the EEPROM.

slip rings anyone?

https://smile.amazon.com/s?k=slip+rings+electric&sprefix=slip+rings%2Caps%2C515&ref=nb_sb_ss_ts-doa-p_2_10

YES YES YES, that's the point. With way simpler number:
let's say 'zero' is literally 0, and I allow for convenience 90° of freedom both side. 'Parking' when unused will be 180°, half way both sides.
First call -say- heading 350. Turn clockwise to that point.
Second call -say- 020. Turn clockwise by 30° to that point. But at 0° raise alarm, 'careful you're going into overlap'.
Next call 150, so other than keep riding clockwise, will go all the way around.

Problem is that the noise from magnetometer, at the change of degree (any degree, between 1 and 2, between 124 and 125 and so on) creates a bit of instability between the two position.

Now, I've got to the point that, to handle acceleration and deceleration (!!) of the motors, I will be using the distance from where I am (say, 180) and where I have to go (say, 350). Shortest distance, clockwise 170 so that we have a +170. Theoretically I will be using the value and the sign to drive the motors AND to record any passing across the 0.

Seems so far, we got the idea clear.
Now, first half turn (180 starting point +-180) will holding a 'no overlap information'. Beyond zero on each direction will trigger the fact we're digging into overlap.
Obviously -and HERE comes the problem-, on the way back I have to reset to the 'no overlap', because we return into normal venture.

Now, I might be missing something into my code:


void updateOverlap (int loc, int aim) { //update overlap status when transit from zero
  if (loc == 0){ //while passing zero point
    if (overlapCW == false and overlapCCW == false) { //if over first turn
    //Serial.print("qui non dovrei passare");
      if (aim > 0) {
        overlapCW = true;
      }
      else if (aim <= 0) {
        overlapCCW = true;
      } 
    } else {
        if (aim > 0) {
          overlapCCW = false;
        } else if (aim <= 0) {
          overlapCW = false;
        }
      }
  }
}

because first transit is OK, when goes over the zero for first time, switch on overlapCW or overlapCCW according to direction of movement.

Problem comes on the way back, because of the instability, the very first time it tricks both overlap goes to false, BUT since I'm still heading the same direction AND both goes false, THEN the opposite overlap triggers unnecessarily.

Eh!

To kind answer contribution of Idahowalter (thank you also!), this is not feasible because antenna arrays are very, very sensitive to cable impedance, and especially at high frequencies using an unshielded connection is simply unfeasible.

My aim, let's see if I can forward a picture, is the drive of such a thing:

Amateur satellite antenna

Er, not sure how to post the picture and/or license.. but link is legitimate.

Let me update myself and add a new perspective for anyone who end up with similar issue:
I ended up to set TWO waypoints. One is mid-run, at 180°. This is a position so far away from the 0° that, even 'noisy' at the time of its transit, will be also very clearly determined when reading goes around the 0. Although not an elegant solution, at least it works.

Updated code follows:


void updateOverlap (int loc, int aim) { //update overlap status when transit from zero
  if (loc == 180){
    if (aim >0) {
      parkingTransit = +1;
    } else {
      parkingTransit = -1;
    }
    //and just to make sure...
    overlapCCW = false;
    overlapCW = false;
  }

  if (loc == 0){ //while passing zero point
    if (aim > 0 and parkingTransit == 1){ //CW and after mid-positive turn
      overlapCW = true;
    } else if (aim < 0 and parkingTransit == 1) {
      overlapCW = false;
    } else if (aim < 0 and parkingTransit == -1) {
      overlapCCW = true;
    } else if (aim > 0 and parkingTransit == -1) {
      overlapCCW = false; 
    }
  }

is there a diagram?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.