Generating Position Set Points for Trapezoidal Motion Profile - pascal

I'm working on a prototype for a motion controller which will accelerate a motor to a maximum velocity, coast at the maximum velocity and then commence deceleration at the correct position for the motor to stop at the target position.
The theoretical position for each timestep will be compared with the feedback from a quadrature encoder and the resulting error will be subjected to a PID loop, the result of which will be represented using PWM.
I currently have the following code to determine the theoretical position for each timestep:
Pos:=0;
Vel:=0;
Acc:=3;
Demand:=300;
Max_Vel:=19;
AccDist := (Max_vel/Acc * Max_vel) / 2;
DecelPoint := Demand - AccDist;
Writeln(AccDist:5:2);
Writeln(DecelPoint:5:2);
Writeln('ACCEL');
While Vel <> Max_vel
Do Begin
Pos := Pos + Vel + Acc/2;
Vel := Vel + Acc;
If Vel >= Max_Vel
Then Begin
Vel := Max_Vel;
Pos := AccDist
End;
Writeln('Position:',Pos:5:2);
End;
Writeln('FLAT');
While Pos < DecelPoint
Do Begin
Pos := Pos + Vel;
Writeln('Position:',Pos:5:2);
End;
Error := Pos - DecelPoint;
Writeln('DECEL');
While Vel > 0
Do Begin
If Error > 0
Then Begin
Pos := Pos - Error;
Error := 0;
End;
Pos := Pos + Vel - Acc/2;
Vel := Vel - Acc;
If Vel <= 0
Then Pos := Demand;
Writeln('Position:',Pos:5:2);
End;
end.
This code seems to give approximate results, but I really need exact results. The accel and flat section seem to yield exact results, but when we come to the decel section, things start behaving oddly.
Where have I gone wrong?

I believe that there are two problems.
Problem 1: You are displaying a list of positions but not the time at which the vehicle reaches those positions. If it is assumed that the accelerations, velocities, positions etc are all in m/s^2, m/s, and m then the data is consistent with 1 second intervals except when the vehicle transitions between acceleration and coasting (or coasting and deceleration). If I add columns for velocity and time then your output data would look like this:
60,17
239.83
ACCEL
Position: 1.50 Velocity: 3.00 Time:1.00
Position: 6.00 Velocity: 6.00 Time:2.00
Position:13.50 Velocity: 9.00 Time:3.00
Position:24.00 Velocity:12.00 Time:4.00
Position:37.50 Velocity:15.00 Time:5.00
Position:54.00 Velocity:18.00 Time:6.00
Position:60.17 Velocity:19.00 Time:6.33 ...vehicle stops accelerating at this point
FLAT
Position:79.17 Velocity:19.00 Time:7.33
Position:98.17 Velocity:19.00 Time:8.33
...
You can see that the actual time the vehicle stops accelerating is only 0.33 seconds (rather than a full second) after the 6 second measurement point at position 54.00 m.
The same problem will occur when the vehicle reaches the deceleration point (which I estimate should occur at approximately T = 15.79 seconds (that is 9.46 seconds after it stops accelerating).
Problem 2 The second issue is that you write the position during the FLAT period before you check if the vehicle should have started decelerating.
While Pos < DecelPoint
Do Begin
Pos := Pos + Vel;
Writeln('Position:',Pos:5:2);
End;
Your code above can increment the position past the deceleration point and output an erroneous data point before it 'corrects' it later. Your program would output:
...
Position:231.17
Position:250.17
DECEL
Position:257.33
...
but the last position before the DECEL is incorrect as the vehicle will already have started decelerating when it reaches 239.83 m.

Related

Algol: correct syntax leads to problems in compilation?

Here is a relatively simple code for "Evaluation of pi using the Mid-ordinate Rule on a quadrant of circle with radius 2 units."
main.alg
BEGIN
REAL x, y, sumy, pi;
INT n := lowerlimit, p := 1, lowerlimit := 10, upperlimit := 100, interval := 10;
FOR n BY interval TO upperlimit DO
sumy := 0.0;
FOR p BY 2 TO n+n-1 DO
x := p/n;
y := sqrt(4.0 - x**2);
sumy := sumy + y;
OD
pi := sumy * (2.0 / n);
print((n,pi))
OD
END
I'm getting the following errors:
a68g: syntax error: 1: possibly a missing or erroneous separator nearby.
sh-4.3$ a68g main.alg
13 sumy := sumy + y;
1
a68g: warning: 1: skipped superfluous semi-symbol.
15 pi := sumy * (2.0 / n);
1
a68g: syntax error: 1: possibly a missing or erroneous separator nearby.
Try it live here.
What am I doing wrong? How to correct it?
The short answer:
The following code has your specific problem fixed...
The thing to remember is that a ";" is a "statement separator"... so all "compound statement" should have each statement separated by a ";".. eg consider:
statement; statement; statement # is a valid program #
statement; statement statement; # is not valid #
(statement; statement; statement) # is a valid program #
(statement; statement; statement;) # is not valid #
(statement; statement; statement); # is not valid #
The moral is... separate all statements with a ";" and dont put a ";" after the last statement. (eg before an END, FI, DO, ")" or ESAC)
BEGIN
REAL x, y, sumy, pi;
INT n := lowerlimit, p := 1, lowerlimit := 10, upperlimit := 100, interval := 10;
FOR n BY interval TO upperlimit DO
sumy := 0.0;
FOR p BY 2 TO n+n-1 DO
x := p/n;
y := sqrt(4.0 - x**2);
sumy := sumy + y
OD;
pi := sumy * (2.0 / n);
print((n,pi))
OD
END
It is interesting to note that you can often use a "," instead of a ";", this tells the compiler you dont care what order the statements are run. It is called a GOMMA. (A contraction of go on and comma)
e.g. The GOMMA should be used sparingly as the compiler is not required to warn you about side effects... for example (in theory)
#!/usr/bin/a68g --script #
# -*- coding: utf-8 -*- #
INT x:=0;
(x+:=1, x+:=2); # allow the compiler the choice of threading #
PAR(x+:=10, x+:=20); # force statements into different threads #
printf(($"Answer="gl$,x))
What is the Answer? ... it could be 33, but it might also be 21 or 12 etc. depending on your compiler.
In this case the +:= operation is so small and fast that the answer will probably be 33.
The long answer:
The location of the statement separators in languages cause caused grief through the years. For example consider the following FORTRAN code with a missing comma:
DO 999 I=1 1000
PRINT *,I
999 CONTINUE
This bug was found and corrected before the launch of Project Mercury. Urban myth has it that the Mariner Program had a similar bug causing it to crash.
Note that it is often useful to have "Fake" statements, this is used to fulfill syntax/semantic requirements. Python as various examples, eg: "None", "NoneType" and "pass". Algol68 has "VOID", "SKIP" and "~"
To demonstrate the usage of SKIP (or "~").
BEGIN
REAL x, y, sumy, pi;
INT n := lowerlimit, p := 1, lowerlimit := 10, upperlimit := 100, interval := 10;
FOR n BY interval TO upperlimit DO
sumy := 0.0;
FOR p BY 2 TO n+n-1 DO
x := p/n;
y := sqrt(4.0 - x**2);
sumy := sumy + y; SKIP # insert a "fake statement after the ";" #
OD; # the ";" is still needed #
pi := sumy * (2.0 / n);
print((n,pi))
OD
END
SKIP is often used to permit code to be cleanly commented out:
statement1;
statement2;
SKIP COMMENT
statement3;
statement4 # eg. no ";" on the last statement #
END COMMENT
Without the SKIP the program would not compile.
In the case of Algol68, there is a weird case Yoneda's ambiguity. This ambiguity has haunted numerous programming languages ever since, including Ada and python, and maybe even C...
To find out more go to your university library and read: "A History of ALGOL 68" - by C. H. Lindsey - [Includes a candid reflection of the language design process "Revision by mail", language feature struggles "The Bend" and included/excluded ambiguities (eg Yoneda's ambiguity and incestuous unions)]
In python they tries to side step the "Separator" by making it optional and hiding it with indentation... but the comma ambiguity remained.. eg. spot the syntax/semantic error, and run time error in the following...
print [i for i in ()]
print [i for i in (1)]
print [i for i in (1,2)]
print [i for i in (1,2,3)]
ab="ab etc etc etc"
print "first 2 only: %c,%c"%ab[0:2]
C also suffers a little from "where do I put a semicolon and comma"... the logic is that the ";" need never follow a "}", eg always ";}" but never "};"... It turns out that sometimes you do need ";};"
Then C completely throws a spanner in the works for commas with never ",)" but sometimes "),".
1968's Algol68 does produce an error message for this class of ambiguities. The moral to the story might be: if your compiler does not pick up this kind of ambiguity at compile time, then (just maybe) you should pick another language.
BTW: You can find some sample Algol68 Programs here... And next is your code with the sharp edges removed.
INT lower limit = 10, upper limit = 100, interval = 10;
PROC circle = (REAL x)REAL: sqrt(4 - x**2);
FOR n FROM lower limit BY interval TO upper limit DO
REAL sum y := 0;
FOR p FROM 1 BY 2 TO 2*n DO
REAL x = p/n;
REAL y = circle(x);
sum y +:= y
OD;
REAL pi := sum y * 2 / n;
printf(($g(0)": "g(-real width,real width-2)l$,n,pi))
OD
Compare the code changes to see if you can figure out the effect and what hints they provide... :-)
Or... here is how a standard numerical quadrature program might be coded for sharing. Note the use of passing functions as arguments, in particular there is a concept called Currying here circle(2,) ... where the comma is significant!
INT lower limit = 10, upper limit = 100, interval = 10;
PROC circle = (REAL radius, x)REAL: sqrt(radius**2 - x**2);
PROC mid point integrate = (PROC(REAL)REAL f, REAL lwb, upb, INT num steps)REAL: (
REAL dx := (upb - lwb ) / num steps;
REAL x := lwb + dx/2;
REAL sum y := 0;
FOR p TO num steps DO
REAL y = f(x);
sum y +:= y;
x +:= dx
OD;
sum y * dx
);
FOR num steps FROM lower limit BY interval TO upper limit DO
REAL pi := mid point integrate(circle(2,),0,2,num steps);
printf(($g(0)": "g(-real width,real width-2)l$,num steps,pi))
OD

A game with 100 oponnents, win as much money as possible

You play a game with 100 opponents. The game has k rounds. Every round you can eliminate some opponents (always atleast 1). You are rewarded for eliminating them.
The reward is: 100.000 * '# of eliminated opponents' / '# of opponents' <= in integers (rounded down)
I want to eliminate the opponents in a way, that gets me the largest amount of money possible.
Example game:
number of rounds = 3
first round we eliminate 50 opponents, so we get 100.000 * 50 / 100 = +50.000
second round we eliminate 30, so we get 100.000 * 30 / 50 = +60.000
last round we eliminate last 20 opponents, so we get 100.000 * 20 / 20 = +100.000
so the total winnings are: 210.000
I tried to write up something, but I don't think it's the most effective way to do it?
Program EliminationGame;
var
selectedHistory : array [1..10] of integer;
opponentCount,roundCount : integer;
maxOpponents,numberSelected : integer;
totalMoney : integer;
i : integer;
begin
totalMoney := 0;
maxOpponents := 100;
opponentCount := maxOpponents;
roundCount := 3; {test value}
for i:=1 to roundCount do begin
if (i = roundCount) then begin
numberSelected := opponentCount;
end else begin
numberSelected := floor(opponentCount / roundCount);
end;
selectedHistory[i] := numberSelected;
totalMoney := floor(totalMoney + (numberSelected / opponentCount * 100000));
opponentCount := opponentCount - numberSelected;
end;
writeln('Total money won:');
writeln(totalMoney);
writeln('Amount selected in rounds:');
for i:= 0 to Length(selectedHistory) do
write(selectedHistory[i],' ');
end.
Also it seems that floor function does not exist in pascal?
It seems the question has a maths answer that can be calculated in advance. As #Anton said it was obvious that the number of points given during the third round did not depend upon the number of eliminated enemies. So the third round should eliminate 1 enemy.
So We get the following function for a thre-round game.
f(x)=100000x/100+100000(99-x)/(100-x)+100000*1/1, where x- the number
of enemies eleminated at first round.
if we find the extrema (local maximum of the function) it appears equal to 90. That means the decision is the following: the first round eliminates 90 the second - 9, the third - 1 enemy.
Of course, for consideration: 90=100-sqrt(100).
In other words: the Pascal decision of the task is to loop a variable from 1 to 99 and see the maximum of this function. X-will be the answer.
program Project1;
var
x, xmax: byte;
MaxRes, tmp: real;
begin
xmax := 0;
MaxRes := 0;
for x := 1 to 99 do
begin
tmp := 100000 * x / 100 + 100000*(99 - x) / (100 - x) + 100000 * 1 / 1;
if tmp > MaxRes then
begin
MaxRes := tmp;
xmax := x;
end;
end;
writeln(xmax);
readln;
end.
The general decision for other number of enemies and rounds (using recursion) is the following (Delphi dialect):
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
Uses System.SysUtils;
var
s: string;
function Part(RemainingEnemies: byte; Depth: byte;
var OutputString: string): real;
var
i: byte;
tmp, MaxRes: real;
imax: byte;
DaughterString: string;
begin
OutputString := '';
if Depth = 0 then
exit(0);
imax := 0;
MaxRes := 0;
for i := 1 to RemainingEnemies - Depth + 1 do
begin
tmp := i / RemainingEnemies * 100000 + Part(RemainingEnemies - i, Depth - 1,
DaughterString);
if tmp > MaxRes then
begin
MaxRes := tmp;
imax := i;
OutputString := inttostr(imax) + ' ' + DaughterString;
end;
end;
result := MaxRes;
end;
begin
writeln(Part(100, 3, s):10:1);//first parameter-Enemies count,
//2-Number of rounds,
//3-output for eliminated enemies counter
writeln(s);
readln;
end.
This problem can be solved with a dynamic approach.
F(round,number_of_opponents_remained):
res = 0
opp // number_of_opponents_remained
for i in [1 opp]
res = max(res, opp/100 + F(round-1,opp - i) )
return res
I should say this not the complete solution and you add some details about it, and I am just giving you an idea. You should add some details such as base case and checking if opp>0 and some other details. The complexity of this algorithm is O(100*k).

Delphi write pixel colors to memo

I Want to make a program, that can write my TImage pixels rgb color to a memo, the program don't know the resolution.
so how can i recover every pixel color to String variable (R,G,B)?
code
var
Image_width,Image_height,x,y,i,i2:integer;
Colors:TColor;
begin
Image_width:=Image1.Width;
Image_height:=Image1.Height;
memo1.text:='$image_width=*'+IntToStr(Image_Width)+
'$$image_height='+IntToStr(Image_Height)+'*$';
x:=1;
y:=1;
for i := 1 to Image_width do begin
for i2 := 1 to Image_height do begin
Colors:=Image1.Canvas.Pixels[x,y];
memo1.Text:=memo1.Text+ColorToString(Colors);
y:=y+1;
end;
x:=x+1
end;
end
|Edited|
procedure TForm1.Button2Click(Sender: TObject);
var
Image_width,Image_height,x,y:integer;
Colors:TColor;
s:string;
begin
Image_width:=Image1.Width;
Image_height:=Image1.Height;
memo1.text:='$image_width=*'+IntToStr(Image_Width)
+'*$ $image_height=*'+IntToStr(Image_Height)+'*$';
x:=0;
y:=0;
memo1.Lines.BeginUpdate;
for x := 0 to Image_width do begin
for y := 0 to Image_height do begin
Colors:=Image1.Canvas.Pixels[x,y];
memo1.Text:=Memo1.Text+ColorToString(Colors);
end;
memo1.Lines.EndUpdate;
end;
end;
It completes very slow, i think 19 second with a 640x480 picture.
And after the program ready, and it put to memo1, i see something in memo1 but the program "not responding" again..
I tried with the s variable, and the program do the same.
First correction, the pixels start from 0, 0. Not 1, 1 as you have. Replace the initial values before the for loops as follows:
x := 0; // not 1;
y := 0; // not 1;
Actually, assigning y at this point is not required as we will soon see.
Second correction, look closer at the inner for loop. i2 steps from 1 to Image_height and y is incremented in each round of the loop, which is ok. Then a visit to the outer loop increments x and we return to the inner loop. What happens with y? It continues from where it stopped previously, while it should first be reset to 0. The cure is to add a line as follows. This is also why the assignment above is redundant.
for i := 1 to Image_width do
begin
y := 0; // Add this line
for i2 := 1 to Image_height do
begin
Suggestions for improvement
It is unclear what you mean with resolution being unknown. If you mean color depth, or bits per pixel, it may be that your approach using pixels is more straight forward. Otherwise, as has been suggested in the comments, you could consider using scanline instead of pixels for faster access to the actual bitmap data . For an excellent article see: How to use ScanLine property for 24-bit bitmaps?
Repeatedly concatenating the color strings to the Text property is very slow. In fact it was so slow with a 64 x 64 pixel image that I couldn't wait for it to run to the end. To speed it up you can use a separate string variable to which you concatenate the color strings
Colors := Image1.Canvas.Pixels[x, y];
s := s + ColorToString(Colors);
and then after the loops concatenate to the Text property
Memo1.Text := Memo1.Text + s;
If your intention was to add each pixels colorstring as a new line to the memo you can instead use the Add method
Colors := Image1.Canvas.Pixels[x, y];
Memo1.Lines.Add(ColorToString(Colors));
Surrounding the for loops with Memo1.Lines.BeginUpdate; and Memo1.Lines.EndUpdate; further improves speed.
Finally, you could just as well, either use x and y as loop control variables or i and i2 as pixel indexes. I would ditch i and i2, and use x and y as both loop control variables and pixel indexes. But I leave that for you to decide.

Stats with random numbers

I want to create 7 stats for a character, randomly generating a value from 3-21, with the stat's sum being no higher than 91. I've tried arranging the stats into an array, and just going through them like this:
1) add random(15) to each array member
2) computing the total, subtracting from the 91 maximum
3) dividing this difference by 7
4) do step 1 with random(difference) adding it to the stat
5) Until I hit the 91 total.
Doing this a few hundred times I seem to get a curve where the 5,6, and 7th stats tend to be higher. And sometimes I hit the 4 or 5th stat and there are no more numbers to be added, meaning then that the first few stats get the most points. I think I am approaching this the wrong way to begin with. Any ideas? I have tunnel vision at this point I think.
It sounds like you're overthinking this. I might do something like this :
const
STAT_QTY = 7;
STATSUM_MAX = 91;
STAT_MIN = 3;
STAT_MAX = 21;
type
TStatArray = Array [0..STAT_QTY-1] of integer;
Then in implementation :
function GenerateStats : TStatArray;
var statArr : TStatArray;
i, statSum, excess, debit : integer;
done : boolean;
begin
Randomize;
done := false;
while not done do begin
done := true;
statSum := 0;
for i := 0 to STAT_QTY - 1 do begin
statArr[i] := STAT_MIN + Random(STAT_MAX - STAT_MIN);
statSum := statSum + statArr[i];
end;
if statSum > STATSUM_MAX then begin
excess := statSum - STATSUM_MAX;
debit := excess div STAT_QTY + 1;
for i := 0 to STAT_QTY -1 do begin
statArr[i] := statArr[i] - debit;
end;
end;
for i := 0 to STAT_QTY -1 do begin
if statArr[i] < STAT_MIN then done := false;
end;
end;
result := statArr;
end;
This generates a list of random stats in the range 3-21. If the sum is more than 91 then divide the excess by the number of stats (use div then round up the answer) and subtract an equal number from each. In the rare case that you end up with stats less than three, just do it again. Job done.
Tested over 2000 iterations I get average stats of :
[1] : 11.13893053
[2] : 11.15692154
[3] : 11.16141929
[4] : 11.11444278
[5] : 11.10194903
[6] : 10.9800100
[7] : 10.86856572
That's a total average of 11.07 with a standard deviation of 0.11 - certainly about what one would expect from a generally random set with your construction parameters.
Here's C-ish pseudo code for a slightly different approach, assuming a suitable random(N) function that returns numbers in the range 0 - N-1.
int stats[7], deficit = 70;
for (int i = 0; i < 7; ++i)
stats[i] = 3; // initial assignments of the minimum to each stat
while (deficit)
{ int tmp = random(7); // pick a random stat to bump
if (stats[tmp] == 21) // but not if it's already at max
continue;
++stats[tmp];
--deficit;
}
Assuming your random() is uniformly distributed, that should give pretty good results.

Convert decimal to latitude and longitude

I have a GPS device which sends data to my server, I need to convert the decimal values that the device sends into latitude and longitude. I am bad at math so all my attempts failed, here are the specs:
Latitude
Occupy 4 bytes, representing the latitude value.
Number range is from 0 to 162000000, which represents the range form 0°to 90°.
Unit: 1/500 second Conversion method:
A) Convert the latitude (degrees, minutes) data from GPS module into a new form which represents the value only in minutes;
B Multiply the converted value by 30000, and then transform the result to hexadecimal number
For example22°32.7658′,(22×60+32.7658)×30000=40582974, then convert it to hexadecimal number 0x02 0x6B 0x3F 0x3E
Longitude
Occupy 4 bytes, representing the longitude value of location data. Number ranges from 0 to 324000000, representing the range form 0°to 180°.Unit: 1/500 seconds, Conversion method is the same as latitude’s.
I came up with this function but it doesn't seem to work:
procedure GetDegree(const numar : DWORD; out min,sec : Extended);
var
eu : Extended;
begin
eu := numar / 30000;
min := Trunc(eu / 60);
sec := eu - min * 60;
end;
numar is specified in 1/500th of a second. So, the following equations hold:
num/500 = seconds
num/500/60 = minutes
num/500/60/60 = degrees
I would calculate it all like this:
var
degrees, minutes, seconds: Integer;
....
degrees := num div (500*60*60);
minutes := num div (500*60) - degrees*60;
seconds := num div 500 - minutes*60 - degrees*60*60;
If you need to calculate the fractional part of seconds then do it like this. Note that there's simply no need for Extended here.
var
degrees, minutes: Integer;
seconds: Double;
....
degrees := num div (500*60*60);
minutes := num div (500*60) - degrees*60;
seconds := num/500 - minutes*60 - degrees*60*60;
Plug your value of 40582974 into these formula and the results are:
degrees: 22
minutes: 32
seconds: 45
Judging from comments what you actually want is degrees as an integer and minutes as a floating point. That you can do like this:
var
degrees: Integer;
minutes: Double;
....
degrees := num div (500*60*60);
minutes := num/(500*60) - degrees*60;
Plug your value of 40582974 into these formula and the results are:
degrees: 22
minutes: 32.7658

Resources