View Full Version : Spin>Assembly conversion

T Chap
10-16-2006, 03:01 AM
I have a few sections of code in Spin that I need converted to assembly. Looking through the manual there aren't a lot of examples to adapt from, so I am looking for advice on how to get the info needed to convert the working Spin parts. In a perfect world you could put in Spin and press convert, and it would give the asm equivalent that you could substitute where needed. Here are a few examples of simplpe parts needed converting:

Post Edited (originator99) : 11/3/2006 1:29:59 AM GMT

Mike Green
10-16-2006, 03:35 AM
You need to start with "where's the data coming from?". The cogs have their own memory space and the HUB memory is sort of a peripheral device as far as the cog is concerned, like who keeps track of XpinState, YpinState, and ZpinState?

The next question is "what are you trying to do?". It doesn't always make sense to try to translate SPIN to assembly. It's often better to go back to the function you're trying to accomplish. For example, there's no case statement in assembly. It looks like you want to have pin 26 a one unless several different values are in some range. Maybe it'd be better to use some other way to keep track of the ranges.

Mike Green
10-16-2006, 03:50 AM
Just for a simple conversion, the 2nd piece would look like:

:loop mov temp1,OUTA ' get the current OUTA value
andn temp1,mask ' and mask off the other bits
mov temp2,XpinState ' the bits are in the wrong order
rev temp2,#32-4 ' so reverse them
shl temp2,#6 ' and shift into position
or temp1,temp2 ' accumulate new bits
mov temp2,YpinState ' the bits are in the wrong order
rev temp2,#32-4 ' so reverse them
shl temp2,#10 ' and shift into position
or temp1,temp2 ' accumulate new bits
mov temp2,ZpinState ' the bits are in the wrong order
rev temp2,#32-4 ' so reverse them
shl temp2,#14 ' and shift into position
or temp1,temp2 ' accumulate new bits
mov outa,temp2 ' and copy to OUTA
jmp #:loop
mask long %00000000_00000011_11111111_11000000
temp1 long 0
temp2 long 0

The new OUTA bits are accumulated separately to avoid glitches in the I/O pin settings as the output register is changed. If they didn't need to all be changed at once, the bits could be masked off one item at a time and or'd directly into OUTA. If the bits didn't need to be reversed, that would be another instruction we wouldn't need. It would actually be easier for XpinState, YpinState, and ZpinState to all be kept in the same long, already shifted into position. It all depends on where they come from and what other manipulations you need to do on them.

Post Edited (Mike Green) : 10/15/2006 8:53:53 PM GMT

T Chap
10-16-2006, 04:23 AM
Thanks Mike for the info. Regarding your question Who keeps track of X,Y,Zpinstate: They are pin states generated by 3 different cogs X,Y,Z pub generating the states. Each state is a half step sequence in a stepper routine, for each step input, the sequence increments/decrements through 8 data patterns. You can see the file below for clarification. As each state is loaded ionto the variable, the looping function simply picks up any new state and outputs it to the pins. This way, I can alter the output states externally of the half step sequence. I will test the code above you wrote, many thanks for taking the time to put that together.

PS the file posted is messy but works! I tested a 2.5 hour machine code last night, where the machine is set to stop if the step counter and encoder counter are out by more than a few pulses on 3 different motors/encoders, it never stopped. What is happening is that there is a small discrepancy when going faster, in that with Spin, first it checks to read the Xposition counter, then it checks the encoder position and compares. If the difference is more than a few pulses, there is a problem, and what is happening is that at high speeds, the time between checking the xpos and encoder is causing a variance versus low speeds. I suspect that there is too much time passing between checking Xpos and the encoder value pos[0]. I was thinking the asm would solve it. Although, with the right range specified as a mask, there is no problem for normal operation.

Post Edited (originator99) : 11/3/2006 1:30:48 AM GMT

Mike Green
10-16-2006, 05:22 AM
If XpinState, YpinState, and ZpinState are coming from HUB memory, you'll have to change the "mov" instructions to appropriate "RDBYTE" instructions.

It sounds like you'd have more than enough time for a single cog in assembly to do the stepping for all three axes plus checking for the encoder vs stepper error.

T Chap
10-16-2006, 05:57 AM
Ok that sounds good, how does one start to learn how to do it?

Graham Stabler
10-16-2006, 07:14 AM
Start with the manual under RDBYTE

Many of the parallax objects (such as mouse) use this command.

In the mouse object the cog is passed the address of the globle variables when it is initialized:

cognew(@entry, @par_x)

The @entry is the label at the start of the assembly code, the @par_x is the name of the first global variable the cog might want to read or write to.

In the assembly that address is called par which stands for parameter.

mov p,par ' puts parameter address in p (so par is not destroyed)
add p,#5*4 ' Adds 20 to the address (5 longs is 5*4 bytes)
rdlong _dpin,p ' Loads par_dpin (the 5th global variable) into _dpin
add p,#4 ' Adds 4 to the address to get to the next variable (one long is 4 bytes)
rdlong _cpin,p ' Loads par_cpin (the 6th global variable) into _cpin

So the program does its thing comunicating with the mouse, it updates some local variables which it then wants to write to the global variables in main ram.

I'll move away from the mouse.spin code because it does the writing in a loop using self modifying code, its not hard to understand but lets stick to the basics.

The first global variable was par_x, this holds the x position. In the asm the x position is held in _x so to write this number and the next one _y:

mov p,par ' puts parameter address in p (so par is not destroyed)
wrlong _x,p ' puts _x into par_x in main memory
add p,#4 ' Adds 4 to the address to get to the next variable (one long is 4 bytes)
rdlong _y,p ' puts _y into par_y

Finally you have a function in your spin to get the varible from main memory:

PUB abs_x : x

x := par_x

I hope that helps. If it makes sense have a proper look at the way mouse.spin writes all the variables, read up on the movs and movd commands.


Mike Green
10-16-2006, 08:09 AM
Attached is a simple translation of your X, Y, Z cogs. I haven't had time to do any significant commenting, but I'll work on it.
What about the encoder stuff?

Post Edited (Mike Green) : 11/3/2006 2:17:29 AM GMT

T Chap
10-16-2006, 10:29 AM
Thanks guys, that is a lot to digest. I'll study the mouse object as you suggested Graham. I'll plug in the code right now Mike and see if I can make sense of it. I appreciate the effort. As far as your question on the encdoder, I'm not sure what you meant but I am using the Rotary Encoder object that is already in asm. The first code block of the thread takes the xpos and multiplies it by 2. Since one rev = 800 quadrature pulses, and 1 rev = 400 half step pulses: you have to multiply the xpos to get the same count as the encoder. Since I have never seen any such code for comparing the encoder to the actual step pulses, this is what I came up with:

Post Edited (originator99) : 11/3/2006 1:31:33 AM GMT

T Chap
10-16-2006, 11:02 AM

Just curious how to run it, I already had in the var's xpos, ypos, zpos, but it gives an error that it is expecting some instructions, so I remove the variables, and it gives other errors, below is what I was tring as a test.

Ok another thought the original version in spin had a function called timer, which basically just watched the pins, and if no input for 1000 clocks, it set the outa pinstates to %0000 as you recall. What this allows is for the motors to be turned off when not being used to get rid of the pwm noise, zinging the motors. Is the asm code you wrote considering the need to shut the motors? Sorry to waste away your Sunday at this.

Mike Green
10-16-2006, 11:46 AM
Here's a slightly polished version complete with other routines to stop and start the assembly routine and to allow for a timeout. When you start the routine, you supply an initial x/y/z position. This position can be read at any time by calling the appropriate routines which return the current value of xPos/yPos/zPos. These values get changed when a step pulse is seen by the assembly routine. The timeout value is also zeroed if any position changes occur. In your SPIN calling program, you can check for inactivity periodically (say once a second) by doing a:

if stepper.timeStep > maxTime

This assumes that stepper is the name of the object with these routines in it. I didn't put in a built-in way to restart things if step pulses start coming in again. If that's important, I'll show you how to do that another time. Note that when the assembly cog is stopped, the I/O direction gets set to inputs which let the motor control bits float. If they're pulled down to 0V, that should shut off the motors.

Post Edited (Mike Green) : 11/3/2006 2:17:51 AM GMT

T Chap
10-16-2006, 01:20 PM
Hey Mike that is great! It works very well. For some reason X and Z are in reverse though for what is needed. I went in and reversed these:

Not sure how much it screws up stuff but it works to solve the direction.

1. Keep getting an error 'Expecting a variable" at return ++timeStep, so I change it to just timeStep and it compiles. I added the variables on both the object and program but that creates other errors.

2. Not sure why the OUTA you have is affecting the ability to change pin output states from the main program. Your outa assignements only go to P23, I am trying to alter P26, and something in the stepmotor object is preventing any outside use of the pin. I'll sort that out with trial and error.

This is very cool Mike, I appreciate it. Let me know if you want one of the final boards when I get the revisions in this week.

Post Edited (originator99) : 11/3/2006 1:34:44 AM GMT

Mike Green
10-16-2006, 09:28 PM
That's fine, I must have gotten the direction (0 forward/ 1 reverse or 1 forward / 0 reverse) wrong.

1) I mistyped. It should be "return ++timeOut"

2) I don't understand. The only DIRA bits set to one (during initialization) are 6-17. If a DIRA bit is zero, that shouldn't affect any other cog's use of the pin (they're or'd together in the chip).

T Chap
10-17-2006, 02:46 AM
Thanks for the explanation, I'll retry that timeout fix. Just to make sense of the need to turn off the motors, what happens is when the motors are not in use they are noisey, with the high pitched sound they receive from the PWM'd signals. What I did on the Spin version was to create a loop that does nothing but send out 3 variables to the motors: i.e.

outa[14..17] := XpinState

So that the motor move code really doesn't move the motors, it just sets the state for the global variables. The control loop sends out whatever the variable state is, and that varibale can be changed by any other code, such as the "timer" code which checks for step pulses, if none, start counting to X, then set the variable to 0. If a new Step pulse comes along, the timer is restarted, but at the same time, the motor move code is sending out new states. The motor control loop picks up whatever vaiable change it sees and sends it to the motors. This may be convoluted, but it's the best solution I could find, as having the motor move code directly connected to the outa's meant it was hard to get then to release their grip on the outs, thus resetting them to 0 was difficult for the timer function, they were fighting each other for control of the pins. I hope this makes sense. Certainly no rush to find a solutuion,but stopping the cog is not workable, as this is something that happens repeatedly, the cog would have to reamain active always retaining the step and encoder count. This timeout needs to be adjustable, as there may be cases to not release the motors ever, as they may need to stay "braked". When at 0, they are freewheeling. Well not 100% free, close.

Mike Green
10-17-2006, 03:30 AM
OK, here's another update. The comments should be self explanatory, but, when the timeOut variable is set to -1, the assembly routine turns off the motor drive, but it continues to check for step pulses. If any come in, the motor steps as usual and the timeOut variable is zeroed as usual. Your SPIN routine still needs to check for a timeout using a call to timeStep, but calls timeStop instead of stop when the timeout is exceeded. That way, the cog continues to run, checking for step pulses.

Post Edited (Mike Green) : 11/3/2006 2:18:18 AM GMT

T Chap
10-17-2006, 11:01 AM
Mike I have been working with the object(renamed to CNC). The timeout is working fine, I can read the values from the main program and have it timestop at a value.

The test for x, y, and zPosValue is puzzling. I've included the code, if you would kindly tell me if there is something wrong with the test. The max value for testing > or < the x,y, z values is 65537, or a byte value max. Above that value in the equation, all motors are DOA. The travel of X table is greater than 65k travel, or it wouldn't be a big deal.

Post Edited (originator99) : 10/17/2006 5:21:28 AM GMT

T Chap
10-17-2006, 01:47 PM
17 days straight of hard work, frustration, learning, and designing the new cnc controller board, and finally now back to getting some parts made. Special thanks to Mike for all the help with assembly. Shown are heat sink strips being cut, although they haven't been really been needed.

Mike Green
10-17-2006, 09:23 PM
The problem you're having doesn't make any sense. The variables involved are all 32 bit longs and the values that cause you trouble are not at 16 bit word boundaries since -65537 is $FFFEFFFF and -65538 is $FFFEFFFE.

Just to see if it would help, change the problematic IF statement to:

IF -stepmotor.zPosValue > 66000

Graham Stabler
10-18-2006, 03:37 AM
is it worth trying a longer delay in motor stop test?

T Chap
10-18-2006, 07:30 AM
I am not sure what was going on, I deleted all that code related to the 65k issue, started over from scratch with it and all seems fine now. Must have been a typo on my part on the earlier version. So far I a m happy with the system. One day it might be nice to see how an asembly version of the comparison code works, but that can wait. Here is the final version that is workable. The error max is high, but there are times when it hits it when runnng high speeds, not when real work is being done at the lower speeds. Here is the final code for the project. Mike's assembly program is named CNC. The Rotary Encoder Object is required, as well as the PWM object.

Mike Green
10-18-2006, 10:28 AM
Add the following to the CNC object in anticipation of having the comparison code in assembly:

PUB posBuffer
return @xPos

T Chap
10-18-2006, 10:54 AM
Ok got it. Hey Mike if you can think on this and see if there is a way for me to drive the motor from within the program(not extrenally via step) as needed, let me know how it could be done. The reason, when you first boot up the controller, the sequence that the motor driver is sitting at upon reset is usually out of sync with where the motor really was left prior to shut down. This creates a situation where sometimes, you press jog on an axis after boot up, the driver is sending out a sequence not knowing exactly where the motor was left. The result, occasional missed steps on first jog after boot up. For example, say the motor was left braked at sequence 5 of 8 half steps, and you boot up, send it part 1 of the sequence, it takes a few steps to get the motor back in sync with where the driver is. I may not be stating this correctly, but in my previous Spin code, the problem manifested in occasional missed step triggers, and was easily solved by jogging the motor 32 steps on boot up prior to running the encoder cog. After the init jog, the motors were in sync with the code sequence, and I then booted the encoder object, simulatneaously resetting the x,y, and zPos to 0. A that point, all motors were synced, encoders set to 0, all pos to 0, ready for work. I am noticing a few missed pulses on first jogs now as well, but am hamstrung at making the motors jog with the assembly object. Slowly I will get some feel for what you wrote and may come to be able to manipulate it, hopefully that is.

Mike Green
10-18-2006, 11:26 AM
I've rewritten your main routine and done the error checking in assembly. Read the comment about why other cogs were not needed.

About resyncing the motor, 1) Can all the motors be stepped 32 steps initially at the same time? 2) Would it work to change "driver" to do 16 steps in one direction, then go back 16 steps in the other direction? Is there some other way that would work better.

Post Edited (Mike Green) : 11/3/2006 2:18:49 AM GMT

T Chap
10-18-2006, 12:02 PM
Ok thanks for the edits. I am getting an error "Undefined Symbol", where you put:

:fetchDriver xxx x xxxx
add " fetchDriver" highlighted in blue on error message

All motors can be jogged at the same time, no difference, any short amount is fine, 8, 16, 32 should do it. As far as going forwards and back, makes no difference as it just takes a few steps to get in sync. The trick is to not start comparing until after the they are jogged.

Post Edited (originator99) : 10/18/2006 5:13:38 AM GMT

Graham Stabler
10-18-2006, 08:48 PM
try making it :fetchDriver in the add statement

Mike Green
10-18-2006, 09:34 PM
Thanks Graham.

Here's the modified "driver" with an initialization routine that effectively does 8 step pulses forward before starting operations as usual. I haven't tried it, but it looks correct with the changes.


Post Edited (Mike Green) : 11/3/2006 2:19:40 AM GMT

T Chap
10-19-2006, 12:44 AM
Thanks Graham that solved the issue with the error. I am experiencing the strangest behavior I have seen to date. Check the comments in the update. They make absolutely no sense.

**Jog works as planned!

Post Edited (originator99) : 10/18/2006 5:58:29 PM GMT

T Chap
10-19-2006, 02:31 AM
Here are updated files for both the main program and the CNC driver.

You can see attempts to re-Zero the x,y,z positions after bootup, plus start the encoder reading after everything else is reset. I deleted the code not required for out of range. The timer code is fine. What is going on is that on boot, the move_ena is going low at once, causeing the PC software to be paused since it sees a warning. Trying nubers from 0 - 10000 for upper and 0 - -100000 doesn't affect it. Upon bootup and reset after jogging, all values should be showing up as 0, so the formula (xpos*2) - encoderxPos should = 0 as well.

Mike Green
10-19-2006, 06:10 AM
You can't re-zero the positions that way. The driver object never looks at the values in the SPIN variables, just keeps them updated. Easiest thing is to zero the cog copies after initialization stepping rather than before. I've modified CNC.spin to do this and the driver.start routine waits until the initialization stepping is done and the xPos/yPos/zPos parameters are used to initialize the cog's copies.

I also changed CNC_Controller.spin again to eliminate a separate cog for the timer. If the last statement in a SPIN thread is a COGNEW or COGINIT, there's probably no point to starting a new thread (cog) since the one containing the COGNEW or COGINIT is just going to stop itself on the next statement (an implicit RETURN to a COGSTOP(COGID)).

On the "move_ena" problem. The I/O pin is initially an input (until compareEntry sets it up as an output). If the circuit has a pulldown resistor in it or whatever it's connected to treats a high impedance as a logic low, then that's what's going on. The best thing would be to add a pullup resistor to 3.3V to the signal line. Alternatively, you could put "OUTA[move_ena] := 1" and "DIRA[move_ena] := 1" right after the "PUB START" in CNC_Controller and a "WAITCNT(CLKFREQ/1000 + CNT)" followed by a "DIRA[move_ena] := 0" right after the call to "doCompare". That last addition makes sure that the "compareEntry" routine can control the move_ena pin once it has time to start up.

Post Edited (Mike Green) : 11/3/2006 2:20:06 AM GMT

T Chap
10-19-2006, 10:02 AM
Hello. I just did some tests and the move_ena is always floating using the object. I ran other tests to check the pin is functioning properly and it is. The pin feeds both inputs on a 7400 NOR, which inverts the logic before leaving the board. I can pull the NOR either direction with a resistor but it makes no difference to what the Prop is doing.

Some strange reults that aren't making sense:

1. If I comment out the timer routine, then the doCompare routine causes the move-ena pin to float forever, but hovers on the low side causing the active low alarm.

2. If I leave the timer routine in just as you saw it, the doCompare routine still leaves move_ena floating, but somehow it floats at a different place, and does allow the motors to run without an alarm, but Y doesnt run at all with the timer in.

3. With the timer in, if there actually is comparing taking palce, it is not known as the pin is floating. Grabbing the motor and stalling it doesn't not reflect a change on the pin, it still floats.

Mike I certainly don't wont to create a bottomless pit with this project, I can work with the original Spin version, so please do not feel obligated to spend more time solving the issue. You have alrady gone above and beyond.

Mike Green
10-19-2006, 10:55 AM
Try this. I made a common mistake trying to be "smart" and used some immediate source operands as counters. Unfortunately, they held HUB memory addresses which can easily exceed 9 bits. Anyway, that's fixed and the compare routine will (hopefully) compare the proper values. I added the SPIN statements to set move_ena to a logic high early in the program, yet not interfere with its use later. Let me know if this helps.

Post Edited (Mike Green) : 11/3/2006 2:20:23 AM GMT

T Chap
10-19-2006, 11:23 AM
Ok some progress! How many steps are you jogging on the init? The error is exactly 16 steps prior to any manual jogging. I suppose you are jogging 16 steps in that case and there is no reset or compensation taking place. The mask has to be set at -17,17 to run, at 16 it alarms. I tried setting longer delay 80_000_000 prior to doCompare. Any other suggestions on how to reset or sync the encoders and xyzPos? I tried setting the init start state to16,16,16, but improved the error by 1 only. Pretty close now!

Mike Green
10-19-2006, 11:37 AM
The initialization routine does 8 steps. Multiplying by 2 gives 16!

In any event, I found a mistake in the driver where I cleared the initial values for xPos/yPos/zPos before I used them to set the initial position. Try this version of CNC.

Post Edited (Mike Green) : 11/3/2006 2:20:40 AM GMT

T Chap
10-19-2006, 11:50 AM
That seems to have done the job! I have the error cap at +- 8, and ran it pretty fast and it was good. With the spin comparison, I had to keep it at +-20 at least at high speeds. Keep in mind that you can't cut that near as fast I am I producing errors, but when rapid planing or going home it does hit high speeds, but not on a regular basis as I have been dong in tests. At low speeds, an error rate of lower would suffice, but 8 is great for detecting stalls, binds etc. I'll cut some parts and post tomorrow how it went. Many many thanks!

Mike Green
10-19-2006, 12:14 PM
You're welcome.