- 3D-Druck Einstieg und Tipps         
Seite 1 von 2 12 LetzteLetzte
Ergebnis 1 bis 10 von 16

Thema: SPI Interrupt wird ZU SPÄT ausgelöst nach Sendevorgang

  1. #1
    Erfahrener Benutzer Begeisterter Techniker
    Registriert seit
    14.04.2005
    Ort
    Freiberg
    Alter
    41
    Beiträge
    311

    SPI Interrupt wird ZU SPÄT ausgelöst nach Sendevorgang

    Anzeige

    E-Bike
    Hallo zusammen,

    wie am Titel zu erkennen, habe auch ich gerade Probleme mit SPI. Es handelt sich bei mir um einen PIC18, der als SPI-Slave mit SS betrieben wird, interner Clock (16MHz).

    Problem: Der interrupt wird viel zu spät ausgelöst: es vergehen 11,5µs zwischen <jetzt müsste er auslösen> und <der Ausgang RD5 wurde umgeschalten>. RD5=0 bleibt für 10µs erhalten.

    Umgehe ich die interrupt-Routine und baue in der Main-Routine eine Abfrage des PIR1bits.SSPIF == 1 und schalte dann den Ausgang RD5=0, geschieht das mit einem Verzug von etwa 1,1-2,4µs (Jitter natürlich deutlich stärker). RD5 bleibt ca. 1,5µs auf low.

    Code:
    void spi_init(void) {
    
        INTCON = 0;         // Disable all interrupts
        PIE1   = 0;
        PIR1   = 0;
        // ----------------------------
        SSPCON1bits.SSPEN = 0;          // SSPCON1[5]; Allow Programming of serial port
        SSPCON1bits.CKP = 1;            // SSPCON1[4]; Clock Polarity Select bit; Idle state for clock is a HIGH level
        SSPCON1bits.SSPM3 = 0;          // SSPCON1[3]; Synchronous Serial Port Mode Select bits
        SSPCON1bits.SSPM2 = 1;          // SSPCON1[2]; [3-0] 0100 = SPI Slave mode, clock = SCK pin, SS pin control enabled
        SSPCON1bits.SSPM1 = 0;          // SSPCON1[1]; 
        SSPCON1bits.SSPM0 = 0;          // SSPCON1[0]; 
        // ----------------------------
        SSPSTATbits.SMP = 0;            // SSPSTAT[7]; Input data sampled at middle data output time
        SSPSTATbits.CKE = 0;            // SSPSTAT[6]; Transmit occurs on active to idle clock state
        // ----------------------------
        PIE1bits.SSPIE = 1;
        PIR1bits.SSPIF = 0;             // Master Synchronous Serial Port Interrupt Flag bit (must be cleared by Software)
                                        // 0 = Waiting to transmit/receive
        INTCONbits.PEIE   = 1;          // Enable Peripheral interupts
        INTCONbits.GIE    = 1;          // Global Interupt enable
        SSPCON1bits.SSPEN = 1;          // End programming and Start serial port
    }
    ISR:
    Code:
    void interrupt isr(void)
    {
        if (PIR1bits.SSPIF == 1)
        {
            spi_reg_addr=SSPBUF;
            SSPBUF=0x00;
            RD5=0;
            PIR1bits.SSPIF = 0;
        }
    }
    Main:
    Code:
    void main(){
        pic_init();
        spi_init();
        while(1)
        {
            PORTDbits.RD5=1;
        }
    }
    Woran kann es liegen, dass die Interrupt-Routine so spät auslöst bzw. wie kann man es beschleunigen?

    Grüß,
    NRicola
    Gurken schmecken mir nicht, wenn sie Pelz haben!

  2. #2
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Hallo NRicola,
    Du hast uns nicht verraten welchen PIC der 18er Serie Du benutzt.
    Es gibt beim 18F252 z.B. ein Problem im SPI Interface im SlaveMode
    Für deinen PIC gibt es ganz sicher auch einen "ERRATA" Sheet, weil es gibt keine funktionierenden PICs...
    Spass muss sein, ich liebe die Dinger trotzdem.
    Schau mal dort rein, eventuell hilft es dann weiter.

    hier mal der Hinweis vom PIC18F252:
    Module: MSSP (SPI, Slave Mode)
    In its current implementation, the SS (Slave
    Select) control signal, generated by an external
    master processor, may not be successfully recognized
    by the PIC® microcontroller operating in
    Slave Select mode (SSPM3:SSPM0 = 0100). In
    particular, it has been observed that faster
    transitions (those with shorter fall times) are more
    likely to be missed than than slower transitions.
    Work around
    Insert a series resistor between the source of the
    SS signal and the corresponding SS input line of
    the microcontroller. The value of the resistor is
    dependent on both the application system’s
    characteristics and process variations between
    microcontrollers. Experimentation and thorough
    testing is encouraged.
    This is a recommended solution. Others may exist.

    zudem würde ich mir mal den Assemblercode von der Interruptfunktion ansehen.
    Ich weis nicht was der Compiler dort für einen "overhead" für Register sichern usw. reinbaut.
    Bei einer Free Version von Microchip hab ich hier schon haarsträubenden code erlebt....
    Wenn er erstmal 50 Zeilen Code abarbeiten muss bis er deinen Zeile zum Bitsetzen erreicht
    kommt da natürlich einiges an Zeit zusammen.
    Geändert von Siro (03.06.2018 um 21:37 Uhr)

  3. #3
    Erfahrener Benutzer Begeisterter Techniker
    Registriert seit
    14.04.2005
    Ort
    Freiberg
    Alter
    41
    Beiträge
    311
    Hallo Siro,

    danke für den Hinweis mit den Errata-Sheets. Das hat mich wachgerüttelt. Ich hatte einen Mikrocontroller als perfektes Gerät angesehen (nennen wir es Gottesteilchen). Ist ja doch erstaunlich, was dann später noch gefummelt werden muss...

    Dennoch habe ich für meinen PIC18f45k20 nicht wirklich den passenden aller Einträge gefunden.
    Den ASM-Code anzuschauen wäre ein Ansatz. Aber zum einen ist mir nicht klar, wie ich da ran komme, wenn ich in C programmiere und mit MPLAB/XC8 arbeite. Zum anderen habe ich aber auch mal den ganz popligen Test gemacht, INT0 auszulösen. Also ein Rechtecksignal an PB0 und dann Ausgang RD5 umschalten lassen.
    Code:
        TRISB  = 1;
        ANSELH = 0x00;
        INTCON = 0;
        INTCON2 = 0;
        INTCON2bits.INTEDG0 = 0; // Flanke ist bei mir negativ
        INTCONbits.INT0IE = 1;
        INTCONbits.INT0IF = 0;
        RCONbits.IPEN     = 0;
        INTCONbits.GIE    = 1;
    Auch hier (mit den internen 16MHz Clock) habe ich einen Verzug zwischen den Flanken von 11µs (=176 Clock cycles). So etwas fundamentales kann ja eigentlich nicht mehr falsch zwischen C und ASM implementiert sein. Oder etwa doch??
    Daher wäre meine Vermutung, dass es eher noch etwas systematisches ist. Was könnte ich vergessen haben oder welches Register könnte mich noch ausbremsen? Ist dem interrupt-Prozess noch irgendein Timer oder anderer Trigger hinterlegt? Nach dem Schaubild "9-1 Intterrupt Logic" im Datenblatt eigentlich nicht.
    Ich habe bis jetzt vorwiegend mit den high/low Prioritäten des Interrupts herumgespielt. Das lief alles auf's gleiche hinaus.

    Grüß
    NRicola
    Gurken schmecken mir nicht, wenn sie Pelz haben!

  4. #4
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    07.03.2011
    Beiträge
    1.899
    Zitat Zitat von NRicola Beitrag anzeigen
    Auch hier (mit den internen 16MHz Clock) habe ich einen Verzug zwischen den Flanken von 11µs (=176 Clock cycles). So etwas fundamentales kann ja eigentlich nicht mehr falsch zwischen C und ASM implementiert sein. Oder etwa doch??
    Daher wäre meine Vermutung, dass es eher noch etwas systematisches ist. Was könnte ich vergessen haben oder welches Register könnte mich noch ausbremsen? Ist dem interrupt-Prozess noch irgendein Timer oder anderer Trigger hinterlegt? Nach dem Schaubild "9-1 Intterrupt Logic" im Datenblatt eigentlich nicht.
    Ich habe bis jetzt vorwiegend mit den high/low Prioritäten des Interrupts herumgespielt. Das lief alles auf's gleiche hinaus.
    Zwei Vorbemerkungen: Die Interruptpriorität spielt nur eine Rolle, wenn zwei Interrupte gleichzeitig eintreffen. Das ist hier aber nicht der Fall. Und der CPU-Takt ist ein Viertel des Systemtaktes. die 176 Clock cycles sind also 44 Befehlstakte.

    Aber mal ganz allgemein zu Interrupten und Interrupthandlern. Ein Interrupt kann jederzeit eintreten, daher muß der Prozessor nach abbarbeiten des Handlers 100% im gleichen Zustand sein, wie davor. Ansonsten würde das Haupprogramm nicht mehr richtig funktionieren. Ein Teil wird automatisch passieren, kost aber auch seine Zeit. So muß der Programmcounter auf dem Stack gesichert werden und die Anfangsadresse des Handlers muß geladen werden. Jeder Speicherzugriff dauert mindestens einen Prozessortakt, wieviel das in Summe bei einem PIC18 sind, weiß icht jetzt nicht. Jetzt ist der Prozessor also im Interrupthandler und muß als erstes alle Prozessorflags sichern, ohne sie dabei zu verändern. Da ein Interrupthandler eine normale C-Funktion ist, müssen dann alle Register gerettet werden, die der Compiler benutzt. Dazu gehören auch die, die in irgendwelchen Systemfunktionen für die Behandlung von longs oder für Multiplikation und Division benutzt werden, der Interrupt könnte ja gerade in der Bearbeitung einer solchen Funktion aufgetreten sein.

    Auch kann der Compiler nicht vorhersagen, welche Register eine Libraryfunktion benutzt, die später nur als Objectcode dazugelinkt wird. Ich halte also die 44 Prozessorzyklen für die Interruptpräambel und für den Code, der vor deinem Portbitsetzen abläuft für nichts ungewöhnliches. Das Wiederherstellen des Prozessorzustands am Ende des Interrupts wird ähnlich lange dauern.

    In Assembler wird das nur marginal besser. Bei ganz simplen Aufgaben des Interrupts kann man da besser sein, man muß aber bei jedem Befehl, den man schreibt, genau überlegen, was man tut. Und das auch, wenn man (oder auch ein anderer Programmierer) das Programm Monate oder Jahre später debugged. In C ist das kein Problem, ein paar Zeilen mehr im Interrupthandler und das Hauptprogramm läuft immer noch ungestört.

    Zitat Zitat von Siro Beitrag anzeigen
    zudem würde ich mir mal den Assemblercode von der Interruptfunktion ansehen.
    Ich weis nicht was der Compiler dort für einen "overhead" für Register sichern usw. reinbaut.
    Bei einer Free Version von Microchip hab ich hier schon haarsträubenden code erlebt....
    Wenn er erstmal 50 Zeilen Code abarbeiten muss bis er deinen Zeile zum Bitsetzen erreicht
    kommt da natürlich einiges an Zeit zusammen.
    Mit Begriffen wie "haarsträubenden" wäre ich zurückhaltend. Es klingt so, als wären die Compilerprogrammier Vollpfosten und hätten ihr Handwerk nicht gelernt. Ich würd mir das erst erlauben, wenn ich selbst einen Compiler geschrieben hätte, der alle Regressionstest besteht.

    Und die Vorstellung, daß ein Optimizer da viel machen kann, ist irrig. Auch der kann später hinzugelinkten Objectcode nicht wirklich analysieren. Ein 8-Bit Prozessor braucht für die Behandlung der in C gängigen Datentypen wie int oder long nun mal mehrere Register. Bei "großen" Prozessoren wird das mit den Registern besser, dafür ist der Code des Interrupthandlers wahrscheinlich nicht im Cache und muß erst aus dem langsamen Hauptspeicher geladen werden (und verdrängt dabei möglicherweise den Code des Hauptprogramms). Das ist dann zwar anders, aber nicht unbedingt besser.

    MfG Klebwax
    Strom fließt auch durch krumme Drähte !

  5. #5
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Ich will den Compiler nicht schlecht machen Klebwax,
    ich weis aber und das wird sogar angezeigt, dass im "Free" Mode der Code "äußerst" ungünstig wird, so würde ich das mal nennen.

    Dies ist meine Standard Meldung bei allen Projekten:

    You have compiled in FREE mode.
    Using Omniscient Code Generation that is available in PRO mode,
    you could have produced up to 60% smaller and 400% faster code.
    See http://www.microchip.com/MPLABXCcompilers for more information.
    Daher ist es gut möglich, dass hier die "zusätzlichen" Zeiten kommen könnten.
    400 Prozent ist doch erheblich.....

  6. #6
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    07.03.2011
    Beiträge
    1.899
    Zitat Zitat von Siro Beitrag anzeigen
    Daher ist es gut möglich, dass hier die "zusätzlichen" Zeiten kommen könnten.
    400 Prozent ist doch erheblich.....
    Ich kenne die Meldung. Aber "60% smaler and 400% faster" glaub ich erstmal nicht. Da müsste eher ein "or" stehen. Typisch dealt man zwischen "faster" and "smaler". Loop unroling macht den Code länger, aber schneller. Berechnungen über Tabellen zu machen ebenfalls. Für die Rechnungen mit ints oder longs Inlinecode zu verwenden oder Systemfunktionen zu callen genauso. Und die Spitzenwerte sind, wie bei Benchmarks so üblich, wirklich Spitzenwerte unter idealen Bedingungen. Der Optimizer arbeitet auch sicher im wesentlichen auf C-Ebene, das wird sich auf sowas wie eine Interrupt-Präambel kaum auswirken. Der Optimizer wird nicht plötzlich umschaltbare Registerbänke oder ähnliches entdecken.

    Der XC8 ist für Microchip sicher ein Verlustgeschäft. Ohne einen funktionierenden, fehlerfreien Compiler kann man aber keine CPUs für Milliarden von $ an Profis verkaufe, sie müssen sich das also antun. Ich glaube, diese Meldung soll die professionellen Kunden, die mit den "deep pockets", dazu bringen, wenigstens einen Beitrag zur Entwicklung und Wartung des Compilers beizutragen.

    MfG Klebwax
    Strom fließt auch durch krumme Drähte !

  7. #7
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Ich finde der Compiler sollte umsonst sein, (logisch aus privaten Interesse natürlich )
    Aber eine Verbreitung der Chips geht meiner Meinung nach auch durch Privatanwender voran,
    was ich privat nutze, setzte ich dann auch in der Entwicklung in der Firma ein.
    Bei mir war es zumindest so.
    -----------

    Zum Thema Assembler Listing:

    Um ein Assembler Listing zu bekommen must Du erst etwas einstellen: ich beziehe mich auf die MPLABX-IDE

    Du klickst oben im Menü
    File
    Project Properties

    oder auch rechte Maustaste in dem Projects Fenster links.


    Dann gibt es vermutlich 2 Konfigurationen
    eventuell aber auch nur eine.
    Dort musst Due auf Conf und dann den Unterpunkt "Loading" wählen.
    Hier muss rechts in das Kästchen ein Haken rein bei "Load symbols when......

    Nun musst das Projekt neu compiliert (Builded) werden.
    Erst dann wird auch ein Assembler File erstellt.

    Nun gehst Du oben im Menü auf
    "Window" --> Debugging --> Output --> Disamply listing file


    Nun sollte sich das Assembler Fenster öffnen.


    Bei mir heisst die Funktion
    void __interrupt isr(void)

    Dann kannst Du Dir den erzeugten code anschauen.

    Ich hab mal Bildchen angehangen:
    Miniaturansichten angehängter Grafiken Miniaturansichten angehängter Grafiken MPLABX_1.jpg   MPLABX_2.jpg  
    Geändert von Siro (07.06.2018 um 13:24 Uhr)

  8. #8
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Ich habe mal eben ein Project mit deinem PIC erstellt
    und dies ist der erzeugte Assembler Code der interrupt Funktion:

    Code:
    unsigned char spi_reg_addr;
    18:            
    19:            void interrupt isr(void)
    20:            {
    21:                if (PIR1bits.SSPIF == 1)
    0052  A69E     BTFSS PIR1, 3, ACCESS
    0054  D006     BRA 0x62
    22:                {
    23:                    spi_reg_addr=SSPBUF;
    0056  CFC9     MOVFF SSPBUF, spi_reg_addr
    0058  F018     NOP
    24:                    SSPBUF=0x00;
    005A  0E00     MOVLW 0x0
    005C  6EC9     MOVWF SSPBUF, ACCESS
    25:                    RD5=0;
    005E  9A83     BCF PORTD, 5, ACCESS
    26:                    PIR1bits.SSPIF = 0;
    0060  969E     BCF PIR1, 3, ACCESS
    27:                }
    28:            }
    0062  C012     MOVFF 0x12, 0x1C


    er sichert keine Register und ein RETFIE vermisse ich auch.
    Muss man da noch was einstellen, vermutlich erscheint es nicht im Listingfile ??
    Geändert von Siro (07.06.2018 um 13:50 Uhr)

  9. #9
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    11.12.2007
    Ort
    weit weg von nahe Bonn
    Alter
    39
    Beiträge
    3.416
    Der XC8 ist für Microchip sicher ein Verlustgeschäft. Ohne einen funktionierenden, fehlerfreien Compiler kann man aber keine CPUs für Milliarden von $ an Profis verkaufe, sie müssen sich das also antun. Ich glaube, diese Meldung soll die professionellen Kunden, die mit den "deep pockets", dazu bringen, wenigstens einen Beitrag zur Entwicklung und Wartung des Compilers beizutragen.
    Also wenn einer Fragen zum Thema XC Compiler von Microchip hat, her damit, hatte gerade erst vor einer Woche den FAE von microchip zu Besuch bei uns und darüber intensiv unterhalten. Der XC8 ist wohl ein übernommener und ewig weiterentwickelter Compiler (der Ursprung ist mir entfallen weil es bei uns um den XC16 ging) der aber so schnell nicht sterben wird, er nannte ihn den wohl am besten optimierten Compiler.

    Microchip ist eine Firma in der keine Querfinanzierung gemacht wird und daher kosten die "Fortgeschrittenen" Feature halt Geld da die Abteilung die u.a. den Compiler pflegt sich damit finanzieren muss.

    Deine Behauptung das es nur für die deep-pockets ein Anreiz sein soll ist definitiv falsch. Die Optimierung kann immense Geschwindigkeitsvorteile bringen und 400% sind nicht aus der Luft. Man nehme z.B. einen Switch Case mit 30 Einträgen (ein etwas großer zugegeben aber nicht zwingend ungewöhnlich). Den kann ich mittels gewöhnlicher "If ElseIf" lösen, entsprechend langsam ist das ganze. Ich kann aber auch (Vorausgesetzt alle cases sind wertemäßig ansteigend und haben unterneinander weniger Abstand als 5) einen switch case in eine Sprungtabelle umwandlen. Das ist codemäßig kleiner als die ganzen Bedingungen und ERHEBLICH schneller!

    Kommt also mehr oder minder auf die zu optimierende Funktion an, dann sind 400% absolut real!

    Viele Funktionen wie "garbage collecting unused methodes" und andere code optimierungen lassen sich aber auch ohne optimierungsstufe realisieren. Schon allein damit methoden in einzelne sektionen packen zu lassen und dann hinterher im linker (hat der XC8 so nicht) dann unreferenzierte sektionen löschen zu lassen kann einen Code schnell viel kleiner machen, vor allem wenn man Softwaremodule einsetzt.
    Das ist stnadradmäßig nicht an, beim atmel GCC aber schon.
    Es gibt 10 Sorten von Menschen: Die einen können binär zählen, die anderen
    nicht.

  10. #10
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Jetzt hab ichs, glaube ich:
    Der Interrupt Vector liegt ja bei 0x0008 bzw. 0x0018
    und dort ist folgender Code zu finden, nämlich der Eintritt in die Interrupt Funktion
    Hier werden alle notwendigen Register gerettet, erstmal unabhängig davon ob diese benutzt werden.

    Code:
    0008  821B     BSF btemp, 1, ACCESS
    000A  CFFA     MOVFF PCLATH, __pcstackCOMRAM
    000C  F001     NOP
    000E  CFFB     MOVFF PCLATU, 0x2
    0010  F002     NOP
    0012  CFE9     MOVFF FSR0, 0x3
    0014  F003     NOP
    0016  CFEA     MOVFF FSR0H, 0x4
    0018  F004     NOP
    001A  CFE1     MOVFF FSR1, 0x5
    001C  F005     NOP
    001E  CFE2     MOVFF FSR1H, 0x6
    0020  F006     NOP
    0022  CFD9     MOVFF FSR2, 0x7
    0024  F007     NOP
    0026  CFDA     MOVFF FSR2H, 0x8
    0028  F008     NOP
    002A  CFF3     MOVFF PROD, 0x9
    002C  F009     NOP
    002E  CFF4     MOVFF PRODH, 0xA
    0030  F00A     NOP
    0032  CFF6     MOVFF TBLPTR, 0xB
    0034  F00B     NOP
    0036  CFF7     MOVFF TBLPTRH, 0xC
    0038  F00C     NOP
    003A  CFF8     MOVFF TBLPTRU, 0xD
    003C  F00D     NOP
    003E  CFF5     MOVFF TABLAT, 0xE
    0040  F00E     NOP
    0042  C01B     MOVFF btemp, 0xF
    0044  F00F     NOP
    0046  C01C     MOVFF 0x1C, 0x10
    0048  F010     NOP
    004A  C01D     MOVFF 0x1D, 0x11
    004C  F011     NOP
    004E  C01E     MOVFF 0x1E, 0x12
    0050  F012     NOP
    00D8  0012     RETURN 0
    54:

    !! Die NOP s sind NICHT etwa reingebastelt um Code zu verschwenden, obwohl ich das auch am Anfangs dachte....
    das ist Prozessorbedingt so bei einem Befehl MOVFF.

    er kommt auch bei einem Interrupt in diesen Code rein, habe ich grad mal simuliert und das dauert dann 40 Cycles für diesen Codeabschnitt

    Bei 16 MHz / 4 wegen der Pipeline Struktur = 4 MHz =250ns pro Cycle
    Bei 40 Cycles also 40*250ns sind 10 us und das entspricht deinem Fehler.

    Vermutlich kommt hier dann die "Optimierung" zur Geltung, wenn man eine Pro Version hat.Ich kann das leider nicht testen, habe nur die Freie VersionIch glaube man kann sich aber auch eine Pro Version Testweise runterladenIdee: Du könntest noch die PLL im Chip einschalten und die Zeit damit vierteln.
    Also den internen Takt vervierfachen. Must deine SPI Einstellung vermutlich noch ändern.
    Geändert von Siro (07.06.2018 um 15:45 Uhr)

Seite 1 von 2 12 LetzteLetzte

Ähnliche Themen

  1. SPI Interrupt wird nicht ausgelöst nach Sendevorgang
    Von steckplatte im Forum PIC Controller
    Antworten: 4
    Letzter Beitrag: 25.04.2018, 21:08
  2. Interrupt wird nicht ausgelöst.
    Von DarkSoldier im Forum C - Programmierung (GCC u.a.)
    Antworten: 1
    Letzter Beitrag: 28.04.2013, 15:42
  3. Interrupt wird nicht ausgelöst
    Von Michael_am32 im Forum C - Programmierung (GCC u.a.)
    Antworten: 4
    Letzter Beitrag: 02.08.2010, 01:37
  4. Es wird kein Interrupt ausgelöst
    Von MrTaco im Forum C - Programmierung (GCC u.a.)
    Antworten: 9
    Letzter Beitrag: 19.07.2010, 17:48
  5. Wann wird ein Interrupt ausgelöst?
    Von CKroll im Forum PIC Controller
    Antworten: 2
    Letzter Beitrag: 08.09.2004, 09:16

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •  

Labornetzteil AliExpress