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 Previous  1, 2
 
Post new topic   Reply to topic    Forum Index -> ZX-24
Author Message
dkinzer
Site Admin


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

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

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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: Reply with quote

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
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 Previous  1, 2
Page 2 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