Go to Post *gasp* Robotics? bad for your health? I refuse to accept that as an answer. - Kiwi_queen [more]
Home
Go Back   Chief Delphi > Technical > Programming
CD-Media   CD-Spy  
portal register members calendar search Today's Posts Mark Forums Read FAQ rules

 
Closed Thread
Thread Tools Rate Thread Display Modes
  #16   Spotlight this post!  
Unread 28-01-2013, 17:25
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Ken Streeter View Post
Below is the definition for the ShooterWheelTask, of which the "run" method will be executed every 20 milliseconds. This code computed the wheel RPM by dividing the number of counts observed since the last invocation by the elapsed time since the last invocation, with appropriate unit conversions.
This is "method 1" above. This is what he claims not to want.

Quote:
Originally Posted by Ken Streeter View Post
Time was measured using the FPGATimestamp, which has a resolution of approximately 6.5usec.
This is inaccurate. The FPGATimestamp itself has a resolution of 1 us. The digital I/O that it timestamps (in the case of digital interrupts or encoder pulse measurements, etc.) is 6.525 us.

Cheers,
-Joe
  #17   Spotlight this post!  
Unread 28-01-2013, 17:50
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Mr. Lim View Post
Code:
public double getPeriod() {
        double period;
        if (m_counter.readTimerOutput_Stalled()) {
            return Double.POSITIVE_INFINITY;
        } else {
            period = (double) m_counter.readTimerOutput_Period() / (double) m_counter.readTimerOutput_Count();
        }
        return period / 1.0e6;
    }
Something isn't jiving here. In the above code, why would you take the period and divide by the count? If the FPGA is measuring the period directly, should we just be taking the period directly? Will count ever be anything other than 1?
The variables are not terribly verbose... the "count" is the number of samples in the sliding window average. Count will always be 1 if you specify not to average (NumberOfSamplesToAverage = 1).

The FPGA does not like to divide without using up a bunch of gates, where and the RT CPU with an FPU has no problem doing this. This is an optimization where all the addition and synchronization is done in hardware where it needs to be done, but the divide operation to get the actual average period is pushed to the driver.

If you want "Method 1", you have to do it the way Ken Streeter described. If you want "Method 2", you must use the FPGA.

Cheers,
-Joe
  #18   Spotlight this post!  
Unread 28-01-2013, 17:53
Ken Streeter's Avatar
Ken Streeter Ken Streeter is offline
Let the MAYHEM begin!
FRC #1519 (Mechanical Mayhem)
Team Role: Engineer
 
Join Date: Feb 2005
Rookie Year: 2005
Location: Team: Milford, NH; Me: Bedford, NH
Posts: 472
Ken Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by jhersh View Post
This is inaccurate. The FPGATimestamp itself has a resolution of 1 us. The digital I/O that it timestamps (in the case of digital interrupts or encoder pulse measurements, etc.) is 6.525 us.
Thanks for the correction, Joe! I'll edit my post above to fix it!
__________________
Ken Streeter - Team 1519 - Mechanical Mayhem (Milford Area Youth Homeschoolers Enriching Minds)
2015 NE District Winners with 195 & 2067, 125 & 1786, 230 & 4908, and 95 & 1307
2013 World Finalists & Archimedes Division Winners with 33 & 469
2013 & 2012 North Carolina Regional Winners with teams 435 & 4828 and 1311 & 2642
2011, 2010, 2006 Granite State Regional Winners with teams 175 & 176, 1073 & 1058, and 1276 & 133
Team 1519 Video Gallery - including Chairman's Video, and the infamous "Speed Racer!"
  #19   Spotlight this post!  
Unread 28-01-2013, 17:58
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Mr. Lim View Post
We had a Counter set up exactly as you describe above. The periods we were getting had a very pronounced stepwise response. As a result, our RPM readings changed in increments of about 375 RPM.
There is a finite resolution on the timing of the pulses. That period is 6.525 us per edge. In your system, does that correspond to 375 RPM? You may want to turn on averaging to improve the resolution (but increase the latency).

Quote:
Originally Posted by Mr. Lim View Post
We did the math, and these increments corresponded with 1 count / 20ms, which led us to believe the reads were still being done via method 1.
Sounds like a coincidence, maybe? Where are you getting 20ms as the refresh period? Just because that's the Driver Station's packet rate?

Quote:
Originally Posted by Mr. Lim View Post
Is it possible that the other modes (other than semi-period) use the FPGA to count pulses over a 20ms period instead of measuring the time between pulses directly?
No. The FPGA only implements method 2.
  #20   Spotlight this post!  
Unread 28-01-2013, 18:02
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Joe Ross View Post
The FPGA doesn't return floating point values, so the period isn't in units you can use directly. Presumably, the Period is actually the period in clock cycles, and the count is the number of clock cycles per second. Dividing those two gives you a period in seconds.
Close. The "period" is in microseconds. The "count" is the number of samples accumulated by the averaging engine. The code also divides by 1.0e6 before returning to give the required "seconds" unit.
  #21   Spotlight this post!  
Unread 28-01-2013, 18:28
Ken Streeter's Avatar
Ken Streeter Ken Streeter is offline
Let the MAYHEM begin!
FRC #1519 (Mechanical Mayhem)
Team Role: Engineer
 
Join Date: Feb 2005
Rookie Year: 2005
Location: Team: Milford, NH; Me: Bedford, NH
Posts: 472
Ken Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond reputeKen Streeter has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by jhersh View Post
This is "method 1" above. This is what he claims not to want.
I had missed that in the original post. However, now I understand why the original poster doesn't want to use "Method 1." Given the statement of seeing RPM values with a resolution of 375 RPM and a hypothesized 20ms sampling period, their system must be using a wheel that has only 8 counts per revolution. In order to get greater RPM measurement resolution while keeping their 8 CPR sensor, they would need to have a longer sampling interval (longer than 20ms).

Similarly, with a 250 CPR sensor such as 1519 used in 2012, our computed RPM values are discretized at units of 12RPM. Measuring wheel speed to the nearest multiple of 12RPM was fine for our wheel shooter last year. Measuring to the nearest 375RPM might be troublesome.
__________________
Ken Streeter - Team 1519 - Mechanical Mayhem (Milford Area Youth Homeschoolers Enriching Minds)
2015 NE District Winners with 195 & 2067, 125 & 1786, 230 & 4908, and 95 & 1307
2013 World Finalists & Archimedes Division Winners with 33 & 469
2013 & 2012 North Carolina Regional Winners with teams 435 & 4828 and 1311 & 2642
2011, 2010, 2006 Granite State Regional Winners with teams 175 & 176, 1073 & 1058, and 1276 & 133
Team 1519 Video Gallery - including Chairman's Video, and the infamous "Speed Racer!"
  #22   Spotlight this post!  
Unread 28-01-2013, 20:29
DrakusDarkus's Avatar
DrakusDarkus DrakusDarkus is offline
Learning something new, every day!
AKA: Alex S.
FRC #1987 (Broncobots)
Team Role: Programmer
 
Join Date: Jan 2013
Rookie Year: 2011
Location: Lee's Summit, MO
Posts: 13
DrakusDarkus is an unknown quantity at this point
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Ken Streeter View Post
As described in another post, hidden in the LabVIEW programming forum (even though we were doing this in Java), Team 1519 had success last year using the Encoder object at 1X sampling to count pulses from a USDigital E7P encoder with a 250CPR optical wheel spinning at around 4000RPM. However, we performed our RPM computation in a different manner than using the built-in functions. We found our approach to give excellent accuracy. This same strategy should work just fine for a Counter object.

First off, we set up a separate task in Java to periodically compute the RPM of our shooter wheels. (This task also will control the motor for the wheels, too.) Below is a code excerpt showing how to set this up:

Code:

    private Encoder wheelEncoder = new Encoder(RobotMap.SHOOTER_WHEEL_ENCODER_A, RobotMap.SHOOTER_WHEEL_ENCODER_B, false, CounterBase.EncodingType.k1X);
    private static final long WHEEL_SPEED_PERIOD = 20; // milliseconds

    timer = new java.util.Timer();
    timer.scheduleAtFixedRate(new ShooterWheelTask(), 10000, WHEEL_SPEED_PERIOD);
Below is the definition for the ShooterWheelTask, of which the "run" method will be executed every 20 milliseconds. This code computed the wheel RPM by dividing the number of counts observed since the last invocation by the elapsed time since the last invocation, with appropriate unit conversions. Time was measured using the FPGATimestamp, which has a resolution of approximately 6.5usec 1usec. (Edit: updated resolution per jhersh posting, below.) We then compared the measured RPM against the desired RPM, and used a "Bang - Bang" controller to set the wheel speed. This simplistic approach worked incredibly well. We plan to use it again this year.

We can't take the credit for the "Bang - Bang" controller application -- lengthy CD discussions on this last year led to Ether's white paper on the subject.

Below is a snippet of our "ShooterWheelTask" code. Various declarations of variables are not in this code snippet, but I think you'll be able to figure out what they are from the context.

PS: See you at GSR, Mr. Lim!

Code:
    private class ShooterWheelTask extends java.util.TimerTask {
        public void run() {
            double power;
            
            double now = Timer.getFPGATimestamp();
            int count = wheelEncoder.get();

            wheelEncoder.reset();
            // NOTE:  60 seconds per minute;   250 counts per rotation
            actualWheelSpeed = (60.0 / 250.0) * count / (now - prevWheelTime);
            prevWheelTime = now;
            
            if (actualWheelSpeed >= wheelSpeed) {
                power = 0;
            } else {
                power = 1;
            }
            
            if (automatic) {
                setWheelJag(power);
            }
            else {
                setWheelJag(0);
            }
        }
    }
Thanks, we will try utilizing this in some way because we are having similar problems
__________________
-Alex S.

Programmer on the Broncobots (Team 1987)
  #23   Spotlight this post!  
Unread 28-01-2013, 20:52
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,102
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by jhersh View Post
the "count" is the number of samples in the sliding window average.
Joe,

How is this implemented under the hood in FPGA? Is there a ring buffer in the FPGA which the FPGA populates with the 6.525us_resolution_timestamp for each rising edge it detects (assuming it's not in semi-period mode), and then when requested retrieves the elapsed time between the N+1 most recent samples in the ring (which the cRIO CPU then divides by N)?

Quote:
Count will always be 1 if you specify not to average (NumberOfSamplesToAverage = 1).
How large can "NumberOfSamplesToAverage" be? I searched the 2012 C++ and the 2013 Java WPILib code but couldn't find that search string.


EDIT:

@all:

I attached a small Excel app that computes the RPM jitter caused by the 6.525us timing resolution of the edge detections. There will be additional jitter due to manufacturing tolerances in the physical locations of the edges in the sensor, but I've not included those here.

Note how large the jitter is with a 360 PPR sensor at 5000 RPM with averaging set to 1. If you set the averaging to 120 (1/3 revolution) you can reduce the jitter dramatically, at the cost of some phase lag in the signal.

If you make a 1 PPR sensor with averaging set to 1 you'll be able to get an updated reading every 12ms at 5000 RPM with very low jitter.


Attached Files
File Type: xls GetPeriod() RPM jitter02.xls (16.5 KB, 49 views)

Last edited by Ether : 29-01-2013 at 14:53. Reason: revising jitter computation to better model quantization
  #24   Spotlight this post!  
Unread 28-01-2013, 22:41
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Ether View Post
How is this implemented under the hood in FPGA? Is there a ring buffer in the FPGA which the FPGA populates with the 6.525us_resolution_timestamp for each rising edge it detects (assuming it's not in semi-period mode), and then when requested retrieves the elapsed time between the N+1 most recent samples in the ring (which the cRIO CPU then divides by N)?
There is a buffer that will hold up to 127 samples for each counter / encoder. It has write pointer that does form a ring buffer. The sum of the samples between the write pointer - average_size (usually) and write pointer are summed every 0.1 us * average_size (yeah I know... overkill) and made available to the driver (i.e. not on demand) along with the number of samples available. When a counter is reset, the write pointer is moved to mark all samples unwritten (so the "count" returned would be 0).

The I/O is 6.525 us from sample to sample. The timer used has a 1 us resolution. So the actual resolution is 6..7 us. However, the error is not cumulative across the average. Regardless of the number of samples averaged, the error is less than 7.525 us.

Quote:
Originally Posted by Ether View Post
How large can "NumberOfSamplesToAverage" be? I searched the 2012 C++ and the 2013 Java WPILib code but couldn't find that search string.
The FPGA interface function is called writeTimerConfig_AverageSize(). Looks like we never exposed that to the WPILib API layer in C++ (and therefore Java). It's exposed in LabVIEW.

Cheers,
-Joe
  #25   Spotlight this post!  
Unread 29-01-2013, 00:52
apalrd's Avatar
apalrd apalrd is offline
More Torque!
AKA: Andrew Palardy (Most people call me Palardy)
VRC #3333
Team Role: College Student
 
Join Date: Mar 2009
Rookie Year: 2009
Location: Auburn Hills, MI
Posts: 1,347
apalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond reputeapalrd has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by jhersh View Post
The FPGA interface function is called writeTimerConfig_AverageSize(). Looks like we never exposed that to the WPILib API layer in C++ (and therefore Java). It's exposed in LabVIEW.
And I was sitting in this discussion wondering how nobody knows that you can configure the hardware averaging. It's a register in the FPGA's counter/encoder timer config register, exposed by the Set Timer functions, and is very useful.

Our 2012 robot (with a 4-line handmade aluminum and gaffers tape disc) averaged 12 samples (three whole rotations) to get a perfect signal with ~2.5rpm minimum step. If I set the graph to autosize I could see the controller oscillate between exactly one step above or below the target speed, when steady state.
__________________
Kettering University - Computer Engineering
Kettering Motorsports
Williams International - Commercial Engines - Controls and Accessories
FRC 33 - The Killer Bees - 2009-2012 Student, 2013-2014 Advisor
VEX IQ 3333 - The Bumble Bees - 2014+ Mentor

"Sometimes, the elegant implementation is a function. Not a method. Not a class. Not a framework. Just a function." ~ John Carmack
  #26   Spotlight this post!  
Unread 29-01-2013, 01:10
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,102
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by apalrd View Post
It's a register in the FPGA's counter/encoder timer config register, exposed by the Set Timer functions
What source code file would that be in? I cannot find it.

It's not in the 2012 C++ WPILib source rev3111 timer.cpp


  #27   Spotlight this post!  
Unread 29-01-2013, 01:13
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Ether View Post
What source code file would that be in? I cannot find it.

It's not in the 2012 C++ WPILib source rev3111 timer.cpp


Encoder.cpp and Counter.cpp
  #28   Spotlight this post!  
Unread 29-01-2013, 01:18
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,102
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by jhersh View Post
Encoder.cpp and Counter.cpp
OK, so we're talking about modifying and re-compiling the WPILib source, not simply passing a parameter?


  #29   Spotlight this post!  
Unread 29-01-2013, 01:23
jhersh jhersh is offline
National Instruments
AKA: Joe Hershberger
FRC #2468 (Appreciate)
Team Role: Mentor
 
Join Date: May 2008
Rookie Year: 1997
Location: Austin, TX
Posts: 1,006
jhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond reputejhersh has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by Ether View Post
OK, so we're talking about modifying and re-compiling the WPILib source, not simply passing a parameter?
Quote:
Originally Posted by jhersh View Post
The FPGA interface function is called writeTimerConfig_AverageSize(). Looks like we never exposed that to the WPILib API layer in C++ (and therefore Java). It's exposed in LabVIEW.
That is what I was saying. It is not in the WPILib API layer in C++ and Java. That means if you care to use the hardware feature, you need to move down a layer.

Cheers,
-Joe
  #30   Spotlight this post!  
Unread 29-01-2013, 01:42
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,102
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Best way to measure period between pulses? Counters and FPGA

Quote:
Originally Posted by jhersh View Post
That is what I was saying. It is not in the WPILib API layer in C++ and Java. That means if you care to use the hardware feature, you need to move down a layer.
Got it.


Closed Thread


Thread Tools
Display Modes Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump


All times are GMT -5. The time now is 13:18.

The Chief Delphi Forums are sponsored by Innovation First International, Inc.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi