Spin and PWM; Abandoned

I am giving up on propeller C and going back to Spin. cant get serial to work so I shall use 4 outputs from one "flip' to the inputs of a second mirror the pulses on the outputs of a second 'Flip'. First question is what is "parse". Second question ,I need a pwm pulse around 50 Hz with a duty cycle of 1.5 to 2.5 ms. How do I set the number of "ticks".
Comments
Your pulse frequency and pulse width is close to the band of common hobby servos (50Hz and 1mS to 2mS with 1.5mS mid-band)
There are a large number of SPIN and C programs on OBEX and elsewhere for hobby servo control, so you can modify one of those for your requirements.
To get fixed-frequency PWM you need to run your code in a loop; this is where waitcnt is your friend. For example:
t := cnt repeat ' this code runs 50x/second waitcnt(t += constant(CLK_FREQ / 50))
I generally think of PWM having a duty cycle expressed as a percentage of the period; your case seems more servo-like. Is that the case? More details would be helpful. Each cog has two counters than can create precision pulses, but in your case if the pulse is will never occupy the entire period, you can use one counter and stack them.If you provide more details I can knock up a bit of code to help.
If that's the case, this code works (I checked with a 'scope). As this is a specialty thing, there's no need to create an external object file -- you can use what I call an "embedded object." They're not always as flexible as standard objects (e.g., which IO pins to use), they're easy to manage because they're right in your main code (and can take advantage of that).
con ' Pulse output pins ' -- code expects contiguous group from base PULSE_3 = 15 ' pulse output pins PULSE_2 = 14 PULSE_1 = 13 PULSE_0 = 12 ' base pin var long postack[32] ' stack for Spin cog long width0 ' pulse width long width1 long width2 long width3 pub start_pulse_outs longfill(@width0, 2000 * US_001, 4) ' default to midpoint cognew(pulse_outs, @postack) ' start the cog pub set_width(ch, us) '' Set channel pulse in microseconds '' -- ch is 0..3 '' -- us is 1500..2500 (1.5 to 2.5ms) if ((ch => 0) and (ch =< 3)) ' good channel? width0[ch] := (1500 #> us <# 2500) * US_001 ' convert us to ticks pri pulse_outs | t, ch t := cnt repeat repeat ch from 0 to 3 ' loop through channels ctra := (%00100 << 26) | (PULSE_0 + ch) ' set to NCO mode frqa := 1 phsa := -width0[ch] ' start new pulse dira[PULSE_0 + ch] := 1 ' make pin an output waitcnt(t += constant(5 * MS_001)) ' let hold for pulse window
Note that the pulse outputs are cascaded: you get one pulse every 5ms; this gives you the 20ms period which is 50Hz.(2) sw2 = Motor2 and direction 2-0.
(3) sw3 = Motor1 and direction 1-1.
(4) sw4 = Motor2 and direction 2-1.
In my program, I have a constant called CLK_FREQ which saves the program a little time (clkfreq is a system variable that has to be retrieved; using a constant is always faster). Repeating myself (from the first post), this loop will run at 50Hz
t := cnt repeat ' this code runs 50x/second waitcnt(t += constant(CLK_FREQ / 50))
IMO, it's better to use a calculated constant instead of a magic number like 100000; this allows you to change to clock frequency and still get the same results. Look at the timing section in my demo program -- there's some good stuff there that I liberated from other forum members.@JonnyMac, In your case, not sure how much stuff is liberated vs. repatriated.
CON _CLKMODE = XTAL1+PLL2X _XINFREQ = 5_000_000 VAR long pulsewidth long cycle_time long period PUB Go dira[7]~~ ctra[30..26]:=%00100 ctra[5..0]:=7 frqa:=1 PulseWidth:=-5000 Cycle_time:=clkfreq/1000 period:=cnt repeat phsa:=PulseWidth period:=period +Cycle_time waitcnt(period)
what I want to do is use this in my programif(ina[5]==1) outa[17]:=pwm
the problem is I tried to set up a variable containing the terms starting with (repeatphsa:=PulseWith
period:=period +Cycle_time
waitcnt(period) but got an error. is there a way to do this or do I have to write this for each instance. There are 5 times I need to do this.
Let's say you have an input that, when active, you want your PWM to run at 25% speed. When that input is low, you want the PWM off (0%).
I suggest you approach it like this
pub main setup repeat if (ina[M0_RUN]) motors.set_speed(0, 25_0) else motors.set_speed(0, 0)
The setup method is where you'd configure all your IO and instantiate objects.Easy, right? And clean and maintainable, too. I've attached my dual motor PWM object. It's simple code that has been used in several motor control applications for my clients.
Tip: Avoid the use a magic numbers where possible. In your code you would have to change the high-time ticks if you decide to change your clock speed.
Edit: Newer version of driver attached to post below.
con M0_RUN = 5
Using named constants like this is standard practice because it makes programs easier to understand. You could be even more verbose if you like be defining additional constants, for example:con #0, STOP, GO
This will assign the value of zero to STOP, and one to GO (this style is called enumerated constants). Note that we use CAPS to tell other programmers that this is a CONSTANT, not a variable. Now you can do this:pub main setup repeat if (ina[M0_RUN] == GO) motors.set_speed(0, 25_0) else motors.set_speed(0, 0)
This works the same as before, it's just a bit more verbose to prevent any confusion. Remember, program listings are for humans, and we should strive to make them easy on the eyes (and brains) of our fellow humans. I have attached my standard template. All of my programs start with this. By using a template I keep things organized and consistent. If you're using Propeller Tool you can point to a template file using the Preferences (F5) dialog -- it's on the Files & Folders tab.I still don't understand your end goal. This is the biggest problem for those seeking help (like my programming clients). You're getting fractured help here because your descriptions have been fractured. This is a tricky thing about being human: we have this thing were we assume others understand what we understand. It happens to us all. Maybe answering these questions will help other forum members help you with your program.
-- How many inputs do you have?
-- How does the state of each input affect the program?
-- What are their interactions?
-- How many outputs do you have?
-- How do the outputs behave in relation to the state of the program?
I have been doing product development for more than 30 years -- describing the "product" is the hardest part of the process. With some of my clients who are producing commercial products that I'm designing circuits and code for, I tell them to write the manual first. I have two patents for a sprinkler controller that I created for Toro and found back then that the easiest way to sell the concept to the company (before I asked the VP to write a $250K check) was to create a user manual that conveyed what the controller did, and how it interacted with the customer. I am proud to say that the product is still in production nearly 25 years later (updated, of course, but still follows my design rules).
I'll conclude this long post with a tip: Experiment with coding outside your project. Connect some LEDs and buttons to a Propeller and just play. We often get tense about a project, so put it aside for a minute so that you can experiment for fun and education. Taking the pressure off of your experimenting often makes that time more fruitful. I experiment every day. It pays off.
main | states setup repeat states := read_inputs case states %00 : two_off_one_off %01 : two_off_one_on %10 : two_on %11 : two_on
This code will run two_on if switch 2 in on, regardless of the position of switch 1. If switch 2 is off, what happens is dependent on the position of switch 1. This code behaves in the same manner:if (states & %10) two_on else if (states & %01) two_off_one_on else two_off_one_off
I think the case structure is a bit cleaner. In either case, the read_inputs method is reading the two inputs an returning a two-bit value with switch 1 state in bit 0, and switch 2 state in bit 1.rdirPin(18) = high
dmotorPin (set_pwm(16,1,0))
ddirPin(15) =low
This is only an example of the case statements.
It appears that pins 17 and 18 are the speed and direction pins for one motor, and 16 and 15 are for the second. Is that the case? If yes, I suggest that you code like pros do and use named constants.
con M1_PWM = 17 M1_DIR = 18 M2_PWM = 16 M2_DIR = 15
If you used the dual motor object I shared with you, it would be instantiated like this:motors.start(-1, M1_PWM, M1_DIR, -1, M2_PWM, M2_DIR , 20_000)
...and now all the pin manipulations are abstracted for you -- just set each motor speed/direction (negative speed is reverse).b1 = input(3); b2 = input(4); b3 = input(5); b4 = input(7); if(b1 == 1) data = 'W'; if(b2 == 1) data = 'N'; if(b3 == 1) data = 'E'; if(b4 == 1) data = 'S') Switch(data) case'W': high(rdirPin); pwm_set(rmotorPin,0,400); if(b1 == 0) data = ' '; break; case'N';.....
How do I rewrite this in Spin?Break the program into small pieces. Start by naming you IO pins; not doing this is foolish, and makes a program more difficult to read and understand. Using well-chosen pin names helps document the behavior of the program (it becomes self commenting).
con BTN_N = 4 BTN_S = 7 BTN_E = 5 BTN_W = 3
If you have to change your wiring later, this is the only section the needs updating; the names will flow through the program.Next I suggest you create a routine than scans the buttons and gives you a direction state. The way you've written your pseudo-code suggests that only one button can be active at a time, hence this routine returns the direction of the first pressed button.
pub scan_buttons '' Scan direction control inputs if (debounce(BTW_N, 5) == YES) return "N" if (debounce(BTW_E, 5) == YES) return "E" if (debounce(BTW_S, 5) == YES) return "S" if (debounce(BTW_W, 5) == YES) return "W" return 0 ' switch is idle pub debounce(pin, ms) repeat ms ' debounce loop time.pause(1) ' wait 1ms if (ina[pin] == 0) ' if button released return NO ' fail return YES ' button was pressed
Now you can put it in your main loop. This assumes the use of my dual motor control object.pub main | move repeat move := scan_buttons case move "N" : motors.set_speed(M_NS, 25_0) "E" : motors.set_speed(M_EW, 25_0) "S" : motors.set_speed(M_NS, -25_0) "W" : motors.set_speed(M_EW, -25_0) other : motors.set_speed(M_NS, STOP) motors.set_speed(M_EW, STOP)
This is not the complete listing but it is the major parts, along with the code and objects I've shared in this thread. Start with the template I provided as it will help you keep organized.con NS_SPD = 10_0 ' speed for N/S motor EW_SPD = 10_0 ' speed for E/W motor STOP = 0_0 pub main | ctrl repeat ctrl := get_direction(5) case ctrl ' NESW %1000 : set_motors( NS_SPD, STOP) ' north %1100 : set_motors( NS_SPD, EW_SPD) ' north-east %0100 : set_motors( STOP, EW_SPD) ' east %0110 : set_motors(-NS_SPD, EW_SPD) ' south-east %0010 : set_motors(-NS_SPD, STOP) ' south %0011 : set_motors(-NS_SPD, -EW_SPD) ' south-west %0001 : set_motors( STOP, -EW_SPD) ' west %1001 : set_motors( NS_SPD, -EW_SPD) ' north-west other : set_motors( STOP, STOP) ' stop pub set_motors(nsspeed, ewspeed) motors.set_speed(M_NS, nsspeed) motors.set_speed(M_EW, ewspeed) pub get_direction(ms) | states, ins states := %1111 ' assume all pressed repeat ms time.pause(1) ins := (ina[BTN_N] << 3) | (ina[BTN_E] << 2) | (ina[BTN_S] << 1) | ina[BTN_W] states &= ins return states
Again... notice that I'm breaking things into small pieces and using names that helps you understand the flow of things. I didn't give names to all the possible switch states because it was easy to insert a comment in the case structure to identify the switch input bits.Thanks;
waitcnt(cnt + (clkfreq / 1000))
...but this isn't as clean to my eyes as time.pause(1).Hmmm... sounds like you need about 10 minutes and the Propeller manual (PDF is linked in the Help menu). FWIW... have a look at www.JonyJib.com. Every one of their products that uses a DC motor uses that driver. Since I am meeting with them today about a couple new projects I touched up the comments in my dual motors object -- maybe this version will be helpful. It's attached.
I don't know what you want anyone to correct -- you haven't posted any Spin code, only pseudo-code that is meaningful to you, but has me (and maybe others) scratching my head. I've done my best to help, but you push back on every suggestion. I really don't know what else to do.
CON _clkmode = xtal1 _xinfreq = 5_000_000 M1_EW = 17 M1_Dir = 18 M2_NS = 16 M2_Dir = 15 BTN_N = 4 BTN_E = 5 BTN_S = 7 BTN_W = 3 VAR long pulsewidth long cycle_time long period PUB trans dira[8..11]~ dira[15..18]~~ outa[15..18]~ outa[17] := ina[8] outa[18] := ina[9] outa[16] := ina[10] outa[15] := ina[11] PUB Go ctra[30..26] := %00100 ' configure counter a to NCO ctra[5..0] := 17 ' set output pins ctrb[14..9] := 16 frqa := 1 PulseWidth := -5000 Cycle_time := clkfreq/100 period := cnt repeat phsa := PulseWidth period := period + Cycle_time waitcnt := (period) PUB Scan_buttons ' ' scan direction control inputs if(debounce(BTN_N,5)==1) return "N" if(debounce(BTN_E,5)==1) return "E" if(debounce(BTN_S,5)==1) return "S" if(debounce(BTN_W,5)==1) return "W" return 0 PUB Debounce(pin,ms) repeat ms time.pause(1) <<<<<<<< error if(ina[pin] == 0) return no return yes PUB main|move repeat move := Scan_buttons case move "N": motors.set_speed(M2_NS,25_0) high(M2_Dir) "E": motors.set_speed(M1_EW,25_0) High(M1_Dir) "S": motors.set_speed(M2_NS,25_0) low(M2_Dir) "W": motors.set_speed(M1_EW,25_0) low(M1_DIR) other: motors.set_speed(M1_EW, STOP) motors.set_speed(M2_NS, STOP) low(M1_Dir) low(M2_Dir)
PUB trans dira[8..11]~ dira[15..18]~~ outa[15..18]~ outa[17] := ina[8] outa[18] := ina[9] outa[16] := ina[10] outa[15] := ina[11]
BTW... putting that method first means it will run first, and having nothing else to do, the Propeller will reset.Why are you running at 5MHz?