74595/74165 Schieberegister C Code in Python?

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
mega-hz
User
Beiträge: 14
Registriert: Sonntag 24. September 2017, 13:48

Hallo,

ich brauche Hilfe um eine Routine die in C auf dem Arduino super läuft, in Python umzusetzen.
Leider hab ich nicht viel Erfahrungen mit Python, besonders mit der Syntax.

Es handelt sich um eine SHIFT-OUT und SHIFT-IN Routine, die 74595 und 74165 Schieberegister ansteuern bzw. auslesen.

hier die beiden Routinen in C :

SHIFT-OUT:

Code: Alles auswählen

void shiftOut32(uint32_t val)
{
  byte i;
  byte i1;
  byte Sbyte;
  byte shiftNo;       
  digitalWrite(OUT_latchPin, LOW);    // Latch-Output disablen, während übertragen wird
        Sbyte = val;
        for (i1 = 0; i1 < 4; i1++) 
        {
          for (i = 0; i < 8; i++)  
          {
            digitalWrite(OUT_dataPin, !!(Sbyte & (1 << i)));
            digitalWrite(OUT_clockPin, HIGH);
            digitalWrite(OUT_clockPin, LOW);
          }
          shiftNo += 8;
          Sbyte = val >> shiftNo;
        }
  digitalWrite(OUT_latchPin, HIGH);    // Latch-Output enablen, nach der Übertragung
  digitalWrite(OUT_clockPin, LOW);
}
SHIFT-IN:

Code: Alles auswählen

uint32_t shiftIn32( void )
{

  digitalWrite(IN_loadPin, LOW);
  digitalWrite(IN_loadPin, HIGH);
  for( uint8_t i = 0; i<32; i++ )
  {
    Inputs >>= 1;
    if( digitalRead(IN_dataPin))
      {
        Inputs |= 0x80000000;
        Input[i] = 1;
      }
     else
      {
        Input[i] = 0;
      }
      
    digitalWrite(IN_clockPin, HIGH);
    digitalWrite(IN_clockPin, LOW);
  }
return Inputs;
}
Ziel ist es, mit einem Aufruf der SHIFT-OUT Routine Werte zu übergeben wie hier z.B.:

Code: Alles auswählen

  Outputs = 0x00000000; // alle Ausgänge aus (LOW)              
  shiftOut32(Outputs);  
oder Werte in eine Variable einzulesen.

Kann mir jemand das in Python übersetzen?

Danke,
Gruß,
Wolfram.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das von Hand zu bitbangen würde ich nicht machen, sondern den SPI Bus bemühen. Und zu dem Thema gibt viele Anleitungen.

Aber wenn man sucht, findet man auch eine mehr oder minder direkte Umsetzung: http://cool-web.de/raspberry/mit-dem-74 ... sparen.htm
mega-hz
User
Beiträge: 14
Registriert: Sonntag 24. September 2017, 13:48

SPI Bus entfällt, da dort bereits ein TFT und ein CAN-Bus Controller dranhängen.
Ausserdem geht es mir um die Übersetzung C -> Python !
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mega-hz: Wo ist denn das Problem mit der Python-Syntax? Soo unterschiedlich ist das in diesem Fall ja nicht zu C. Ich sehe hier eher ein Problem darin, dass da in C anscheinend einiges global, zumindest zur Übersetzungseinheit, definiert wurde.

Ansonsten ist das doch hauptsächlich die Namen an die Namenskonvention anpassen, geschweifte Klammern und Semikolons weglassen, ``for``-Schleifen anpassen. Ordentlich einrücken. Warum auch immer das bei der ersten C-Funktion nicht gemacht wurde.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

mega-hz hat geschrieben: Mittwoch 5. August 2020, 10:34 SPI Bus entfällt, da dort bereits ein TFT und ein CAN-Bus Controller dranhängen.
Ausserdem geht es mir um die Übersetzung C -> Python !
Der PI hat zwei SPI Busse. Und ein SPI kann nicht nur 2, sondern auch problemlos 3 Geräte handhaben. Und der Link, den ich gezeigt habe, zeigt eine Umsetzung. Was daran passt nicht?
mega-hz
User
Beiträge: 14
Registriert: Sonntag 24. September 2017, 13:48

Die Hardware steht bereits, wiegesagt, es geht nicht darum, WIE man die ansteuert sondern um die Umsetzung des C Codes.
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mega-hz: Das erste C-Code-Schnippsel nahezu 1:1 in Python-Syntax:

Code: Alles auswählen

def shiftOut32(val):
    digitalWrite(OUT_latchPin, LOW)    # Latch-Output disablen, während übertragen wird
    shiftNo = 0
    Sbyte = val
    for i1 in range(4):
        for i in range(8):
            digitalWrite(OUT_dataPin, not not (Sbyte & (1 << i)))
            digitalWrite(OUT_clockPin, HIGH)
            digitalWrite(OUT_clockPin, LOW)
        shiftNo += 8
        Sbyte = val >> shiftNo
    digitalWrite(OUT_latchPin, HIGH)    # Latch-Output enablen, nach der Übertragung
    digitalWrite(OUT_clockPin, LOW)
Das war im Grunde nur die ``for``-Schleifen anpassen, die ganzen Semikolons, geschweiften Klammern, und Typdeklarationen rauswerfen, und ``!`` durch ``not`` ersetzen. Und man muss einen Fehler in der Funktion beheben. Es sei denn `byte` im Original ist irgendein Makro-Voodoo, denn `shiftNo` wird im C-Code nirgends initialisiert, kann also irgendeinen Wert haben. Der Code geht aber von 0 aus.

Wenn man die passenden Konstanten und eine `digitalWrite()`-Funktion definiert, sollte das so schon lauffähig sein. Allerdings sind die Namen nicht pythonkonform und teilweise auch schlecht gewählt. Also Namen anpassen:

Code: Alles auswählen

def shift_out_32_bits(value):
    #
    # Latch-Output deaktivieren, während übertragen wird.
    #
    digital_write(OUT_LATCH_PIN, LOW)
    shift_count = 0
    byte_value = value
    for _ in range(4):
        for i in range(8):
            digital_write(OUT_DATA_PIN, not not (byte_value & (1 << i)))
            digital_write(OUT_CLOCK_PIN, HIGH)
            digital_write(OUT_CLOCK_PIN, LOW)
        shift_count += 8
        byte_value = value >> shift_count
    #
    # Latch-Output aktivieren, nach der Übertragung.
    #
    digital_write(OUT_LATCH_PIN, HIGH)
    digital_write(OUT_CLOCK_PIN, LOW)
Jetzt gibt es hier Sachen die in Python so keinen Sinn machen oder komisch sind. Es gibt ganze Zahlen und keinen Unterschied zwischen 32-Bit- und 8-Bit-Variablen, also kann man sich `byte_value`, die äussere Schleife, und damit dann auch `shift_count` sparen. Und ``not not`` wäre in Python besser mit `bool()` ausgedrückt, denn das macht der C-Hack mit der doppelten Negierung in C ja letztendlich:

Code: Alles auswählen

def shift_out_32_bits(value):
    #
    # Latch-Output deaktivieren, während übertragen wird.
    #
    digital_write(OUT_LATCH_PIN, LOW)
    for i in range(32):
        digital_write(OUT_DATA_PIN, bool(value & (1 << i)))
        digital_write(OUT_CLOCK_PIN, HIGH)
        digital_write(OUT_CLOCK_PIN, LOW)
    #
    # Latch-Output aktivieren, nach der Übertragung.
    #
    digital_write(OUT_LATCH_PIN, HIGH)
    digital_write(OUT_CLOCK_PIN, LOW)
Man kann das noch ein bisschen mehr vereinfachen wenn man `value` in jedem Schleifendurchlauf um ein Bit verschiebt, dann braucht man den Wert von `i` nicht mehr und auch den `bool()` aufruf nicht:

Code: Alles auswählen

def shift_out_32_bits(value):
    #
    # Latch-Output deaktivieren, während übertragen wird.
    #
    digital_write(OUT_LATCH_PIN, LOW)
    for _ in range(32):
        digital_write(OUT_DATA_PIN, value & 1)
        value >>= 1
        digital_write(OUT_CLOCK_PIN, HIGH)
        digital_write(OUT_CLOCK_PIN, LOW)
    #
    # Latch-Output aktivieren, nach der Übertragung.
    #
    digital_write(OUT_LATCH_PIN, HIGH)
    digital_write(OUT_CLOCK_PIN, LOW)
Und zuguterletzt würde ich `gpiozero` verwenden, womit man keine Funktion zum Schreiben braucht sondern direkt auf den Objekten die die Pins repräsentieren Attribute zuweisen und Methoden aufrufen kann:

Code: Alles auswählen

def shift_out_32_bits(value):
    #
    # Latch-Output deaktivieren, während übertragen wird.
    #
    OUT_LATCH_PIN.off()
    for _ in range(32):
        OUT_DATA_PIN.value = value & 1
        value >>= 1
        OUT_CLOCK_PIN.on()
        OUT_CLOCK_PIN.off()
    #
    # Latch-Output aktivieren, nach der Übertragung.
    #
    OUT_LATCH_PIN.on()
    OUT_CLOCK_PIN.off()
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
mega-hz
User
Beiträge: 14
Registriert: Sonntag 24. September 2017, 13:48

WOW !

Vielen Dank!

die Variable ShiftNo und die erste For Schleife diente nur dazu, die Routine für eine beliebige Anzahl von 74595's zu ermöglichen.
Da hier aber fest auf 32Bit sprich 4 ICs verdahtet ist, reicht eine Schleife natürlich.

damit kann ich nun per shift_out_32_bits(1024) einfach Werte übergeben? Cool.

Ist es bei der Input Routine gleich?
Benutzeravatar
__blackjack__
User
Beiträge: 13938
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mega-hz: Der zweite Code ist komisch weil weder `Input` noch `Inputs` innerhalb der Funktion deklariert werden, aber `Input` am Ende zurückgegeben wird. Das ist eine schräge API.

Die nahezu 1:1 Umsetzung in Python-Syntax:

Code: Alles auswählen

def shiftIn32():
    global Inputs
    digitalWrite(IN_loadPin, LOW)
    digitalWrite(IN_loadPin, HIGH)
    for i in range(32):
        Inputs >>= 1
        if digitalRead(IN_dataPin):
            Inputs |= 0x80000000
            Input[i] = 1
        else:
            Input[i] = 0
        digitalWrite(IN_clockPin, HIGH)
        digitalWrite(IN_clockPin, LOW)
    return Inputs
Da jetzt das globale `Inputs` zu einem lokalen Wert gemacht und `Input` rausgeworfen:

Code: Alles auswählen

def shiftIn32():
    digitalWrite(IN_loadPin, LOW)
    digitalWrite(IN_loadPin, HIGH)
    Inputs = 0
    for i in range(32):
        Inputs >>= 1
        if digitalRead(IN_dataPin):
            Inputs |= 0x80000000
        digitalWrite(IN_clockPin, HIGH)
        digitalWrite(IN_clockPin, LOW)
    return Inputs
Wieder die Namen inhaltlich und den Konventionen angepasst:

Code: Alles auswählen

def shift_in_32_bits():
    digital_write(IN_LOAD_PIN, LOW)
    digital_write(IN_LOAD_PIN, HIGH)
    value = 0
    for _ in range(32):
        value >>= 1
        if digital_read(IN_DATA_PIN):
            value |= 0x8000_0000
        digital_write(IN_CLOCK_PIN, HIGH)
        digital_write(IN_CLOCK_PIN, LOW)
    return value
Und wieder für `gpiozero`-Objekte:

Code: Alles auswählen

def shift_in_32_bits():
    IN_LOAD_PIN.off()
    IN_LOAD_PIN.on()
    value = 0
    for _ in range(32):
        value >>= 1
        if IN_DATA_PIN.is_active:
            value |= 0x8000_0000
        IN_CLOCK_PIN.on()
        IN_CLOCK_PIN.off()
    return value
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
mega-hz
User
Beiträge: 14
Registriert: Sonntag 24. September 2017, 13:48

Ich habe das mal zusammengefasst als eine Routine und per

Code: Alles auswählen

import shift
in mein Programm eingefügt.

wenn ich shift_out(1024) aufrufen will, kommt eine Fehlermeldung:

Code: Alles auswählen

TypeError: 'module' object is not callable
was hab ich denn falsch gemacht?

Code: Alles auswählen

#!/usr/bin/python

import RPi.GPIO as GPIO          # um die Pins ansteuern zu koennen, brauchen wir die Lib
import time                      # fuer die Delays benoetigen wir die time-Lib

GPIO.setwarnings(False)          # Keine Warnungen anzeigen
GPIO.setmode(GPIO.BCM)           # Programmiermodus, um die Pins zu setzen

OUT_DATA = 19
CLOCK = 13
OUT_LATCH = 20
INP_DATA = 26
INP_LATCH = 21

PAUSE = 0.05

GPIO.setup(OUT_DATA,GPIO.OUT)    # Setze GPIO als Ausgang (OUT_DATA)
GPIO.setup(CLOCK,GPIO.OUT)       # Setze GPIO als Ausgang (CLOCK)
GPIO.setup(OUT_LATCH,GPIO.OUT)   # Setze GPIO als Ausgang (OUT_LATCH)
GPIO.setup(INP_LATCH,GPIO.OUT)   # Setze GPIO als Ausgang (INP_LATCH)
GPIO.setup(INP_DATA,GPIO.IN)     # Setze GPIO als Eingang (INP_DATA)

def shift_out(value):

    OUT_LATCH.off()

    for _ in range(32):
        OUT_DATA.value = value & 1
        value >>= 1
        CLOCK.on()
        CLOCK.off()

    OUT_LATCH.on()
    OUT_CLOCK.off()

def shift_in():
    IN_LOAD_PIN.off()
    IN_LOAD_PIN.on()
    value = 0
    for _ in range(32):
        value >>= 1
        if IN_DATA_PIN.is_active:
            value |= 0x8000_0000
        IN_CLOCK_PIN.on()
        IN_CLOCK_PIN.off()
    return value


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

@mega-hz: Die Fehlermeldung passt nicht zum gezeigten Code. Bitte den tatsächlichen Code und die tatsächliche Fehlermeldung samt Traceback zeigen. Nicht ungefähr abgeschrieben, sondern 1:1 kopiert.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Aufsetzen der Pins gehört da nicht hin, das sollte in einer eigenen Funktion stehen, damit man das Modul importieren kann, ohne dass da gleich irgendwas an der Hardware passiert.

Warnungen sollte man nicht ignorieren sondern deren Ursache beseitigen. Beim `RPi.GPIO`-Modul ist das in der Regel das man am Programmende `GPIO.cleanup()` nicht aufgerufen hat. Das sollte man mit einem ``try``/``finally`` sicherstellen.

Allerdings passt das sowieso nicht zum Code denn die Funktionen sind für `gpiozero`, nicht für `RPi.GPIO`.

Die Kommentare sind allesamt überflüssig. Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Sachen die in der entsprechenden Dokumentation stehen, sind offensichtlich, denn es macht keinen Sinn Dokumentation immer als Kommentare in den Code abzuschreiben.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Sirius3
User
Beiträge: 18227
Registriert: Sonntag 21. Oktober 2012, 17:20

@mega-hz: Wenn Du nur `shift` importierst, dann ist `shift_out` nicht definiert, Du bekommst also einen NameError und keinen TypeError. Zeige also den wirklichen Code, den Du ausführst, inklusive komplettem Traceback.

Das `as` bei `import` ist dazu da etwas umzubenennen, GPIO wird gar nicht umbenannt. time wird importiert aber nicht benutzt. Warnungen sind dazu da, dass man sie behebt, nicht dass man sie ignoriert; dazu muß aber am Ende des Programms verlässlich GPIO.cleanup aufgerufen werden.
Ein Modul sollte nicht einfach so den Mode ändern oder irgendwelche Pins initialisieren. Das sollte in eine initialize-Funktion wandern.
Wie __blackjack__ geschrieben hat, benutzt er gpiozero und nich RPi.GPIO. Ersteres ist modernen, stabiler und einfacher zu bedienen. Du mischst hier Module, das kann nicht funktionieren.
mega-hz
User
Beiträge: 14
Registriert: Sonntag 24. September 2017, 13:48

Danke für die Tips und Hinweise!
gpiozero kannte ich noch gar nicht...
Da ich im Hauptprogramm bereits GPIO BCM benutze, habe ich die Routinen umgeschrieben.

Im Programmkopf steht nun:

Code: Alles auswählen

from shift import shift_out, shift_in
nun klappt der Aufruf auch, keine Fehlermeldungen.

Die Routine sieht nun so aus:

Code: Alles auswählen

#!/usr/bin/python

import RPi.GPIO as GPIO
import time

OUT_DATA = 19
CLOCK = 13
OUT_LATCH = 20
INP_DATA = 26
INP_LATCH = 21

PAUSE = 0.05

def shift_out(value):

    GPIO.output(OUT_LATCH, False)

    for _ in range(32):
        GPIO.output(OUT_DATA, value & 1)
        value >>= 1
        GPIO.output(CLOCK, True)
        time.sleep(PAUSE)
        GPIO.output(CLOCK, False)

    GPIO.output(OUT_LATCH, True)
    time.sleep(PAUSE)
    GPIO.output(CLOCK, False)

def shift_in():
    GPIO.output(INP_LATCH, False)
    time.sleep(PAUSE)
    GPIO.output(INP_LATCH, True)
    value = 0
    for _ in range(32):
        value >>= 1
        if GPIO.input(INP_DATA):
            value |= 0x80000000
        GPIO.output(CLOCK, True)
        time.sleep(PAUSE)
        GPIO.output(CLOCK, False)
    return value
Vielen Dank für die Hilfe!

Wolfram
Antworten