Beliebiges I2C Gerät einbinden (speziell Pixy-Kamera)

More
4 years 11 months ago - 4 years 11 months ago #67 by Tobias Wagner
Liebe Community,

Ziel: Ich habe eine Uni-Projektarbeit begonnen, in der ich ein I2C Modul an ein logi.RTS auf einem RaspberryPi anbinden soll. Bei dem Modul handelt es sich um das Open Hardware Projekt Pixy - eine Kamera, die mit 50fps Farben und Farbcodes erkennen und Koordinaten, Größe und Neigungswinkel der erkannten Objekte über diverse Protokolle ausgeben kann. Ziel ist, diese Daten per ST auszuwerten und unter bestimmten Objektkonstellationen die I/Os zu schalten. Ich würde mir wünschen das Pixymodul anzupollen und die einzelnen 16-Bit Worte der Antwort jeweils in eine Variable zu schreiben.



Frage: Ich habe in der logi.library zwar Funktionsblöcke gefunden, die auf den PCF8574 zugeschnitten sind, frage mich aber, wie ich ein beliebiges Gerät, wie die Pixy-Camera anbinde. Wenn ich es richtig sehe, dann gibt es keine universellen FBs? Ich habe nun mehrere Ideen die Aufgabe zu lösen (nach Vorliebe sortiert)
1. Eine bestehende I2C Erweiterung für Pixy modifizieren
2. Selber ein eigenständiges C oder Pythonskript schreiben und die Daten über eine Schnittstelle in logi.RTS importieren
3. Ein anderes Protokoll wählen
Kann mir jemand kurz darstellen, wie erfolgsversprechend die einzelnen Ansätze (insbesondere der erste) sind oder ob es eine einfachere Möglichkeit gibt?

Details: Raspberry Pi Model B - logi.RTS V2.3.901_Raspbian - logi.CAD3 compact V1.14.0
Pixy-Datenblockinfo: www.cmucam.org/projects/cmucam5/wiki/Pixy_Serial_Protocol

StatusQuo: Pixy per I2C mit Raspberry verbunden und per I2C tools und logicanalyzer getestet. logi.RTS läuft mit Beispielprogrammen.
Attachments:
Last edit: 4 years 11 months ago by Tobias Wagner.

Please Log in or Create an account to join the conversation.

More
4 years 10 months ago #68 by Wolfgang Zeller
Hallo Herr Wagner,
freut mich, dass Sie das Projekt mit logi.CAD 3 umsetzen wollen.
Wir denken derzeit darüber nach, die Integration von eigenem C-Code in logi.CAD 3 Projekte, so wie es in logi.CAD 3 professional möglich ist, auch für logi.CAD 3 compact freizugeben. Diese Variante wird vermutlich auf die Ziel-Plattform Raspberry Pi beschränkt sein.

mit freundlichen Grüßen,
Wolfgang Zeller
logi.CAD 3 Product Owner
The following user(s) said Thank You: Tobias Wagner

Please Log in or Create an account to join the conversation.

  • Karsten Hoffmann
  • Visitor
  • Visitor
4 years 10 months ago #69 by Karsten Hoffmann
Replied by Karsten Hoffmann on topic Beliebiges I2C Gerät einbinden (speziell Pixy-Kamera)
Hallo Herr Wagner,

prinzipiell ist es in logi.CAD 3 compact möglich alles was in Linux über das filesystem abgebildet wird auch anzusprechen.
Auch den I²C Bus, Voraussetzung dafür ist, dass die Module i2c_bcm2708 (i2c_bus) und i2c_dev (i2c filesystem) geladen sind.
Dazu sind die Funktionen unter logi.library/System gedacht. Enschränkung ist, dass derzeit maximal 64 Byte gelesen werden können, wenn ich ihre Tabelle richtig interpretiere. müsste das also ausreichen.

Hier ein kleines Code Beispiel, in welchem auf ein i2c Modul geschrieben, und von einem weiteren gelesen wird:
PROGRAM Template
 VAR
   // Variables to store the cycleinfo
   //sps is in init cycle
    init      :BOOL;
    //sps is in shutdown cycle
    shutdown    :BOOL;
    //sps is running in normal cycle
    run       :BOOL;
    // Variables to access i2c devices (on raspberry pi modules i2c_bcm2708 and i2c_dev have to be loaded)
    // i2c filedescriptor
   i2cFileHandle :DINT;
    //i2c_slave
   I2C_SLAVE   :DINT :=1795; //see i2c-dev.h; meens "Use this slave address"
   // in this example an i2c module with 8 digital outputs stored in one byte is connected at address 32
   // i2c bus address: 32
    I2C_Address   :LINT :=32; // i2c address of module
    //Integer to store 8 Bits of Digital Output
   IntData     :USINT;
   //Array to be send to i2c bus
   ArraydDataOut   :ARRAY[0..63] OF BYTE;
    //Array to be read from i2c bus
   ArraydDataIn    :ARRAY[0..63] OF BYTE;    
  END_VAR
 //first get the cycle info from sps
 RTSCycleInfo(Init=>init,Shutdown=>shutdown,Run=>run);
 
  // if in init cycle (and only in init cycle) open the i2c bus via linux filesystem in read and write mode
 IF init THEN
    IntData:=0;
   i2cFileHandle:=System_open('/dev/i2c-1',2); 
  END_IF;
 
  // if in shutdown cycle close the i2c bus
 IF shutdown THEN
    System_close(fd:=i2cFileHandle);
  END_IF;
 
  // if in run cycle, 
  IF run THEN
   IntData:=IntData+1;
   ArraydDataOut[0]:=TO_BYTE(IntData);
   // tell the system crontol function to access an i2c module at address 32
   System_ioctl(fd:=i2cFileHandle,request:=I2C_SLAVE,data:=I2C_Address);
   // write one byte (first byte of ArrayData) to the module adrressed above 
    System_write(fd:=i2cFileHandle,count:=1,data:=ArraydDataOut);
   
    // now we can switch IO CTL funtion to another module
   System_ioctl(fd:=i2cFileHandle,request:=I2C_SLAVE,data:=56);
    // in this example at address 56 theres an i2c module with 8 digital inputs stored in one byte
    // so we can read 1 byte of data from i2c adddress 56
   System_read(fd:=i2cFileHandle,count:=1,data=>ArraydDataIn);   
  END_IF;
END_PROGRAM

Ich hoffe ihnen damit geholfen zu haben, sollten sie weitere Fragen haben, hier haben wir immer ein offenes Ohr ;)

Please Log in or Create an account to join the conversation.

More
4 years 9 months ago #84 by Tobias Wagner
Endlich habe ich wieder etwas Zeit gefunden und habe die Lösung von Karsten Hoffmann ausprobiert. Nach anfänglichen Problemen, dass ich Variablen nicht richtig einsehen konnte, die aber jetzt behoben sind, habe ich den Code von Karsten Hoffmann eingesetzt und es funktioniert hervorragend!

Trotzdem eine Frage zu den 64 Byte, die ich in einem Durchgang maximal lesen kann:

Für die Funktion System_read steht im Kommentar /* <b>count</b> = number of bytes to read in one go. */
Ich habe also count auf 64 gesetzt und erhalte in der Shell laufend Fehlermeldungen (Ich habe sie grade nicht vorliegen, kann sie aber nachreichen). Außerdem wird das letzte Byte nicht richtig ausgelesen. Dann habe ich gedacht, dass vielleicht intern von 0 bis 63 für 64 Werte gezählt wird, aber bei count = 63 wird auch das letzte Byte nicht ausgelesen. Habe ich einen Fehler gemacht oder etwas nicht bedacht?

Dann habe ich eine Frage zur C-Einbindung in Professional:
Die C-Einbindung funktioniert bei mir ohne zusätzliche Bibliotheken auf Anhieb. Das ist schon einmal genial! Ist es auch möglich einem, als FB eingebundenen C-Code, ein Array zu übergeben? (Ich bin nicht so fit in ST - ist es nach IEC 61131-3 überhaupt erlaubt?) Wenn es möglich ist, wüsste ich nicht genau, wie ich mit dem this-Pointer agieren müsste, weil ich die dahinter liegende Struktur nicht kenne.
Wenn ich VARs für den C-Funktionsblock definieren will, dann nutze ich auch die cschnittstelle genau wie für VAR_INPUT und VAR_OUTPUT? (Es funktioniert, aber ich wollte nur sicher gehen, dass das der Richtige Ort dafür ist)

Please Log in or Create an account to join the conversation.

More
4 years 9 months ago #85 by Rainer Poisel
Hallo Herr Wagner,

freut mich, dass Sie sich entschieden haben, diese Projektarbeit mit unserem logi.CAD3 umzusetzen!

Zur Beantwortung der ersten Frage: es handelt sich hier um einen Software-Bug auf unserer Seite. Dieser wird in einer der nächsten Releases (geschätzter Rahmen 3-4 Wochen) behoben sein. In der Zwischenzeit kann ich aber eine Lösung vorschlagen, die auch gleich ihre zweite Frage beantworten wird:

Sie können einem FB auch ein Array beliebiger Länge übergeben. Dazu sind ein paar einfache Kniffe notwendig. Ich versuche diese anhand eines Beispiels zu erläutern.

Angenommen Sie brauchen eine read()-Funktion, die beliebig viele Bytes von einem File-Deskriptor auf einmal lesen kann. Die Schnittstelle würde z. B. so aussehen:
{ extern_c }
{ Supported_Platforms := Raspbian }

FUNCTION System_read_var : LINT
VAR_INPUT
 fd : DINT;
  count : DINT;
 data : REF_TO BYTE;
END_VAR
VAR_OUTPUT
  errno : DINT;
END_VAR
END_FUNCTION

Bei der INPUT-Variable "data" handelt es sich um eine Referenz auf ein Byte. Diesen Umstand können wir ausnutzen, denn wir werden eine Referenz auf das erste Byte eines Arrays übergeben. In der C-Implementierung der Funktion kann man dann mit der Referenz arbeiten, wie man es bei Pointern auf Array-Elemente gewohnt ist.

Hier der Aufruf der Funktion aus ST-Code:
PROGRAM Program1
  VAR
   fd : DINT := -1;
    path : STRING[64];
    data : ARRAY [0..63] OF BYTE;
   data_len : DINT := 64;
    errno : DINT := -1;
   bytes_read : LINT := -1;

    /* constants from linux kernel */
   flag_rdonly : DINT := 0;
    flag_wronly : DINT := 1;
    flag_rdwr : DINT := 2;
  END_VAR

 IF fd = -1 THEN
   fd := System_open(pathname := '/tmp/sample.dat', flags := flag_rdonly, eno => eno);
 END_IF;

 IF fd <> -1 THEN
    bytes_read := System_read_var(fd := fd, count := data_len, data := REF(data[0]), errno => errno);
 END_IF;

END_PROGRAM

Im Falle der System_read_var-Funktion könnte die Implementierung des Bausteins beispielsweise so aussehen:
#ifndef LC_PROT_LCFU___SYSTEM_READ_VAR__C
#define LC_PROT_LCFU___SYSTEM_READ_VAR__C

#include <lcfu___system_read_var.h>

#include <unistd.h>
#include <errno.h>

/*                            Functions                        */
void  lcfu___SYSTEM_READ_VAR(LC_TD_Function_SYSTEM_READ_VAR* LC_this, LC_TD_DINT LC_VD_FD, LC_TD_DINT LC_VD_COUNT, LC_TD_BYTE (* LC_VD_DATA), struct _lcoplck_epdb_1_impl* pEPDB)
{
  LC_this->LC_VD_SYSTEM_READ_VAR = read(LC_VD_FD, LC_VD_DATA, LC_VD_COUNT);

  if (LC_this->LC_VD_SYSTEM_READ_VAR < 0)
  {
   LC_this->LC_VD_ENO = LC_EL_false;
   LC_this->LC_VD_ERRNO = errno;
  }
}

#endif

Hier ein Auszug aus der Man-Page der read()-Funktion:
ssize_t read(int fd, void *buf, size_t count);

Die Verwendung des Arrays aus dem ST-Code ist also beim zweiten Parameter der read()-Funktion ersichtlich. Die C-Implementierung bekommt hierbei den Wert der Referenz als Funktionsparameter (LC_VD_DATA) übergeben. Auf OUTPUT-Variablen wird über die LC_this-Struktur zugegriffen. Die Struktur-Elemente heißen, so wie die im ST-Code definierten Parameter in Großbuchstaben, wobei LC_VD_ vorangestellt werden muss. Dies ist im gezeigten Beispiel bei der "errno"-Variable der Fall.

Sollte die Situation mit Funktionsblöcken gelöst werden, dann sieht die Sache nur geringfügig anders aus. Hier wird auch auf INPUT-Variablen über LC_this zugegriffen. Hier ein Beispiel-Block:
{ extern_c }
{ Supported_Platforms := Raspbian }

FUNCTION_BLOCK PadZero
VAR_INPUT
 data : REF_TO BYTE;
 data_len : DINT;
END_VAR
END_FUNCTION_BLOCK

Die Implementierung dieses Bausteins sieht so aus:
#ifndef LC_PROT_LCFU___PADZERO__C
#define LC_PROT_LCFU___PADZERO__C

#include <lcfu___padzero.h>

/* dimension nodes */

/* array nodes */

/*                            FunctionBlocks                   */
void  lcfu___PADZERO(LC_TD_FunctionBlock_PADZERO* LC_this, struct _lcoplck_epdb_1_impl* pEPDB)
{
 int cnt = 0;
  for (cnt = 0; cnt < LC_this->LC_VD_DATA_LEN; cnt++)
 {
   LC_this->LC_VD_DATA[cnt] = 0;
 }
}

#endif

Wobei der Aufruf wie gewohnt erfolgt:
PROGRAM Program1
  VAR
   data : ARRAY [0..63] OF BYTE;
   data_len : DINT := 64;

    pad : PadZero;
  END_VAR

   pad(data := REF(data[0]), data_len := data_len);
END_PROGRAM

Hinweis: Wenn nicht ganz klar ist, wie die verschiedenen Variablen im C-Code heißen, besteht die Möglichkeit sich die generierte Schnittstelle anzusehen. Dies wird über die Menüpunkte "Projektexplorer" => weißer Pfeil nach unten "Menü anzeigen" => "Ansicht anpassen" => Häkchen bei "src-gen Ordner" entfernen. Projekt mit der Maus anwählen, F5 drücken. Die generierten Schnittstellen der C-Bausteine befinden sich dann im "src-gen"-Verzeichnis und heißen dann "lcfu___" + Name der Funktion/des Funktionsbausteins + ".h". Die Struktur "_LC_TD_FunctionBlock_" + Name des Bausteins beschreibt dann, wie die LC_this-Struktur aufgebaut ist.

Ich hoffe, dass diese Ausführungen helfen. Wir stehen gerne für Rückfragen bereit!
The following user(s) said Thank You: Tobias Wagner

Please Log in or Create an account to join the conversation.

More
4 years 8 months ago #89 by Tobias Wagner
Hallo,
großartig - von dieser Community bin ich schwer begeistert! Man ist es ja aus Foren schon gar nicht mehr gewohnt, schnelle und sinnvolle Antworten zu bekommen. Die call by reference Lösung funktioniert prima. Einen Zeiger zurück zu geben hat auch soweit gut geklappt.
Danke!
The following user(s) said Thank You: Karsten Hoffmann

Please Log in or Create an account to join the conversation.

LOGI.CALS AUSTRIA

Address

Europaplatz 7/1,
3100 St. Pölten

LOGI.CALS GERMANY

Address

Postfach 1306,
40738 Langenfeld
© 1987 - 2019 logi.cals GmbH