Labornetzteil

BASCOM-Quellcode Interrupts, Unterroutinen und Tabellen

In diesem Teil der Dokumentation werden die Interruptroutinen und Unterprogrammeabschnitte beschrieben. Die erste Interruptroutine wird durch den Empfang eines Zeichens der seriellen Schnittstelle aufgerufen. Es wird nur das empfangene Zeichen in eine Variable übernommen und eine Variable gesetzt. Durch diese wird die Reaktion im Hauptprogramm auf den Empfang gestartet. Danach wird die Kontrolle wieder ans Hauptprogramm übergeben. Der zweite Interrupt wird ausgelöst durch Timer 1. Durch Vorladen des Timers mit dem Wert B9B0 hex geschieht dies im Takt von 0,5 Sekunden. Das umschalten der Variable "Blinken" bewirkt, das im Hauptprogramm, wenn erforderlich, die rote LED und die Anzeige blinkt. Die beiden Sekundenzähler werden benötigt für Intervall und Durchschnittsberechnung der seriellen Ausgabe. Falls die LCD-Hintergrundbeleuchtung eingeschaltet ist, wird ein weiterer Zähler erhöht. Nach Ablauf von 60 Sekunden wird die Beleuchtung abgeschaltet und zwei Variablen zur Steuerung der Anzeige werden gesetzt.

'####################   I N T E R U P T R O U T I N E N   ####################
Int_rx_uart:                                                ' Empfang serielle Schnittstelle
   Rxchar_uart = Udr                                        ' read UDR only once
   Interrupt = 1                                            ' Interrupt ausgelöst
Return

Tmr1_isr:                                                   ' Sekundentakt
   Timer1 = &HB9B0                                          ' Overflow nach 0,5 Sekunden
   Toggle Blinken
   Summer = 1
   If Blinken = 0 Then                                      ' 1 Sekunde
      Incr Sec_count                                        ' Sekundenzähler für Abstand serielle Ausgabe
      Incr Sec_count_l                                      ' Sekundenzähler für Durchschnitt und serielle Ausgabe
      If Lcd_light_off >= 1 Then
         Incr Lcd_light_off
         If Lcd_light_off >= 60 Then                        ' nach 60 Sekunden
            Lcd_light_off = 0
            Reset Lcd_light                                 ' LCD-Hintergrundbeleuchtung aus
            Lcd_refresh = 1                                 ' Anzeige 1 neu aufbauen
            Wert_refresh = 1                                ' Anzeige 2 neu aufbauen
         End If
      End If
   End If
Return

Der nächste Programmabschnitt sorgt für die serielle Ausgabe der Messwerte im Format CSV und OpenFormatZero für Logview. Da die Ausgabe für jeden Kanal konfigurierbar ist, wird zuerst geprüft, ob das entsprechende Bit in der Variablen gesetzt ist. Wenn ja, dann wird zuerst die Spannung, danach der in Str2 gespeicherte String (ein Semikolon oder Komma, je nach Ausgabeformat) und abschließend der Durchschnittswert des Stromes gesendet. Danach erfolgt eine Prüfung, ob weitere Messwerte ausgegeben werden sollen. Je nach Größe der Variablen wird entweder wieder Str2 oder nur ein Zeilenende gesendet. Die Ausgabe der restlichen Kanäle erfolgt nach gleichem Schema, nur das beim letzten Kanal Temperatur und PWM-Wert ausgegeben werden. Zum Abschluss werden die Durchschnittswerte der Ströme und der Zähler zurück gesetzt.

'#######################   U N T E R P R O G R A M M E   #######################
Ser_ausgabe:                                                ' serielle Ausgabe Daten CSV oder OFZ
   If Ser_ausgabe_kanal.= 1 Then                          ' Ausgang 3,3 Volt
      Print U_3 ; Str2 ; I_3_avg_s ;                        ' Spannung und Strom
      If Ser_ausgabe_kanal >= 2 Then
         Print Str2 ;
      Else
         Print                                              ' keine weitere Ausgabe
      End If
   End If
   If Ser_ausgabe_kanal.= 1 Then                          ' Ausgang 5 Volt
      Print U_5 ; Str2 ; I_5_avg_s ;                        ' Spannung und Strom
      If Ser_ausgabe_kanal >= 4 Then
         Print Str2 ;
      Else
         Print                                              ' keine weitere Ausgabe
      End If
   End If
   If Ser_ausgabe_kanal.= 1 Then                          ' Ausgang 1,25-25 Volt
      Print U_25 ; Str2 ; I_25_avg_s ;                      ' Spannung und Strom
      If Ser_ausgabe_kanal >= 8 Then
         Print Str2 ;
      Else
         Print                                              ' keine weitere Ausgabe
      End If
   End If
   If Ser_ausgabe_kanal.= 1 Then                          ' Temperatur
      Str6 = Str(temperatur)                                ' Temperatur übernehmen
      Print Format(str6 , "0.0") ; Str2 ; Fan_pwm           ' Temperatur und PWM Lüfter
   End If
   I_3_avg_s = 0 : I_5_avg_s = 0 : I_25_avg_s = 0           ' Durchschnitte Strom zurück setzen
   I_3_avg1_s = 0 : I_5_avg1_s = 0 : I_25_avg1_s = 0        ' Durchschnitte Strom zurück setzen
   I_avg_count_s = 0                                        ' Zähler für Durchschnitt zurück setzen
Return

Das folgende Unterprogramm setzt die Variable für die Übertragungsrate der seriellen Schnittstelle bei Auswahl des Index im Einstellungsmenü.

Ser_set_baud:                                               ' Einstellung Übertragungsrate serielle Schnittstelle
   Select Case Ser_baudindex
      Case 0
         Baud = 2400
      Case 1
         Baud = 4800
      Case 2
         Baud = 9600
      Case 3
         Baud = 19200
      Case 4
         Baud = 38400
      Case 5
         Baud = 57600
      Case 6
         Baud = 115200
   End Select
Return

Die nächsten beiden Abschnitte steuern Anzeigen auf dem Display. Die erste Unterprozedur zeigt ganz rechts im Display oben einen Pfeil nach rechts und darunter einen Pfeil nach oben. Dies soll die Funktion der Tasten veranschaulichen: "Nach rechts" - einen Menüpunkt weiter, "Nach oben" - Einstellwert erhöhen. Wenn die Hintergrundbeleuchtung ausgeschaltet ist, werden die Symbole und der Cursor nicht angezeigt. Routine 2 zeigt bei Einstellungen, die nur "ein" oder "aus" erlauben den entsprechenden Text an. Eine Infozeile und Versionsnummer, sowie Erstellzeit und -Datum stellt die 3. Unterprozedur dar.

Lcd_pfeil_lo:                                               ' Anzeige Pfeil nach rechts und oben
   If Lcd_light = 1 Then
      Locate 1 , 40 : Lcd "{199}"                           ' Pfeil nach rechts
      Locate 2 , 40 : Lcd "{197}"                           ' Pfeil nach oben
      Cursor On Blink
   Else
      Cursor Off
   End If
Return

Lcd_ein_aus:                                                ' Anzeige ein/aus
   If X = 1 Then
      Lcd "ein"
   Else
      Lcd "aus"
   End If
Return

Lcd_info:                                                   ' Anzeige Version und Compilerzeit
   Upperline : Lcd "LNT 15 - ATmega 328P, INA219, DS1621"
   Lowerline : Lcd "Version: " ; Version(2) ; " " ; Version(1)
Return

Das erste Unterprogramm im folgenden Abschnitt zeigt die abgegebene Leistung des Kanals an. Der Rohwert wird in der Variablen "s" übergeben. Die erste Zeile sorgt für eine Rundung mit zwei Nachkommastellen und Übernahme in einen String. Da bei BASCOM die Formatierung leider nicht richtig funktioniert, wird anschließend die Länge des Strings ermittelt und daraufhin hin eine errechnete Anzahl Leerzeichen angezeigt. Erst jetzt erfolgt die Anzeige des Messwertes und der Maßeinheit. Das darauf folgende Unterprogramm beinhaltet die Anzeige des Stromes. Eine Prüfung des Wertes in der Variablen "S" entscheidet darüber, ob die Anzeige in Ampere oder mA erfolgt. Der eigentliche Messwert wird anschließend gerundet und formatiert in einen String übernommen. Hier muss auch wieder eine Eigenheit von BASCOM korrigiert werden: Die Variable könnte z.B. den Wert -0,00001 haben, nach der Formatierung auf zwei Nachkommastellen übernimmt BASCOM aber das negative Vorzeichen. Da ein Anzeigewert von "-0,00" unschön aussieht, wird das geändert. Anschließend wird auch wieder, wie bei der Ausgabe der Leistung, der String mit führenden Leerzeichen ergänzt. Die Anzeige erfolgt im Normalfall statisch und bei zu hoher Stromaufnahme blinkend.

Lcd_p:                                                      ' Anzeige Leistung
   Str6 = Fusing(, "#.##")                                ' Leistung runden und formatieren
   X = Len(str6) : Y = 6 - X
   Locate 1 , Lcd_pos                                       ' Anzeigeposition
   Lcd Space(y) ; Str6 ; " W"                               ' Leistung anzeigen
Return

Lcd_i:                                                      ' Anzeige Strom
   If S >= 1000 Then
      S = S / 1000
      Str6 = " A "
   Else
      Str6 = " mA"
   End If
   Str8 = Fusing(, "#.##")                                ' Strom runden und formatieren
   If Str8 = "-0.00" Then : Str8 = "0.00" : End If
   X = Len(str8) : Y = 6 - X
   Str12 = Space(y)
   Str12 = Str12 + Str8                                     ' Messwert
   Str12 = Str12 + Str6                                     ' Maßeinheit
   Locate Lcd_zeile , Lcd_pos
   If Warning.= 0 Then                                    ' keine Warnung
      Lcd Str12                                             ' Strom anzeigen
   Else                                                     ' Anzeige blinkt
      If Blinken = 0 Then
         Lcd Space(6)                                       ' Leerzeichen anzeigen
      Else
         Lcd Str12                                          ' Strom anzeigen
      End If
   End If
Return

Der nächste Abschnitt zeigt drei Routinen zur Kommunikation mit dem Strom-/Spannungswandler INA219 über den I²C-Bus. Der erste Teil liest eine Word-Variable vom Schaltkreis, die in zwei Byte übermittelt wird. Dazu wird zuerst die Startsequenz auf dem Bus ausgegeben. Danach folgt die Schreibadresse des anzusprechenden Schaltkreises und die Registeradresse, die ausgelesen werden soll. Nach einem Repeat-Start mit anschließender Ausgabe der Leseadresse des Schaltkreises werden zwei Byte vom Bus gelesen. Anschließend wird die Stopp-Sequenz auf den Bus gegeben und zurück ins Hauptprogramm gesprungen. Im zweiten Teil passiert dasselbe, nur das diesmal eine Integer-Variable übermittelt wird. Der letzte Teil wird zum Schreiben der Konfigurationsregister verwendet. Auch hier wird wieder zuerst die Schreib- und Registeradresse auf den Bus gegeben. Darauf erfolgt sofort anschließend das Schreiben des High- und Low-Byte der temporären Word-Variable.

Ina219_reg_read_w:                                          ' Register INA219 lesen
   I2cstart                                                 ' send start
   I2cwbyte Ina219_adr_w                                    ' send write address
   I2cwbyte Ina219_reg                                      ' send register pointer
   I2crepstart
   I2cwbyte Ina219_adr_r                                    ' send read address
   I2crbyte W_high , Ack                                    ' read MSB
   I2crbyte W_low , Nack                                    ' read LSB
   I2cstop
Return

Ina219_reg_read_i:                                          ' Register INA219 lesen
   I2cstart                                                 ' send start
   I2cwbyte Ina219_adr_w                                    ' send write address
   I2cwbyte Ina219_reg                                      ' send register pointer
   I2crepstart
   I2cwbyte Ina219_adr_r                                    ' send read address
   I2crbyte I_high , Ack                                    ' read MSB
   I2crbyte I_low , Nack                                    ' read LSB
   I2cstop
Return

Ina219_reg_write:                                           ' Register INA219 schreiben
   I2cstart                                                 ' send start
   I2cwbyte Ina219_adr_w                                    ' send write address
   I2cwbyte Ina219_reg                                      ' Calibration Register
   I2cwbyte W_high                                          ' write MSB
   I2cwbyte W_low                                           ' write LSB
   I2cstop
Return

Die Kommunikation mit dem Thermometer-IC DS1621 über den I²C-Bus verläuft ähnlich wie beim INA219. Zuerst wird der Temperatur-Wert in zwei Bytes gelesen. In diesen ist die Temperatur allerdings nur in Schritten zu 0,5 enthalten. Für eine höhere Auflösung werden zwei zusätzliche Variablen gelesen: COUNT_REMAIN und COUNT_PER_C. Mit diesen ist nach einer im Datenblatt angegeben Formel eine genauere Berechnung möglich. Ich verwende hier eine angepasste Variante, die ohne Gleitkomma-Berechnung auskommt. Übernommen wird dazu nur das MSB in eine Integer-Variable. Sollte die Temperatur negativ sein (Bit 7 = 1), so wird das obere Byte auf 255 gesetzt. Der Temperaturwert wird mit 100 multipliziert und 25 (statt 0,25) subtrahiert. Anschließend werden die beiden Count-Werte subtrahiert, mit 100 multipliziert und anschließend dividiert. Beide temporären Variablen addiert und anschließend mit 10 dividiert ergibt die Temperatur in einer Integer-Variablen. Die Darstellung mit Kommastelle erfolgt dann bei der Anzeige mit dem Befehl "Format".

Ds1621_read:
   I2cstart                                                 ' I2C-Kommunikation starten
   I2cwbyte Ds1621_adr_w                                    ' Address of DS1621 Thermometer for write
   I2cwbyte &HAA                                            ' Temperaturmessung Lesekommando
   I2crepstart                                              ' I2C-Kommunikation starten
   I2cwbyte Ds1621_adr_r                                    ' Address of DS1621 Thermometer for read
   I2crbyte Ds_msb , Ack                                    ' MSB holen
   I2crbyte Ds_lsb , Nack                                   ' LSB holen
   I2cstop
   I2cstart                                                 ' I2C-Kommunikation starten
   I2cwbyte Ds1621_adr_w                                    ' Address of DS1621 Thermometer for write
   I2cwbyte &HA8                                            ' Count Remain anfordern
   I2crepstart                                              ' I2C-Kommunikation starten
   I2cwbyte Ds1621_adr_r                                    ' Address of DS1621 Thermometer for read
   I2crbyte C_r , Nack                                      ' Count Remain holen
   I2cstop
   I2cstart                                                 ' I2C-Kommunikation starten
   I2cwbyte Ds1621_adr_w                                    ' Address of DS1621 Thermometer for write
   I2cwbyte &HA9                                            ' Count per C anfordern
   I2crepstart                                              ' I2C-Kommunikation starten
   I2cwbyte Ds1621_adr_r                                    ' Address of DS1621 Thermometer for read
   I2crbyte C_p_c , Nack                                    ' Count per C holen
   I2cstop
   I_low = Ds_msb
   If I_low.= 1 Then
      I_high = 255
   Else
      I_high = 0
   End If
   I = I * 100 : I = I - 25
   I1 = C_p_c - C_r
   I1 = I1 * 100
   I1 = I1 / C_p_c
   Temperatur = I + I1
   Temperatur = Temperatur / 10
Return

Die letzten beiden Unterprogramme werden bei Tastenbetätigung aufgerufen. Die obere Taste hat hauptsächlich die Funktion, die Menüpunkte bis zu einem Maximalwert zu erhöhen. Zuerst werden einige Variablen gesetzt, um dem Hauptprogramm zu vermitteln, das eine Taste betätigt wurde. Falls die LCD-Hintergrundbeleuchtung aus war, wird sie jetzt eingeschaltet und die Menünummer noch nicht erhöht. Außerdem wird Timer 1 gestartet, der nach einer bestimmten Zeit die Hintergrundbeleuchtung wieder abschaltet. Der nächste Tastendruck wechselt dann ins nächste Menu. Falls die Hintergrundbeleuchtung schon eingeschaltet war, wird die Zeit für die LCD-Beleuchtung wieder auf 1 zurück gesetzt. Auch die untere Taste ist mit diesen Funktionen für die Beleuchtung versehen. Ansonsten hat sie als Hauptfunktion den Wert in den Einstellungsmenüs bis zu einem Maximalwert zu erhöhen. Abweichend davon erfolgt in den Menüpunkten 1 und 2 das Rücksetzen der Minimal-, Maximal- und Durchschnittswerte der Ströme. In den Menüs 3, 10 und 11 erfolgt ein Sprung in eine andere Menüebene.

Taste_o:                                                    ' Taste oben
   Lcd_refresh = 1 : Wert_refresh = 1 : Quittung = 1
   If Lcd_light = 0 Then
      Lcd_light = 1                                         ' LCD-Hintergrundbeleuchtung einschalten
      Lcd_light_off = 1                                     ' Timer LCD-Hintergrundbeleuchtung ein
   Else
      Incr Menu                                             ' nächster Menüpunkt
      If Menu = 4 Then : Menu = 0 : End If                  ' letzter Menüpunkt Ebene 1 ist 3
      If Menu >= 19 Then : Menu = 10 : End If               ' letzter Menüpunkt Ebene 2 ist 18
      Wertmax = 0
   End If
   If Lcd_light_off >= 1 Then
      Lcd_light_off = 1                                     ' Timer LCD-Hintergrundbeleuchtung ein
   End If
Return

Taste_u:                                                    ' Taste unten
   Wert_refresh = 1 : Quittung = 1
   If Lcd_light = 0 Then
      Lcd_light = 1                                         ' LCD-Hintergrundbeleuchtung einschalten
      Lcd_refresh = 1
      Lcd_light_off = 1                                     ' Timer LCD-Hintergrundbeleuchtung ein
   Else
      If Wertmax >= 1 Then
         Incr Wert                                          ' Wert erhöhen
         If Wertmax < Wert Then : Wert = 0 : End If
      End If
      Select Case Menu
         Case 1                                             ' Anzeige MIN/MAX Strom
            I_3_max = -99 : I_5_max = -99 : I_25_max = -99
            I_3_min = 999 : I_5_min = 999 : I_25_min = 999
         Case 2                                             ' Anzeige Durchschnitt Strom
            I_3_avg = 0 : I_5_avg = 0 : I_25_avg = 0
            I_3_avg1 = 0 : I_5_avg1 = 0 : I_25_avg1 = 0
            I_avg_count = 0
            Sec_count_l = 0
         Case 3                                             ' Anzeige Leistung
            Menu = 10 : Lcd_refresh = 1                     ' Sprung in Menüebene 2
         Case 10
            Menu = 0 : Lcd_refresh = 1                      ' Sprung in Ebene 0
         Case 11
            Menu = 0 : Lcd_refresh = 1                      ' Sprung in Ebene 0
      End Select
   End If
   If Lcd_light_off >= 1 Then
      Lcd_light_off = 1                                     ' Timer LCD-Hintergrundbeleuchtung ein
   End If
Return

Letzter Teil im Programm sind 3 Tabellen mit Daten. Die erste Tabelle beinhaltet Texte zur Anzeige des eingestellten Datenformates für die Ausgabe der Messwerte über die serielle Schnittstelle. Tabelle 2 enthält Anzeigestrings für die eingestellte Baudrate und die letzte Tabelle erfasst die Zeit der Intervalle der seriellen Ausgabe.

'####################   D A T E N   ####################
Table_ser_format:                                           ' Ausgabeformat seriell
   Data "keine Ausgabe                   "                  ' keine Ausgabe
   Data "Text                            "                  ' Textformat
   Data "CSV (comma separated)           "                  ' CSV
   Data "OpenFormat Zero (LogView Studio)"                  ' LogView Studio OpenFormat Zero

Table_ser_baud:                                             ' Baudrate seriell
   Data "  2400" , "  4800" , "  9600" , " 19200"
   Data " 38400" , " 57600" , "115200"

Table_intervall:                                            ' Intervall in Sekunden
   Data 0 , 1 , 2 , 5 , 10 , 15 , 30 , 60

Hiermit endet die Beschreibung des BASCOM-Quellcodes für das Labornetzteil. Insgesamt umfasst der Quellcode etwas über 1000 Zeilen. Hier und da könnte sicher noch etwas optimiert werden, aber irgendwann muss man ein Projekt auch mal abschließen.