Hallo Leute.
Dieser Thread erklärt den im VDR verwendeten Code und die mathematischen Funktionen für H-H Polarmount Motoren (Rotorsteuerung). Alle wichtigen Funktionen aus dem positioner.c ab VDR Version 2.1.3 werden in menschenverständlicher Form abgeleitet.
Bedeutung von Polarmount
Polarmount ist eine Montagemethode für bewegliche Satellitenantennen. Der einzige Motor dreht die Antennenhalterung in einer Ebene, die zur Äquatorialebene parallel verläuft. Die H-H Motorhalterung wird auf einem Antennenmast befestigt, welcher zum Erdoberfläche lotrecht montiert ist, genauso wie bei einer Festinstallation. Abhängig vom Standort wird der Winkel zwischen Motor und Motorhalterung nach Skala eingestellt. Die korrekte Einstellung ist die, bei der die tatsächliche Motorachse parallel zur Rotationsachse der Erde verläuft. Die Montage der Antenne auf dem um ca. 35° abgewinkelten Motorflansch erfolgt nach Herstellervorgaben. Eingestellt wird dabei einmalig der Elevationswinkel. Nach erfolgreicher Montage kann, allein durch das Drehen des Motors, die Antenne auf alle sichtbaren geostationären Nachrichtensatelliten mit Hilfe von DiSEqC korrekt ausgerichtet werden. Die Polarmount Montage unterscheidet sich von der Montagemethode Azimut, Elevation, Skew, bei der prinzipiell drei Achsen, bzw. Winkel (Azimut, Elevation und Skew) mit drei Motoren gleichzeitig verstellt werden müssen.
Berechnung des Drehwinkels für den Motor „CalcHourAngle(int Longitude)“
Bild 3D:
- Die Punkte (NP)AB(SP) liegen auf einem Großkreis, welcher ein Längengrad ist.
- Die Punkte (NP)AB(SP)M liegen auf einer Ebene, die die Erde halbiert, M ist der Erdmittelpunkt.
- In dieser Ebene befindet sich das rechtwinklige Dreieck MAC.
- Von dem Punkt A (Antenne) fällen wir ein Lot auf dem Äquator, damit entsteht bei C ein rechter Winkel.
- NP-SP ist die Erdachse, AC die Motorachse.
- Die Strecke MA entspricht dem Erdradius, MC ist die Entfernung Motorachse zum Erdachse.
- Punkt A liegt auf dem Breitengrad des Antennenstandortes, der Winkel Theta entspricht der geographischen Breite.
- In dem rechtwinkligen Dreieck MAC ist die Strecke MA die Hypotenuse und MC die Ankathete.
MA=Erdradius=r (re)
MC=r*cos(Theta), also Erdradius*cos(Breite) ergibt die Entfernung Motorachse zum Erdachse.
Bild 2D:
- Wir schauen die Erde genau von Norden nach Süden an, direkt in die Erdachse hinein.
- Die Strecke MS ist die Entfernung Erdmittelpunkt zum Satellit -> R (rs).
- Die Strecke MC=r*cos(Breite), SQ=R*sin(Alpha), MQ=R*cos(Alpha).
- Im rechtwinkligen Dreieck CQS gilt: tan(Delta)=SQ/CQ.
- CQ=MQ-MC.
Die Gleichung:
tan(Delta)=R*sin(Alpha)/(R*cos(Alpha)-r*cos(Breite)) #/R
tan(Delta)=sin(Alpha)/(cos(Alpha)-cos(Breite)*r/R)
Aus dem Motorwinkel die Orbitposition berechnen „CalcLongitude(int HourAngle)“
Nachdem wir den Motorwinkel in Abhängigkeit zur Orbitposition abgeleitet haben, werden wir die Funktion umkehren. Wir bestimmen aus einem vorgegebenen Motorwinkel die dazu gehörige Orbitposition. Ich nehme dazu den Sinussatz und zwei Winkelsummen, so ist die Gleichung nicht quadratisch.
Bild 2D:
Im Dreieck MCS sind die Strecken MS, MC und der Außenwinkel Delta bekannt. Wir suchen den Winkel Alpha.
1. MS/sin(Beta)=MC/sin(Gamma) -> sin(Gamma)=sin(Beta)*MC/MS aus dem Sinussatz.
2. Alpha+Beta+Gamma=180° Innenwinkelsumme eines Dreiecks.
3. Beta+Delta=180° die beiden Winkel bilden einen Halbkreis bei Punkt C.
Aus 2. und 3. Gamma=Delta-Alpha, Beta=180°-Delta und Alpha=Delta-Gamma. Beta setzen wir in der ersten Gleichung ein.
Die Gleichung:
Alpha=Delta-Gamma=Delta-asin(sin(180°-Delta)*cos(Breite)*r/R)
Bestimmen des maximal sichtbaren Ausschnittes des Satellitenkreises „HorizonLongitude(ePositionerDirection Direction)“
Der maximal sichtbare Ausschnitt des Sattelitenkreises wird über die Elevation bestimmt. Dort wo der Satellitenkreis den Horizont schneidet, ist die Elevation Null. Es gibt zwei solcher Punkte, sie bestimmen den Winkel. In dem speziellen Fall, dass die geografische Breite des Standortes Richtung 81° geht, tangiert der Winkel gegen null, die zwei Punkte verschmelzen ineinander. Darüber hinaus ist kein Satellit mehr sichtbar, die Funktion ist danach nicht mehr gegeben. Die Elevation hängt vom Winkel zwischen Standort und Subsatellitenpunkt ab.
Ich verwende zwei Gleichungen aus der sphärischen Trigonometrie: für den Winkel der Orthodrome zwischen dem Standort und Subsatellitenpunkt und die Elevation. Sie sind schon mehrfach abgeleitet worden, ich werde sie ohne Beweis verwenden. Einen Link gibt es trotzdem dazu.
Bild 3D:
- Winkel der Orhrodome -> cos(Delta)=cos(Breite)*cos(Längendifferenz).
- Elevation -> tan(Epsylon)=(cos(Delta)-r/R)/sin(Delta). #Epsylon wird nicht dargestellt.
- Die Elevation ist dann null, wenn der Quotient null ist. Dazu müssen wir den Dividenden nullsetzen: cos(Delta)-r/R=0 -> cos(Delta)=r/R, das setzen wir in der ersten Gleichung ein:
- cos(Längendifferenz)=r/R/cos(Breite).
Die Gleichung:
Delta=acos(r/R/cos(Breite))
/*
* positioner.c: Steerable dish positioning
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* For an explanation (in German) of the theory behind the calculations see
* [url]http://www.vdr-portal.de/board17-developer/board97-vdr-core/p1154305-grundlagen-und-winkelberechnungen-f%C3%BCr-h-h-diseqc-motor-antennenanlagen[/url]
*
* $Id: positioner.c 2013/10/30 09:56:34 kls Exp $
*/
#include "positioner.h"
#include <math.h>
#include "config.h"
#define SAT_EARTH_RATIO 0.1513 // the Earth's radius, divided by the distance from the Earth's center to the satellite
#define SAT_VISIBILITY_LAT 812 // the absolute latitude beyond which no satellite can be seen (degrees * 10)
#define RAD(x) ((x) * M_PI / 1800)
#define DEG(x) ((x) * 1800 / M_PI)
cPositioner *cPositioner::positioner = NULL;
cPositioner::cPositioner(void)
{
capabilities = pcCanNothing;
frontend = -1;
targetLongitude = lastLongitude = Setup.PositionerLastLon;
targetHourAngle = lastHourAngle = CalcHourAngle(lastLongitude);
swingTime = 0;
delete positioner;
positioner = this;
}
cPositioner::~cPositioner()
{
positioner = NULL;
}
int cPositioner::NormalizeAngle(int Angle)
{
while (Angle < -1800)
Angle += 3600;
while (Angle > 1800)
Angle -= 3600;
return Angle;
}
int cPositioner::CalcHourAngle(int Longitude)
{
double Alpha = RAD(Longitude - Setup.SiteLon);
double Lat = RAD(Setup.SiteLat);
int Sign = Setup.SiteLat >= 0 ? -1 : 1; // angles to the right are positive, angles to the left are negative
return Sign * round(DEG(atan2(sin(Alpha), cos(Alpha) - cos(Lat) * SAT_EARTH_RATIO)));
}
int cPositioner::CalcLongitude(int HourAngle)
{
double Lat = RAD(Setup.SiteLat);
double Lon = RAD(Setup.SiteLon);
double Delta = RAD(HourAngle);
double Alpha = Delta - asin(sin(M_PI - Delta) * cos(Lat) * SAT_EARTH_RATIO);
int Sign = Setup.SiteLat >= 0 ? 1 : -1;
return NormalizeAngle(round(DEG(Lon - Sign * Alpha)));
}
int cPositioner::HorizonLongitude(ePositionerDirection Direction)
{
double Delta;
if (abs(Setup.SiteLat) <= SAT_VISIBILITY_LAT)
Delta = acos(SAT_EARTH_RATIO / cos(RAD(Setup.SiteLat)));
else
Delta = 0;
if ((Setup.SiteLat >= 0) != (Direction == pdLeft))
Delta = -Delta;
return NormalizeAngle(round(DEG(RAD(Setup.SiteLon) + Delta)));
}
int cPositioner::HardLimitLongitude(ePositionerDirection Direction) const
{
return CalcLongitude(Direction == pdLeft ? -Setup.PositionerSwing : Setup.PositionerSwing);
}
void cPositioner::StartMovementTimer(int Longitude)
{
if (Setup.PositionerSpeed <= 0)
return;
cMutexLock MutexLock(&mutex);
lastLongitude = CurrentLongitude(); // in case the dish was already in motion
targetLongitude = Longitude;
lastHourAngle = CalcHourAngle(lastLongitude);
targetHourAngle = CalcHourAngle(targetLongitude);
swingTime = abs(targetHourAngle - lastHourAngle) * 1000 / Setup.PositionerSpeed; // time (ms) it takes to move the dish from lastHourAngle to targetHourAngle
movementStart.Set();
Setup.PositionerLastLon = targetLongitude;
}
void cPositioner::GotoPosition(uint Number, int Longitude)
{
if (Longitude != targetLongitude)
dsyslog("moving positioner to position %d, longitude %d", Number, Longitude);
StartMovementTimer(Longitude);
}
void cPositioner::GotoAngle(int Longitude)
{
if (Longitude != targetLongitude)
dsyslog("moving positioner to longitude %d", Longitude);
StartMovementTimer(Longitude);
}
int cPositioner::CurrentLongitude(void) const
{
cMutexLock MutexLock(&mutex);
if (targetLongitude != lastLongitude) {
int Elapsed = movementStart.Elapsed(); // it's important to make this 'int', otherwise the expression below yields funny results
if (swingTime <= Elapsed)
lastLongitude = targetLongitude;
else
return CalcLongitude(lastHourAngle + (targetHourAngle - lastHourAngle) * Elapsed / swingTime);
}
return lastLongitude;
}
bool cPositioner::IsMoving(void) const
{
cMutexLock MutexLock(&mutex);
return CurrentLongitude() != targetLongitude;
}
cPositioner *cPositioner::GetPositioner(void)
{
return positioner;
}
void cPositioner::DestroyPositioner(void)
{
delete positioner;
}
Alles anzeigen
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define SAT_EARTH_RATIO 0.1513 // the Earth's radius, divided by the distance from the Earth's center to the satellite
#define SAT_VISIBILITY_LAT 812 // the absolute latitude beyond which no satellite can be seen
#define RAD(x) ((x) * M_PI / 1800)
#define DEG(x) ((x) * 1800 / M_PI)
struct tSetup {
int SiteLat;
int SiteLon;
} Setup;
int NormalizeAngle(int Angle)
{
while (Angle < -1800)
Angle += 3600;
while (Angle > 1800)
Angle -= 3600;
return Angle;
}
int CalcHourAngle(int Longitude)
{
double Alpha = RAD(Longitude - Setup.SiteLon);
double Lat = RAD(Setup.SiteLat);
int Sign = Setup.SiteLat >= 0 ? -1 : 1; // angles to the right are positive, angles to the left are negative
return Sign * round(DEG(atan2(sin(Alpha), cos(Alpha) - cos(Lat) * SAT_EARTH_RATIO)));
}
int CalcLongitude(int HourAngle)
{
double Lat = RAD(Setup.SiteLat);
double Lon = RAD(Setup.SiteLon);
double Delta = RAD(HourAngle);
double Alpha = Delta - asin(sin(M_PI - Delta) * cos(Lat) * SAT_EARTH_RATIO);
int Sign = Setup.SiteLat >= 0 ? 1 : -1;
return NormalizeAngle(round(DEG(Lon - Sign * Alpha)));
}
int HorizonLongitude(int Lat)
{
double Delta;
if (abs(Lat) <= SAT_VISIBILITY_LAT)
Delta = acos(SAT_EARTH_RATIO / cos(RAD(Lat)));
else
Delta = 0;
if (Lat >= 0)
Delta = -Delta;
return NormalizeAngle(round(DEG(RAD(Setup.SiteLon) + Delta)));
}
int main(void)
{
Setup.SiteLat = 0;
Setup.SiteLon = 0;
for (int i = -850; i <= 850; i += 50)
fprintf(stderr, "%5d %5d %5d -> %5d %5d %5d\n", Setup.SiteLat, Setup.SiteLon, i, CalcLongitude(i), CalcHourAngle(CalcLongitude(i)), HorizonLongitude(i));
return 0;
}
Alles anzeigen
Albert