Spin and PWM

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".


  • bbrien,
    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.
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-13 - 00:40:53
    Parse means to pull apart and evaluate. For example, you might give the Propeller a command using simple text input. The Propeller would parse the string for commands and values and act accordingly. I do this quite frequently. It's easy to test through the programming/debug port, and then change to a Bluetooth module for remote commands.

    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
        ' 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.
  • bbrienbbrien Posts: 209
    edited 2020-06-13 - 00:25:27
    Not at all , it is simply a 9vdc clock motor from Meade instruments which is a part of a LX50 SCT Telescope 10 inch. The code will run the tracking clock when all eight inputs are inactive . yes I have fighting with system for about 3 years , and nothing has worked well. I can't get the serial to work so I am trying this approach. No I will use two counters one for the RA motor and other for the DEC motor
  • I willl use one cog and test both groups if input in turn and run the whole program at 5MHz.
  • I'm still a little fuzzy about what you're after. It seems, though, that you want four pulse outputs of that can be 1.5 to 2.5ms wide, running at 50Hz.

    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).
      ' 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
      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 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.

  • Actually there is only one pin that outputs this pulse. two of the for pins will output a high or low, the fourth pin also outputs a pulse, which drives the DEC motor.The pulse width will have to be adjusted in the end. I am looking at page 163 in Propeller Education Kit Labs : Fundamentals." tC := clkfreq", can I replace the term clkfreq with a expression like 100000 to generate a pulse period of 50 Hz.
  • Let me add a little bit of info, In the first part of the program there are four inputs(switches=iso-opto couplings)Each switch will operate a motor and a direction bit (0 or 1) (1) sw1 = Motor1 and direction 1-0.
    (2) sw2 = Motor2 and direction 2-0.
    (3) sw3 = Motor1 and direction 1-1.
    (4) sw4 = Motor2 and direction 2-1.
  • The period for 50Hz is 1/50th of a second. To get the ticks in 1/50th of a second, divide clkfreq (ticks in one second) by 50.

    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
        ' 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.
  • frank freedmanfrank freedman Posts: 1,634
    edited 2020-06-13 - 12:21:03
    @bbrien, In your initial post, you mentioned serial and then creating a PWM output to run a motor. Are you replacing a hand controller on an LX50 or creating your own computer interface to one? Also, this looks to be related to your posting in the general forum "fdserial between two flip modules"

    @JonnyMac, In your case, not sure how much stuff is liberated vs. repatriated.
  • bbrienbbrien Posts: 209
    edited 2020-06-13 - 18:48:19
    Frank: Every thing is being redesigned from the bottom up.first the brain in the mount is being replaced by a 'Flip' and the hand controller, which was no longer available, also has a 'Flip'. Since I was unable to use serial communication, I have to use the output from the hand controller and feed to the input of the mount brain. There two groups of inputs in the mount brain.(assignment)
  • Am looking an example program for pwm.
    _XINFREQ = 5_000_000
    long pulsewidth
    long cycle_time
    long period
    PUB Go
      period:=period +Cycle_time
    what I want to do is use this in my program
    the problem is I tried to set up a variable containing the terms starting with (repeat
    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.

  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-20 - 21:39:44
    I can't tell what your magic numbers mean, so I'm going to ignore them.

    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
        if (ina[M0_RUN])
          motors.set_speed(0, 25_0)
          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.
  • can you expand on "setup" and [mo_run] ,I don't quite understand these. The inputs need to be done individually or I can setup as a "case"
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-17 - 17:02:16
    can you expand on "setup" and [mo_run] ,I don't quite understand these.
    Setup is the name of a method that you call to configure all of your objects and IO (this is not a rule, it is how I do things to keep my code clean and organized). M0_RUN is the constant name for the pin that will tell that motor to run. At the top of your program you would have a CONstants section to define such things.
      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:
      #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
        if (ina[M0_RUN] == GO)
          motors.set_speed(0, 25_0)
          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.
    The inputs need to be done individually or I can setup as a "case"
    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.
  • The inputs need to be done individually or I can setup as a "case"
    Perhaps this is what you mean: Can the inputs be considered as a group? Yes, they can. For example:
    main | states
        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)
        if (states & %01)
    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.
  • I have a total of 8 inputs in 2 groups of four. The first group of inputs are assigned to the outputs in groups of two. I am concerned with the second group. This group has four switches which operate individually or possibly in two directions at the same time. There are two e-w direction switches and two n-s switches which handle two output pins in conjunction. The output enables are on different counters(a and b). Case(a) rmotorPin (set_pwm(17,0,400))
    rdirPin(18) = high
    dmotorPin (set_pwm(16,1,0))
    ddirPin(15) =low
    This is only an example of the case statements.
  • If you have four switches there are sixteen possible states. Maybe write out a truth-table to describe the states. If you're looking to get additional feedback from forum members, let me suggest that you stick with English descriptions -- I don't think your pseudo-code descriptions are helping others understand what you're after.

    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.
      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).

  • The four switches from the guide camera only operate for a matter of a few milli seconds when they are in operation. They only operate when the target star moves from one pixel to a nearby pixel. usually one direction at a time. I use both counters just in case two happen to move at the same time.
  • 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')
        if(b1 == 0)
        data = '  ';
    How do I rewrite this in Spin?
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-19 - 05:53:08
    I am beginning to understand the hamster's plight with the wheel...

    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).
      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
        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.
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-19 - 19:47:34
    In the event you have one of those joystick like things (navigation stick) that can have one or two outputs active at the same time, this is a better way to handle the code. It debounces all inputs at once and can return a multi-directional input (e.g., north-east).
      NS_SPD = 10_0                                                 ' speed for N/S motor
      EW_SPD = 10_0                                                 ' speed for E/W motor
      STOP   =  0_0
    pub main | ctrl
        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
        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.
  • The camera program can in itself be akin to a digital joystick if such even exists. also I ddidnt include all of the #defines which identify all of the pins and states, too many!.
  • ...if such even exists.
    It does, else I wouldn't have mentioned it. Good luck with your project -- I think I've done all I can do.
  • bbrienbbrien Posts: 209
    edited 2020-06-20 - 21:50:02
    "time.pause(1)" results in an error. I am sending my program which resulted in several errors, I didn't understand your dual motors object fully. please check my work correct if you can.
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-20 - 21:57:15
    "time.pause(1)" results in an error.
    Look in the template archive I posted; it contains a file called jm_time_80.spin that handles delays and facilitates delta timing. If you don't want to use that library, there are plenty of mechanisms to delay the program for one millisecond. For example:
      waitcnt(cnt + (clkfreq / 1000))
    ...but this isn't as clean to my eyes as time.pause(1).
    I didn't understand your dual motors object fully.
    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.
    please check my work correct if you can.
    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.
  • I tried to zip the project so I could send but unable to locate the files in the mem. so I will type it longhand so it will be late.
  • Use the archive in the prop tool to zip up your Spin project.
  • bbrienbbrien Posts: 209
    edited 2020-06-20 - 23:32:19
                _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
                long pulsewidth
                long cycle_time
                long period
             PUB  trans
                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
                            phsa := PulseWidth
                            period := period + Cycle_time
                            waitcnt := (period)
                ' ' scan direction control inputs
                     return "N"
                     return "E"
                     return "S"
                     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
                   move := Scan_buttons
                   case move
                          motors.set_speed(M1_EW, STOP)
                          motors.set_speed(M2_NS, STOP)
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-21 - 00:46:14
    Magic numbers (for IO pins) are never a good idea. What does this code do? Obviously, I can see that it stomps on your motor control pins. Why? What purpose does that serve?
    PUB trans
      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?
  • JonnyMacJonnyMac Posts: 6,737
    edited 2020-06-22 - 14:48:33
    Looking at your code -- and pounding my forehead into my desk until it's bloody -- it doesn't seem like you're actually doing PWM the way we would normally think about it.. The pulse output is just goosing a motor, up to 100x per second (100Hz) if a button is held down. Is this what's happening?
Sign In or Register to comment.