Shop OBEX P1 Docs P2 Docs Learn Events
Noise on ADC — Parallax Forums

Noise on ADC

DiverBobDiverBob Posts: 1,110
edited 2014-02-26 01:42 in Propeller 1
Still working on my motor control/ramping routines. The biggest problem I'm seeing right now is noise on the ADC causing eradic outputs to the Prop. I'm using a MCP3208 ADC and Jon's jm_mcp3208_ez object on a breadboard (which is another problem too!). I take an average of 8 samples which can have some fairly random values at times. I've hit this with a scope and I'm seeing +/- 20 to 140 mv variences in the signal line to the ADC chip that are coming in at random intervals. The linear actuator pot is 10k across 5 volts. The motor power leads and pot leads are in the same unshielded cable coming from the actuator. In general while watching the ADC output I see a fairly smooth change but then there comes a wild value that can throw the programming totally out of whack

I had hoped averaging would smooth things out but unfortunately it isn't enough. I'm experimenting with taking 10 values, dropping the high and low values, then averaging the remaining 8, but it got too late to complete that last night. I'm looking for circuit or programming suggestions. One thought just hit me is would slowing down the speed that the ADC is being read make a difference?

Thanks

Bob

Comments

  • Mark_TMark_T Posts: 1,981
    edited 2014-02-17 04:16
    DiverBob wrote: »
    The motor power leads and pot leads are in the same unshielded cable coming from the actuator.

    That's the problem, you need to separate the sensitive signals away from the motor leads, you're coupling
    switching transients directly into the signal wire.

    The motor leads should be at least twisted pair, preferrably shielded twisted pair.

    The signal lead from the pot should be shielded (should be the case for any analog sensor).
    Add 10nF to ground at the ADC end of the cable.
  • dMajodMajo Posts: 855
    edited 2014-02-17 05:12
    Instead of averaging the values, apply a mediane filter. Eg. sort samples from n to n-4 and work on n-2. This removes occasional spikes but doesn't change the waveform. If you have an edge in the signal, its shape is preserved without smoothing it, you have only 2 samples of delay.


    Beside shielding the cables to avoid coupling between power and signal lines, if you have the possibility you can try to wire the pot in the field with one side to the cursor and the other to the +5v while inserting a resistor between the adc input and ground thus lowering the input impedance and forming a voltage divider of which one part is on the board and the other in the field but basically transforming your signal from voltage to a kind of current-loop that is less sensible to noise coupled from surrounding power leads.
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-17 09:53
    I kind of figured I needed to seperate the motor leads from the pot, I was hoping to avoid that as I have 12 actuators that I would have to void the warrantee on in addition to all the re-wiring needed. I'll do one to start and see how well that smooths things out. The voltage signal is relatively slow changing, I just need to remove the spikes that show up.

    I will research the mediane filter, not familiar with that one.

    Thanks for the suggestions!
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2014-02-17 10:08
    Here's a method by PhiPi to compute the running median...

    QuickMedian-Algorithm-in-Spin

    If the response can be slow, an RC filter right at the Prop pin could smooth out much of the noise.
  • LawsonLawson Posts: 870
    edited 2014-02-17 15:54
    Two things. First Tracy's suggestion of an anti-aliasing filter in front of the ADC is excellent. Also a 10K pot will be significantly loaded down by the MCP3208 inputs. (1K would be better) Second, is the propeller also generating the PWM? If so, synchronizing the ADC sampling with the PWM can also eliminate the coupled noise. (I.e. have the ADC sample between switching events)

    Marty
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-17 19:19
    Thanks for all the suggestions! I've read up on the median filter and based on the frequency of the values I'm seeing this could go a long way towards solving this particular issue. I will try out the filter code in post 5 and see how that affects the output. I'll post the results as soon as I can test it.
  • shimniokshimniok Posts: 177
    edited 2014-02-18 00:10
    You might also consider a "leaky integrator" filter which reduces bandwidth but may help with the noise (better than averaging). Oversampling/decimation could help at the cost of bandwidth.

    http://www.bot-thoughts.com/2013/07/oversampling-decimation-filtering.html

    You could combine one or both with the median filtering.

    I wonder if you might be lucky enough that the bandwidth of the noise is outside the bandwidth of the desired signal?
  • John AbshierJohn Abshier Posts: 1,116
    edited 2014-02-18 07:07
    Here are several optimal median filters in c.

    John Abshier
  • Mark_TMark_T Posts: 1,981
    edited 2014-02-19 14:38
    Lawson wrote: »
    Also a 10K pot will be significantly loaded down by the MCP3208 inputs. (1K would be better)

    Huh? Leakage current 1nA, that's 10uV error from a 10k source resistance.
  • frank freedmanfrank freedman Posts: 1,983
    edited 2014-02-19 15:34
    Also check your reference voltage. Twisted pairs or using the '08 as 4 differential inputs can help clean up common mode noise, but if the reference voltage has spikes/noise on it these will still be a problem. Is the reference voltage isolated and filtered? I have seen noise addressed on the Planet Analog site. Also, how solid are your grounds?
    Mark_T wrote: »
    That's the problem, you need to separate the sensitive signals away from the motor leads, you're coupling
    switching transients directly into the signal wire.
    '
    The motor leads should be at least twisted pair, preferrably shielded twisted pair.

    The signal lead from the pot should be shielded (should be the case for any analog sensor).
    Add 10nF to ground at the ADC end of the cable.
  • LawsonLawson Posts: 870
    edited 2014-02-19 17:56
    Mark_T wrote: »
    Huh? Leakage current 1nA, that's 10uV error from a 10k source resistance.

    Taking one ADC sample involves charging up an internal capacitor to the input voltage and then discharging it back to ground via an internal charge DAC. The net effect is that every ADC sample charges and discharges approximately a 100pf cap. When sampling "quickly" (say ~30,000 samples per second) the load from the capacitor charging is similar to a 10K resistor. If you sample "slowly" (say less than 100Hz) and the signal source has significant capacitance (like 0.1uf or more) the average load current from the ADC input will approach the 1nA leakage spec.

    Hook a digital multi-meter to one of the ADC inputs. With the 10K pots set to mid-scale, the DMM will read a different voltage when the ADC is sampling compared to when the ADC isn't sampling.

    Marty
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-19 18:23
    Got a chance to try some different options out tonight. The first try was just reading the ADC output while the motor was running, using a button to control direction. The output was remarkably steady, hardly any variation. Using the same ADC code in the motor run routine ends up with lots of variation! Anyway, I then put in the median filter, didn't get the results I was hoping for. Next I used an array of 10 values, sorted the values and then averaged the middle 8 values. This gave me the best output so far.
    I want to try the median filter again, it may be that I didn't set it up correctly, I was getting some strange numbers out of it! I will probably attempt to re-write one on my own just so I'm sure of the operation.
  • dMajodMajo Posts: 855
    edited 2014-02-21 07:06
    DiverBob wrote: »
    Next I used an array of 10 values, sorted the values and then averaged the middle 8 values.

    This is basically the job done by a median filter. You define the number of samples, they are sorted and, if the number is odd the middle sample is picked, if the number is even the middle two are averaged. You can use your moving average of x samples, discard the 1,2,3 lower and higher values and average the others. The more samples you discard, the more median filter it is. The less values you discard, the more it is close to a moving average.
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-21 18:46
    Ran into some issues when I applied the filter to the next motor, ADC values were all over the place again. So I added some debugging statements (using the PST) to see what values were being averaged in the array and see the output of the array after sorting. This is when I found the array is not sorting. I've been troubleshooting the array but am coming up brain dead in that I can't see the problem. So I'm looking for some fresh eyes to tell me where I've messed up!

    Here is the code of the ADC and sort routines. The GetADC routine runs on its own cog continuously updating three locations.
    pub GetADC | n, ch, ave 
      adc.start(CS, CLK, DIO)                                                       ' start adc driver
      repeat
        pst.Str(String(pst#NL, "Start: "))
        pst.Str(String(pst#NL))
        repeat ch from 0 to 2                                                       'read 3 channels
          repeat n from 0 to 9                                                      'read 9 values into array
            array[n] := adc.read(ch, 1)
            if ch == 1
              pst.Dec(array[n])                                    
              pst.Str(String(", "))
          if ch == 1  
            arraySort(@array, 10)                                                      'sort array
          if ch == 1
            pst.Str(String(pst#NL))
            repeat n from 0 to 9
              pst.Dec(array[n])                                    
              pst.Str(String(", "))
          repeat n from 1 to 8                                                      'drop low and high from array
            ave += array[n]                                                         'total remaining values
          pst.Str(String(pst#NL))
          case ch
            0: femurADC := ave >>= 3                                                'divide by 8 
            1: tibiaADC := ave >>= 3
               pst.Str(String(pst#NL, "Average: "))
               pst.Dec(ave)
            2: coxaADC  := ave >>= 3
    
    
    pub ArraySort (arrayAddr, arrayLength) | j1, i1, val
      'sort input array ascending
      arrayLength--
      repeat i1 from 1 to arrayLength
        val := long[arrayAddr][i1]
        j1 := i1-1
        pst.Str(String(pst#NL, "Val: "))
        pst.Dec(val)
        repeat while (long[arrayAddr][i1] > val) 
          long[arrayAddr][j1+1] := long[arrayAddr][i1]
          pst.Str(String(pst#NL, "Set1: "))
          pst.Dec(long[arrayAddr][i1])
          if (--j1 < 0)
            quit
          long[arrayAddr][j1+1] := val
          pst.Str(String(pst#NL, "Set2: "))
          pst.Dec(val)
    
    

    Here is a snippet from PST of the output from the code above:
    Start:
    
    1500, 1544, 1568, 1537, 1538, 1544, 1488, 1536, 1494, 1389,
    Val: 100730400
    Val: 101189122
    Val: 100664784
    Val: 91030998
    Val: 0
    Val: -1
    Val: 7
    Val: 8
    Val: 9
    1500, 1544, 1568, 1537, 1538, 1544, 1488, 1536, 1494, 1389,
    
    Average: 1681
    
    Start:
    
    1456, 1445, 1457, 1376, 1457, 1452, 1551, 1441, 1472, 1431,
    Val: 90178993
    Val: 95159729
    Val: 94438927
    Val: 93783488
    Val: 0
    Val: -1
    Val: 7
    Val: 8
    Val: 9
    1456, 1445, 1457, 1376, 1457, 1452, 1551, 1441, 1472, 1431,
    
    Average: 1601
    
    Start:
    
    1408, 0, 2656, 1371, 1354, 1368, 1354, 1565, 1328, 1153,
    Val: 89852512
    Val: 89654602
    Val: 102565194
    Val: 75564336
    Val: 0
    Val: -1
    Val: 7
    Val: 8
    Val: 9
    1408, 0, 2656, 1371, 1354, 1368, 1354, 1565, 1328, 1153,
    
    Average: 1520
    
    

    Even the average value doesn't make sense when I review the values going into the calculation. Funny thing is that the other motor was working pretty good with this same code.

    Bob
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-22 10:53
    That's the problem with coding late at night, it amazing the number of typing and indenting errors I can make! Revisiting the code this morning I realized the array was initialized as a WORD but is used as a LONG in code. Also the variables i and j look remarkably alike in the screen font and I indented the last line a bit too much. Once that was corrected the sort started running well and generally the way I wanted it to.
    I did some more searching in the forum for other sorting options and found one by Tracey Allen that runs faster so I have decided to use that sort routine instead. Here is the code snippet:
    PUB insertionsort2 (arrayAddr, arraylength) | j, i, k, val
      arraylength--  
      REPEAT i FROM 1 TO arraylength
        val := long[arrayAddr][i]   'store value for later
        j := i
        k~
        REPEAT WHILE long[arrayAddr][--j] > val  ' compare values
          if ++k == i      ' no swaps in inner loop
            QUIT
        if k
          longmove(j:=arrayAddr+(i-k+1)<<2,j-1,k) ' one longmove
        long[arrayAddr][i-k] := val
    

    After examining the output values I then average the middle 4 values as my ADC output. The robot leg movements have become much more predictable.

    So long until the next problem!

    Bob
  • Mark_TMark_T Posts: 1,981
    edited 2014-02-22 13:35
    Lawson wrote: »
    Taking one ADC sample involves charging up an internal capacitor to the input voltage and then discharging it back to ground via an internal charge DAC. The net effect is that every ADC sample charges and discharges approximately a 100pf cap. When sampling "quickly" (say ~30,000 samples per second) the load from the capacitor charging is similar to a 10K resistor. If you sample "slowly" (say less than 100Hz) and the signal source has significant capacitance (like 0.1uf or more) the average load current from the ADC input will approach the 1nA leakage spec.

    Hook a digital multi-meter to one of the ADC inputs. With the 10K pots set to mid-scale, the DMM will read a different voltage when the ADC is sampling compared to when the ADC isn't sampling.

    Marty

    So long as the source impedance is low enough for the sample cap (which is 20pF, not 100pF) to
    charge to 12-bit accurate level (9 RC time constants) in the 1.5 clock sample window, it will read
    accurate. Doesn't matter what the multimeter says, its averaging the re-charge spikes which are
    not seen by the ADC (only the voltage at the end of the sample/hold window, when the cap is
    fully charged).

    Running at full whack for 5V, which is 2MHz clock, there is 0.75us available, meaning a source
    impedance of ~5k or below is needed for full accuracy.

    However it does seem that this chip does indeed discharge the sample cap as it digitizes it, which
    isn't common for SAR ADC's which normally just compare the voltage with the successive
    approximation.... Other chips have no need to recharge the sample cap for a DC input, unless
    switching between input channels.
  • LawsonLawson Posts: 870
    edited 2014-02-22 21:40
    Mark_T wrote: »
    So long as the source impedance is low enough for the sample cap (which is 20pF, not 100pF) to
    charge to 12-bit accurate level (9 RC time constants) in the 1.5 clock sample window, it will read
    accurate. Doesn't matter what the multimeter says, its averaging the re-charge spikes which are
    not seen by the ADC (only the voltage at the end of the sample/hold window, when the cap is
    fully charged).

    Running at full whack for 5V, which is 2MHz clock, there is 0.75us available, meaning a source
    impedance of ~5k or below is needed for full accuracy.

    Ah, I'm glad someone read the datasheet. Mostly what I remember from my last reading is that the input loading was "significant" and that assuming it was a 10K resistor was a safe approximation.
    Mark_T wrote: »
    However it does seem that this chip does indeed discharge the sample cap as it digitizes it, which
    isn't common for SAR ADC's which normally just compare the voltage with the successive
    approximation.... Other chips have no need to recharge the sample cap for a DC input, unless
    switching between input channels.

    Discharging the sample cap isn't that strange. It lets the chip use a much simpler single supply internal comparator instead of a rail to rail comparator. Additionally, the DAC inside this chip is a switched capacitor design. It looks like Microchip saved the area that would be used by a rail to rail buffer amp and sampling capacitor by using the DAC as the sampling capacitor.

    Marty
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2014-02-23 00:29
    @DiverBob,
    The algorithm two posts back is an adaptation of one in Brandon Nimon's (Bob Fwed) obex object 556.

    How are you acquiring the data that goes into the sort? It is not clear how data gets into the array for the call to insertionsort2(arrayAddr, arraylength)

    It seems to me it would take at least a wrapper, or a different algorithm. The newest sample replaces the oldest sample in a circular buffer, following a pointer makes the rounds sequentially. It would not do to sort that buffer directly and in so doing to scramble the history.

    One way to do it is to move the data to an auxiliary buffer and sort that.

    Another way, one I prefer, is to use an auxiliary ranks table. The ranks table rather than the historical values table is sorted, making comparisons on values[rank[idx]]. Say there are 15 values. Then values[ranks[7]] is the median. When a new value is added to the historical buffer, sorting by ranks is fast, because there will only be one shift to put the new value in its place. The ranks[.] can usually be a byte variable, instead of a long or a word or a string (unless you have more than 256 entries in the main array!). You get one median output for each new data value put in.

    Here is an snippet:
    [COLOR=#020FC0][SIZE=1][FONT=courier new][COLOR=#020FC0][SIZE=1][FONT=courier new] {TTA, EME Systems LLC.
    sorting ranks instead of values, returns median.
    Init must be used first to put allowable values into the ranks table.
    Ranks from 0 to LENGTH-1 index values from lowest to highest}[/FONT][/SIZE][/COLOR]
    CON
      LENGTH = 15  ' best for median calc if LENGTH is odd
                   ' values numbered 0 to LENGTH-1
    
    VAR
      long values[LENGTH]
      byte ranks[LENGTH]
    
    PUB Init  | i   ' put reasonable values in the ranks[.]
      repeat i from 0 to constant(LENGTH-1)
        ranks[i] := i
    
    PUB InsertionSortRanks | i, j, k, val, rank
      repeat i from 1 to constant(LENGTH-1)
        rank := ranks[i]   'store subject rank for later insertion
        val := values[rank]
        j := i
        k~
        REPEAT WHILE values[ranks[--j]] > val  ' compare values
          if ++k == i    ' can test up to k=i values this time through the inner loop
            QUIT
        if k              ' made at least one swap
          bytemove(j:=@ranks+(i-k+1),j-1,k) ' move ranks up to make room
          ranks[i-k] := rank                ' insert the subject rank
      return values[ranks[LENGTH/2]]   ' return the median (could be other statistic)
    [/FONT][/SIZE][/COLOR]
    

    Optimizations are possible with larger tables, for example, by comparing the new value with the existing median and adjusting the sort order. Usually arrays for median filtering are rather small though. The median filter is robust against shot noise. Averaging the middle quartiles seems like a good idea too.
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-23 07:23
    Array[] is a global long variable initialized with as long array[10].
    Pub GetADC | ch, n, ave
      adc.start(CS, CLK, DIO)                                                       ' start adc driver
      repeat
        repeat ch from 0 to 2                                                       'read 3 channels
          repeat n from 0 to 9                                                      'read 10 values into array
            array[n] := adc.read(ch, 1)
          InsertionSort(@array, 10)                                                      'sort array
          repeat n from 3 to 6                                                      'drop low and high from array
            ave += array[n]                                                         'total remaining values
          case ch
            0: femurADC := ave >>= 2                                               'divide by 4 and save to variable
            1: tibiaADC := ave >>= 2           
            2: coxaADC  := ave >>= 2
    

    This is the code used to read 3 channels and the load the global variables. GetADC runs continuously in its own cog. I use jm_mcp3208_ez to run the ADC. I may look into combining these two items to save a cog since I'm using all 8 right now.

    Bob
  • JonnyMacJonnyMac Posts: 9,107
    edited 2014-02-23 08:48
    My object, jm_mpc3208_ez, doesn't use a cog -- those methods run in the cog that launches/calls it.
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-23 12:39
    JonnyMac wrote: »
    My object, jm_mpc3208_ez, doesn't use a cog -- those methods run in the cog that launches/calls it.
    I noticed that once I started looking in depth at the cog usage. Right now I need 9 cogs, got to figure out how to share one now.
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2014-02-23 19:07
    How often does the program call for those results from the ADC--Maybe it doesn't need to be running in its own cog? If possible slip the ADC reads into the quiet times then the actuator is not moving.

    Hmm "tibia", "femur", "coxa". Humanoid?

    With regard to ADC chips and input current errors. The effect is most evident when a muliplexer is involved, as in the MCP3208. You know the (Rinput*Csample)/Tsample is whack when the readings on one channel is affected by the reading on another channel.
  • DiverBobDiverBob Posts: 1,110
    edited 2014-02-24 03:27
    How often does the program call for those results from the ADC--Maybe it doesn't need to be running in its own cog? If possible slip the ADC reads into the quiet times then the actuator is not moving.

    Hmm "tibia", "femur", "coxa". Humanoid?

    With regard to ADC chips and input current errors. The effect is most evident when a muliplexer is involved, as in the MCP3208. You know the (Rinput*Csample)/Tsample is whack when the readings on one channel is affected by the reading on another channel.

    Thanks for the suggestion, that may be possible although I'm looking at the floating point cog right now as a possible multi tasking cog.

    The tibia, femur and Coxa are references for a large hexapod I've been developing for over 2 years. The code is for running a single leg. Servos rant strong enough for a 200 pound robot so I'm using linear actuators with potentiometer position feedback, hense the mcp3208 ADC. Each leg has its own prop board with a seperate central processor to control them. The project is documented over in the Parallax Robotics forum under "next large robot' if you are interested in more information. This has been my baby for a while and I'm committed to getting this thing walking sometime this year! So I will be back with more questions later! It's time to start developing the circuit boards next, haven't been able to find any out there that have the capabilities I'm looking for.

    Bob
  • dMajodMajo Posts: 855
    edited 2014-02-26 01:42
    I think the quickest way to sort this is:

    -use a linear array of longs
    -the hi world have the sample value while the lower keeps the sample number
    - in running condition the array is always sorted except for the last sample that is going to be inserted.

    procedure
    - first scan: search for lower loword (or lower sampleid to avoid a complete pass)
    - replace the position found with the new sample (hiword)
    - compare the new sample value with position-1 value
    - start second scan (insertionsort/bubblesort just one pass) from the replaced position to one boundary or the other based on the previous comparison (or on the fact that the oldest sample id was found at one boundary or the other)
    - during the sort scan you can compare hiwords or longs (being the value in the hi word) but always swap longs (to preserve sample age)

    only two array scans, most of the times partial, independently of how many array elements. No nested loops, no additional buffers required. Easy to average the middle samples because the array is always sorted.
Sign In or Register to comment.