Kapazitätsmesser an der RS232-Schnittstelle          

von Frank Behlich                       
 
                      Elektronik-Labor  Bastelecke  Projekte  Mikrocontoller                         



Auf die serielle Schnittstelle bin über die Zusammenarbeit mit Ralf Beesner an dem Dattenlogger zur Messung der Netzfrequenz gekommen. Er hat mir freundlicher Weise einen zweiten Prototyp mit einem USB/RS232-Adapter kostenlos zugesandt und so ist ein sehr netter Emailkontakt über Herrn Kainka entstanden.





Ich bin elektronischer interessierter Laie und Wohn/Esszimmertischbastler. So sieht mein provisorisches „Bastlerlabor“ aus. Wenn Ruhe einkehrt ist, dann steht dem abendlichen Basteln in Kombination mit Programmieren nichts mehr im Weg. Nach dem wöchentlichen Hausputz sollten der Staubsauger entleert werden, denn es finden sich verschollen geglaubte Bauteile wieder und geben Anregung zu neuen Taten. 

Recherchen im Netz führen einen immer wieder zu Herrn Kainkas Büchern und auch zu dieser Anleitung:
https://informatik.bildung-rp.de/fileadmin/user_upload/informatik.bildung-rp.de/InformatikAG/pdf/MA-RS232.pdf
Nachbasteln der Adapterplatine hätte bei vorhandenen Teilen auch Spaß gebracht, doch ich habe sie mir für einen angemessenen Preis bei www.ak-modul-bus.de bestellt und konnte gleich ohne „Verdrahtungsfehlersuche“ loslegen.

Der USB-Adapter wird unter meinen System (Ubuntu Mate → 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux) erkannt und funktioniert einwandfrei und mit „lsusb“ wird er im Terminal angezeigt.



Er ersetzt natürlich keine echte RS232-Schnitttstelle, und auf Grund seiner hohen Latenzzeiten sind keine schnellen Messvorgänge möglich. Dies verringert auch den Messbereich des Kapazitätsmessers. Dieser fängt erst bei ca. 1-2 µF an und nach  oben konnte ich bis 2200 µF messen (keine höheren Werte vorhanden). Der Vergleich mit meinen „Schätzgeräten“ ergab die gleichen Werte.

Ich habe einige Schaltungen aus dem PDF ausprobiert und auch die Umsetzung in Python war nicht problematisch. Wer mit Kritik und manchmal auch niederschmetternden Beurteilungen seiner eigenen Programmierversuche umgehen kann, der bekommt kompetente Hilfe im „Deutschen Python Forum“ - nur keine fertigen „Produkte“! Manchmal haben die Profis auch Mitleid und zeigen fast fertige Lösungen.


Hier sieht man den Aufbau des Kapazitätsmessers aus der o.g. Anleitung








!!! abgeänderte Schaltung für mein Script !!!


Im Script gibt es die Möglichkeit zur Kalibrierung auf die eigene Hardware. Ich habe hierfür einen 100µF Elko verwendet.

Zeile 18 → CALIBRATION = 50

Das Script reagiert auf alle möglichen Fehler (Stecker/Bauteile abziehen), doch die „Queue“ kann in solchen Fällen noch mit Daten gefüllt sein. Bei der nächsten Messung können diese das Ergebnis beeinflussen. In der Zeile 174 kann der Befehl #print(self.queue.qsize()) gesetzt werden und es wird die Anzahl der Einträge in der „Queue“ angezeigt. Das Script läuft mit python3 und falls auf dem eigenen System (Ubuntu/Debian-Derivate Angaben ohne Gewähr !) python2 noch Standard ist, dann python3 nachinstallieren mit:

→ sudo apt install python3

Im Terminal startet man das Script mit:

→ python3 condensator_gui_rs232_python3.py

Falls noch weitere Module fehlen, sollten diese als Fehlermeldung beim Starten in der Konsole angezeigt werden:

Traceback (most recent call last):
  File "condensator_gui_rs232_python3.py", line 4, in <module>
    import tkinter as tk
ModuleNotFoundError: No module named 'tkinter'

Nachinstalliert werden diese z.B:

→ sudo apt install python3-tk

Die Pakete haben nicht immer die gleichen Paketnamen, die im der Fehlermeldung angezeigt werden - die Suche in der Paketliste schafft Abhilfe:

→ sudo apt search tkinter

(Update 10.1.22)

#/usr/bin/env python
# -*- coding: utf-8

import tkinter as tk
import serial, serial.tools.list_ports
import time
from threading import Thread, Event
from functools import partial
import queue

WIDTH = 270
HEIGHT = 100

class CapacitanceUI(tk.LabelFrame):

MEASURE_COUNTER = 10
UPDATE_INTERVAL = 2000

def __init__(self, parent, serial_thread_client, queue, width, height):
tk.LabelFrame.__init__(self, parent, text = "CAPACITANCE",
relief = "solid")
self.parent = parent
self.serial_thread_client = serial_thread_client
self.queue = queue
self.width = width
self.height = height
self.device = None
self.update_interval = self.UPDATE_INTERVAL
self.measure_counter = self.MEASURE_COUNTER
self.after_id = None
self.measuring_data = list()
self.display_conf = {"cap" : (
self.width / 2,
self.height / 2,
"{0:2.2f} uf",
"system 18 bold",
"blue",
"cap"),
"counter" : (
40,
self.height - 10,
"COUNTS: {0}",
"system 7 bold",
"green",
"counter"),
"error" : (
self.width / 2,
10,
"{0}",
"system 5 bold",
"red",
"error"),
"wait" :(
self.width / 2,
self.height / 2,
"{0}",
"system 18 bold",
"magenta",
"cap")}
self.menubar = tk.Menu()
parent.config(menu=self.menubar)
self.menubar.add_cascade(label="PORTS", menu = self.get_ports(
self.menubar))
self.display = tk.Canvas(self, width = width, height = height,
bg="cyan")
self.display.pack(padx = 5, pady = 5)
self.update_cap_display(0, "cap")
self.update_cap_display(self.measure_counter, "counter")
button_frame = tk.Frame(self)
button_frame.pack(padx = 5, pady = 5)
self.buttons = list()
for column, (text, width, command, var) in enumerate(
(("START", 3, self.measure_start, ()),
("+", 2, self.measure_counter_up_down, (1,)),
("-", 2, self.measure_counter_up_down, (-1,)))):
button = tk.Button(button_frame, text=text, width=width,
command=partial(command, *var))
button.grid(column=column, row=0)
self.buttons.append(button)
self.buttons[0].config(state = "disabled")

def measure_start(self):
self.menubar.entryconfig("PORTS", state = "disabled")
for button in self.buttons:
button.config(state = "disabled")
self.display.delete("error")
self.measuring_data = list()
self.serial_thread_client.start(self.device)
self.measure()

def get_ports(self, menubar):
port_menu = tk.Menu(menubar, tearoff = 0)
for serial_ports in serial.tools.list_ports.comports():
device, desc, hwid = serial_ports
port_menu.add_command(label = "{0}:{1}:{2}".format(
device, desc, hwid), command = partial(
self.set_device, device))
return port_menu

def set_device(self, device):
self.buttons[0].config(state = "normal")
self.device = device

def measure_stop(self):
self.menubar.entryconfig("PORTS", state = "normal")
for button in self.buttons:
button.config(state = "active")
self.measure_counter = self.MEASURE_COUNTER
self.update_cap_display(self.measure_counter, "counter")
self.serial_thread_client.stop()
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None

def measure_counter_up_down(self, step):
if self.measure_counter > -step:
self.measure_counter += step
self.update_cap_display(self.measure_counter, "counter")

def update_cap_display(self, text, tag):
width, height, text_conf, font, color, tag = self.display_conf[tag]
self.display.delete(tag)
self.display.create_text(width, height, text = text_conf.format(text),
font = font, fill = color,tag = tag)

def measure(self):
measure_time = 1
if not self.queue.empty():
while True:
try:
capacity, measure_time, error = self.queue.get_nowait()
except queue.Empty:
break
if error:
self.update_cap_display(capacity, "error")
self.measure_stop()
else:
self.measuring_data.append(capacity)
self.update_cap_display(capacity, "cap")
self.update_cap_display(self.measure_counter, "counter")
self.measure_counter -= 1
else:
if len(self.measuring_data) == 0:
self.update_cap_display("!! WAIT !!", "wait")

if self.measure_counter > 0:
self.after_id = self.after(int(self.update_interval
* measure_time if isinstance(measure_time, float)
else 1), self.measure)
else:
if len(self.measuring_data) > 1:
self.update_cap_display(sum(self.measuring_data)\
/ len(self.measuring_data), "cap")
self.measure_stop()
else:
self.measure_stop()

def release(self):
if self.after_id:
self.measure_stop()
self.parent.destroy()


class SerialThreadedClient(object):
CALIBRATION = 150
def __init__(self, queue):
self.queue = queue
self.run_event = None
self.thread = None

def __enter__(self):
return self

def __exit__(self, *args):
self.stop()

def stop(self):
if self.thread:
self.run_event.set()
self.thread.join()
self.thread = None

def start(self, device):
self.run_event = Event()
self.thread = Thread(target=self.worker_thread, args=[device],
daemon = True)
self.thread.start()

def worker_thread(self, device):
measure_time = 0
while not self.run_event.is_set():
try:
with serial.Serial(device) as ser:
start_time = time.time()
ser.setRTS(True)
while not ser.getDSR():
measure_time = time.time() - start_time
ser.setRTS(False)
self.queue.put((measure_time * 1000 - self.CALIBRATION\
* measure_time, measure_time, False))\
if measure_time > 0 else\
self.queue.put(("capacity < 1 µf or not connected",
False, True))
except (serial.SerialException, IOError) as error:
self.queue.put((error, False, True))
self.run_event.wait(measure_time * 3)

def main(queue):
root = tk.Tk()
root.title("CAPACITANCE")
root.resizable(0, 0)
queue = queue.Queue()
with SerialThreadedClient(queue) as serial_thread_client:
capacitance_ui = CapacitanceUI(root, serial_thread_client, queue,
WIDTH, HEIGHT)
capacitance_ui.pack(expand=tk.YES, padx=5, pady=5)
root.protocol("WM_DELETE_WINDOW",capacitance_ui.release)
root.mainloop()

if __name__ == '__main__':
main(queue)



Elektronik-Labor  Bastelecke  Projekte  Mikrocontoller