|
|
| Author |
Message |
dkinzer Site Admin
Joined: 03 Sep 2005
Posts: 2593
Location: Portland, OR
|
|
Posted: 09 January 2009, 20:13 PM Post subject: |
|
|
| mikep wrote: | | So there are 3 cases: | Your summary is close but needs refinement. There is also some difference between VM devices and native mode devices. With VM mode devices, the VM executor runs in a loop: fetch a VM instruction for the current task, execute the instruction, repeat. Just before fetching each VM instruction, it looks to see if a task switch is pending. The task switch request could have been caused by an RTC tick or by the last-executed VM instruction being one that requested a task switch (such as Sleep(), Yield(), InputCapture(), OutputCapture(), WaitForInterrupt(), WaitForInterval(), etc.). If a task switch is pending, the executor finds the next ready-to-run task (which may, in fact, be the current task) and makes it current. This means, of course, that a task switch can only occur at the granularity of a VM instruction.
For native mode devices, a task switch can occur at the granularity of an AVR instruction including, one should note, AVR instructions which comprise a System Library routine. The stimulus that causes a task switch may be an RTC interrupt, an external interrupt (including a pin change interrupt) or by a task requesting a task switch (Sleep(), Yield(), etc. as listed earlier). In response to the task switch request, the task manager locates the next ready-to-run task and makes it current.
It is useful to note that you can control the occurrence of interrupt-triggered task switches by using the Atomic Block construct. This is more useful on native mode devices than for VM devices, of course.
| 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? | When the description of a System Library routine says that "interrupts are disabled" it means that interrupts are globally disabled. This is necessary for certain routines to ensure the accuracy of their timing-related functions. The routines that require this operating mode monitor the compare-match flag of the RTC timer and, on each occurrence, a local count of "missed ticks" is incremented. When the routine is completed, the RTC variables are adjusted to account for the "missed ticks" just before interrupts are re-enabled. The net effect for the RTC is the same as if interrupts had never been disabled. Not so, of course, for all other interrupts. |
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 09 January 2009, 22:37 PM Post subject: |
|
|
wow, seems i've sparked some serious key tapping! Thanks for all your thoughts on this guys. I hope there is a solution.
So Ill try the PulseOut() first but i think that will kill too much time and as it seems from the thread stop my other interrupts from running.
With regards the OutputCapture() or OutputCaptureEx() method, can i use timer2 (8bit) for this, as timer1 is already busy? and also can i use this to drive 8 servos, as i thought Outputcapture was limited to the 2 ouputcapture pins per timer, can i use it on any IO pins?
Also, quick question, Can timer0 be used for anything else if I dont need to use the RTC? |
|
| Back to top |
|
 |
spamiam
Joined: 13 Nov 2005
Posts: 689
|
|
Posted: 09 January 2009, 23:49 PM Post subject: |
|
|
| sturgessb wrote: | wow, seems i've sparked some serious key tapping! Thanks for all your thoughts on this guys. I hope there is a solution.
So Ill try the PulseOut() first but i think that will kill too much time and as it seems from the thread stop my other interrupts from running. |
You are correct in your concern. Doing 8 of PulseOuts back to back can burn up at least 12mS and as much as 16mS where no other task really has a chance to run. I think it would be preferable to use OutputCompareEx() which will not lock up the VM.
Do the 8 OutputCompareEx() commands for each of the 8 channels, then do one more with the time period being 20mS minus the sum of the 8 PWM pulse lengths to create the dead time. You may have to add or subtract a constant from that duration to get the frequency centered on 50Hz
You will have to be careful about the how OutputCompareEx sets the output pin (high vs low), especially on the last one which generates the dead time.
To be sure that each PWM pin is returned to low after the PWM is done, maybe it will be easier to give the 8 OutputCompareEx commands an array for the pulse durations so it will set the bit with the first value on the array, then clear the bit with a second entry in the array, and the second entry is the smallest permissible value (i.e. 1).
For the dead time generator, the maybe the flag value should be set to create a falling edge on one of the PWM pins (i.e. it started low and stays low for the duration).
I have never actually used OutputCompareEx so I am not positive about the effect of the flags, but I think this is correct based on my reading of the documentation.
-Tony |
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 09 January 2009, 23:52 PM Post subject: |
|
|
Thanks Tony, but can OutputCompareEx even be used with the 8 bit timer, from what i can see in the docs it uses timer1 (16bit)?
If its worth anything ive just tried the following as a crude test.
| Code: | 'TIMER 2 8bit
If Semaphore(Register.Timer2Busy) Then
Register.TCCR2A = Bx0000_0010
Register.TCCR2B = &H05 '1024 divisor
Register.OCR2A = 229
Register.TIMSK2 = Bx0000_0010
End If
'**************************************************************************
'TIMER INTERRUPTS
ISR Timer2_CompA()
tempint = tempint + 1
If tempint = 10 then ' gives 50hz loop
tempuint = tempuint + 1
tempint = 0
'OUTPUT
call PutPin(13,1)
For n = 0 to 1500 '1000 seems to equal 1ms
tempint2 = tempint2 + 1 'just need something to do here rather than be blank
Next
Call PutPin(13,0)
end if
END ISR |
and that produces a good servo output, and the value in the For loop seems to be equal to msec. |
|
| Back to top |
|
 |
dkinzer Site Admin
Joined: 03 Sep 2005
Posts: 2593
Location: Portland, OR
|
|
Posted: 10 January 2009, 0:31 AM Post subject: |
|
|
| sturgessb wrote: | | With regards the OutputCapture() or OutputCaptureEx() method, can i use timer2 (8bit) for this, as timer1 is already busy? | No, OutputCapture() and all other time-sensitive I/O routines use the I/O Timer (typically Timer1) exclusively.
| sturgessb wrote: | | can i use this to drive 8 servos, as i thought Outputcapture was limited to the 2 ouputcapture pins per timer, can i use it on any IO pins? | OutputCaptureEx() can generate a pulse stream on any I/O pin but it is more precise when doing so on certain special pins (listed in the table in the description of OutputCaptureEx()).
Another observation is that you can use a single output pin to drive multiple output lines using a multiplexer if only one will be active at a time. For a 1-8 multiplexer you'd need 3 I/O pins to select which output is active.
| sturgessb wrote: | | Can timer0 be used for anything else if I dont need to use the RTC? | Perhaps but note that the RTC provides timing for other functions. For example, multi-tasking is driven by the RTC interrupt. Also, Sleep(), Delay(), Pause() and similar routines utilize the RTC for their timebase.
We've briefly looked into building a special single-tasking form of the ZX Library that could be used in "minimalist" applications. We've not actually embarked on making the code changes to support it so it is quite likely that some issues would arise that we've not yet considered. |
|
| Back to top |
|
 |
spamiam
Joined: 13 Nov 2005
Posts: 689
|
|
Posted: 10 January 2009, 3:51 AM Post subject: |
|
|
| sturgessb wrote: | Thanks Tony, but can OutputCompareEx even be used with the 8 bit timer, from what i can see in the docs it uses timer1 (16bit)?
If its worth anything ive just tried the following as a crude test. |
Is there any reason you have to use something other than the I/O timer?
Here is the code that I thought might be a good technique:
| Code: | 'Global variables and constants.
'Use some ZX-24 type pins. Should be adjusted to suit
Const Chan1_Pin as Byte = 5
Const Chan2_Pin as Byte = 6
Const Chan3_Pin as Byte = 7
Const Chan4_Pin as Byte = 8
Const Chan5_Pin as Byte = 9
Const Chan6_Pin as Byte = 10
Const Chan7_Pin as Byte = 11
Const Chan8_Pin as Byte = 12
Const DummyPin as Byte = 13 'be sure to specify an UNUSED pin!!!!
Public Chan1_time as Integer = 17699 ' this is the count to give a 1.5mS Pulse (1.5mS/67.8nS)
Public Chan2_time as Integer = 17699
Public Chan3_time as Integer = 17699
Public Chan4_time as Integer = 17699
Public Chan5_time as Integer = 17699
Public Chan6_time as Integer = 17699
Public Chan7_time as Integer = 17699
Public Chan8_time as Integer = 17699
Sub Main()
Dim Duration(1 to 2) as Integer
Dim DeadTime as UnsignedLong
Call PutPin(Chan1_Pin,zxOutputLow)
Call PutPin(Chan2_Pin,zxOutputLow)
Call PutPin(Chan3_Pin,zxOutputLow)
Call PutPin(Chan4_Pin,zxOutputLow)
Call PutPin(Chan5_Pin,zxOutputLow)
Call PutPin(Chan6_Pin,zxOutputLow)
Call PutPin(Chan7_Pin,zxOutputLow)
Call PutPin(Chan8_Pin,zxOutputLow)
Duration(2) = 100 'the smallest effective pulse width is about 6.8uS
Duration(1) = Chan1_Time
Call OutputCaptureEx(Chan1_Pin, Duration, 2, 0)
Duration(1) = Chan2_Time
Call OutputCaptureEx(Chan2_Pin, Duration, 2, 0)
Duration(1) = Chan3_Time
Call OutputCaptureEx(Chan3_Pin, Duration, 2, 0)
Duration(1) = Chan4_Time
Call OutputCaptureEx(Chan4_Pin, Duration, 2, 0)
Duration(1) = Chan5_Time
Call OutputCaptureEx(Chan5_Pin, Duration, 2, 0)
Duration(1) = Chan6_Time
Call OutputCaptureEx(Chan6_Pin, Duration, 2, 0)
Duration(1) = Chan7_Time
Call OutputCaptureEx(Chan7_Pin, Duration, 2, 0)
Duration(1) = Chan8_Time
Call OutputCaptureEx(Chan8_Pin, Duration, 2, 0)
'Now create the dead time to achieve about 50 Hz
'Easiest is to use Sleep() but will try it with OutputCaptureEx()
'dead time = 20mS - (Chan1_Time+Chan2_Time....+Chan8Time)
Deadtime = 294985 '=20mS/67.8nS
Deadtime = Deadtime - (CuLng(Chan1_Time)+CuLng(Chan2_Time)+CuLng(Chan3_Time)+CuLng(Chan4_Time)+CuLng(Chan5_Time)+CuLng(Chan6_Time)+CuLng(Chan7_Time)+CuLng(Chan8_Time))
while (DeadTime > 65535) 'maximum pulse is about 4.4mS
Call OutputCaptureEx(DummyPin, 65535, 1, 0) 'create dead time in 4.4mS increments
DeadTime = DeadTime - 65535
Wend
Call OutputCaptureEx(DummyPin, 65535, 1, 0) 'create the remainder dead time
'alternate technique is to:
'Call Sleep(CSng(Deadtime)*1000000000.0/67.8)
'and this is probably going to be quite adequate since servos do not need precisely 50Hz
End Sub |
-Tony |
|
| Back to top |
|
 |
mikep
Joined: 24 Sep 2005
Posts: 771
Location: Austin, TX
|
|
Posted: 10 January 2009, 4:44 AM Post subject: |
|
|
| spamiam wrote: | | Here is the code that I thought might be a good technique: | I think this is a good start. I haven't tested it yet but I can see some improvements as follows:
1. Use constants whenever possible and calculate timer intervals by multiplying by 14745600 rather than dividing by 67.8 ns
2. I still prefer the idea of having 2ms between each output rather than calculating the dead time at the end of all 8 channels. If each channel suddenly changes say from 2ms to 1.5ms then the time for the last channel can be off by as much as 4ms which could be too much jitter for a servo.
3. I added a constant so that less than 8 channels could be used.
4. I think the code can be cleaned up some more by using an array for each servo time.
Here are my updates to your code: | Code: | 'Global variables and constants.
'Use some ZX-24 type pins. Should be adjusted to suit
Const Chan1_Pin as Byte = 5
Const Chan2_Pin as Byte = 6
Const Chan3_Pin as Byte = 7
Const Chan4_Pin as Byte = 8
Const Chan5_Pin as Byte = 9
Const Chan6_Pin as Byte = 10
Const Chan7_Pin as Byte = 11
Const Chan8_Pin as Byte = 12
Const DummyPin as Byte = 13 'be sure to specify an UNUSED pin!!!!
Public Const CHAN_COUNT as Single = 8.0
Public Const FREQUENCY as Single = 50.0
Public Const CLOCK_SPEED as Single = 14745600.0
Public Const SMALL_PULSE as Integer = CInt(0.0000068 * CLOCK_SPEED) ' 6.8 us
Public Const MAX_PULSE_WIDTH as Single = 0.002
Public Const MAX_PULSE_TIME as Integer = CInt(MAX_PULSE_WIDTH * CLOCK_SPEED) + SMALL_PULSE + 1 ' 2ms
' (1 is added in case of rounding error resulting in a large negative result instead of zero)
Public Const DEAD_TIME as Long = CLng((1.0/FREQUENCY - CHAN_COUNT * 0.002) * CLOCK_SPEED - CHAN_COUNT * CSng(SMALL_PULSE))
Public Chan1_time as Integer = CInt(0.0015 * CLOCK_SPEED) ' 1.5ms
Public Chan2_time as Integer = CInt(0.001 * CLOCK_SPEED) ' 1ms
Public Chan3_time as Integer = CInt(0.002 * CLOCK_SPEED) ' 2ms
Public Chan4_time as Integer = CInt(0.0012 * CLOCK_SPEED) ' 1.2ms
Public Chan5_time as Integer = CInt(0.0015 * CLOCK_SPEED) ' 1.5ms
Public Chan6_time as Integer = CInt(0.0015 * CLOCK_SPEED) ' 1.5ms
Public Chan7_time as Integer = CInt(0.0015 * CLOCK_SPEED) ' 1.5ms
Public Chan8_time as Integer = CInt(0.0015 * CLOCK_SPEED) ' 1.5ms
Sub Main()
Dim Duration(1 to 2) as Integer
Dim DeadTime as Long
Call PutPin(Chan1_Pin,zxOutputLow)
Call PutPin(Chan2_Pin,zxOutputLow)
Call PutPin(Chan3_Pin,zxOutputLow)
Call PutPin(Chan4_Pin,zxOutputLow)
Call PutPin(Chan5_Pin,zxOutputLow)
Call PutPin(Chan6_Pin,zxOutputLow)
Call PutPin(Chan7_Pin,zxOutputLow)
Call PutPin(Chan8_Pin,zxOutputLow)
Duration(2) = SMALL_PULSE
Duration(1) = Chan1_Time
Call OutputCaptureEx(Chan1_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan1_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan2_Time
Call OutputCaptureEx(Chan2_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan2_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan3_Time
Call OutputCaptureEx(Chan3_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan3_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan4_Time
Call OutputCaptureEx(Chan4_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan4_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan5_Time
Call OutputCaptureEx(Chan5_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan5_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan6_Time
Call OutputCaptureEx(Chan6_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan6_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan7_Time
Call OutputCaptureEx(Chan7_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan7_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
Duration(1) = Chan8_Time
Call OutputCaptureEx(Chan8_Pin, Duration, 2, 0)
Duration(1) = MAX_PULSE_TIME - Chan8_Time
Call OutputCaptureEx(DummyPin, Duration , 1, 0)
'Now create the dead time to achieve about 50 Hz
'Easiest is to use Sleep() but will try it with OutputCaptureEx()
Deadtime = DEAD_TIME
while (DeadTime > 65535) 'maximum pulse is about 4.4mS
Call OutputCaptureEx(DummyPin, 65535, 1, 0) 'create dead time in 4.4mS increments
DeadTime = DeadTime - 65535
Wend
Call OutputCaptureEx(DummyPin, 65535, 1, 0) 'create the remainder dead time
'alternate technique is to:
'Call Sleep(CSng(Deadtime)*1000000000.0/67.8)
'and this is probably going to be quite adequate since servos do not need precisely 50Hz
End Sub |
|
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 10 January 2009, 18:55 PM Post subject: |
|
|
Tried that code, but it didn't run fast enough, servo updates were too slow, plus timer1 is busy for me so its not really an option, however i have come up with a solution that works using timer2, just tidying up and commenting and then ill post, and hopefully you guys can give some pointers on optimisation and improvement.
Ben |
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 10 January 2009, 19:18 PM Post subject: |
|
|
How about this, im getting jitter free outputs on 8 servos. 8 bit res. Could be 16bit if i was using TIMER1
| Code: | 'DECLARES
PUBLIC pwmstage AS BYTE
PUBLIC servoi AS BYTE
PUBLIC servovalue(1 to 4) AS BYTE
PUBLIC servopin(1 to 4) AS BYTE
PUBLIC servocount AS BYTE
Sub Main()
'SET UP TIMER 2
Register.TCCR2A = Bx0000_0010
Register.TCCR2B = &H04 '256 divisor
Register.TIMSK2 = Bx0000_0000 'interrupts disabled for now
'VARS, just 4 servos in this, but works fine with 8 also, just change the arrays
servoi = 1
servopin(1) = 13
servopin(2) = 14
servopin(3) = 15
servopin(4) = 16
servovalue(1) = 125 'servo mid point 0-255
servovalue(2) = 125
servovalue(3) = 125
servovalue(4) = 125
servocount = CByte(UBound(servopin))
'MAIN LOOP
Do
'START PWM
pwmstage = 1
Register.TIMSK2 = Bx0000_0010 'enable ISR
Register.OCR2A = 1 'cause ISR to fire now, is there another way to do this?
Register.TCNT2 = 0
Call sleep(0.020) 'keep a 50hz loop (roughly)
Loop
END SUB
'INTERRUPT
ISR Timer2_CompA()
IF pwmstage = 3 THEN
Call PutPin(servopin(servoi),0) 'PIN OFF
pwmstage = 1 'prepare for next run
IF servoi = servocount THEN 'we have reached last servo, disable the ISR and wait for next main task loop
Register.TIMSK2 = Bx0000_0000
servoi = 0
pwmstage = 4 'to prevent any of the if statements running, can i use some kindof exit here?
END IF
servoi = servoi + 1
END IF
IF pwmstage = 2 THEN
Register.OCR2A = servovalue(servoi) '1ms to 2ms range in 8 bit res
Register.TCNT2 = 0
pwmstage = 3
END IF
IF pwmstage = 1 THEN 'generate first 1ms of pulse as we will always need it
Register.OCR2A = 120 'roughly 1ms
Register.TCNT2 = 0
Call PutPin(servopin(servoi),1) 'PIN ON
pwmstage = 2
END IF
END ISR |
Give it a go and see how it works for you, im sure there are tonnes of improvements you guys can suggest too!
Ben[/code] |
|
| Back to top |
|
 |
spamiam
Joined: 13 Nov 2005
Posts: 689
|
|
Posted: 10 January 2009, 20:11 PM Post subject: |
|
|
| sturgessb wrote: | Tried that code, but it didn't run fast enough, servo updates were too slow, plus timer1 is busy for me so its not really an option, however i have come up with a solution that works using timer2, just tidying up and commenting and then ill post, and hopefully you guys can give some pointers on optimisation and improvement.
Ben |
I am a little surprised that it did not run fast enough. What frequency did you get? Probably you could subrract a constant from the dead time to raise the frequency somewhat, but Mike's version will consume about 16mS of the ~20Ms period. You could also reduce the OutputCompareEx times for the space between the channels.
Of course this is all academic for you because you don't have the 16bit timer available to you.
It would be cool if you could hook into the timer1 ISR vector to execute your own code, then call the original ISR when you are done. Since you know what frequency Timer1 is going to interrupt, it would not be hard to use a couple of counters to get whatever timing you are looking for. You could even trigger a "software" interrupt.
-Tony |
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 10 January 2009, 23:58 PM Post subject: |
|
|
| spamiam wrote: |
I am a little surprised that it did not run fast enough. What frequency did you get? Probably you could subrract a constant from the dead time to raise the frequency somewhat, but Mike's version will consume about 16mS of the ~20Ms period. You could also reduce the OutputCompareEx times for the space between the channels. |
I just cant get any sensible servo movements out of it, no matter what i tweak. ill keep trying.
| spamiam wrote: |
It would be cool if you could hook into the timer1 ISR vector to execute your own code, then call the original ISR when you are done. Since you know what frequency Timer1 is going to interrupt, it would not be hard to use a couple of counters to get whatever timing you are looking for. You could even trigger a "software" interrupt. |
Not sure I follow, do you mean a time slice and act on each slice?
I think I might be able to use the 16 bit timer, if I switch the other stuff over to 8 bit timer.
Ben |
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 11 January 2009, 0:21 AM Post subject: |
|
|
ah ive got the outputcapture one working ok now, with simplified code
| Code: |
Duration(1) = 22124' for 1.5ms
Call OutputCaptureEx(Chan1_Pin, Duration, 1, 1)
|
just running the above x6 every 20ms |
|
| Back to top |
|
 |
spamiam
Joined: 13 Nov 2005
Posts: 689
|
|
Posted: 11 January 2009, 16:22 PM Post subject: |
|
|
| sturgessb wrote: | ah ive got the outputcapture one working ok now, with simplified code
| Code: |
Duration(1) = 22124' for 1.5ms
Call OutputCaptureEx(Chan1_Pin, Duration, 1, 1)
|
just running the above x6 every 20ms |
Is this the speed of a simple loop where you set the duration and call OutputCaptureEx over and over? Does this execute only 6x in a 20mS period? I would have expected it to be rather faster.
-Tony |
|
| Back to top |
|
 |
sturgessb
Joined: 25 Apr 2008
Posts: 246
Location: Norwich, UK
|
|
Posted: 11 January 2009, 16:29 PM Post subject: |
|
|
No I mean't i have that 6 times and then a Sleep(0.020) in the loop.
It could run many many more times in that period. |
|
| Back to top |
|
 |
|