Shop OBEX P1 Docs P2 Docs Learn Events
Problems with decimal values and displaying them. Please help! — Parallax Forums

Problems with decimal values and displaying them. Please help!

RussMRussM Posts: 27
edited 2013-03-01 00:18 in Propeller 1
I am new to SPIN so I am sorry if this problem seems trivial to you all. I am working a project that using a rotary encoder to measure distance in thousands of an inch. What I want to do is each time the encoder value gets incremented, to add 0.000983 to my overall distance. When the encoder value gets decremented, i want to subtract 0.000983 from my overall distance. My problem is that I can't represent decimal values at all. I tried just writing the value 0.000983 to the screen and it printed out 981522434 instead. I tried utilizing the f32 object but to no avail. Any help would be greatly appreciated!
CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000
  '_xinfreq = 6_250_000
  
  TX_PIN = 0
  BAUD = 19_200
  'MS_001 = CLK_FREQ / 1_000
  'CLK_FREQ = ((_clkmode-xtal1)>>6)*_xinfreq
  
  LCD_PIN       = 27
  LCD_BAUD      = 19_200
  LCD_LINES     = 4
  LCD_COLS      = 20


  ENCODER_PIN   = 4


OBJ
  quad  :       "QuadDecoder"
  LCD    :       "FullDuplexSerial"
  f32           : "F32"
  f32_orig      : "Float32Full"
  fs            : "FloatString"


VAR
  long  offset                                          ' example variable that will be accumulated to
  long distance
  long temp
  long distance_tick
  long f1
  long fA
  


PUB main


  LCD.start(TX_PIN, TX_PIN, 00, 19_200)    'Initialize FullDuplexSerial.spin
  f32.start
  f32_orig.start 


  offset := 0                                          ' initialize the accumulator
                                                        ' You can set it to any desired value
  LCD.tx($0C)
  quad.start(ENCODER_PIN, @offset)                      ' start the encoder reader




    LCD.str(string("Value = "))
    LCD.tx($0D)
    LCD.str(string("Distance = "))
    
    distance_tick := 0.000983
    distance := 0
    temp := 25


    repeat
      if offset > temp
         distance := distance + distance_tick
      if offset < temp
        distance := distance - distance_tick
      LCD.tx($89)
      LCD.dec(offset)       'Encoder always initializes at 25 for some reason
      LCD.tx(32)
      LCD.tx($9F)
      LCD.dec(distance_tick)
      temp := distance
      waitcnt(clkfreq/10 + cnt)

Comments

  • kwinnkwinn Posts: 8,697
    edited 2013-02-27 09:06
    The problem with using floating point for this is rounding errors, and they accumulate when you do the calculations like you are doing them. It would be more accurate and simpler to use encoder counts as the basis for your positioning, convert the encoder count to position, and convert desired position to the encoder count.
  • Mike GreenMike Green Posts: 23,101
    edited 2013-02-27 09:21
    In addition to kwinn's comments, the .dec method works with integers, not floating point. There is a specific floating point input/output conversion object called FloatString that you'd need to use if you still want to use floating point. It converts floating point values to strings in a temporary buffer suitable for display with the .str method.

    You initialize some variables like distance to zero. It just so happens that a floating point zero is the same as the integer zero. It would be better from a documentation standpoint if you used 0.0 when you want to initialize a variable to zero for floating point use. There's a similar issue with temp and offset. You either need to convert distance to an integer using .FTrunc or .FRound to assign it to temp or you need to treat offset and temp as floating point throughout your program. That includes calling the floating point compare routine to compare the two values in your if statements.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2013-02-27 10:11
    I agree with Kwinn's suggestion to use encoder pulses as you base unit.

    If you don't want to use encoder pulses as the base unit, you use millionths of an inch as the unit instead of inches. Then your math could still be done with integers.

    If using kwinn's method, you could do the conversion when needed (for display purposes) using millionths of inches in the calculation and then use the method "DecPoint" to place the decimal point in the desired position.

    Here's the DecPoint method.
    PUB DecPoint(value, denominator)
      if value < 0
        Pst.Char("-")
        -value
          
      if value => denominator
        result := value / denominator
        Pst.Dec(result)
        value //= denominator     
      else    
        Pst.Char("0")
      Pst.Char(".")  
      repeat while denominator > 1
        denominator /= 10
        if value => denominator
          result := value / denominator
          Pst.Dec(result)
          value //= denominator
        else
          Pst.Char("0")
    
    

    An example of how to use it can be found in post #4 of this thread.
  • RussMRussM Posts: 27
    edited 2013-02-27 10:33
    Thank you both Mike Green and kwinn for your help! I managed to get my code working using the FloatString object as follows:
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
      '_xinfreq = 6_250_000
      
      TX_PIN = 0
      BAUD = 19_200
      'MS_001 = CLK_FREQ / 1_000
      'CLK_FREQ = ((_clkmode-xtal1)>>6)*_xinfreq
      
      LCD_PIN       = 27
      LCD_BAUD      = 19_200
      LCD_LINES     = 4
      LCD_COLS      = 20
    
    
      ENCODER_PIN   = 4
    
    
    OBJ
      quad  :       "QuadDecoder"
      LCD    :       "FullDuplexSerial"
      f32           : "F32"
      f32_orig      : "Float32Full"
      fs            : "FloatString"
    
    
    VAR
    
    
      long  offset                                          ' example variable that will be accumulated to
      long distance
      long temp
      long distance_tick
      long f1
      long fA
      
    
    
    PUB main
    
    
      LCD.start(TX_PIN, TX_PIN, %1000, 19_200)    'Initialize FullDuplexSerial.spin
      f32.start
      f32_orig.start 
    
    
      offset := 0                                          ' initialize the accumulator
                                                            ' You can set it to any desired value
      LCD.tx($0C)
      LCD.tx($11)
      quad.start(ENCODER_PIN, @offset)                      ' start the encoder reader
    
    
    
    
        LCD.str(string("Value = "))
        LCD.tx($0D)
        LCD.str(string("Distance = "))
        
        distance_tick := 0.000983
        distance := 0.0
        temp := 25.0
    
    
        repeat
          if offset > temp
             distance := f32_orig.FAdd(distance,distance_tick)
             temp := offset
          if offset < temp
            distance := f32_orig.FAdd(distance,distance_tick)
            temp := offset
          LCD.tx($89)
          LCD.dec(offset)       'Encoder always initializes at 25 for some reason
          LCD.tx(32)
          LCD.tx($9F)
          LCD.str(fs.FloatToFormat(distance, 8, 6))
          waitcnt(clkfreq/10 + cnt)
    
  • Mike GreenMike Green Posts: 23,101
    edited 2013-02-27 10:40
    It's still not going to work properly because the comparisons in the if statements are done with integer arithmetic, but you're initializing temp to 25.0 which is not the same as 25. You probably want to initialize temp to 25.
  • RussMRussM Posts: 27
    edited 2013-02-27 10:47
    I changed temp to 25 when I initialize it. Also a new problem. I tried to change FAdd to FSub in the second if statement and now it wont display any information at all anymore.
  • MagIO2MagIO2 Posts: 2,243
    edited 2013-02-28 23:43
    I also do not understand why you want to stay with float numbers. Fix-point integer is much easier, faster, needs no additional code and no additional COG for the float AND it is more accurate!
    ( http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems )

    An 32 bit integer can have the values ranging from 0 to 4,294,967,295 (unsigned). So, in your case you can use the least significant digits for the fraction and still have a range of 0-4,294.
    distance_tick:=0_000983
    distance:=0_000000
    offset:=25_000000
    ...
    if step_direction
      distance += distance_tick
    else
      distance -= distance_tick
    ...
    
    (I don't really understand how the offset and temp variables should work in your code. The offset is changed by the encoder and the code probably works fine when the encoder is turned slow and/or the code to calculate the new distance is fast enough. But in any other case the offset might have changed by a value that is equivalent to more than just one single distance_tick!)

    The only thing you have to adapt is the dec-method which needs to insert a "." at the fixed position and start printing the number at least with 7 zeros (0.000000)!

    If the range is not enough it's easy to add another long and do keep the fractional part in one long, the integer part in another.

    I would calculate the distance in the encoder-code directly! The encoder code already has a loop to read the encoder and increment/decrement an offset. So, why add another loop (which wastes a COG) to convert the encoder ticks into a distance?
  • Duane DegnDuane Degn Posts: 10,588
    edited 2013-03-01 00:18
    I agree with MagIO2 about using integer math.
    MagIO2 wrote: »
    The only thing you have to adapt is the dec-method which needs to insert a "." at the fixed position and start printing the number at least with 7 zeros (0.000000)!

    The "DecPoint" method I posted above does this for you.
Sign In or Register to comment.