Das PicoBasic-TestLab                              

Elektronik-Labor  Projekte  Mikrocontroller  PicoBasic         




Download: (update 30.10.24) PicoBasic21.zip

Die PicoBasic-Version  2.1 enthält eine neue Schaltfläche: TestLab. Mit einem Klick darauf öffnet man ein zusätzliches Fenster mit einem kompletten kleinen Testlabor für unterschiedliche Messungen und Software-Tests. Bei der Entwicklung hat das Arduino-Messlabor Pate gestanden. Der Unterschied ist aber, dass ein gerade gestartetes PicoBasic-Programm gleichzeitig läuft und damit zu einem Teil des Testlabors wird. Damit hat man unendliche Möglichkeiten für ganz spezielle Messungen und Tests. 

Während ein PicoBasic-Programm läuft, lauscht die Firmware auf Signale von der seriellen Schnittstelle. Das erste ankommende Zeichen wird analysiert. Handelt es sich um ein p oder ein e, soll ein Programm ins RAM oder ins EEPROM übertragen werden. Dann wird das laufende Programm angehalten und das neue Programm geladen. Handelt es sich um eine Ziffer 0…9,  soll offensichtlich eine Zahl übertragen werden, die mit Input A vom Programm gelesen werden kann.

Eine Datenübertragung im Hintergrund funktioniert also reibungslos und ohne den Programmablauf zu stören. Da könnte man also auch noch ganz andere Dinge tun und z.B. bei laufendem Programm die AD-Wandler oder die Ports abfragen. Zur Unterscheidung von den bisherigen Daten wird festgelegt, dass die neuen Kommandos zur direkten Kommunikation im Hintergrund mit Großbuchstaben  beginnen. Ein A soll z.B. bedeuten, dass der aktuelle Inhalt der Variablen A ausgelesen werden soll, und mit E wird die Spannung an AD0 gemessen. Im Laufe der Entwicklung kamen immer noch mehr Funktionen dazu, sodass inzwischen Kommandos von A bis V existieren. Sie werden als Direkt-Kommandos bezeichnet, weil sie ohne ein Programm in einer direkten Kommunikation mit dem PC ausgeführt werden. Die meisten Funktionen sind fast identisch mit den entsprechenden Funktionen für den Basic-Interpreter.

  void d65(){Serial.println(a);}  //A
  void d66(){Serial.println(b);}  //B
  void d67(){Serial.println(c);}  //C
  void d68(){Serial.println(d);}  //D
  void d69(){Serial.println(analogRead(A0)>>2);}  //E  AD0
  void d70(){Serial.println(analogRead(A1)>>2);}  //F  AD1
  void d71(){Serial.println(analogRead(A2)>>2);}  //G  AD2
  void d72(){Serial.println(255 & gpio_get_all());} //H  Pin
  void d73(){weiter = 0xFFFFFFFF;} //I  Stop!
  void d74(){weiter = 0;} //J  Go!
  void d75(){uint16_t n = Serial.parseInt();gpio_set_dir_masked (mask, n);} //K  DIR
  void d76(){uint16_t n = Serial.parseInt();gpio_put_masked (mask, n);} //L  OUT
  void d77(){uint16_t n = Serial.parseInt();for(int j=0; j<8; j++){gpio_set_pulls(j,1 &(n>>j), 0);}} //M  Pullup
  void d78(){uint16_t n = Serial.parseInt();for(int j=0; j<8; j++){gpio_set_pulls(j, 0,1 &(n>>j));}} //N  Pulldown
  void d79(){uint16_t n = Serial.parseInt();pwm_set_gpio_level(8,n*2); pw1=n;} //O  PWM1
  void d80(){uint16_t n = Serial.parseInt();pwm_set_gpio_level(9,n*2); pw2=n;} //P  PWM2
  void d81(){for(int j=0; j<256; j++){uint16_t n = Serial.parseInt();ram[j]=n;}} //Q  RAM füllen
  void d82(){uint16_t n = Serial.parseInt();a = n;} //R  A=
  void d83(){uint16_t n = Serial.parseInt();b = n;} //S  B=
 
void d84(){uint16_t n = Serial.parseInt();c = n;} //T  C=
  void d85(){uint16_t n = Serial.parseInt();d = n;} //U  D=
  void d86(){                                       //V  PWM-Vorteiler
    uint16_t n = Serial.parseInt();
   
pwm_set_enabled(4,true);
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_int (&cfg, n);
    pwm_init(4, &cfg, true);
    pwm_set_wrap(4, 511);
    pwm_set_gpio_level(8,pw1*2);
    pwm_set_gpio_level(9,pw2*2);
   
pwm_set_enabled(4,true);
  }

 

  void (*direkt[22])() = {
  d65, d66, d67, d68, d69, d70, d71, d72,
  d73, d74, d75, d76, d77, d78, d79, d80,
  d81, d82, d83, d84, d85, d86};

...

      }
      if (ch>64 && ch<88){
        direkt[ch-65]();
     
}

Was man mit diesen direkten Zugriffen anfangen kann, zeigt ein Beispiel aus der Anwendersoftware. Hier wurde ein PicoBasic-DDS-Programm gestartet, das einen Sinusgenerator mit dem PWM1-Ausgang bildet. Dann wurde das TestLab gestartet. Die Kurvenform wird mit der Schaltfläche „Sin“ in das 256 Byte lange Datenarray übertragen. Das Ausgangssignal durchläuft ein zweistufiges Tiefpassfilter und wird bei laufendem DDS-Generator mit dem Zweikanal-Oszilloskop angezeigt.



Die Arduino-Software gibt für die PWM-Ausgänge eine Frequenz von 1 kHz vor. Allerdings kann der Rpi Pico wesentlich mehr. Weil vor allem für die Signalgeneratoren höhere PWM-Frequenzen sinnvoll sein können, wurde die PWM-Ausgabe mit nativem C umgeschrieben. Deshalb gibt es nun mit dem Direktkommando V die Möglichkeit, die PWM-Frequenz zwischen 1 kHz und 250 kHz einzustellen. An dieser Stelle geht das TestLab über die Möglichkeiten von PicoBasic hinaus.

Für die Umstellung mussten die PWM-Ausgänge im Setup anders initialisiert werden. Entsprechend  ändert sich auch der Zugriff in den PicoBasic-Befehlen. Um glatte PWM-Frequenzen zu bekommen, wurde der Prozessortakt auf 128 MHz eingestellt. Die PWM-Kanäle laufen tatsächlich mit einer Auflösung von 9 Bit, sodass die PWM-Timer den Takt durch 512 teilen. Bei der Übergabe eines Bytes müssen daher die Bits um eine Stelle nach links geschoben werden. Dabei entsteht die höchste PWM-Frequenz von 250 kHz. Mit dem 8-Bit-Vorteiler können nun mit dem Direktkommando V mehrere glatte Frequenzen gewählt werden: 1 kHz, 1,5 kHz, 2 kHz, 2,5 kHz, 5 kHz, 10 kHz,  12,5 kHz, 25 kHz, 50 kHz, 125 kHz und 250 kHz.  

void setup1() {
  gpio_init_mask (mask);
  gpio_put_masked (mask, 0);
  gpio_set_function(8,GPIO_FUNC_PWM);
  gpio_set_function(9,GPIO_FUNC_PWM);
  pwm_config cfg = pwm_get_default_config();
  pwm_config_set_clkdiv_int (&cfg, 250);
  pwm_init(4, &cfg, true);
  pwm_set_wrap(4, 511);
  pwm_set_gpio_level(8,0);
  pwm_set_gpio_level(9,0);
 
pwm_set_enabled(4,true);
}

Im ursprünglichen PicoBasic gab es eine Schwäche, wenn längere Wartezeiten in einem Programm vorkamen. Die Firmware war dann solange taub für neue Kommandos. Deshalb werden nun alle Wartezeiten ab einer Millisekunde anders behandelt. Die globale Variable „warten“ enthält im Normafall null, wird aber durch einen Wartebefehl mit der gewünschten Anzahl Millisekunden geladen. In der Interpreterschleife werden dann mit millis() die verstrichenen Millisekunden seit dem letzten Neustart abgefragt und der nächste Basic-Befehl erst dann wieder aufgerufen, wenn die gewünschte Wartezeit abgelaufen ist. In jeder Millisekunde werden so mehrmals mögliche Daten von der Schnittstelle abgeholt und behandelt.

  void k25(){warten = dat;adr++;}         // Dekay ms
  void k26(){warten = dat*1000;adr++;}    // Delay s
  void k27(){warten = dat*60000;adr++;}   // Delay min

       if (ch>64 && ch<88){
        direkt[ch-65]();
     
}
    }
    if(warten>0){weiter = millis()+warten; warten = 0;}
    if(millis()>weiter){
      kom = code[adr] >> 8;
      dat = code[adr] & 255;
      befehl[kom]();
    }

PicoBasic enthält neben den Variablen A, B C und D ein 256 Byte langes Daten-Array, das man mit [B+] = A beschreiben und mit A = [B+] auslesen kann. Die vier Variablen können nun auch im Direktmodus mit den Kommandos R, S, T und U beschreiben werden. Mit dem Kommando Q gibt es die Möglichkeit, das Array mit 256 Datenbytes zu füllen. Im TestLabor hat man drei Schaltflächen (sin, rec, tri), mit denen die Signalverläufe Sinus, Rechteck und Dreieck übertragen werden. Das ist vor allem für programmierte Signalgeneratoren sinnvoll.

Das Messergebnis zeigt ein ausgegebenes Dreiecksignal an einem zweistufigen Tiefpassfilter. Durch Setzen von D = 2 wurde der DDS-Generator auf die dreifache Grundfrequenz eingestellt. Man sieht ein stark abgerundetes Dreiecksignal und nach der zweiten Filterstufe bereits ein sinusähnliches Signal.


Elektronik-Labor  Projekte  Mikrocontroller  PicoBasic