Impulse zählen und wenn keine Veränderung über bestimmte Zeit --> Code ausführen

Hallo Zusammen,

ich knabber seit einer ganzen Weile an folgendem Problem:

Für eine Art Hebebühne benötige ich folgenden Ablauf:

Wenn ich einen Button auf meinem Steckbrett drücke soll der Linearmotor ausfahren (einfaches 5V Steuersignal) und der Arduino soll anfangen Impulse pro Sekunde zu messen.
In dem Motor sind hierfür Hall-Sensoren verbaut, welche mir Impulse relativ zum Abstand ausgeben.
Wenn die Impulse pro Sekunde den Wert 0 erreichen, ist mein Linearmotor entweder ganz ausgefahren bzw. ganz eingefahren und das Steuersignal kann auf 0V gehen.

Jetzt soll ca. 20 Sekunden gewartet werden, bevor der Motor wieder einfährt (wieder 5V Steuersignal). Wieder die Impulse der Hallsensoren pro Sekunde messen und wenn der Wert 0 erreicht, dass Steuersignal auf 0V.

Das ganze soll in einer Endlosschleife stattfinden, bis ich den Button auf meinem Steckbrett drücke.
Dieser soll als eine Art An-Aus-Switch dienen.

Code zum Messen der Impulse:

int pin = 2; 
volatile unsigned int pulse; 
constintpulses_per_litre = 450; 
 
void setup() 
{ 
Serial.begin(9600); 
 
pinMode(pin, INPUT); 
attachInterrupt(0, count_pulse, RISING); 
} 
 
void loop() 
{ 
pulse=0; 
interrupts(); 
delay(1000); 
noInterrupts(); 
 
Serial.print("Pulses per second: "); 
Serial.println(pulse); 
} 
 
voidcount_pulse() 
{ 
pulse++; 
}

Mein Problem ist aktuell, dass das Messen der Impulse pro Sekunde aktuell über einen Interrupt Befehl läuft und ich daher nicht wirklich mit meinem Button arbeiten kann (debounce usw.)

Habe mich jetzt bereits ein bisschen schlau gemacht und festgestellt, dass während dem interrupt keine delay Befehle ausgeführt werden können.

Ich muss die Befehlskette mit Hilfe der Impulse ausführen und kann leider nicht einfach eine feste Zeit zum abstellen des Motors einstellen, da dieser je nach Last unterschiedlich viel Zeit benötigt.

Gesamter Code bisher:

int pin = 2; 
volatile unsigned int pulse; 

int Hoch = 1;
const int rausPin =  12;   
const int reinPin =  13;

int buttonPin = 3;         // the number of the input pin
int LEDPin = 4;       // the number of the output pin

int state = HIGH;      // the current state of the output pin
int reading;           // the current reading from the input pin
int previous = LOW;    // the previous reading from the input pin

// the follow variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long time = 0;         // the last time the output pin was toggled
long debounce = 200;   // the debounce time, increase if the output flickers

void setup()
{
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
  pinMode(LEDPin, OUTPUT);
  attachInterrupt(0, count_pulse, RISING); 

}

void loop()
{
  reading = digitalRead(buttonPin);

  // if the input just went from LOW and HIGH and we've waited long enough
  // to ignore any noise on the circuit, toggle the output pin and remember
  // the time
  if (reading == HIGH && previous == LOW && millis() - time > debounce) {
    if (state == HIGH)
      state = LOW;
    else
      state = HIGH;

    time = millis();    
  }

  digitalWrite(LEDPin, state);

  previous = reading;

  if (state == HIGH)
{
  if (Hoch == 1)
  {
  Serial.println("Hoch Start");    
  digitalWrite(rausPin, HIGH);
  delay(200);
  pulse=0; 
  noInterrupts(); 
  delay(500); 
  interrupts();

  if (pulse == 0) 
    {
    Serial.println("Fertig Hoch!");
    delay(200);
    digitalWrite(rausPin, LOW);
    delay(20000);
    Hoch = 0;
    }
  }

else if (Hoch == 0)
  {
  Serial.println("Runter Start"); 
  digitalWrite(reinPin, HIGH);
  delay(200);
  pulse=0; 
  noInterrupts(); 
  delay(500); 
  interrupts();

  if (pulse == 0) 
    {
    Serial.println("Fertig Runter!");
    delay(200);
    digitalWrite(reinPin, LOW);
    delay(20000);
    Hoch = 1;
    }
  }

}
}


void count_pulse() 
{ 
pulse++; 
}

Würde mich über eure Hilfe, Ideen und Anregungen sehr freuen.
Ich verwende auch gerne eine Lösung ohne Interrupt :slight_smile:

Tausend Dank
Grüße

Smoerebrot

[edit]

Evtl. sollte ich mein Problem noch ein bisschen besser beschreiben.
Ich kann die einzelnen Impulse ohne Interrupt mit Hilfe folgenden Codes zählen:

const int pulsePin = 2;     // the number of the pulse pin
boolean pulseState, oldpulseState;
unsigned long anzeigeMillis, aktMillis;
const int anzeigeZeit = 100;

void setup() {
  pinMode(pulsePin, INPUT_PULLUP);
  Serial.begin(9600);
  pulseState = digitalRead(pulsePin);
  oldpulseState = pulseState;
}

void loop() {
  static int Puls;
  aktMillis = millis();
  pulseState = digitalRead(pulsePin);
  
  if ( pulseState && !oldpulseState ) {   // positive Flanke erkannt
    Puls++;
  }
  oldpulseState = pulseState;
  if (aktMillis - anzeigeMillis >= anzeigeZeit) {
    anzeigeMillis = aktMillis;
    Serial.print("Puls: ");
    Serial.println(Puls);
  }
  }

Dabei kommen für einen einfachen Weg (--> Raus bzw. Reinfahren) immer zwischen 156 und 157 Impulse zustande.

Das wäre mein alternativer Weg, immer über die Impulsanzahl zu gehen.
Das Problem hier ist aber, dass wenn ein Impuls zu wenig geliefert wird mein ganzer Code "hängen" bleibt...

Es sollte mit und ohne Interrupts funktionieren, aber die (alle!) Interrupts sollte man nie über längere Zeit abschalten.

Auf delay() sollte man in einem fertigen Programm tunlichst verzichten. Das BlinkWithoutDelay hast Du ja anscheinend schon verstanden :slight_smile:

Ohne System kommst Du da vermutlich nicht weit. Du kannst einen Automaten (state machine) benutzen, oder Task Makros, um alles in den Griff zu bekommen.

In Lokomotiven ist ein Knopf eingebaut, der vom Lokomotivführer regelmäßig gedrückt werden muß, sonst bleibt die Lokomotive automatisch stehen. Das Prinzip eines solchen Todmannknopfes benötigst Du: Wenn keine Impulse, dann halt.

Ich beziehe mich auf den letzten Code aus #0.

Setze anzeigeZeit auf die Zeit zwischen zwei Impulsen + 10%.

Verschiebe anzeigeMillis = aktMillis; unter Puls++;

Was passiert?

Hallo,

mir fehlen aber noch 2 Endschalter oben/unten. Nur auf den Zählerrückgabewert kannste nicht verlassen.

Ich empfehle hier einen Ablaufplan zu erstellen und solange rumzukritzeln bis alles schlüssig ist von der Logik her. Erst dann in Code "übersetzen". Definiere dir klare Zustände. Schau dir switch-case und enum an.

Deine pulse Variable als volatile zu deklarieren ist schon richtig. Nur der Zugriff darauf noch fehlerhaft. Sonst kann das schief gehen. Im Hauptpgramm zum Bsp. so.

#include <util/atomic.h>

...
...



ATOMIC_BLOCK (ATOMIC_RESTORESTATE) { // Interrupt sicherer Zugriff auf 'pulse'
  ...
  hier irgendwas mit 'pulse' machen
  !delay strengstens verboten!
  ...
}

Hi

Dafür würde ich nicht mit Interrupts um mich werfen wollen.
Bei jedem Impuls die Systemzeit merken (millis() ) und wenn so_und_so_viel Millisekunden kein neuer Impuls kommt, sind wir auf Block gefahren (mechanisches Ende), oder das Auto wird gerade der Decke angepasst - Abschalten ebenfalls erwünscht.

Dann: Abschalten, 'Uhrzeit' merken.
5 Sekunden später in Gegenrichtung starten. Goto 10

MfG

Hier mal mein aktueller Code mit dem das ganze "funktioniert" (auch wenn ein wenig getrickst wurde):

const int pulsePin = 2;     // the number of the pulse pin
boolean pulseState, oldpulseState;
unsigned long anzeigeMillis, aktMillis;
const int anzeigeZeit = 500;

const int rausPin =  12;
const int reinPin =  13;

int Puls = 0;
int Richtung = 0;
boolean Reset = 0;
boolean Switch = 0;

void setup() {
  pinMode(pulsePin, INPUT_PULLUP);    //  PULLUP --> dadurch kann 2. Widerstand nach Optokoppler gespart werden 
  pinMode(rausPin, OUTPUT);
  pinMode(reinPin, OUTPUT);
  delay(10000);    // Zeit um Motor anzustecken, nicht wundern ;)
  
  Serial.begin(9600);
  pulseState = digitalRead(pulsePin);
  oldpulseState = pulseState;
}

void loop() {

aktMillis = millis();
pulseState = digitalRead(pulsePin);

if ((Puls < 150) && (Richtung == 0)) 
  {
    if (Switch == 0)
    {
    Serial.println("Rausfahren!");
    Switch = 1;
    }
   digitalWrite(rausPin, HIGH);
   Reset = 0;
   
   if ( pulseState && !oldpulseState )    // positive Flanke erkannt
    {
     Puls++;
    }
   oldpulseState = pulseState;
  
   if (aktMillis - anzeigeMillis >= anzeigeZeit)
     {
     anzeigeMillis = aktMillis;
     Serial.print("Puls: ");
     Serial.println(Puls);
     }
   }

if ((Puls == 150) && (Richtung == 0)) // Pulse gehen immer bis 156/157 ich höre 7 Impulse zuvor auf und lass den Motor noch 0,5 Sekunden für die restlichen Impulse weiterfahren 
  {
  if (Reset == 0)
  {
    Reset = 1;
    Switch = 0;
    Serial.println("Rausfahren STOP!");
    Puls = 157; // hier ein "Reset" um Impulse Counter immer auf 157 zu haben (falls z.B. nur bei 156 stehengeblieben)
  }
  delay(500);
  Richtung = 1;
  digitalWrite(rausPin, LOW);
  digitalWrite(reinPin, LOW);
  delay(20000);
  }

if ((Puls > 7) && (Richtung == 1))
{
  if (Switch == 0)
    {
    Serial.println("Reinfahren!");
    Switch = 1;
    }
  digitalWrite(reinPin, HIGH);
  Reset = 0;
  
  if ( pulseState && !oldpulseState ) {   // positive Flanke erkannt
    Puls--;
  }
  oldpulseState = pulseState;
  if (aktMillis - anzeigeMillis >= anzeigeZeit) {
    anzeigeMillis = aktMillis;
    Serial.print("Puls: ");
    Serial.println(Puls);
  }
}

if ((Puls == 7) && (Richtung == 1)) // selbes Spiel wie oben: 7 Impulse vor 0 wird noch 0,5 Sekunden weitergefahren
  {
  if (Reset == 0)
  {
    Reset = 1;
    Serial.println("Reinfahren STOP!");
    Puls = 0;
  }  
  delay(500);
  Richtung = 0;
  digitalWrite(rausPin, LOW);
  digitalWrite(reinPin, LOW);
  delay(20000);
  }
}

So ganz glücklich bin ich noch nicht damit.
Falls ich den Not-Aus an meiner Konstruktion betätige, bleibt der Motor irgendwo auf halber Strecke stehen.
Wenn ich jetzt den Not-Aus lösen sollte, kann ich mein Programm nicht wieder einfach so aufnehmen.

Mir ist bewusst, dass nach einem Not-Aus, ein extra Programm laufen sollte, dass die Konstruktion in eine sichere Ausgangslage bringt. Aber auch für dieses muss der Motor ganz ausgefahren werden und es muss erkannt werden, ab wann das geschehen ist. (Komme hier bestimmt nicht auf meine 150 Impulse, wenn z.B. der Motor kurz vor seinem ganz ausgefahrenen Zustand gestoppt wurde).

Ist es so, dass ihr grundsätzlich keine Fans von der delay() Funktion seid? :sob:
Oder nur im Zusammenhang mit einem Interrupt?

@agmue & Doc_Arduino
Ich werde morgen mal eure Vorschläge versuchen umzusetzen.

Muss mich dafür aber heute Abend noch ein bisschen in Dinge wie "switch-case", "enum" usw. einlesen.
Habe davon noch nie gehört :sweat_smile:

@DrDiettrich: Mit Task Makros zu arbeiten, ist glaube ich für dieses Projekt ein bisschen zu viel, habe deinen Link kurz überflogen und ich merke, dass es noch viel zu lernen gibt mit dem Arduino :astonished:

Danke für eure Hilfe (:

Du bist automatisch dann, wenn du kleine Programme einigermaßen selbst erstellen kannst, kein Freund mehr von Delay.
So ein Loop braucht ein paar millisekunden für einen Durchlauf. Wenn du nur an einer Stelle in dem Loop z.B. delay(1000); stehen hast, und du möchtest irgendwo in diesem Loop abfragen ob du eine Taste gedrückt hast, dann wirst du ganz bestimmt fast nie diese Abfrage mit deinem Tastendruck erwischen, weil der Rechner immer nur eine Sek. völlig ausser Betrieb ist, und dann wieder für 1 Tausenstel Sekunde prüft ob die Taste gedrückt ist, dann ist wieder eine Sekunde alles tot, und wieder ein tausenstel Sekunde die Prüfung ob eine Taste gedrückt ist, u.s.w. Jetzt ist das nur ein einziges delay(1000) in deinem Programm, und schon ist die Schei** am dampfen.

Dieses Programm nagelst du nach ein paar Tests an die Wand, und schaust es nie wieder an!!

Hi

Nebenbei: In einem Interrupt hat delay() gleich zwei Mal Nichts verloren.

  1. Es funktioniert nicht, da delay() selber von einem Interrupt abhängt und in einer ISR kein anderer Interrupt 'darf'
  2. eine ISR (Interrupt Service Routine) so kurz/schnell wir möglich sein MUSS, damit Sie nicht stört

Während eines delay() klappen Interrupts natürlich noch - nur kannst Du kaum drauf reagieren, da Du ja gerade 'Nichts machst'.
(... man könnte yield() nutzen ... besser ist aber ohne delay() )

Mein aktuelles Projekt hat eine loop()-Durchlaufzeit von maximal 1212ns, wenn ich nur alle Sekunde ein Lebenszeichen ausgebe (... mit 9600Baud) und die Sensoren stimuliere (einen Dreh-Encoder verdrehe).
(dabei steigt die Latenzzeit beim ersten Herzschlag von 150ns bei 'Teil 1' über 416 bei 'Teil 2' über 740 bei 'Teil 3' bis auf 1212, wenn wieder 'Teil 1' an der Reihe ist. Diese 1212 bleiben so lange, wie ich die Sensoren 'stimuliere'.)
Wenn mein Sketch denkt, der Sensor wäre ausgefallen (bzw. das Ende der Fahrstrecke wäre erreicht) und eine große Fehler-/Statusmeldung ausgegeben wird (und einige Dinge im Programm passieren), steigt diese Zahl auf >83ms.
Alles aber noch im dicken grünen Bereich und im späteren Betrieb werden die Infos nicht mehr per Serial sondern per CAN-Bus übertragen bzw. die Befehle so empfangen.

MfG

Ohhh man, ich habe das Programm gerade erst angesehen. Da habe ich ja mit meiner Delay Beschreibung nur kleine graue Wölkchen aufgezeigt im Vergleich zu dem Programm hier :slight_smile:

postmaster-ino:
Hi

Dafür würde ich nicht mit Interrupts um mich werfen wollen.
Bei jedem Impuls die Systemzeit merken (millis() ) und wenn so_und_so_viel Millisekunden kein neuer Impuls kommt, sind wir auf Block gefahren (mechanisches Ende), oder das Auto wird gerade der Decke angepasst - Abschalten ebenfalls erwünscht.

Dann: Abschalten, 'Uhrzeit' merken.
5 Sekunden später in Gegenrichtung starten. Goto 10

MfG

Ich habe mich mal an deiner Idee versucht, doch so ganz klappen tut das ganze immer noch nicht.
Scheinbar sind genau die 5 sekunden delay das Problem bzw. das es zu lange dauert, bis der Motor seinen ersten Impuls rausschickt.

--> der Motor fängt garnicht erst das fahren an.

Also muss man noch an irgendeiner Stellschraube drehen.
Bin für Tipps und Tricks dankbar :wink:

P.S. Wenn ich den Motor manuell hochfahren lasse und dann meinen Code nebenherlaufen lasse, funktioniert das mit der "timebetweenpulses" hervoragend.
Wie gesagt. ist vermutlich das Problem, dass der 1. Puls zu spät ankommt und ich schon wieder aus meiner while Schleife ausgestiegen bin.

const int pinpulse = 2;     // the number of the pulse pin
boolean pulseState, oldpulseState;
unsigned long anzeigeMillis, aktMillis;
const int anzeigeZeit = 100;

const int pinraus =  12;
const int pinrein =  13;

int Puls = 0;
int hochfahren = 0;
boolean Reset = 0;
boolean Switch = 0;

unsigned long timebetweenpulses = 0;
unsigned long time1 = 0;

void setup() {
  pinMode(pinpulse, INPUT);    //  evtl. PULLUP --> dadurch kann 2. Widerstand nach Optokoppler gespart werden 
  pinMode(pinraus, OUTPUT);
  pinMode(pinrein, OUTPUT);
  delay(5000);            // Zeit um Motor anzustecken
  
  Serial.begin(9600);
  pulseState = digitalRead(pinpulse);
  oldpulseState = pulseState;
}


void loop() {

delay(5000);
rausfahren();
delay(5000);
reinfahren();

}





void reinfahren(){
   
  digitalWrite(pinraus, LOW);                             // Sicherstellen, dass der rausPin LOW ist

    timebetweenpulses = 0;
  
  while (timebetweenpulses < 500){

 
    aktMillis = millis();
    pulseState = digitalRead(pinpulse);
    
    if (Switch == 0){
    Serial.println("Runterfahren!");
    Switch = 1;
    }

   digitalWrite(pinrein, HIGH);
   if (pulseState && !oldpulseState)    // positive Flanke erkannt; Aktuelle Zeit --> auf time1
    {
     time1 = millis();
     Puls++;
    }
   
   oldpulseState = pulseState;
   timebetweenpulses = (millis() - time1);
  Serial.print("timebetweenpulses: ");
  Serial.println(timebetweenpulses);

   if (aktMillis - anzeigeMillis >= anzeigeZeit)
     {
     anzeigeMillis = aktMillis;
     Serial.print("Puls: ");
     Serial.println(Puls);
     }
   }
   Serial.println("Runterfahren STOP");
   digitalWrite(pinrein, LOW);
   Switch = 0;
return;
}




void rausfahren(){
    
  digitalWrite(pinrein, LOW);                             // Sicherstellen, dass der reinPin LOW ist

  timebetweenpulses = 0;
  
  while (timebetweenpulses < 500){
    
    aktMillis = millis();
    pulseState = digitalRead(pinpulse);
    
    if (Switch == 0){
    Serial.println("Hochfahren!");
    Switch = 1;
    }

   digitalWrite(pinraus, HIGH);
   if (pulseState && !oldpulseState)    // positive Flanke erkannt; Aktuelle Zeit --> auf time1
    {
     time1 = millis();
     Puls++;
    }
    
     oldpulseState = pulseState;

   timebetweenpulses = ( millis() - time1);
Serial.print("timebetweenpulses: ");
Serial.println(timebetweenpulses);
  
   if (aktMillis - anzeigeMillis >= anzeigeZeit)
     {
     anzeigeMillis = aktMillis;
     Serial.print("Puls: ");
     Serial.println(Puls);
     }
   }
   Serial.println("Hochfahren STOP");
   digitalWrite(pinraus, LOW);
   Switch = 0;
return;
}

So hab die Lösung:

Einfach for der while Schleife die Startzeit messen und in die while Schleife

|| ((millis() - timestart) < 500)

einfügen.

Der Motor braucht ca. 200ms um den ersten Impuls an den Arduino zu schicken, das ist mit diesem Code Schnippsel sichergestellt.

Ansonsten habe ich noch die Zeit zwischen den Impulsen auf < 200ms gestellt, da der Abstand zwischen den Impulsen laut Monitor maximal 60ms sind.
Muss evtl. je nach Last, die vom Hubmechanismus gehoben werden muss noch angepasst werden. Aber im Normalfall reichen die 200ms locker.

Euch allen vielen Dank für die Hilfe :slight_smile:
Anbei noch der vollständige Code, welchen ich jetzt irgendwie in eine Library packen muss :o :sob:

const int pinpulse = 2;     // the number of the pulse pin
boolean pulseState, oldpulseState;
unsigned long anzeigeMillis, aktMillis;
const int anzeigeZeit = 100;

const int pinraus =  12;
const int pinrein =  13;

int Puls = 0;
int hochfahren = 0;
boolean Reset = 0;
boolean Switch = 0;

unsigned long timebetweenpulses = 0;
unsigned long time1 = 0;
unsigned long timestart = 0;

void setup() {
  pinMode(pinpulse, INPUT);    //  evtl. PULLUP --> dadurch kann 2. Widerstand nach Optokoppler gespart werden 
  pinMode(pinraus, OUTPUT);
  pinMode(pinrein, OUTPUT);
  delay(5000);            // Zeit um Motor anzustecken
  
  Serial.begin(9600);
  pulseState = digitalRead(pinpulse);
  oldpulseState = pulseState;
}


void loop() {

delay(5000);
rausfahren();
delay(5000);
reinfahren();

}





void reinfahren(){
   
  digitalWrite(pinraus, LOW);                             // Sicherstellen, dass der rausPin LOW ist

    timebetweenpulses = 0;
    timestart = millis();
    
   while ((timebetweenpulses < 200) || ((millis() - timestart) < 500)){     // 2000 ms Zeit, damit die ersten Impulse vom Motor am Arduino ankommen

 
    aktMillis = millis();
    pulseState = digitalRead(pinpulse);
    
    if (Switch == 0){
    Serial.println("Runterfahren!");
    Switch = 1;
    }

   digitalWrite(pinrein, HIGH);
   if (pulseState && !oldpulseState)    // positive Flanke erkannt; Aktuelle Zeit --> auf time1
    {
     time1 = millis();
     Puls++;
    }
   
   oldpulseState = pulseState;
   timebetweenpulses = (millis() - time1);
  Serial.print("timebetweenpulses: ");
  Serial.println(timebetweenpulses);

   if (aktMillis - anzeigeMillis >= anzeigeZeit)
     {
     anzeigeMillis = aktMillis;
     Serial.print("Puls: ");
     Serial.println(Puls);
     }
   }
   Serial.println("Runterfahren STOP");
   digitalWrite(pinrein, LOW);
   Switch = 0;
return;
}




void rausfahren(){
    
  digitalWrite(pinrein, LOW);                             // Sicherstellen, dass der reinPin LOW ist

  timebetweenpulses = 0;
  timestart = millis();
  
  while ((timebetweenpulses < 200) || ((millis() - timestart) < 500)){     // 2000 ms Zeit, damit die ersten Impulse vom Motor am Arduino ankommen
    
    aktMillis = millis();
    pulseState = digitalRead(pinpulse);
    
    if (Switch == 0){
    Serial.println("Hochfahren!");
    Switch = 1;
    }

   digitalWrite(pinraus, HIGH);
   if (pulseState && !oldpulseState)    // positive Flanke erkannt; Aktuelle Zeit --> auf time1
    {
     time1 = millis();
     Puls++;
    }
    
     oldpulseState = pulseState;

   timebetweenpulses = ( millis() - time1);
Serial.print("timebetweenpulses: ");
Serial.println(timebetweenpulses);
  
   if (aktMillis - anzeigeMillis >= anzeigeZeit)
     {
     anzeigeMillis = aktMillis;
     Serial.print("Puls: ");
     Serial.println(Puls);
     }
   }
   Serial.println("Hochfahren STOP");
   digitalWrite(pinraus, LOW);
   Switch = 0;
return;
}

Hi

Super, daß Du Das hinbekommen hast und ein ganz großes Danke, daß Du Deinen Code hier angehangen hast.
Eine eigene LIB ... bin derzeit an einer eigenen Klasse am wuseln - wobei eine externe Klasse (fast schon ?) eine LIB ist ... oder?

Wie Dem auch sei - viel Erfolg dabei!

MfG