Shop OBEX P1 Docs P2 Docs Learn Events
PWM on a single pin for 1 second @20 khz without starting a cog? — Parallax Forums

PWM on a single pin for 1 second @20 khz without starting a cog?

As the title says, how would I be able to pulse a pin at 20khz with a 50% duty cycle for 1 second when needed without starting a new cog? I already have all cogs used in my current program. Any help is appreciated!

Comments

  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2016-04-08 23:26
    Since you are talking about a square wave then just use a counter in frequency mode. In Tachyon I would have that running on pin 5 as fast as I could type:
    5 BLINK 20 KHZ 1 second MUTE
  • Since you are talking about a square wave then just use a counter in frequency mode. In Tachyon I would have that running on pin 5 as fast as I could type:
    5 BLINK 20 KHZ 1 second MUTE

    Sometime you are just evil, Peter, but you are always correct.

    I really need a vacation down under to get the gist out of RPN and FORTH.

    Maybe I start with mounting my globe upside down. Just to get a feeling for it.

    Thanks,

    Mike
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2016-04-09 00:03
    msrobots wrote: »

    Sometime you are just evil, Peter, but you are always correct.

    I really need a vacation down under to get the gist out of RPN and FORTH.

    Maybe I start with mounting my globe upside down. Just to get a feeling for it.

    Thanks,

    Mike

    My globe has Australia on top and you guys down under (it's all the same to Sol) so that means RPN is actually AFN ( Australian Forward Notation) so that is probably why it's all back to front for you guys :):)
  • Ok @eagletalotim,

    each cog has two timers, they run in parallel to normal execution. You can set modes and frequencies and pins and tons of other things I do not really understand by myself, but they are there. The nice thing is the timer are very fast, even when used out of spin, not assembly.

    @Evanh posted the link for a detailed description. You basically need to set pin, frequency and mode, start it, wait a second and stop it.

    @Peter, looks like I need to add AFN to my known Acronyms 'Australian Forward Notation'. Very nice. It looks like I am on the right trail there with the Globus south side up, since you did that too. Will try, wait some weeks to adjust, and test Tachyon again.

    A friend of mine told me he has "CDO". I ask what "CDO" is. He said: It is like "OCD" but in alphabetical order...

    Enjoy!

    Mike
  • Would it be possible to adjust the duty cycle while the counter is running over the period of 1 second? Basically, it would pulse at 50% duty cycle and increment down to 0% over the 1 second. The time may be a little longer than 1 second, but I can stick with 1 second for now.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2016-04-09 01:12
    The Prop doesn't have any real PWM modes, there is the duty-cycle mode but the frequency varies with duty cycle and could be as high as 80MHz. You have to use software to emulate PWM however most are fairly low frequency and even my built-in PWM only manages 32 channels of 8-bits at up to 4.67kHz although you can reduce the resolution to increase the frequency on any channel. So the question is rather than being vague and us having to guess what you really want (first PWM, then square wave, now PWM) just tell us what you are trying to do rather than how you think it should work.

    BTW, here is a scope shot of Tachyon executing the command:
    22 BLINK 5 KHZ 8 ms MUTE
    800 x 480 - 30K
  • I am trying to control the flow of fluid once pressure has built up enough using a solenoid. I want to start with a high flow (50% duty cycle) and bring it back to no flow (0% duty cycle) over the span of 1 second to avoid abrupt fluid pulses in the system I am designing. Hard, on / off makes it jolt and I can't reduce the flow all the time.

    The solenoid needs around 20khz from what I read a long time ago. I can't find the spec sheet on it anymore :(

    I don't know what Tachyon is and I am sure it would be a PAIN converting my entire program (over 1000 lines of code) to another language I know nothing about :( It does seem quite simple if it takes 1 line of code to do what 3 or 4 lines of spin can do.

    This is what I have figured out so far..... which is not much at all....

    ctra := %00100_000 << 26 + Controller_Pin
    frqa := $A000_0000 ' ????????? lost.....
  • The thing is not to convert code but to understand what you are trying to do and to write it anew, even in the same language you can make heaps of improvements. However in rewriting it into Tachyon I'm afraid that you won't end up with 1,000 lines of code because as you saw in my little one liner you can do a lot with Tachyon very simply. So I would be surprised if you even had 100 lines of Tachyon code to replace your old code. Even then if I were to look at that code I would probably reduce it to 50% of that again.

    The other thing to remember is that Tachyon is interactive to you can just try an idea with instant results (and feedback) much as I did the one liner.
  • You can't program the counters in advance to alter the duty cycle over a period of time, but you could have a spot in one of your other cogs that changed the counter settings. The Prop has a duty mode PWM, but it's not a fixed 20khz with a varying duty. It's a varying frequency as well though, better for things like generating voltages than precise period timings.

    You could probably cherry pick 20-ish very specific settings for frequency and duty to give you 20khz with the timings you need, and have one of your cogs push those settings through the counter registers for you over your 1 second period. Would it need to be really fine granularity for it to work?
  • jmgjmg Posts: 15,173
    edited 2016-04-09 06:56
    Would it be possible to adjust the duty cycle while the counter is running over the period of 1 second? Basically, it would pulse at 50% duty cycle and increment down to 0% over the 1 second. The time may be a little longer than 1 second, but I can stick with 1 second for now.

    I think you can do exactly that, using two counters in NCO mode, and you can push that 1 second out to longer (or shorter).
    What you do is pgm one NCO to 20.000kHz and the other to 20.0005kHz, and then externally XOR with a SingleGate Logic device.
    (1G57/58/97/98 etc)
    Over a time of 1 second the phase of those walks half a period, which swings XOR from 0% to 100%
    If you want a movement of 0 to 50% or 50% to 0, then you need a quarter period in 1 second, or 20.00025kHz
    Once you hit the target, you need to reconfig the counters, otherwise the phase movement continues and you will get a triangle wave modulation of Duty Cycle : Up-Down-up-Down

  • Actually I'm beginning to question this 20kHz figure for PWMing a solenoid. That would be very ineffective due to the inductance and most solenoids are only run at a few hundred Hz. Are you sure that figure wasn't actually 200Hz??? Even a simple Spin loop could probably do what you want.
  • jmgjmg Posts: 15,173
    Actually I'm beginning to question this 20kHz figure for PWMing a solenoid. That would be very ineffective due to the inductance and most solenoids are only run at a few hundred Hz. Are you sure that figure wasn't actually 200Hz??? Even a simple Spin loop could probably do what you want.

    Maybe it's a smarter solenoid, and the PWM is converted to a DAC value ?

  • jmg wrote: »
    Actually I'm beginning to question this 20kHz figure for PWMing a solenoid. That would be very ineffective due to the inductance and most solenoids are only run at a few hundred Hz. Are you sure that figure wasn't actually 200Hz??? Even a simple Spin loop could probably do what you want.

    Maybe it's a smarter solenoid, and the PWM is converted to a DAC value ?

    In which unlikely you would be able to use duty cycle mode effectively.

  • evanhevanh Posts: 15,916
    edited 2016-04-09 10:05
    So exact frequency isn't important and 50% is the max duty wanted ... which means the hardware duty mode is adequate all by itself.

    What Tim did make clear at the outset was the Cogs were all busy ... so having the hardware maintain the set level is important.

    Assuming its a DC coil, 20KHz probably is a good starting point for duty mode. Worst case will be a noisy valve down near 0%. Just don't use that part of the range.

  • @eagletalotim

    I am not that keen on counters, but it sounds to me that you could get what you want by altering my stepper driver code. This code alters the duration of the pulse, for a specific number of pulses, however, I suppose it would be easy enough to alter it to being time based (1 second), instead of being pulse based. Of course most of this code is useless to you, but the important thing is the math that alters the frequency. Here is an example:
    PUB MoveStagePosition(Direction, TotalSteps) | Counter, RampingSteps, RunningSteps
    {{
      Method Description:
        This method will ramp up a stepper motor until the maximum speed
        is obtained and it will maintain that maximum speed for a determined number
        of steps, at which point it will begin a ramp down of the stepper motor
        until it comes to a stop.
    
      Parameters:
        Direction = This parameter sets the OUTA register utilized for bDirectionPin.
          This setting will control the direction of rotation for stepper drives
          and it should be a value of 0 (LOW) or 1 (HIGH).
    
        TotalSteps = This is the total amount of steps or microsteps that you want the
          motor to rotate, and this should be equivalent to the number of high pulses
          sent to the stepper driver unit.
    
      Local Variables:      
        Counter = This variable is used to hold the value of the System Counter for
          timing accuracy of the stepper driver.
    
        RampingSteps = After computations, this variable will contain the number of pulses
          required to obtain the highest speed possible with the provided parameters.
    
        RunningSteps = Providing TotalSteps is larger than the value of the global variable
          lTotalRampingSteps, this variable will contain the number of maximum speed steps.
          However, if TotalSteps is smaller than the value of the global variable
          lTotalRampingSteps, the value of this variable will become either 1 or 0.
    
      Example Usage:
        This method can be called directly as follows:
    
        X.MoveStagePosition(0, 5000) or X.MoveStagePosition(1, 5000)
    
        or it can be called through the IncreaseStagePosition and DecreaseStagePosition methods.
    }}  
    
      'If maximum speed can be obtained, determine the actual number of
      'ramping and running steps.
      IF TotalSteps > lTotalRampingSteps
    
        'Copy the global amount of ramping steps
        RampingSteps := lRampingSteps
    
        'Determine the number of full speed steps.  
        RunningSteps := TotalSteps - lTotalRampingSteps    
    
       'Else ramp upto the half-way point and then ramp down.    
      ELSE
      
        RampingSteps := TotalSteps / 2
        RunningSteps := TotalSteps // 2
    
      'Set the OUTA register to match the desired direction of rotation.
      OUTA[bDirectionPin] := Direction     
      
      'Set up the CTRMODE of Counter A for NCO/PWM single-ended.
      CTRA[30..26] := %00100
    
      'Set the output pin for Counter A.
      CTRA[5..0] := bStepPin
    
      'Set the value to be added to PHSA with every clock cycle.
      FRQA := 1
    
      'Set APIN as an output.
      DIRA[bStepPin] := 1
    
      'Get the current System Counter value.
      Counter := CNT
    
      'Ramp up the stepper motor to maximum speed.
      REPEAT RampingSteps
      
        'Send out a high pulse on the step pin for the desired duration.
        PHSA := -lHighPulseWidth
    
        'Wait for a specified period of time before sending another
        'high pulse to the step pin.
        WAITCNT(Counter += lStartStopSpeed -= lRampIncDec)
    
      'Maintain maximum speed    
      REPEAT RunningSteps
       
        'Send out a high pulse on the step pin for the desired duration.
        PHSA := -lHighPulseWidth
    
        'Wait for a specified period of time before sending another
        'high pulse to the step pin.
        WAITCNT(Counter += lStartStopSpeed)
    
      'Ramp down the stepper motor and come to a stop. 
      REPEAT RampingSteps
      
        'Send out a high pulse on the step pin for the desired duration.
        PHSA := -lHighPulseWidth
    
        'Wait for a specified period of time before sending another
        'high pulse to the step pin.
        WAITCNT(Counter += lStartStopSpeed += lRampIncDec)
    
  • JonnyMacJonnyMac Posts: 9,105
    edited 2016-04-09 18:19
    As others have pointed out you cannot do pwm with fixed-frequency and variable duty cycle PWM without using a loop. If your program will tolerate a blocking method, this will work for you.
    pub pwm_burst(pin, freq, duty, ms) | cycletix, dutytix, t
    
    '' Output pwm burst on pin
    '' -- WARNING: Blocks while running
    
      cycletix := clkfreq / freq                                    ' ticks in full pwm cycle
      dutytix  := -cycletix * duty / 100                            ' ticks in active portion of cycle
    
      ctra := (%00100 << 26) | pin                                  ' set counter for pwm/nco mode
      frqa := 1
      dira[pin] := 1
    
      ms *= clkfreq / 1000                                          ' convert millis to ticks
    
      t := cnt
      repeat
        phsa := dutytix
        waitcnt(t += cycletix) 
        if ((ms -= cycletix) =< 0)
          quit
    
      dira[pin] := 0
    
  • AribaAriba Posts: 2,690
    This is a full program that repeatedly outputs a PWM with 50%..1% decreasing over a bit more than 1 second:
    CON
      _clkmode  = xtal1 + pll16x
      _xinfreq  = 5_000_000
    
      PIN = 0
         
    PUB Main | width
      ctra := %00100<<26 + PIN     'init counter
      frqa := 1
      dira[PIN] := 1
      repeat
      
        width := -21_000           'do pwm for 1 sec
        repeat 20_000
          waitcnt(2200 + cnt)      'repeat with 17 kHz
          phsa := width/10
          width += 1               'decrease pulswidth
    

    Frequency is about 17 kHz but you can adjust time, frequency and width by changing the numbers.
    The first 3 lines are only once needed to initalize, the last 5 lines do one shot of PWM for a second.

    Andy
  • lardomlardom Posts: 1,659
    edited 2016-04-09 19:35
    As the title says, how would I be able to pulse a pin at 20khz with a 50% duty cycle for 1 second when needed without starting a new cog? I already have all cogs used in my current program. Any help is appreciated!

    This runs at 20khz. I tested it with an LED on pin 9. It contains a cognew so I could test it. You can simply initialize it in a child object that has an available counter and call it from a parent.
    edit: I didn't configure the cog to run @ 80Mhz.
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2016-04-09 21:27
    Look ma, no loop, no external parts. This one overlaps ctra and ctrb to one shared output pin but at 1/2 Hz apart to make an autonomous running 20kHz PWM triangle waveform. Like all Prop outputs, the counters are wired-OR, so the beat-note of the phase shows up as a waveform that oscillates from 50% to 100% and back. By starting and stopping it after one second you could get the single linear rampdown. It is possible to use another Prop pin as an inverter to get 50% -> 0% on another pin.
    CON
      _CLKMODE = XTAL1 + PLL16X
      _XINFREQ = 5_000_000
    
      PIN_DEFAULT = 17
      FRQX_DEFAULT = 1073741  ' about 20kHz, frqx = frequency/clkfreq * 2^32
      FRQX_OFFSET = 27   ' about 1/2 Hz @ clkfreq 80MHz
      
    VAR
    
    OBJ
    
    PUB demo
      wheel(PIN_DEFAULT, FRQX_DEFAULT, FRQX_OFFSET)
      repeat  ' keep cog alive, PWM continues autonomous.
    
    PUB wheel(pin, frqx, offset)
      dira[pin]~~
      frqa := frqx
      frqb := frqx+offset
      ctra := %00100 << 26 + pin
      ctrb := %00100 << 26 + pin
    
  • I added the ramp/triangle waveform generator to OBEX,
    http://obex.parallax.com/object/839
    It is like the one in the previous post but with counter initialization added for the inverted waveform on a second pin, and a square wave synchronized at the beat frequency on a third pin. It is bare bones so that the essentials don't get lost in apps overhead.

    The cog counters are great for picking up little tasks. They're limited, of course, but when appropriate, they are a great but I think underused feature of the Prop 1.

Sign In or Register to comment.