Forum Index
HomeZBasic Home   Forum RulesForum Rules   Forum FAQForum FAQ   MemberlistMemberlist   UsergroupsUsergroups   RSS FeedRSS Feed
Site SearchSite Search   LinksLinks   DownloadDownload   Digests and SubscriptionsDigests and Subscriptions
ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in   RegisterRegister
Software PWM generation
Goto page 1, 2  Next
 
Post new topic   Reply to topic    Forum Index -> ZX-24
Author Message
sturgessb



Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK

Posted: 09 January 2009, 11:03 AM    Post subject: Software PWM generation Reply with quote

Morning all.

With a need to generate 8pwm channels (50hz 8bit) on zx-24n, using the standard PWM functions is not an option so ive been looking around to see if it could be done in software. Ive found the following forum thread.

http://www.electro-tech-online.com/micro-controllers/34390-drive-33-servos-one-pic-usart.html

As you will see its for PIC but I'm wondering if the same method could be used on the 24n.

Ive posted a BASIC version of the code below, as you will see the ISR runs at 50hz and loops though a buffer outputting to the port. One thing i dont understand is how they are ensuring the For n = 1 to 254 runs at 4usec intervals (the time slice they are using to get the 8bit resolution), are they just relying on the speed of the loop being this?

My first thought was to have an interrupt running at 4usec, (which is 1ms / 256 if im correct?) and using a similar XOR method to output the contents of a buffer to the port. But am i being totally unrealistic to think that this could all be done in under 4usec?

Thoughts?

Code:
;  ' 8 pwm 'servo' or digital 'on/off' channel output example
;  ' 254 servo steps, 1..254, 992 to 2008 usecs, 4 usec steps
;  ' step value 0 is a digital 'off' output
;  ' step value 255 is a digital 'on' output
;
;  Dim n As Byte                '
;  Dim Servo(8) As Byte         ' 8 channel values of 0..255
;  Dim WBuff(256) As Byte       ' work (toggle/interval) buffer
;
;  Interrupt example            ' once exactly every 20 msecs
;    PIR1.TMR2IF = 0            ' clear Timer 2 interrupt flag
;
;    Mask = %00000001           ' servo channel bit mask
;    For n = 0 To 7             ' add servo "off" bits to buffer
;      WBuff(Servo(n)) = WBuff(Servo(n)) Or Mask
;      Mask = Mask << 1         ' shift bit for next channel
;    Next
;
;    WBuff(0) = WBuff(0) Xor (Not PORTB)  ' prep 1st XOR value
;
;    PORTB = PORTB Xor WBuff(0) ' step 0
;
;    DelayUs(992)               ' delay 992 usecs
;
;    For n = 1 to 254           ' steps 992 thru 2008 usecs
;      PORTB = PORTB Xor WBuff(n) ' 4 usec output update 'step'
;      WBuff(n) = 0             ' zero buffer after using it
;    Next                       '
;
;  End Interrupt
Back to top
dlh



Joined: 15 Dec 2006
Posts: 266
Location: ~Cincinnati

Posted: 09 January 2009, 11:25 AM    Post subject: Reply with quote

Sorry - I can't help with your question but FYI Warren Schroeder, who wrote the code you found, is a bit of a superstar with PICs and Basic. He is also the sole USA dealer for mikroElektronika who make the compiler this was written in. Interesting that he would specifically note it will run in the free demo version.
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 666

Posted: 09 January 2009, 13:47 PM    Post subject: Reply with quote

Well, we recently had a discussion of how to READ the pwm signals coming out of a receiver. There were 2 major ways the receiver could do the 8 channels of output. Parallel, or serial.

In the parallel mode, all the pwm signals started simultaneously, then ended individually 1-2 mS later. 20mS after starting the first time, it all started over. This means that you need to do a lot of work between 1 and 2 milliseconds after starting the PWM signals. This sort of accuracy might be hard to do in the ZX platform.


The serial method might work better.

You do the PWM one after another. When one finishes, you start another.

One way is to PulseOut() on channel 1, then sleep(0), then PulseOut() on channel 2, and so on through channel 8. Then Sleep for the remainder of the 20mS period, and then start all over.

Servos do not require a PRECISE 50Hz signal, so the Sleep() functions ought to be accurate enough for servos.

-Tony
Back to top
mikep



Joined: 24 Sep 2005
Posts: 765
Location: Austin, TX

Posted: 09 January 2009, 14:46 PM    Post subject: Reply with quote

spamiam wrote:
The serial method might work better.

You do the PWM one after another. When one finishes, you start another.

One way is to PulseOut() on channel 1, then sleep(0), then PulseOut() on channel 2, and so on through channel 8. Then Sleep for the remainder of the 20mS period, and then start all over.
I think this is a reasonable idea to have the AVR timer do most of the work for you and 8 channels are doable in less than 20ms needed between the pulses on each channel.

You should definitely implementd this in a separate task so that the ZX-24n can be doing other work while pulses are being generated. The other task(s) must give up control to the PWM task as often as possible by either using Sleep(), calling Yield(), or ResumeTask(). I would also use another PulseOut to channel 0 for the remainder of the 2ms and also for the 4ms wait time after channel 8. In this way you can ensure that the pulses for all channels are at 50 Hz.

You could probably also use the parallel method but the code it a little harder. I believe the 256 steps are spread over a 2ms interval (not 1ms). That gives you slightly less than 8us per step. With a 14.7456 MHz clock, that is 118 cycles (or about 75 instructions). I think it is doable but you may have to write some AVR assembler to get the correct timing. The interesting thing about this algorithm is that the AVR is idle for 90% of the time and then very busy for the other 10%. You will need to find a way to block all other interrupts and work during this critical 2ms interval.
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 666

Posted: 09 January 2009, 15:14 PM    Post subject: Reply with quote

mikep wrote:
You should definitely implementd this in a separate task so that the ZX-24n can be doing other work while pulses are being generated. The other task(s) must give up control to the PWM task as often as possible by either using Sleep(), calling Yield(), or ResumeTask(). I would also use another PulseOut to channel 0 for the remainder of the 2ms and also for the 4ms wait time after channel 8. In this way you can ensure that the pulses for all channels are at 50 Hz.


Doesn't the PulseOut() halt all other activity for the duration of the pulse? So if you do a PulseOut for the dead time too, then there is essentially NO time for other activity?

I think that a timer would work: Set up the timer for the correct PWM period, turn on a certain channel's pin, start the timer. Then when the timer interrupt occurs turn off the active pin, and start all over again with the next channel. You can then use the timer for the dead time after the 8th PWM channel until the next round of PWM pulses is due.

I have never used a timer in this manner in the ZX system (definitely have done it in a plain old AVR).

I also have done the more crude PulseOut technique, with a sleep() to wait out the dead time. The dead time sleep was adjusted by adding a constant to give something that averaged close to 50 Hz. THe servos were happy, and did not have any jitter.

-Tony
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 09 January 2009, 15:39 PM    Post subject: Reply with quote

spamiam wrote:
Doesn't the PulseOut() halt all other activity for the duration of the pulse?
Yes, it does. You might be able to do what you need using OutputCapture(). That operation is done in the background (interrupt driven).
Back to top
mikep



Joined: 24 Sep 2005
Posts: 765
Location: Austin, TX

Posted: 09 January 2009, 16:32 PM    Post subject: Reply with quote

dkinzer wrote:
spamiam wrote:
Doesn't the PulseOut() halt all other activity for the duration of the pulse?
Yes, it does. You might be able to do what you need using OutputCapture(). That operation is done in the background (interrupt driven).
I thought it was only for the task that called the routine which is why I suggested using multiple tasks. If it truely halts the whole VM then this should definitely be mentioned in the System Library Reference.

BTW There is a typo in the reference for PulseOut. There is an incomplete sentence that starts "See the".
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 09 January 2009, 17:13 PM    Post subject: Reply with quote

mikep wrote:
If it truely halts the whole VM then this should definitely be mentioned in the System Library Reference.
Perhaps it could be stated more explicitly. The fundamental logic is that a System Library routine disables interrupts only if it needs complete control of the processor for some period of time in order to implement its objective effectively. Therefore, if the description of a System Library routine says that it disables interrupts, you can be sure that that routine will have complete control for the duration. This, in turn, implies (among other things) that there will be no task switching during that time.

mikep wrote:
BTW There is a typo in the reference for PulseOut. There is an incomplete sentence that starts "See the".
Thanks.
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 666

Posted: 09 January 2009, 17:28 PM    Post subject: Reply with quote

dkinzer wrote:
You might be able to do what you need using OutputCapture(). That operation is done in the background (interrupt driven).


I take it you mean that it would substitute for PulseOut() even for the PWM pulse generation? It would be really nice to avoid stopping all processes for as much as 16mS (averaging 12mS) every 20mS!

Does OutputCapture() have more jitter in the timing that PulseOut()? If so, by how much? This might be yet another job for Mike's logic analyzer!

-Tony
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 09 January 2009, 18:20 PM    Post subject: Reply with quote

spamiam wrote:
Does OutputCapture() have more jitter in the timing that PulseOut()?
Much less, I would postulate. The timing sequence for OutputCapture() (and OutputCaptureEx() when using specific output capture pins) is generated by hardware assisted by interrupt software. Assuming that the time periods are large compared to other ISR execution times, the timing for OutputCapture() will be exact within the resolution of the hardware.

On the other hand, pulse trains created using sequences of PutPin() and Delay() may be rather imprecise due to the setup overhead of each call, ISR execution times, execution of other tasks, etc.
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 666

Posted: 09 January 2009, 19:19 PM    Post subject: Reply with quote

dkinzer wrote:
The timing sequence for OutputCapture() (and OutputCaptureEx() when using specific output capture pins) is generated by hardware assisted by interrupt software. Assuming that the time periods are large compared to other ISR execution times, the timing for OutputCapture() will be exact within the resolution of the hardware.


Hmm, it seems that you are referring to the case where you use the specified OutputCapture pin. What about the case where one uses a general I/O pin for the output pulse train using OutputCaptureEx(). Is this also more accurate than PulseOut()?

From what you are saying, it looks as if the most accurate way (while interfering as little as possible with other tasks) is something like this:

For channel 1-8: OutputCaptureEx() for the desired times as a single pulse for each using their respective general I/O pins, using a rising edge (assuming the "on" state is high). Then reset the pin to low.

Then do another OutputCaptureEx() on one of the PWM pins (i.e. channel 1) using a falling edge (assuming that the rest state of the pins is low), and the duration is 20mS less the accumulated time of pulses 1 thru 8.

Does this seem like a reasonable approach for the OP?

-Tony
Back to top
mikep



Joined: 24 Sep 2005
Posts: 765
Location: Austin, TX

Posted: 09 January 2009, 19:37 PM    Post subject: Reply with quote

dkinzer wrote:
The fundamental logic is that a System Library routine disables interrupts only if it needs complete control of the processor for some period of time in order to implement its objective effectively. Therefore, if the description of a System Library routine says that it disables interrupts, you can be sure that that routine will have complete control for the duration.
I think this should be mentioned more explicitly in the section on Interrupts in the System Library Reference. So there are 3 cases:
1. Library function is called, does work and returns. A task switch can occur after the routine is finished but before the next "instruction" for that task is executed. This is the case for most library functions.
2. Library function is called and interrupts are disabled. The routine does work and returns. A task switch can occur after the routine is finished but before the next instruction for that task is executed e.g. PulseOut()
3. Library function is called, and starts some work. The task is then suspended and a task switch can occur e.g. InputCapture()
dkinzer wrote:
This, in turn, implies (among other things) that there will be no task switching during that time.
So even though interrupts are disabled the RTC is kept correct. That is good but I also read it to mean that task switches would be processed on every RTC interrupt as usual but instead the task is effectively locked.

When you say interrupts are disabled, does this mean that all AVR interrupts are disabled? In that case how is the RTCTick kept up to date? Persumably the timer overflow interrupt is handled but the code path shortened to just incrementing the RTCTick. The same for other interrupts.
Back to top
mikep



Joined: 24 Sep 2005
Posts: 765
Location: Austin, TX

Posted: 09 January 2009, 19:42 PM    Post subject: Reply with quote

spamiam wrote:
What about the case where one uses a general I/O pin for the output pulse train using OutputCaptureEx().
I thought OutputCapture/OutputCaptureEx was restricted to only the 1 (or 2) output capture pins on a ZX device.
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Posts: 2499
Location: Portland, OR

Posted: 09 January 2009, 19:44 PM    Post subject: Reply with quote

spamiam wrote:
What about the case where one uses a general I/O pin for the output pulse train using OutputCaptureEx(). Is this also more accurate than PulseOut()?
In order of decreasing accuracy:
  • OutputCapture() or OutputCaptureEx() with a hardware-assist pin
  • OutputCatpureEx() with a generic I/O pin
  • PulseOut(), PulseOut(0) sequence with the latter used for inter-pulse timing
  • PulseOut(),Delay()
Back to top
spamiam



Joined: 13 Nov 2005
Posts: 666

Posted: 09 January 2009, 19:53 PM    Post subject: Reply with quote

mikep wrote:
When you say interrupts are disabled, does this mean that all AVR interrupts are disabled? In that case how is the RTCTick kept up to date? Persumably the timer overflow interrupt is handled but the code path shortened to just incrementing the RTCTick. The same for other interrupts.


I think that it works by looking at the main clock and determining how many RTC ticks were missed and then update the RTC to that value. This implies that the longest pulse has to be less than the full 16 bits of the timer's register.

When I read this, I then assumed that if the most vital process, the RTC is not serviced, then NOTHING else at all is serviced either. I am sure that Don can give a clear more concise answer.

-Tony
Back to top
Display posts from previous:   
Post new topic   Reply to topic    Forum Index -> ZX-24 Time synchro. with the server - Timezone/DST with your computer
Goto page 1, 2  Next
Page 1 of 2

 


All content Copyright © 2005-2012 Elba Corp. All Rights Reserved.
Opinions expressed in posts are those of the author and not necessarily those of Elba Corp.
Powered by phpBB © 2001, 2005 phpBB Group