Taschenrechner mit 7-Segment Anzeigen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Arkasia
User
Beiträge: 3
Registriert: Montag 11. März 2019, 09:13

Hey, ich versuche nun schon seit meheren Wochen einen Taschenrechner zu programmieren und die Faktoren und das Ergebnis auf 4 7-Segment-Anzeigen auszugeben.
Ich habe mich dazu bisher so viel belesen wie ich gefunden habe. So habe ich vor die 7Segment-Anzeigen Widerstände geklemmt und davor jeweils einen BCD Decoder(um von 4 auf 8 bit zu kommen um so 4 Anzeigen anzusteuern).
Bisher habe ich es geschafft den Taschenrechner zum laufen zu bekommen, allerdings habe ich keine Ahnung wie ich die Eingaben und Ergebnisse auf den Anzeigen erscheinen lasse.
Mein Quellcode bisher in Python dafür ist dieser hier:

weitermachen=True
while (weitermachen==True):
while True:
try:
num1 = int(input("Gib die erste Zahl ein: "))
break
except ValueError:
print("Ups! Das war keine gültige Zahl. Versuche es noch einmal...")

oper = input("Welche Rechenoperation soll durchgeführt werden? (+,-,/.,*): ")

while True:
try:
num2 = int(input("Gib die zweite Zahl ein: "))
break
except ValueError:
print("Ups! Das war keine gültige Zahl. Versuche es noch einmal...")

while True:
if (oper == "+"):
print("Deine Rechnung:", num1, " + ", num2)
print("Ergebnis:", num1 + num2)

elif (oper == "-"):
print("Deine Rechnung:", num1, " - ", num2)
print("Ergebnis:", num1 - num2)

elif (oper == "/"):
print("Deine Rechnung:", num1, " / ", num2)
print("Ergebnis:", num1 / num2)

elif (oper == "*"):
print("Deine Rechnung:", num1, " * ", num2)
print("Dein Ergebnis:", num1 * num2)
else:
print("Deine Eingaben sind nicht gültig")
break

while True:
jein = input("Willst du weiter rechnen? (Ja/Nein)")

jein = jein.lower()
if jein == 'nein':
weitermachen=False
break

Die Anzeigen sollen jeweils nur 0-9 und dann mit der nächsten weiter machen, wie auf dem Taschenrechner auch.
Ich weiß wie ich sowas in Schaltungstechnik realisiere aber habe keine Ahnung wie ich es beim programmieren mache und wie ich die Eingaben und Ergebnisse auslese und dort weiter zur Anzeige verwerten lasse...
Ich bedanke mich schonmal für die hoffentlich bald eintreffende Hilfe!
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst dein Ergebnis ablegen, und dann für jede Stelle die notwendigen Bits setzen. Ob ein Bit gesetzt ist kannst du mit den &-Operator prüfen. Die nächste Stelle bugsierst du durch Ganzzahl-Division an die richtige Stelle. Die Zuordnung von (Stelle, IO-Pin) kannst du mit einer doppelt verschachtelten Liste machen.

Code: Alles auswählen

PINS = [[0, 1,2,3], [17, 4, 8, 7], ...] # für alle 4 stellen. 

def set_bcd_pins(result):
    for digit in range(4):
         for bit in range(4):
               set_gpio_value(PINS[digit][bit], result & 1 << bit)
         result //= 10 # nächste stelle bereitstellen. 
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Arkasia: Ein paar Anmerkungen zum Quelltext: Da sind zu viele ``while``-Schleifen. Eine Schleife die *grundsätzlich* nur *einmal* durchlaufen wird, ist keine Schleife. Schleifen sind dazu da um zumindest potentiell Code mehrfach auszuführen.

Um Bedingungen bei ``while`` und ``if`` gehören keine Klammern.

Man vergleicht nicht auf literale Wahrheitswerte (`True`/`False`) auf (Un)Gleichheit (``==``/``!=``). Da kommt ja nur wieder ein Wahrheitswert heraus. Entweder den, den man sowieso schon hatte, oder dessen Gegenteil. Im ersten Fall nimmt man den einfach den schon vorhandenen Wert. Im letzteren Fall negiert man den mit ``not``.

Letztlich benötigt man `weitermachen` aber auch gar nicht, weil man diese Schleife auch einfach mit ``break`` verlassen kann, an der Stelle wo man `False` zuweist.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Die beiden Schleifen zur Eingabe von Zahlen sind fast identisch. Das verletzt das DRY-Prinzip („Don't Repeat Yourself“). Code oder Daten sollten nur einmal im Quelltext stehen und nicht 1:1 oder in leicht angepasster Form mehrfach. Das macht mehr Arbeit beim schreiben und lesen und erhöht die Wahrscheinlichkeit das man Fehler macht wenn man Änderungen vornimmt, weil man aufpassen muss, das man *jede* Kopie ändert *und* das man jede Kopie *gleich(wertig)* ändert.

Beide Schleifen unterscheiden sich nur durch den Namen der Variable für das Ergebnis und den Text für den Benutzer. Das kann man leicht in eine Funktion heraus ziehen der man den Text als Argument übergibt.

Bei der Division möchtest Du vielleicht die Ganzzahldivision (`//`) verwenden, sonst kommt da eine Gleitkommazahl als Ergebnis, im Gegensatz zu allen anderen Operationen. Oder Du musst Dich zusätzlich mit der Darstellung des Dezimalpunkts beschäftigen für die Anzeige auf den 7-Segment-Anzeigen.

Der Code zum Auswerten in den ``if``/``elif``-Zweigen ist im Grunde gleich. Der unterscheidet sich nur das ausgegebene Symbol für die Rechnung und die eigentliche Rechenoperation. Das Symbol hat man ja bereits als Variable. Jetzt müsste man nur noch die Rechenoperation als Funktion haben, dann bräuchte man den Code nur einmal schreiben, statt für jede Rechenoperation. Netterweise gibt es Funktionen für alle Operatoren bereits in der Standardbibliothek im `operator`-Modul. Damit kann man sich ein Wörterbuch erstellen das die Symbole auf die dazugehörige Funktion abbildet.

Wenn man eine Datenstruktur mit allen für die Eingabe gültigen Operatorsymbolen hat, kann man auch sehr einfach schon bei der Eingabe ungültige Symbole zurückweisen und die Eingabe wiederholen lassen, wie bei der Eingabe der Zahlen.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from operator import add, sub, mul, floordiv

OPERATION_SYMBOL_TO_FUNCTION = {'+': add, '-': sub, '*': mul, '/': floordiv}


def ask_integer(prompt):
    while True:
        try:
            return int(input(f'{prompt}: '))
        except ValueError:
            print('Ups! Das war keine gültige Zahl. Versuche es noch einmal…')


def main():
    while True:
        operand_a = ask_integer('Gib die erste Zahl ein')
        
        operations = ' '.join(OPERATION_SYMBOL_TO_FUNCTION)
        while True:
            operation_symbol = input(
                f'Welche Rechenoperation soll durchgeführt werden?'
                f' ({operations}): '
            )
            try:
                operation = OPERATION_SYMBOL_TO_FUNCTION[operation_symbol]
                break
            except KeyError:
                print(
                    'Ups! Das war keine gültige Operation.'
                    '  Versuche es noch einmal…'
                )
        
        operand_b = ask_integer('Gib die zweite Zahl ein')
      
        print('Deine Rechnung:', operand_a, operation_symbol, operand_b)
        print('Ergebnis:', operation(operand_a, operand_b))
     
        answer = input('Willst Du weiter rechnen? (Ja/Nein)')
        if answer.lower() == 'nein':
            break


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Arkasia
User
Beiträge: 3
Registriert: Montag 11. März 2019, 09:13

Danke für die schnellen Antworten!
@deets das werde ich nachher mal probieren wenn ich meinen Aufbau fertig gelötet habe!

@blackjack Ich kenne leider nur die einfachsten Grundlagen im Programmieren, darum entschuldige ich mich für den mies geschriebenen Quelltext
Freue mich auf jeden Fall für diese Ausführliche Antwort, allerdings ist es grad ein wenig zu viel Input für mich als Anfänger :)
Das Programm das ich versuche zu schreiben soll für den Rasperry so laufen, das es ohne Bildschirm bedient werden kann(falls das möglich ist).
Die Ausgaben in meinem Quelltext waren quasi Hilfen für mich wann was abgefragt wird.
Es muss quasi nur ausgegeben werden welche Zahl gerade eingeben wird, die zweite Zahl und am Ende das Ergebnis.
An das Dividieren um Kommazahlen zu vermeiden hatte ich bisher noch gar nicht gedacht, ist aber eine klasse Idee das so umzusetzen!
Ich bin heute mit dem Aufbau der Schaltung des Rasperrys beschäftigt um das Programm dann dort zu testen, melde mich aber sobald ich hier weiter probieren kann!
Das ganze läuft im Rahmen eines Projektes meiner Berufsschule.
Eigentlich lerne ich Mechatroniker aber mein Lehrer hat seine Ziele meiner Meinung sehr weit aus dem Fenster gelehnt weshalb ich jetzt hier um Hilfe bitte.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Arkasia: Ein bisschen mehr als das was Du gezeigt hast brauchst Du aber schon. Also zum Beispiel Funktionen sind ein Muss, wenn das noch irgendwie verständlich bleiben soll. Nicht nur wegen dem DRY-Prinzip, sondern auch um nicht am Ende einen Riesenhaufen Code zu haben, mit Unmengen an Variablen, den kein Mensch mehr durchblickt. Die Eingabe von der Operation in meinem Code könnte man nämlich auch gut in eine eigene Funktion auslagern, auch wenn die dann nur ein einziges mal aufgerufen wird, um den Code lesbarer zu machen.

Wenn Du kein Terminal für Ein- und Ausgaben hast, dann ändert sich der Programmaufbau aber auch ein bisschen, denn dann musst Du einzelne Tastendrücke verarbeiten wenn Du die Zahlen nicht ”blind” eingeben möchtest. Am einfachsten kann man einzelne Tastendrücke wohl mit dem `curses`-Modul aus der Standardbibliothek verarbeiten. Da will ich gleich auf `curses.wrapper()` hinweisen, weil man sich sonst leicht das Terminal in einen komischen Zustand versetzen kann, oder die Aufräumarbeiten die das erledigt, selber programmieren muss.

Dann kannst Du Dir entweder eine Funktion schreiben, die so ähnlich funktioniert wie `ask_integer()` sich aber eben um die Tasten selbst kümmert und nur gültige Eingaben überhaupt zulässt und immer den aktuellen Wert auf den 7-Segment-Anzeigen darstellt, oder Du überlegst Dir für den gesamten Taschenrechner(ablauf) einen Zustandsautomaten, den Du dann in Code umsetzt.

Ich finde 4 Stellen ja ein bisschen wenig. Beziehungsweise 3 Stellen wenn eine negative Zahl dargestellt wird, sofern man *das* nicht noch irgendwie anders/zusätzlich zu den 4 Ziffern anzeigt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Arkasia
User
Beiträge: 3
Registriert: Montag 11. März 2019, 09:13

Ich habe jetzt mal deinen Quellcode probiert, allerdings erkennt mein python die f' -Befehle nicht
so springt er nicht in die andere operation sondern zurück nach oben und ich habe bei den Faktoren/Summanden etc 2 mal die Aussage von oben "prompt:"
Ich habe die f' entfernt damit es läuft, deshalb nimmt der gleich prompt:
Ich arbeite auf Python 3.5.3 , weiß aber nicht wie man diese f' Befehle ersetzen kann... offentsichtlich gehören sie ja zu den 2 Operationen zwischen denen er wechseln soll.
So wie das Programm sonst läuft, ist eigentlich schon perfekt so... muss quasi nur noch die Faktoren und das Ergebnis auslesen lassen und dann damit die GPIO´s ansteuern oder?

Wir haben halt bischen recherchiert und haben nur gefunden das man mittels BCD Decoder 4Anzeigen ansteuern kann mit den verfügbaren GPIO´s.
Für einen Zustandsautomaten bin ich glaube zulange raus um das schnell mal von der Hand zu machen :D
Auch will ich es nicht zu kompliziert machen, muss es ja selber verstehen können :)
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das sind "f-strings", die gehen ab Python 3.6. Du kannst

f"{schluessel}"

mit

"{schluessel}".format(schluessel="wert")

ersetzen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Arkasia: Die f-Zeichenketten müsstest Du dann durch die `format()`-Methode ersetzen um Werte in die Zeichenkette hinein zu formatieren.

Wenn Du das ohne Bildschirm bedienen können willst, dann gibt es ja weder `print()` noch `input()`, also muss die Eingabe entweder ”blind” erfolgen, also man gibt die Ziffern einer Zahl ein und die werden erst angezeigt wenn man die Eingabetaste drückt. Dann brauchst Du an dem Programm natürlich nicht viel ändern, ausser statt oder zusätzlich zum `print()` die Operanden und das Ergebnis auf der 7-Segment-Anzeige darzustellen.

Probleme dabei natürlich wie Du mit Zahlen umgehst die negativ sind und/oder mehr als 3 oder 4 Ziffern haben. Oh, und was wird ausgegeben wenn der Benutzer versucht eine Zahl durch 0 zu teilen? Damit kommt das bisherige Programm auch noch nicht klar.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten