Forum: Mikrocontroller und Digitale Elektronik 32 Relais über einen Arduino mit SCPI


von Tim T. (wild-breeze)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich stehe gerade mit einem Brett vorm Kopf vor meinem Code, und weiß 
aktuell nicht weiter.
Mein Ziel ist es 32 Relais über SCPI über USB mittels eines Arduino Mega 
2560 zu schalten.

Ich habe zu diesem Thema im Internet folgende Seite gefunden.

https://community.element14.com/challenges-projects/project14/open-arduino/b/blog/posts/arduino-in-test-instrumentation---intro-scpi-programmable-switch

Ich habe nun sein Programm genommen und die Funktionsweise 
nachvollzogen. anschließend habe ich nun zum Test ein 8 Relay Module zum 
Testen hergenommen, und 8 Kanäle im Programm implementiert.
Ergebnis: Alles funktioniert perfekt!
Daraufhin erweiterte ich den Code auf 32 Kanäle. Leider funktioniert es 
nicht ab Kanal 10.
Sobald ich über SCPI den Befehl: :DIGI:SWITCH10 ON an den Arduino sende, 
bekomme ich die Antwort OFF… eigentlich sollte bei diesem Befehl Relay 
10 Schalten.
Zusammengefast funktionieren die ersten 9 Kanäle und alle über 9 nicht.

Ich komme damit nun etwas an meine Grenzen, was Embedded Software 
betrifft. Meine Vermutung ist, dass es an den Datentypen liegt, um die 
ich mir bei beispielsweise Python recht wenig Gedanken mache.

Ich hoffe mir kann hier jemand helfen und mir das Ganze auch erklären wo 
mein Fehler liegt.
Danke im Voraus!

von Achim S. (Gast)


Lesenswert?

Ich hab deinen Code nicht im Detail durchsucht. Aber beim Aufruf von

scpi_register_command(digital, SCPI_CL_CHILD, "SWITCH1?", 8, "SWIT1?", 
6, get_digital_1);

und von

scpi_register_command(digital, SCPI_CL_CHILD, "SWITCH10?", 8, "SWIT10?", 
6, get_digital_10);

fällt mir eine Inkonsistenz auf. Der vierte Aufrufparameter ist 
long_name_length. Du nutzt dazu jeweils 8, obwohl "SWITCH10?" ein 
Zeichen länger ist als "SWITCH1?"

Wenn nur die ersten 8 Zeichen des Kommando überprüft werden, dann ist 
"SWITCH10?" (Abfrage des Registerwerts) identisch zu "SWITCH10" 
(schreiben des Registerwerts. Und deswegen wird wohl beim Senden von 
"SWITCH10" versehentlich "SWITCH10?" durchgeführt.

Passe mal die Längenangaben der Kommandostrings an deren tatsächliche 
Länge an.

von Lothar (Gast)


Lesenswert?

Tim T. schrieb:
> Mein Ziel ist es 32 Relais über SCPI über USB mittels eines Arduino Mega
> 2560 zu schalten.

Wenn das sicher sein soll wäre es besser dafür zwei von denen zu nehmen:

https://www.controllino.com/product/controllino-mega-pur/

von oszi40 (Gast)


Lesenswert?

Tim T. schrieb:
> Sobald ich über SCPI den Befehl: :DIGI:SWITCH10 ON an den Arduino sende,
> bekomme ich die Antwort OFF… eigentlich sollte bei diesem Befehl Relay
> 10 Schalten.

Dann mach doch die Gegenprobe und lass die ersten 9 einfach weg.
Evtl. geht bei der Masse Zeug eine Spannung in die Knie oder die Zeit 
reicht nicht?

von Falk B. (falk)


Lesenswert?

Tim T. schrieb:

> Ich hoffe mir kann hier jemand helfen und mir das Ganze auch erklären wo
> mein Fehler liegt.

Naja, man kann ein paar Hinweise geben. Ich hab gestern Abend mal ein 
bisschen mit dem Code gespielt. Der Fehler liegt ca. hier
1
scpi_error_t
2
scpi_execute_command(struct scpi_parser_context* ctx, const char* command_string, size_t length)
3
{
4
  struct scpi_command* command;
5
  struct scpi_token* parsed_command;
6
  
7
  parsed_command = scpi_parse_string(command_string, length);  
8
  command = scpi_find_command(ctx, parsed_command);
9
        // TESTAUSGABE HIER!!!
10
11
  if(command == NULL)
12
  {
13
    return SCPI_COMMAND_NOT_FOUND;
14
  }
15
  
16
  if(command->callback == NULL)
17
  {
18
    return SCPI_NO_CALLBACK;
19
  }
20
  
21
  
22
  return command->callback(ctx, parsed_command);
23
}

Ich hab mir an der oben per Kommentar gekennzeichneten Stelle die beide 
strucs ausgeben lassen. Achtung, das sind POINTER auf structs!
Dabei sieht man, daß die Zerlegung (Parsing) des Strings funktioniert, 
allerdings die Dekodierung nicht. Also muss man den Fehler in 
scpi_find_command suchen.

von DerEgon (Gast)


Lesenswert?

Im Debugger könnte man auf Zeile 11 einen Breakpoint setzen und sich den 
Inhalt der Strukturen anzeigen lassen ...

Wenn man nur mit Bootloadern arbeitet, muss man hier na

Achim S. schrieb:
> Ich hab deinen Code nicht im Detail durchsucht. Aber beim Aufruf von
>
> scpi_register_command(digital, SCPI_CL_CHILD, "SWITCH1?", 8, "SWIT1?",
> 6, get_digital_1);
>
> und von
>
> scpi_register_command(digital, SCPI_CL_CHILD, "SWITCH10?", 8, "SWIT10?",
> 6, get_digital_10);
>
> fällt mir eine Inkonsistenz auf. Der vierte Aufrufparameter ist
> long_name_length. Du nutzt dazu jeweils 8, obwohl "SWITCH10?" ein
> Zeichen länger ist als "SWITCH1?"

Es wäre sinnvoll, die Funktion scpi_register_command zu verändern, so 
daß sie die Stringlänge aus den übergebenen Texten bestimmen kann, statt 
sie redundant und --hier auf jeden Fall-- fehlerträchtig getrennt 
anzugeben.

(Auszug ab Zeile 208 von scpiparser.cpp)
1
  current_command->long_name = long_name;
2
  current_command->long_name_length = long_name_length;
3
4
  current_command->short_name = short_name;
5
  current_command->short_name_length = short_name_length;

ändern in
1
  current_command->long_name = long_name;
2
  current_command->long_name_length = long_name_length ? long_name_length : strlen(long_name);
3
4
  current_command->short_name = short_name;
5
  current_command->short_name_length = short_name_length ? short_name_length : strlen(short_name);

Dann kann für die Stringlänge eine 0 übergeben werden und die Funktion 
kümmert sich selbst drum.
1
scpi_register_command(digital, SCPI_CL_CHILD, "SWITCH10?", 0, "SWIT10?", 0, get_digital_10);

von DerEgon (Gast)


Lesenswert?

(oh, die ersten Zeilen bitte ignorieren)

von Falk B. (falk)


Lesenswert?

Achim S. schrieb:
> Passe mal die Längenangaben der Kommandostrings an deren tatsächliche
> Länge an.

Naja, besser der Op lernt was über die übliche Stringverarbeitung in C 
bzw. C++. Die kommt in den allermeisten Fällen OHNE Längenangabe aus, 
weil Nullterminiert. Auch diverse andere Stellen des Programm sind eher 
"anders". Das liegt aber am Originalautor, nicht am OP, der hat nur 
übernommen und kopiert.

von Falk B. (falk)


Lesenswert?

BINGO! Mit einer kleinen Änderung geht es jetzt!
1
struct scpi_command*
2
scpi_register_command(struct scpi_command* parent, scpi_command_location_t location,
3
            const char* long_name,  size_t long_name_length,
4
            const char* short_name, size_t short_name_length,
5
            command_callback_t callback)
6
{
7
  
8
  struct scpi_command* current_command;
9
  
10
  if(location == SCPI_CL_CHILD)
11
  {
12
    current_command = parent->children;
13
  }
14
  else
15
  {
16
    current_command = parent;
17
  }
18
  
19
  if(current_command == NULL)
20
  {
21
    parent->children = (struct scpi_command*)malloc(sizeof(struct scpi_command));
22
    current_command = parent->children;
23
  }
24
  else
25
  {
26
    while(current_command->next != NULL)
27
    {
28
      current_command = current_command->next;
29
    }
30
    
31
    current_command->next = (struct scpi_command*)malloc(sizeof(struct scpi_command));
32
    current_command = current_command->next;
33
  }
34
  
35
  current_command->next = NULL;
36
  current_command->children = NULL;
37
  
38
  current_command->long_name = long_name;
39
  current_command->long_name_length = strlen(long_name);
40
  
41
  current_command->short_name = short_name;
42
  current_command->short_name_length = strlen(short_name);
43
  
44
  current_command->callback = callback;
45
  
46
  return current_command;
47
}

Ich empfehle trotzdem, die Sache mit den Längen aus den Funktionen 
rauszuschmeißen und wie der Rest der Welt Strinverarbeitung zu 
praktizieren.

von Einer (Gast)


Lesenswert?

Oder feste Längen aber SWITCH01..10 anstatt SWITCH1..10

von Einer (Gast)


Lesenswert?

Das 10 im Geiste durch 32 ersetzen

von Tim T. (wild-breeze)


Lesenswert?

Hallo zusammen,

@ Achim S.
Ich habe die Längen angepasst und es funktionierte bis zu Switch13… nach 
mehrmaligem kompilieren habe ich den code editor gewechselt zur original 
Arduino IDE. Und dann funktionierte alles perfekt.
Ich arbeite normalerweise mit Visual Studio Code mit Platform IO. Da ich 
aktuell mehr in Python arbeite scheint dort etwas mit den Erweiterungen 
durcheinandergekommen sein. Nach Neuinstallation von Platform IO ging es 
dann auch damit.
Das war der richtige Ansatz den ich brauchte! Danke

@ Lothar
Das Produkt merke ich mir vor und ist interessant für ein sich in 
Planung befindliches Projekt.

@ falk
Ich habe es nach deinen Angaben versucht mit dem code ohne angepasste 
Längen und mit angepassten Längen. Mit angepassten Längen funktioniert 
die Dekodierung. Mit nicht angepassten längen klappt es nicht. Was ich 
dabei jetzt aber nicht kapier ist das die Dekodierung so schief geht, 
dass ein anderer Befehl daraus wird. Also aus einem Set ein Get Befehl 
wird und er mir über den Seriellen Monitor ein OFF ausspuckt.

@ DerEgon & falk

Ich werde die Änderungen am Montag Einpflegen und testen!

@ all

Ich danke euch für eure kompetente Hilfe! Es ist nicht 
selbstverständlich wie ich aus anderen Foren schmerzvoll lernen musste.

Nun noch mal was zu mir bezüglich des Kommentar von falk:

>Naja, besser der Op lernt was über die übliche Stringverarbeitung in C
>bzw. C++. Die kommt in den allermeisten Fällen OHNE Längenangabe aus,
>weil Nullterminiert. Auch diverse andere Stellen des Programm sind eher
>"anders". Das liegt aber am Originalautor, nicht am OP, der hat nur
>übernommen und kopiert.

Ich würde so gerne das ganze von null auf selber schreiben, weil man nur 
so am besten lernt wie das ganze funktioniert.
Für mich ist es wichtig, das wenn ich code von anderen übernehme ich ihn 
auch wirklich verstehe! Ich programmiere seit jetzt ca. 4 jahren mehr 
oder weniger komplexe Dinge. Gelernt habe ich S7 und Arbeite aktuell 
viel mit WAGO SPS. Zudem schreibe ich viel in Python.
Da bin ich ganz froh, dass ich auf fertiges zurückgreifen kann. Leider 
scheitere ich sehr oft bei eigenem code an den so unterschiedlichen 
Syntax von AWL (SPS),C/C++ und python, was zeitlich gesehen oft 
schwierig ist. Solange Zeit ist gelingt mir das meiste. :-)

Das nächste was ich implementieren möchte ist, dass mit einem Befehl 
(Cell_1) zwei der Ausgänge Switch1&2 gleichzeitig anspreche. Mit einer 
gegenseitigen Verriegelung, dass nur jeweils Cell_1 oder Cell_2 (Switch 
3&4) usw. angehen und niemals mehr als ein Cell_x gleichzeitig 
geschaltet werden kannen. Das einzelne ansprechen der Kanäle muss dabei 
erhalten bleiben.
Ich hoffe das ich bald die Gelegenheit habe mich damit in ruhe 
auseinander zu setzen. Vielleicht hat ja der ein oder andere dazu eine 
Idee?

von Falk B. (falk)


Lesenswert?

Tim T. schrieb:
> Ich würde so gerne das ganze von null auf selber schreiben, weil man nur
> so am besten lernt wie das ganze funktioniert.
> Für mich ist es wichtig, das wenn ich code von anderen übernehme ich ihn
> auch wirklich verstehe! Ich programmiere seit jetzt ca. 4 jahren mehr
> oder weniger komplexe Dinge. Gelernt habe ich S7 und Arbeite aktuell
> viel mit WAGO SPS. Zudem schreibe ich viel in Python.

Naja, wenn gleich die elementaren Grundlagen der meisten 
Programmiersprachen sehr ähnlich sind, hat man bei SPS selten was mit 
Stringverarbeitung zu tun ;-)

> Da bin ich ganz froh, dass ich auf fertiges zurückgreifen kann. Leider
> scheitere ich sehr oft bei eigenem code an den so unterschiedlichen
> Syntax von AWL (SPS),C/C++ und python, was zeitlich gesehen oft
> schwierig ist. Solange Zeit ist gelingt mir das meiste. :-)

Klingt mehr nach Überleben als nach Programmieren. Mach es richtig. 
Schnapp dir ein Grundlagenbuch zu C, arbeite das durch, dann bist du 
halbwegs fit. C++ würde ich dir erstmal nicht empfehlen, das ist zu 
komplex, wenn gleich natürlich in dem Code schon einiges an C++ 
drinsteckt und die Stringverarbeitung in C++ etwas anders läuft. Hmmm.

von Falk B. (falk)


Lesenswert?

Noch ein Hinweis. Die "Millionen" Funktionen für SWITCH1...SWICHTxy 
stechen dem fortgeschrittenen Programmierer ARG ins Auge. Das macht man 
anders. Man muss im Befehl den Kanal dekodieren und dann an EINE 
Funktion übergeben. Dann braucht es nämlich nur zwei lausige Funktionen 
für set und get. Das macht den Code nicht nur kürzer, sondern auch 
übersichtlicher und weniger fehlerträchtig. Copy & Paste ist zu 99% 
Bäääääähhhh!!!

Wie das bei SCPI im Befehl offiziell kodiert wird, weiß ich nicht. 
Vielleicht so?

:DIGITAL:SWITCH99 on
:DIGITAL:SWITCH:99 on

von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

Moin,

ich war mal so frei, das Ganze ein wenig aufzuräumen. Siehe Anhang.

- alle Anwenderfunktionen in eine separate Datei incl. Header verlagert 
(scpi_user.c), dadurch deutlich mehr Übersicht, scpi.ino wird sehr kurz
- Testfunktionen zur Ausgabe der wichtigen Datenstrukturen in test.c
- die unsägliche Verwendung einer zusätzlichen Längenangabe bei der 
Stringverarbeitung komplett entfernt, das war der größte Aufwand
- Stringverarbeitung konsequent mittels libc-Funktionen und gemäß 
gängiger Praxis in C
- diverse Kleinigkeiten verbessert und aufgeräumt
- Parser des Kommandostrings ist jetzt robuster, mehrfach hintereinander 
liegende Trennzeichen (':' und ' ') werden gefiltert
- Ausgänge für Relais sind jetzt HIGH aktiv, d.h. nach einem Reset sind 
alle Ausgänge auf LOW. Das ist eigentlich der Normalfall, wenn man 
normale Treiber ala ULN2803 nutzt.

Jetzt funktioniert es sehr gut, stabil und ist auch leicht erweiterbar. 
Die Funktion scpi_parse_numeric habe ich nicht angefaßt, die wird hier 
nicht verwendet, da ist auch viel zu tun, wenn man es denn braucht. Sie 
ist per #ifdef auskommentiert.

Was könnte man verbessern?

- Der Kommandobaum ist im Normalfall statisch, da braucht es keine 
dynamischen Funktionen für den Aufbau. Man könnte das alles als 
Konstanten definieren, das spart vor allem beim AVR viel RAM, der im 
Moment verschwendet wird (die konstanten Strings in den vielen 
Funktionen scpi_register_command() verbrauchen Flash UND RAM!).

- Die Tokenliste könnte man vereinfacht über ein Array von Pointern 
machen, eine verkettete Liste ist hier ohne Vorteil, hat eher den 
Nachteil des zusätzlichen Aufwands und Resourcenverbrauchs durch 
dynamische Speicherverwaltung

- die vielen Funktionen für SWITCH könnte man mit einer allgemeinen 
Funktion lösen, das spart viel Schreiberei. Dazu muss man aber die 
Dekodierung des Befehls intelligenter machen.

Viel Spaß damit

Beitrag #7122446 wurde vom Autor gelöscht.
von Falk B. (falk)


Angehängte Dateien:

Lesenswert?

Sooo, und weil's so schön ist, hier die 2. Version ein paar
Verbesserungen.

- Command tree rein statisch, liegt zu 100% im Flash. Da muss man zwar
an einigen Stellen beim AVR mit den pgm_read_word() Funktionen arbeiten,
was den Code etwas schlechter lesbar macht, ist aber noch OK. Der Lohn
dafür ist eine massive Einsparung an RAM
- Die Tokens sind nur noch ein einfaches Array aus Pointern, keine
verkettete Liste mehr. Damit entfällt die komplette, dynamische
Speicherverwaltung mit malloc()
- Der Zustand der Kanäle wird komprimiert in Bits gespeichert, nicht
einfachen bool, denn die brauchen 1 Byte/Element, spart immerhin 28
Bytes

Speicherbedarf

Version 1, dynamische Speicherverwaltung im RAM
Flash: 8234 Bytes
RAM:   1566 Bytes

Wobei das nur der STATISCHE Verbrauch ist! Der dynamische wird von der 
IDE nicht angezeigt und liegt deutlich höher, schätzungsweise bis 3kB! 
Denn es werden ca. 70x10 Bytes für SCPI Kommandos benötigt, dazu noch 
die dynamische Liste der Tokens mit ca 5x5 Bytes, plus der 
Speicherbedarf für die Verwaltung von malloc().

Version 2, statischer command tree ohne dynamische Speicherverwaltung
Flash: 7616 Bytes
RAM:    395 Bytes

Damit läuft das auch auf einem eher kleinen Arduino UNO mit noch
reichlich Reserven. Ok, die Ansteuerung der Relais müßte man dann auf
Schieberegister umbauen, denn er hat keine 32 freien IOs.

: Bearbeitet durch User
von chris (Gast)


Lesenswert?

Ich empfehle als SCPI lib die Vrekrer_scpi_parser_v2.h  zu nehmen,

von Achim S. (Gast)


Lesenswert?

Tim T. schrieb:
> Was ich
> dabei jetzt aber nicht kapier ist das die Dekodierung so schief geht,
> dass ein anderer Befehl daraus wird. Also aus einem Set ein Get Befehl

Ich hatte versucht, das schon oben zu beschreiben.

Der Set-Befehl lautet "SWITCH10", der Get-Befehl lautet "SWITCH10?".

Wenn du nur die ersten 8 Buchstaben davon auswertest, unterscheiden sich 
die SCPI-Kommandos für Set und Get nicht mehr, in beiden Fällen wird nur 
noch auf "SWITCH10" geprüft.

Wenn (aufgrund der Reihenfolge im Code) der Set-Befehl zuerst als Match 
erkannt wird, dann wird er ausgeführt (sowohl für "SWITCH10" als auch 
für "SWITCH10?"). Aber bei dir wird halt der Get-Befehl zuerst als Match 
erkannt, und deshalb der Get-Befehl ausgeführt.

von Tim T. (wild-breeze)


Lesenswert?

Ich möchte mich einmal bei allen bedanken! Das Gerät funktioniert 
perfekt. Ich bin noch auf ein Hindernis gestoßen bezüglich der 
automatischen Resetfunktion des Arduino Mega. Nach dem dies gelöst war 
habe ich das ganze erfolgreich in Betrieb nehmen können.

@Falk

Ich danke dir für deine Mühen, es ist echt klasse zu sehen wie das ganze 
vernünftig auszusehen hat und wie man das Strukturiert. In den kommenden 
Tagen wird hoffentlich mein Buch eintreffen „C Programmieren für 
Einsteiger“ vom BMU Verlag. Anhand des Buchs werde ich versuchen deine 
Herangehensweise zu verstehen und künftige Projekte strukturiert von 
Null auf zu beginnen und es richtig zu machen.
Ich bin bisher mit den Büchern vom BMU Verlag ganz gut gefahren mit 
Python und Git, daher hoffe ich dass dieses Buch mich weiterbringt.
Noch mal in die Runde gefragt, giebt es noch andere Bücher die man 
empfehlen kann bezüglich C?

@Achim S.

Der Groschen ist bei mir nun gefallen. Danke dir

@ chris

Diese SCPI lib ist mir bekannt, weil einem diese auch direkt 
vorgeschlagen wird wen man bei Platform IO oder der Arduino IDE im 
Lib-Manager nach SCPI sucht. Ich hatte mir aus Zeitnotgründen an einem 
bestehenden Projekt dessen Link ich zu Anfang gepostet habe orientiert.

von DerEgon (Gast)


Lesenswert?

Tim T. schrieb:
> Noch mal in die Runde gefragt, giebt es noch andere Bücher die man
> empfehlen kann bezüglich C?

Der K&R in der zweiten Ausgabe ("Programmieren in C", Hanser-Verlag, 
1990)

Definitiv abzulehnen ist das Machwerk eines gewissen "Schellong".

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.