RPi Pico Frequenzzähler bis 65 MHz          

Elektronik-Labor  Projekte  Mikrocontroller  Raspberry            




Siehe auch: RPi Pico Schaltungen und Projekte, Kap 16

Für höhere Frequenzen reicht die Abfrage von Portzuständen nicht mehr aus, sondern man braucht einen Hardware-Zähler. Hier wird die PWM-Einheit 2 (Slice 2) als 16-Bit-Zähler verwendet. Der Kanal B kann statt des Systemtakts auch einen externen Takt zählen. Er liegt nun an P5.

Mit pwm_get_default_config() erhält man eine Default-Struktur mit allen Einstellungen einer PWM-Einheit. Mit config_set_clkdiv_mode wird die Einstellung so geändert, dass am Eingang B externe Impulse bei steigenden Flanken gezählt werden. Diese Konfiguration wird dann mit pwm_init der PWM-Einheit 2 zugewiesen, wobei der Parameter false angibt, dass keine PWM-Ausgabe gewünscht wird. Stattdessen wird diesmal mit pwm_get_counter der Zählerstand ausgelesen. 

//Pico_Freq3
 
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "pico/multicore.h"

void setup() {
  Serial.begin(115200);
}

void loop() {} 
 
void setup1() {
  gpio_set_function(0, GPIO_FUNC_PWM);
  pwm_set_wrap(0, 1);  //PWM 62,5 MHz
  pwm_set_gpio_level(0, 1);
  pwm_set_enabled(0, true);

  while (true) {
    uint32_t f = 0;
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);
    pwm_init(2, &cfg, false);
    gpio_set_function(5, GPIO_FUNC_PWM); 
    uint32_t t = time_us_32()+2;
    while (t>time_us_32());
    pwm_set_enabled(2, true);
    t += 1000;
    while (t>time_us_32());
    pwm_set_enabled(2, false);
    f= pwm_get_counter(2);

    Serial.print(f);     
    Serial.println(" kHz   "); 
  }


void loop1() {}


Auch diesmal wird der PWM-Ausgang am Port GP0 als Signalquelle verwendet. Hier wird die höchste mögliche PWM-Frequenz von 62,5 MHz erzeugt. Als Zeitbasis wird der Mikrosekunden-Timer verwendet. Damit die Messung genau bei einer beginnenden Mikrosekunde beginnt, wird vorher der Beginn der zweiten Mikrosekunde abgewartet. Nach weiteren 1000 µs endet die Messung. Das Messergebnis bestätigt die Messfrequenz von 62500 kHz mit den üblichen Schwankungen am letzten Digit.



Der Anschluss GP5 kann nun als Eingang für einen vielseitig einsetzbaren Zähler verwendet werden. Zum Schutz des Eingangs vor negativen oder zu hohen Signalspannungen sollten ein Widerstand und ein Kondensator in Reihe gelegt werden.






Netzfrequenzmesser, Auflösung 1 mHz von Ralph Maas

Ich habe auf der Basis des Buchs zu den RPi Pico Schaltungen einen Netzfrequenzmesser mit 7-Segment-Anzeige gebaut. Die Berechnung ist auf 1 mHz genau und mein längerfristiges Ziel ist einen Schalter zu bauen,der elektrische Verbraucher nur bei Frequenzen über 50,01 Hz einschaltet, wenn auch die Primärregelleistung zuschlägt und Energie aus dem Netz nimmt.  Die Anpassung der Maske zum gemeinsamen Adressieren der Ausgänge auf Seite 100 hatte mich übrigens längere Zeit gekostet.  Daher habe ich das statt mit 0x mit 0b gemacht. Siehe Zeile 50 und 61.

Siehe auch 7-Segment-Anzeige am  RPi Pico
/ Programm zur Messung der Netzfrequenz auf 1mHz genau und Anzeige auf 7-Segment-Anzeige
// Pico_Freq1 Beispiel Buch Seite 60 Burkhard Kainka, RPi Pico Schaltungen und Projekte
// Ergänzt um Funktion, um 50hz zu erzeugen aus https://www.i-programmer.info/programming/hardware/14849-the-pico-in-c-basic-pwm.html?start=2
// Erweitert um Multicore-Beispiel Seite 62
// Ergänzt um Ideen von Jens Mueller www.netzfrequenzanzeige.de ; www.pc-projekte.de
// Ergänzt um Funktionen für 7-Segment-Anzeige Buch Seite 99

#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "pico/multicore.h"

//#define TEST // Wenn das aktiv ist, dann ist ein interner PWM-Generator aktiv

// Variablen für Frequenzmessung
#ifdef TEST
const int pin_out = 0; // geeignet für PWM
const int pin_in = pin_out; // 0 ist auch gleichzeitig das PWM-Ausgangspin für Tests ohne externe Beschaltung
#else
//const int pin_in = 1; // Daran ist der Opto-Koppler angeschlossen
const int pin_in = 26; // Daran ist der Draht parallel zur Stromleitung (induktive Einkopplung)
#endif

word Frequenz = 50001; // Frequenz in mHz
const word Ueberfrequenz = 50010; // Überfrequenz ab 50,01 Hz (Totband 10mHz)
const word Unterfrequenz = 49990; // Unterfrequenz unter 49,99 Hz (Totband 10mHz)
uint32_t Startzeit = 0; // Zwischenspeicher für Startzeit
uint32_t Laufzeit = 0; // Zwischenspeicher für Zeitdifferenz
byte Periode = 1; // Laufende Periode
const byte Ziel_Perioden = 50 ; // Anzahl der Perioden über die gezählt werden soll
// 10 Perioden sind ein guter Wert, hier entspricht 1us 0,25mHz, Schwankungen um 1-2 us beeinflussen also die letzte Ziffer nicht

const unsigned long Multiplikator = 1000000 * Ziel_Perioden; // Für die Berechnung der Frequenz in Hz
// (Faktor 1000 für mHz muss in Rechnung ergänzt werden, weil sonst overflow)
// Weil unsigned long maximal
// 4,294,967,295 abbilden können, funktioniert die Rechnung nur bis 4 Perioden ohne Tricks
// 1.000.000 Multiplikator für Hz
// 1.000.000.000 Multiplikator für mHz

// Variablen für LED-Anzeige
const int led_Ueberfrequenz = 15; // GPIO 15 ist an Anzeige-12 (+)
const int led_FrequenzTotband = 14; // GPIO 14 ist an Anzeige-11 (o)
const int led_Unterfrequenz = 17; // GPIO 17 ist an Anzeige-14 (-)
// Nicht verwendete LED an GPIO 6,9,11,22
//const int led_Test = 25; // Buildin

// 7-Segment
const int led_Dezimalpunkt = 10; // GPIO 10 ist an Anzeige-6 (p)
int Ziffer[6]; // Ziffern für Anzeige
uint32_t Segment[14];
// 7-Segment-Ausgabe incl. der Einzel-LED
// GPIO-1.ste 221111111111
// GPIO-2.ste 1098765432109876543210
// Rückwärts decb-g+oa3.p.1f.42....
uint32_t Output = 0b1111111111010110110000; // Diese werden auf einmal auf Output gestellt
uint32_t Maske = 0b1111010011000110110000; // Diese werden über maskierten Output nach Segment auf 1 gestellt

// Watchdog ob die Messung läuft
int Watchdog = 5; // Frequenzanzeige in Core 0 reduziert, Messung in Core 1 erhöht den Wert

void setup() { // Core 0 für Kommunikation und Anzeige
// 7-Segment-Ausgabe
// 221111111111
// 1298765432109876543210
// Rückwärts decb.g..a3...1f.42....
Segment[0] = 0b1111000011000110110000;
Segment[1] = 0b0011000001000100110000;
Segment[2] = 0b1101010011000100110000;
Segment[3] = 0b1011010011000100110000;
Segment[4] = 0b0011010001000110110000;
Segment[5] = 0b1010010011000110110000;
Segment[6] = 0b1110010011000110110000;
Segment[7] = 0b0011000011000100110000;
Segment[8] = 0b1111010011000110110000; // Das ist dezimal: 4.010.416 ; uint32 kann bis 4,294,967,295
Segment[9] = 0b1011010011000110110000;
Segment[10]= 0b0110010001000100110000; // n für Anzeige "nono" wenn keine Frequenzmessung
Segment[11]= 0b1110010001000100110000; // 0 für Anzeige "nono" wenn keine Frequenzmessung

gpio_init_mask(Output);
gpio_set_dir_out_masked(Output); // Stellt alle in Output mit 1 belegten GPIO auf OUTPUT

gpio_set_drive_strength(7, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(13, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(16, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(18, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(19, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(20, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(21, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
pinMode(led_Dezimalpunkt, OUTPUT);
gpio_set_drive_strength(led_Dezimalpunkt, GPIO_DRIVE_STRENGTH_2MA);

// LED für Unter-/Überfrequenz/Totband
pinMode(led_Ueberfrequenz, OUTPUT);
pinMode(led_FrequenzTotband, OUTPUT);
pinMode(led_Unterfrequenz, OUTPUT);

gpio_set_drive_strength(led_Ueberfrequenz, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände
gpio_set_drive_strength(led_FrequenzTotband, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände
gpio_set_drive_strength(led_Unterfrequenz, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände

// Allgemein
set_sys_clock_khz(125000, true);
Serial.begin(115200);
}

void loop() {
ZiffernAufteilen();

/*// Nur Test ob die Aufteilung auf Ziffern funktioniert hat
Serial.print(Ziffer[1]); Serial.print(Ziffer[2]); Serial.print(Ziffer[3]); Serial.print(Ziffer[4]); Serial.print(Ziffer[5]);*/

gpio_put(led_Ueberfrequenz, LOW);
gpio_put(led_Unterfrequenz, LOW);
gpio_put(led_FrequenzTotband, LOW);

if (Watchdog >=0) {
Serial.print(Laufzeit);
Serial.print(" Laufzeit ");

Serial.print(Frequenz);
Serial.print(" mHz ");

if (Frequenz >= Ueberfrequenz) {
gpio_put(led_Ueberfrequenz, HIGH);
Serial.println(" + ");
}
else if (Frequenz <= Unterfrequenz) {
gpio_put(led_Unterfrequenz, HIGH);
Serial.println(" - ");
}
else {
gpio_put(led_FrequenzTotband, HIGH);
Serial.println(" O ");
}
Watchdog--;
}
else {
Ziffer[1] = 10; // "n"
Ziffer[2] = 11; // "o"
Ziffer[3] = 10; // "n"
Ziffer[4] = 11; // "o"
}

Anzeige7Segment(); // Summe der Anzeigeschleife ist 1000ms = 1s passt zu 50 Perioden bei 50Hz = 1s

}

void setup1() { // Core 1 für zeitkritische Messung
#ifdef TEST
// Hier wird PIN 0 als Ausgang des PWM-Signals definiert, um für Tests keine externe Quelle zu benötigen
gpio_set_function(pin_out, GPIO_FUNC_PWM);
uint slice_num = pwm_gpio_to_slice_num(pin_out);
uint chan = pwm_gpio_to_channel(pin_out);
//pwm_set_freq_duty(slice_num,chan, 50, 75); // 50 ist 50Hz, 75 ist 75% duty cycle
// unklar, warum das 53 Hz gibt, wenn man die nicht die Systemfrequenz genau auf 125000 einstellt
pwm_set_freq_duty(slice_num,chan, 51, 75); // 50 ist 50Hz, 75 ist 75% duty cycle
pwm_set_enabled(slice_num, true);
#else
//pinMode(pin_in, INPUT_PULLUP); // Hier wird PIN 5 als Eingang definiert mit Pull-Up, damit der Opto-Transistor versorgt wird
pinMode(pin_in, INPUT); // Hier wird PIN 5 als Eingang definiert ohne Pull-Up, um induktive Einkopplung zu testen
#endif

while (true) {
bool in_old = true;
bool in_new = true;
while (gpio_get(pin_in));
while (!gpio_get(pin_in));
// Hier ist pin_in gerade auf true gegangen, die erste Periode beginnt
Startzeit = time_us_32();
while (Periode <= Ziel_Perioden) {
in_new = (gpio_get(pin_in));
if (in_new && !in_old) Periode++;
in_old = in_new;
}
Laufzeit = time_us_32() - Startzeit;

// Frequenz = 1000 * Multiplikator / Laufzeit; // Diese Variante geht nur bis 4 Perioden, weil sonst overflow
Frequenz = Multiplikator * ( 1000.0 / float(Laufzeit)); // Mit diesem Umweg können auch mehr als 4 Perioden gemessen werden

Periode = 1; // Neustart
Serial.print(" NEU ");
Watchdog = 5;
}
}


void loop1() {
}

void ZiffernAufteilen(){
word Rest = Frequenz;

Ziffer[5] = Rest%10; // ....0 mHz
Rest = Rest/10;
Ziffer[4] = Rest%10; // ...0. mHz
Rest = Rest/10;
Ziffer[3] = Rest%10; // ..0.. mHz
Rest = Rest/10;
Ziffer[2] = Rest%10; // .0... mHz
Rest = Rest/10;
Ziffer[1] = Rest%10; // 5.... mHz
Rest = Rest/10;
}

void Anzeige7Segment() {
// Summe der Anzeigeschleife ist 1000ms = 1s passt zu 50 Perioden bei 50Hz = 1s
for (int i=0; i<250; i++) {
gpio_put(led_Dezimalpunkt, 1);
gpio_put_masked(Maske, Segment[Ziffer[1]]);
gpio_put(8,0);
sleep_ms(1);
gpio_put_masked(Maske, Segment[Ziffer[2]]);
gpio_put(4,0);
sleep_ms(1);
gpio_put_masked(Maske, Segment[Ziffer[3]]);
gpio_put(12,0);
sleep_ms(1);
gpio_put_masked(Maske, Segment[Ziffer[4]]);
gpio_put(5,0);
sleep_ms(1);
}
}

#ifdef TEST
// Ergänzt um Funktion, um 50hz zu erzeugen aus https://www.i-programmer.info/programming/hardware/14849-the-pico-in-c-basic-pwm.html?start=2
uint32_t pwm_set_freq_duty(uint slice_num,
uint chan,uint32_t f, int d)
{
uint32_t clock = 125000000;
uint32_t divider16 = clock / f / 4096 + (clock % (f * 4096) != 0);
if (divider16 / 16 == 0)
divider16 = 16;
uint32_t wrap = clock * 16 / divider16 / f - 1;
pwm_set_clkdiv_int_frac(slice_num, divider16/16, divider16 & 0xF);
pwm_set_wrap(slice_num, wrap);
pwm_set_chan_level(slice_num, chan, wrap * d / 100);
return wrap;
}
#endif



Elektronik-Labor  Projekte  Mikrocontroller  Raspberry