Shop OBEX P1 Docs P2 Docs Learn Events
Prop controlled via serial, floating point and variable scaling [solved] — Parallax Forums

Prop controlled via serial, floating point and variable scaling [solved]

xanaduxanadu Posts: 3,347
edited 2016-10-26 03:08 in Propeller 1
I'm working on part of a project. The entire thing goes like this:

1. API reads two coords (x,y) from some Windows program and writes them to a text file.
2. My Java app reads the text file every second and sends the values to the Propeller via serial.
3. Propeller read values, moves servos, sends confirmation it's done and waits for more.

Here is a video of everything working. I also demonstrate it not going to bad values that could physically damage it.



I have two issues.

Issue #1: The values in the text file need to be floating point.

Is that possible using SPIN/Serial?

Issue #2: The values in the text file need to be 1-180 for pan and 1-360 for tilt.

The API is going to write values that the Dynamixels can't use. I need to scale these values accordingly.

Pan servo = 1 to 360 degrees in text file, converted to 0 to 4095 by Propeller

Tilt servo = 1 to 180 degrees in text file, converted to 200 to 550 by Propeller

I could take it apart and make it so the bottom of the tilt is 0 and not 200, I feel like that would make it a lot easier.

For pan I just need to move 11.3 encoder ticks per degree, which seems easy enough. But before I get into that I really need to make sure I can pass the floating point numbers to the Propeller.

This doesn't need to be extremely accurate. I attached the SPIN code. Thank you.

EDIT: Added "serial math.zip" this is a broken down version of the problem, requiring only Propeller to run.

Comments

  • I am not sure why you need floating point for degrees.

    Just use fixed point integer, say 0-360.00 and 0 to 180.00 and convert this in java on the PC.
    Write it as 36000 (or18000) in your text file and divide on the prop while scaling to 4095.

    value0to4095:= (degree0to36000 * 100) / 879

    value200to550:=((degree0to18000 * 100) / 5142) + 200

    should do this quite good

    The bottom tilt is also just a addition of 200.00 to your calculated value out of the degrees.

    Enjoy!

    Mike
  • I'm not really sure why I was planning on doing the converting on the Prop I should do it in Java.

    The app needs to have floating point because the API will only output degrees in 0.5 degree increments to the text file.

    I will read the number in Java and do the math there to get the Dynamixel friendly value. Thanks for the example of the variable scaling.
  • msrobots has given the best answer ... do the floating point on a machine where it is trivial, and do integer work on the prop.
    If you do need to do floating point on the prop, there are some libraries like F32 that work, with trig function, conversions, log, etc. F32 is limited to single precision (32 bits per float), and the code becomes less transparent to read (everything is a function call).
    I am using Tachyon Forth, and represent all angle in tenths of a degree. Pretty simple and fast.
  • Thanks. I completely agree. It's not up to me though. I guess I had originally wanted the Propeller doing the work because this Java app is temporary. They want their software to connect to the device directly. I asked again this morning and got confirmation. I'm not getting paid to do any of this, it's just for a learning experience, plus I blew the deadline by months.

    I put the project aside and started playing with serial objects. I'm able to echo a floating point number via serial using this object.
        repeat 
          ser.RxStr(@myStr) 'get serial data
          ser.str(@myStr)   'echo serial data
    

    That is great. Now I have to find a floating point Object and see if I can get the echo to include math.

  • xanaduxanadu Posts: 3,347
    edited 2016-10-24 22:19
    Well I'm stuck.

    Can anyone tell me why this doesn't work? All you need is PST, no servos, etc.

    It is supposed to -

    1. rx serial string with decimal (works)
    2. float serial data string (no go)
    3. float multiply by 11.37 (no go)
    4. decimal to integer so rest of the code can use it (works)

    Just need to figure out #2 and #3...
  • You declared mystr as a long array with 1 element. This is only four bytes, and is not big enough to hold the received string. It should be big enough to hold the longest string you might receive. Try making it an array of 20 longs.
  • I bumped it up to 20. I guess I left it like that from the echo test that works. It is still hanging on floating the string and the multiply operation. It will echo the string in floating point or integer just fine so I know it is the two float lines.
  • Dave HeinDave Hein Posts: 6,347
    edited 2016-10-24 23:16
    You are using stre.decimalToInteger(posa) incorrectly. decimalToInteger converts an ASCII string containing decimal digits to an integer. It expects a string pointer for the parameter. You are passing it a floating point value. You should use f.FRound(posa) instead.

    I wish Spin had a way to check calling parameter types. That would probably solve over half the problems that people encounter.
  • The stre.decimalToInteger(posa) actually works. What I should do is round first and then convert to integer. Round alone won't work because it still outputs the decimal and the Dynamixel can't use it.

    I'm glad there was Dynamixel code in SPIN, even if this part takes me a few days I'm still ahead :)
  • I also wrote a Dynamixal driver that may finally get some commercial use in a client product. I've attached it if you'd like to give it a whack. I does let you access all the features of the Dynamixel (I've tested with a couple of AX-12s). It's written in my typically verbose style, so it should be easy to understand.

  • I don't see how stre.decimalToInteger(posa) could possibly work. The code for decimalToInteger is shown below. It converts a decimal string into an integer number. You are passing a floating point number contained in posa, which is formatted as a 32-bit IEEE floating point number. decimalToInteger will interpret this as a string pointer, which will point to some random location in hub RAM or in the ROM. decimalToInteger will most likely return a value of zero unless the random memory location happens to contain byte values between $30 and $39. In that case it will return some random decimal number.

    I think you are confused by the meaning of "decimal". Just saying "decimal" by itself is a bit ambiguous. You should use the term "decimal string" instead. decimalToInteger converts a decimal string to a 32-bit integer. FRound rounds a 32-bit floating point value and converts it to a 32-bit integer. The dec method prints a 32-bit integer as a decimal number.

    I'm not familiar with Dynamixel, but from what I can tell it's a line of servos. I assume the driver code requires a fixed-point value to control it, so the result from FRound should work as long as it's scaled properly.
    PUB decimalToInteger(characters) | sign '' 10 Stack Longs
    
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '' // Converts a decimal string into an integer number. Expects a string with only "+-0123456789" characters.
    '' //
    '' // If the string has a "-" sign as its leading character the converted integer returned will be negated.
    '' //
    '' // If the string has a "+" sign as its leading character the converted integer returned will not be negated.
    '' //
    '' // Returns the converted integer. By default the number returned is positive and the "+" sign is unnecessary.
    '' //
    '' // Characters - A pointer to the decimal string to convert. The number returned will be 2's complement compatible.
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
      characters := checkSign(ignoreSpace(characters), @sign)
    
      repeat (strsize(characters) <# 10)
        ifnot(checkDigit(characters, "0", "9"))
          quit
    
        result := ((result * 10) + (byte[characters++] & $F))
      result *= sign
    
  • JonnyMac wrote: »
    I also wrote a Dynamixal driver that may finally get some commercial use in a client product. I've attached it if you'd like to give it a whack. I does let you access all the features of the Dynamixel (I've tested with a couple of AX-12s). It's written in my typically verbose style, so it should be easy to understand.

    Thanks I will check it out. I'm using an AX and MX. There are only a couple of differences, the limit address and the increased goal positions. I like the Dynamixels a lot, I'm happy that there is more Propeller support for them, especially in SPIN.
  • Dave,

    I attached the file where stre.decimalToInteger will echo an integer if you give it a decimal string via pst. I'm not really sure why it works, but I would rather it round anyway.

    It has to take a number, say 135.5 * 11.37 = 1540.63 then convert to int. 1540 = Dynamixel happy. The only part that doesn't work is * 11.37.
  • xanadu wrote: »
    JonnyMac wrote: »
    I also wrote a Dynamixal driver that may finally get some commercial use in a client product. I've attached it if you'd like to give it a whack. I does let you access all the features of the Dynamixel (I've tested with a couple of AX-12s). It's written in my typically verbose style, so it should be easy to understand.

    Thanks I will check it out. I'm using an AX and MX. There are only a couple of differences, the limit address and the increased goal positions. I like the Dynamixels a lot, I'm happy that there is more Propeller support for them, especially in SPIN.

    FTR, I use a FET (BSN20) level shifter between the Propeller and the Dynamixels. The Propeller serial pin is pulled up to 3.3v with a 3.3K resistor; this connects to the Source pin. The Drain pin connects to the Dynamixel buss and is pulled up to 5V with a 4.7K resistor. The Gate is connected to 3.3V. It's a standard circuit that gets used in I2C apps -- works well for half-duplex serial as well. Luckily, EFX-TEK products include this circuit so I had a ready test platform when I wanted to experiment with Dynamixels.

  • Dave HeinDave Hein Posts: 6,347
    edited 2016-10-25 12:44
    Xanadu, it's amazing that your test program works at all. You have two errors in it that happen to work together. Let's look at you repeat loop:
      repeat
       
        posa := ser.RxStr(myStr)                     'rx serial data string with decimal
    
        posa := stre.decimalToInteger(posa)   'convert to whole number for Dynamixels
    
        ser.dec(posa)                         'tx serial data 
    
    The first two lines in the repeat loop should be:
      ser.RxStr(@myStr)
      posa := stre.decimalToInteger(@myStr)
    
    The only reason your code works is that myStr initially contains a value of zero, and RxStr always returns a value of zero, which you store in posa. So you end up using location zero as your string buffer. If you typed in a very long string you would overwrite the method table in your program, and it would hang. However, RxStr limits the input string to 16 bytes, so it is inadvertently protecting you from scribbling into the method table. The first 16 bytes in memory contain the Spin header, which is only used at boot up. So all these factors work together to allow your program to run without crashing.
  • Thanks for the explanation. What you have posted works great. I don't want to strip the decimal places off before doing the math, else there would be no movement for 0.5 degrees.

    So the only thing left is how to multiply @mystr by 11.37 before doing the decimal to int.
  • Dave HeinDave Hein Posts: 6,347
    edited 2016-10-25 17:16
    This should work:
      repeat
       
        ser.RxStr(@myStr)                     'rx serial data string with decimal
    
        posa := fs.StringToFloat(@myStr)      'float serial data string (not working)
    
        posa := f.FMul(posa, 11.37)           'scale float variable (not working)
        
        posb := f.FRound(posa)                'convert to whole number for Dynamixels
    
        ser.dec(posb)                         'tx serial data 
    
  • I thought so too, but it hangs. If you start removing lines it's the f.Fmul that kills it. I'm not sure it that is because I'm using the float object wrong or not.
  • I still do not get for what you need floats.

    You multiply by 11.37 and then round.

    why not multiply by 1137 and then divide by 100?

    No need for decimal point in your serial data, make 36000 out of 360 degrees and you have 2 fixed decimal places and are able to use integers for serial and for the calculation.

    Way faster as any float object.

    Stupid question - did you start the float Object? It needs a own COG.

    And FRound is just rounding a float number, but it is still a Float, not a integer.

    So your example

    135.5 * 11.37 = 1540.63 then convert to int. 1540

    is the same as

    (1355 * 1137 ) / 1000 = 1540

    confused,

    Mike
  • Mike, thanks. I completely agree that we don't need floating point over the serial connection. I have this working perfectly without it. The problem is that I forgotten a few factors regarding the Java app and now that may not be used at all. The application presenting the data has a Smile API that will let you output the values, but that is it, you're stuck with values like 123.5. There is really no way to work with it before it hits the text file. Then eventually the text file will be a thing of the past and the coords will go straight to the serial point. I'm stuck using the Prop for all math. The cool thing is now they can enter coords manually if needed, and I have a feeling they will.

    I did start the float object, and I did try floating the rounding variable.
  • The call to FMul should work. I tried a short test program, and it works fine for me. Can you try this code in your repeat loop and post the output?
      repeat
        ser.RxStr(@myStr)                     'rx serial data string with decimal
        ser.str(@myStr)
        ser.tx(13)
        posa := fs.StringToFloat(@myStr)      'float serial data string (not working)
        ser.hex(posa, 8)
        ser.tx(13)
        posa := f.FMul(posa, 11.37)           'scale float variable (not working)
        ser.hex(posa, 8)
        set.tx(13)    
        posb := f.FRound(posa)                'convert to whole number for Dynamixels
        ser.dec(posb)                         'tx serial data 
        ser.tx(13)
    
  • I was going to try to debug this code since I have used the F32 object. However, you need to archive your entire project so potential helpers don't have to search for the objects your program uses. The FloatString object in the Propeller library (version 1.3.2) does not have a StringToFloat method. I cannot see the code you are running. I am worried that myStr is only 4 bytes long. RxStr uses bytemove to move 16 bytes to @myStr, overwriting myStr, posa, posb and whatever the next 4 bytes in memory are.

    John Abshier
  • Dave, it will echo the first number I give it, then stops echoing numbers.

    Josh, good call. That might be why Dave is getting different results. I uploaded the archive, there are additional objects I've been using in there as well. The notes at the top of the code explain the workflow. Thanks!
  • Dave HeinDave Hein Posts: 6,347
    edited 2016-10-26 00:48
    I tried your program, and it hangs on the call to StringToFloat. The problem is that you have references to the Float32Full object from two different objects, and Float32Full uses VAR variables to store state information. Float32Full is referenced from the top object and FloatString. I got the program to work by modifying Float32Full.spin, and changing the VAR variables to DAT variables. Other people must have encountered this problem before. I'm curious how this problem has been resolved in the past.

    EDIT: Another solution would be to just copy the StringToFloat method to your top object. Then you would only have one reference to Float32Full.
  • You guys are the best... I would have never figured that out.

    Thanks!!

    1=11
    1.5=17
    1.9=22
    2=23

    The Dynamixels are going to love this!
  • I just discovered something else about the floating point math that may have attributed to some of the troubleshooting issues.

    As it turns out the Dynamixel turns CCW as the absolute encoder values increase. That's no good so I had to add another operation to reverse it.

    This doesn't work:
           posap := f.FSub(4096, posap)         'reverse motor direction
    

    This does:
           posap := f.FSub(4096.0, posap)         'reverse motor direction
    

    While it may be obvious to some, the Float32 Object should mention the decimal is required.

  • This is a Spin issue, because without the decimal it looks like an integer.
  • There's probably a SPIN manual I should read.

    I ran an accuracy test using a pretty cheesy method but things are looking really good at this point.

Sign In or Register to comment.