3. Technik der Hardwareansteuerung


In diesem Teil geht es um die Ansteuerung der Hardware.

Die RTK-Computer sind jeweils mit einer Motoren Controllerkarte vom Typ C-812 und einer Karte vom Typ C-832 ausgerüstet. Hersteller dieser Karten ist Physik Instrumente.

Die C-812 kann 4 Gleichstrom-Motoren und die C-832 2 Gleichstrom-Motoren ansteuern. Die Kommunikation zwischen Host-Rechner und Controller-Karte kann unterschiedlich erfolgen.
Bei der C-812 Controllerkarte hat man die Wahl zwischen RS232, PC-Bus und IEEE-488 Schnittstelle.
Die Kommunikation über die RS232 Schnittstelle funktioniert ähnlich wie die bei einem Modem.
Die Steuerbefehle können beispielsweise mit einem Terminalprogramm an die Controllerkarte gesendet werden.
Die Kommunikation mit der Schnittstelle PC-Bus erfolgt über einen Dual-Ported-RAM, d.h. ein Speicherbereich über 640 KByte wird von der Controllerkarte als auch vom Host-Rechner für Schreib-/Lese- Operationen benutzt.
Bei der dritten Möglichkeit der Kommunikation, also der IEEE-488 Schnittstelle, wird vom Hersteller eine 16 Bit DLL zur Verfügung gestellt, mit der man Funktionen zur Steuerung der Motoren importieren kann.

Bei der Controllerkarte C-832 hat man nur eine Möglichkeit zur Kommunikation. Hier kann man nur über Port-Befehle, die auf den I/O-Adress-Bereich zugreifen, mit der Karte kommunizieren.

Die zentralen Funtionen in der motors.dll, die dann endgültig den Zugriff auf die Hardware realisieren, sind
Drive628 bzw. Drive628c (C-832) bzw. TC_812::PutChar und TC_812::GetChar (C-812).


 

DC-Controller-Karte C-812

- Kommunikation über RS-232 (COM-Schnittstelle), PC-Bus (ISA) oder IEEE-488 Schnittstelle
- Kommandoübergabe über ASCII-Zeichen (Befehlssatz)
- Befehlssatz > 80 Befehle
- Bewegungs-, Positionierungs- und Reportbefehle sowie Befehle für Automatisierung
- jeder Befehl besteht aus
     2 Zeichen (Abkürzung der auszuführenden Funktion z.B. Move Absolute = MA, Tell Position = TP)
     + vorangestellte Ziffer = Motorachse = Nr. des Motors für aktuellen Befehl
     + nachfolgender Wert = spezifizierte Größe (z.B. Geschwindigkeit, anzufahrende Position, Beschleunigung)
     + abschliessendes RETURN (ASCII-Wert 13)
   Bsp: Move Absolute Achse 3 zur Pos. 55000 (Motorschritte) = "3MA55000"<RET>
- 4 versch. Befehlsmodi:
   * Einzelbefehle (z.B. MA, MR = Move Relative, TV=Tell Velocity, RT = Reset Board)
   * zusammengesetzte Befehle (z.B. "1MR2000,3MR4300" <RET>)
   * Makrobefehle (abgespeicherte zusammengesetzte Befehle)
      (z.B. "MD3,1MR1000,WS77,WA500,RP5"<RET>; Aufruf mit EM3 oder MC3)
   * Einzeichenbefehle (Sonderzeichen mit bestimmter Bedeutung, z.B. % = TS = Tell Status = Status Lesen)

PC_BUS (ISA) Kommunikation

- Dual Port RAM zum Direktzugriff (ohne Kommandos) z.B. zum Positionlesen
- 2 Speicherregister für Kommunikation (schreiben/lesen der Daten)
- Timing-Kontrolle über Handshake Flags
- 20 Bit IBM-Bus-Adresse:
  - die oberen 8 Bits der Basisadresse werden durch DIP-Switches auf
    Controllerboard eingestellt
  - die nächsten 2 Bits auf 0 (gesetzt durch PAL)
  - restliche 10 Bits ueber Dual Port RAM erreichbar
- normalerweise: Basis Adresse = D8000 hex
- damit steht 1KByte grosser Speicherbereich von 000 bis 3FF hex für Zugriff bereit
- auf diesen Speicherbereich kann Prozessor auf Controller-Board als auch PC-Bus zugreifen

Lesen:
- über "mailbox" mit Adresse 3FE hex relativ zur Basisadresse
- Data-Flag = Bit 1 von 800 hex rel. zur Basisadresse
              wenn gesetzt -> Daten liegen im Speicher an Adresse 3FE hex
Schreiben:
- Busy-Flag = Bit 0 an Adr. 800 hex relativ zu Basisadr.
              wenn nicht gesetzt -> Controller erwartet Daten
- Schreiben eines Bytes an Adresse 3FC hex relativ zur Basisadresse
- selbes Byte an Adresse 3FF hex (wichtig: in dieser Reihenfolge)

Lesen in motors.cpp

char TC_812ISA::GetChar(void)
{
  char chr = 0;

  Delay(1);

// Wenn Bit 1 gesetzt ist, so liegen Daten von der Steuerung an.
  if(!(*lpFlag & 0x02))  // text ready-flag (Data-Flag)
    return 0;        // no data available

  chr = *lpIn;        // read data
  Delay(2);          // wozu ???
  return chr;
};
 

Schreiben in motors.cpp

int TC_812ISA::PutChar(const char c)
{
  int time1,time2;

  Delay(3);        // wozu ????
  //solange Bit 0 (Busy-Flag) an Adresse lpFlag gesetzt (=1)
  //oder time1 > 0 wird die Schleife abgearbeitet (gewartet)
  for(time1 = 30000; (*lpFlag & 0x01) && time1; time1--); // solange Busy-Flag gesetzt

  //checken ob timeout (time1=0) aufgetreten
  //--> falls nicht (time1>0) und damit Bit 0 (Busy-Flag) an lpFlag=0
  //wird an Adresse lpOut1 und danach an lpOut2 mit Verzoegerung geschrieben
  if(time1)
  {
    *lpOut1 = c;   // write data
    Delay(3);      // Verzoegerung
    *lpOut2 = c;   // generate internal signal
  }
  //Warten bis Controller wieder schreibbereit (Bit 0 (Busy-Flag) von lpFlag = 0)
  for(time2 = 30000; (*lpFlag & 0x00) && time2; time2--);
  Delay(3);        // wozu ?????
  return time1;    // Returnwert 0=timeout, sonst OK
};


IEEE-488 Kommunikation
 

ieee.h          (Prototypen der dll-Fkt., Typedefs für Fkt.typen (zum 'Überladen' der dll-Funktionen)
win488.dll   (16-Bit DLL)
motors.cpp  (Motortyp TC_812GPIB)

im Headerfile werden Typedefs für Funktionshüllen erstellt, die später die Funktionen
aus win488.dll übernehmen sollen

Beispiel:

ieee.h

Prototyp:
int far pascal ieee488_transmit (char far *,unsigned,int far *);

Typedef:
typedef int (WINAPI *TTransmit)  (LPSTR,WORD,LPINT); // char far * -> LPSTR
                                                   // unsigned   -> WORD
                                                      // int far *  -> LPINT

motors.cpp

// Laden der 16-Bit Dll
hGPIBModule = LoadLibrary("win488.dll");

// Ueberladen von ieee488_transmit aus win488.dll nach gTransmit
(FARPROC)gTransmit = GetProcAddress(hGPIBModule,"IEEE488_TRANSMIT");
 


 DC-Controller-Karte C-832

- Motorcontroller fuer 2 Achsen basierend auf dem Controller-
  chip LM628 von National Semiconductor
- die Kommunikation zwischen dem C-832 und dem Computer erfolgt
  ueber den I/O Adress Bereich ueber 200 hex mittels Portbefehle
- um zu schreiben oder zu lesen sind folgende 2 Schritte not-
  wendig:
  - als erstes wird ein select byte in das interne Adressregister
    (siehe oben) geschrieben
  - als naechstest wird das Kommando oder die Daten in den ge-
    waehlten Kanal (entsprechende Achse) geschrieben
- z.B.: will man ein Kommando schreiben oder den Lesestatus
  fuer den Kanal 1 erfragen, so schreibt man eine 00 in das
  Register, welches als I/O Adresse festgelegt wurde
  ( Standard Einstellung auf dem Board mittels DIP-Schalter
   ist 210 hex )
- die Adresse 210hex ist fuer das Adressregister und 211hex ist
  fuer das Datenregister
- um den jeweiligen Controller fuer eine Achse auszuwaehlen,
  ist eine Auswahl im Adressregister zu machen
- Regadr 7 6 5 4 3 2 1 0

  Bit 4 bis 7 werden nicht benutzt !
  Aufschluesselung fuer Bit 0 bis 3 :
    x000 Command/Status register, Controller #1
    x001 Data register, Controller #1
    x010 Command/Status register, Controller #2
    x011 Data register, Controller #2
    x111 Read and Reset interrupt source

- desweiteren werden ueber diese Bits noch interne Funktionen
  aktiviert oder deaktiviert
  z.B. Interrupt Enable/Disable

Zentrale Funktionen in unserem Programm:

long TC_832::Drive628(BYTE cmd,WORD ctrl_word,long param)
 {
 // load static variables
 ::config = cConfig;    // Konfigurations Register
 ::drive  = nOnBoardId;
 ::baddr  = wBaseAddr;  // 210 hex, wird aus ini-File gelesen
 ::raddr  = (::config | (::drive<<1));

 return Drive628c(cmd,ctrl_word,param,::baddr,::raddr);
 };

typedef struct
 {
 BYTE   cmd;      // command
 int    report;     // Ctrl(0)/Report(1) command
 int    data;     // max. datawords
 int    length32;    // 16(0)/32(1) bit valus
 }  TCSet;
 

static TCSet CmdSet[] =
 {// LM628/629-commands exept RDSTAT
 {RESET, 0,0,0}, {PORT8, 0,0,0}, {PORT12,0,0,0}, {DFH,   0,0,0},
 {SIP,   0,0,0}, {LPEI,  0,1,0}, {LPES,  0,1,0}, {SBPA,  0,2,1},
 {SBPR,  0,2,1}, {MSKI,  0,1,0}, {RSTI,  0,1,0}, {LFIL,  0,4,0},
 {UDF,   0,0,0}, {LTRJ,  0,3,1}, {STT,   0,0,0}, {RDSTAT,1,1,0},
 {RDSIGS,1,1,0}, {RDIP,  1,1,1}, {RDDP,  1,1,1}, {RDRP,  1,1,1},
 {RDDV,  1,1,1}, {RDRV,  1,1,0}, {RDSUM, 1,1,0}  };


long Drive628c(BYTE cmd,WORD ctrl_word,long param,WORD base,WORD regaddr)
 {
  TCSet    *pCmdDesc;

   if(TC_832::bIdle)          // Ist Drive nicht in Benutzung ?
      {
       TC_832::bIdle = FALSE; // Ja! Dann Status in "benutzt"
      };
   TC_832::bIdle = TRUE;      // sonst, Status wird in "nicht benutzt" gesetzt
                                                       // Wozu macht er das eigentlich ??
                                  // an dieser Stelle ist TC_832::bIdle immer True!

   pCmdDesc   = CmdSet;       // Kommandosatz durchgehen
   if(cmd==RDSTAT)            // Liegt Kommando "RDSTAT"(Read Status) vor ?
      {
       outp(base,regaddr);    // Ja! Fordere Auslesen des Configuration Data Registers
       TC_832::bIdle = FALSE; // Setze Status in "benutzt"
        return((long)inp(base + 1)); // Lese die entsprechenden Daten aus dem Datenregister und
      }                              // gebe sie zurueck
     else
       while(cmd != pCmdDesc->cmd) pCmdDesc++; // Wenn nicht RDSTAT, dann gehe Kommandosatz durch

    LM628Ready(base);                // Test ob Busy-Bit gesetzt,(Test ist aber sinnlos,
                                         // wenn er nicht ausgewertet wird !!)

    outp(base + 1,cmd);              // Sende Kommando an Controller

    if(pCmdDesc->data)               // nur alle Data Kommandos
      {
      // read/write data
      if((cmd==LTRJ) | (cmd==LFIL))  // wenn "LTRJ"- oder "LFIL"-Kommando
        PutWord(ctrl_word,base,regaddr); // dann schreibe
      if(pCmdDesc->report)           // Liegt ein Report-Kommando vor?
        {
                                     // Ja! Report Kommando liegt vor
         if(pCmdDesc->length32)      // Hat es einen 32Bit Parameter ?
           {
            TC_832::bIdle = FALSE;        // Setze Status in "benutzt"
            return GetDWord(base,regaddr); // hole 32 Bit data
           }
          else                         // ansonsten
           {
            TC_832::bIdle = FALSE;        // Setze Status in "benutzt"
           return (long)GetWord(base,regaddr);// hole 16 Bit data
           }
        }
       else                               // wenn kein Report Kommando
         {                                // dann nur noch Control Kommando moeglich
          if(pCmdDesc->length32)          // 32Bit ?
             PutDWord(param,base,regaddr);    // hole 32 Bit parameter
           else                           // ansonsten
             PutWord((int)param,base,regaddr);// hole 16 Bit parameter
         }
      }
 TC_832::bIdle = FALSE;                   // Setze Status in "benutzt"
 return 0;
 };
 

int GetWord(WORD base,WORD regaddr)
 {
 union {
    int  integer;
    BYTE byte[2];
    } temp;

 LM628Ready(base);
 outp(base,regaddr + 1);
 temp.byte[1] = inp(base + 1);
 temp.byte[0] = inp(base + 1);
 return temp.integer;
 };

long GetDWord(WORD base,WORD regaddr)
 {
 union {
    long  lval;
    BYTE  byte[4];
    } temp;

 LM628Ready(base);
 outp(base,regaddr + 1);
 temp.byte[3] = inp(base + 1);
 temp.byte[2] = inp(base + 1);
 LM628Ready(base);
 outp(base,regaddr+1);
 temp.byte[1] = inp(base + 1);
 temp.byte[0] = inp(base + 1);
 return temp.lval;
 };

void PutWord(int data,WORD base,WORD regaddr)
 {
 union {
    int  integer;
    BYTE byte[2];
    } temp;

 LM628Ready(base);
 outp(base,regaddr + 1);
 Delay(2);
 temp.integer = data;
 outp(base + 1,temp.byte[1]);
 outp(base + 1,temp.byte[0]);
 data = 1;
 };

void PutDWord(long data,WORD base,WORD regaddr)
 {
 union {
    long  lval;
    BYTE byte[4];
    } temp;

 temp.lval = data;
 LM628Ready(base);
 outp(base,regaddr+1);
 outp(base + 1,temp.byte[3]);
 outp(base + 1,temp.byte[2]);
 LM628Ready(base);
 outp(base,regaddr+1);
 outp(base + 1,temp.byte[1]);
 outp(base + 1,temp.byte[0]);
 data = 1;
 };

BOOL LM628Ready(WORD base)
 {
 int   tt;
 int   time_out=1000;

 // command register vom aktiven drive
 outp(base,::config | (::drive<<1));
 do {
     tt = inp(base+1);
     if(!(time_out--)) return FALSE; // wenn timeout, dann false ->"nicht bereit"
    }
 while(tt & 0x01);   // teste ob bit 0 von "tt" gesetzt (BUSY-Bit)
 if(tt & 0x02)       // teste ob bit 1 von "tt" gesetzt, welche Bedeutung ?
   {
    time_out++;      // warum nochmal time_out um eins erhoehen ??
   }
 return TRUE;  // BUSY-Bit geloescht
 };
 

zurück zur Übersicht