Source file: /~heha/basteln/PC/Programmiergeräte/PEPS-III/peps4win32.zip/src/sendiii.c

/*--------------------------------------------------------------
 Datenübertragung über Centronics-Port an PEPS-III,
 alternativ via Usb2Prn und Adapterschaltung oder h#s USB2LPT

BUGBUG	Keine geprüfte Funktion mit mehreren kaskadierten PEPS-Geräten
*120621	Keine Ausgabe auf Dummy-Port 80h (für Delay),
	sondern mehrfach auf die gleiche Portadresse
*120719	Optimierung der Schreibroutine SendData()
	- Cache für gleiche Bytes
	- Optimiertes Laden für partielle Bitmuster
	- Laden von gleichen Bytes parallel
*120729	Optimierung der Leseroutine ReadData()
	- für Usb2Prn (3-Bit-Zusammenfassung, theoretisch 0,33 kByte/s)
	- für USB2LPT (8-Bit-Zusammenfassung, theoretisch 8 kByte/s bei High-Speed)
--------------------------------------------------------------*/

/*---------------------- Includes ------------------------------*/
#include "peps.h"

#ifndef PEPSIII
#error Modul SENDIII only for PEPSIII compilation
#endif

/*┌───────────┐*
 *│ Variablen │*
 *└───────────┘*/
/* GLOBAL */
IODEVICE iodevice = PARPORT;
WORD PPort;		// Portadresse oder 0-basierter Geräte-Index (Usb2Prn, USB2LPT)
BYTE Cen_Clk;		// Takt-Bitmaske für angesprochene PEPSe
	// (typischerweise, aber nicht notwendigerweise zusammenhängend)
	// Nur Bits 4..7; 0 = Auto-Detect
BYTE outcycles;		// Anzahl der OUT-Befehle (>1 = Warteschleifen, ≈ 1 µs)
			// bei Port-Ausgabe nötig ist, 0 = Auto-Detect
bool debug;		// IN- und OUT-Bytes auf <stderr> ausgeben

/* LOKAL */

static BYTE KnownStates;	// Bit 0 = LastByte, Bit 4..7 = ShiftRegs[]
static BYTE LastByte;		// I/O-Operationen minimieren
static BYTE ShiftRegs[4];	// Die vier Schieberegister (74HC299)
static int Units;		// Anzahl verschachtelter PEPSe = BitCount(Cen_Clk)
static long PepsSize;		// RAM-Größe, in Byte, des kleinsten PEPS
static long CurrentAddr;	// 19-bit-Adresszählerwert aller PEPSe

/*┌──────┐*
 *│ Code │*
 *└──────┘*/
#ifdef WIN32

#include "DIRECTNT.H"

void setioperm() {
 TDirectNTInfo I;
 HANDLE hIO;
 DWORD br;
 static const TCHAR SvcName[]=T("\\\\.\\Dev_DirectNT");
 
 if ((long)GetVersion()<0) return;	// Win9x/Me: Nichts zu tun!
 if (!InstallDirectNT(false)) DoExit(GetLastError()==ERROR_ACCESS_DENIED ? 46 : 47);
 hIO = CreateFile(SvcName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
 if (hIO == INVALID_HANDLE_VALUE) DoExit(48,SvcName+4);
 
 I.OpCode = OP_GiveIO;
 I.Par1 = PPort;
 I.Par2 = PPort+2;
 I.Par3 = 0;
 br = 0;
 
 if (!DeviceIoControl(hIO,IOCTL_DIRECTNT_CONTROL,&I,sizeof(I),&br,sizeof(br),&br,NULL)) DoExit(48,SvcName+4);
 CloseHandle(hIO);
 InstallDirectNT(true);
}

#else
#include <sys/io.h>	// iopl() und ioperm()
#include <sys/ioctl.h>	// ioctl()

void setioperm() {
#ifndef ___TEST___
 uid_t uid = getuid ();
 if (setuid(0)) DoExit(46);
// get permissions for IO (2 Bytes at PPort)
 if ((PPort>=0x400 && iopl(3))	// welcher Level (3?) erforderlich ist, ist unbekannt
 || ioperm(PPort,2,1)) DoExit(47,PPort,errno);	// aufs Steuerport (+2) ist hier kein Zugriff nötig
 if (setuid(uid)) rprintf(48,uid);
#endif
}

static inline void _outp(WORD port, BYTE value) {
  __asm__ volatile ("outb %0,%1" : : "a"(value), "d"(port));
}

static inline BYTE _inp(WORD port) {
  BYTE value;
  __asm__ volatile ("inb %1,%0" : "=a" (value) : "d"(port));
  return value;
}

#endif


/*-------------------------------------------------------------------*/

#ifdef WIN32
#include <conio.h>
#include <setupapi.h>
#include <winioctl.h>
#include "usbprint.h"		// IOCTL_USBPRINT_GET_LPT_STATUS
#include "usb2lpt.h"		// IOCTL_VLPT_OutIn

HANDLE hIO;
BYTE (_stdcall*Inp32)(WORD);
void (_stdcall*Out32)(WORD,BYTE);
OVERLAPPED o;

#ifdef _M_AMD64
#define _outp __outbyte		// Intrinsische AMD64-Compiler-Funktionen
#define _inp  __inbyte		// (werden anscheinend inline ausgeführt)
#endif

#else
#include <asm/ioctl.h>
#include <linux/ppdev.h>
#include <linux/lp.h>
#include <errno.h>
int hIO;
#endif

/*-------------------------------------------------------------------------*\
	Definitionen und Makros
\*-------------------------------------------------------------------------*/
// Bitzuweisung des 8-Bit-Datenports
#define	CEN_M0		(1<<0)
#define	CEN_M1		(1<<1)
#define	CEN_M2  	(1<<2)
#define	CEN_DATA	(1<<3)
#define	CEN_CLK0	(1<<4)
// PEPS-Steuermodes
#define CNT_MODE	0		// Adresszähler aufwärts zählen
#define SND_MODE	CEN_M0		// Ausgänge der Zähler übernehmen Zählerstand
#define LOD_MODE	CEN_M1		// Schieberegisterinhalt in RAM schreiben
#define SIM_MODE	(CEN_M0|CEN_M1)		// Simulations-Modus
#define RD_MODE		(CEN_M2|SND_MODE)	// RAM-Byte ins Schieberegister laden
#define RES_MODE	SIM_MODE	// Zählerstand auf 0

// Anzahl mehrfach ausgeführter OUT-Befehle, um ca. 1 µs zu warten
#define MAX_PORTDELAY 10	// man sollte kurze Leitungen bevorzugen!

#define OutB(y) OutByte((BYTE)(y))			// Out Byte
#define OutC(y) OutB(y), OutB((y)|Cen_Clk), OutB(y)	// Out Clocked


BYTE OutBuf[4096];
int OutFill;
static struct{
 BYTE Bits;
 BYTE InFF;	// 0..2 im Flipflop (Usb2Prn-Adapter)
}Collected;

#define USBPRN_MINFLUSHSIZE 64
// Komisch, hier taucht der Fehler gar nicht auf! NUR beim InpOut32-Projekt!?
// Weder unter Linux noch unter Windows.
// A workaround for the buggy "WCH CH340S" (VID_1A86&PID_7584) chip inside "LogiLink" device:
// If write() is followed by ioctl(), the write() data will not output at all!!
// It seems that ioctl() must be in the next USB frame, otherwise, data
// of a non-full packet are lost. It is a silicon (firmware) bug.
// This is important for printing! Because status-checking may eat up some printing data.
// Instead of waiting a millisecond (USB frame) somehow, I simply send a _full_ packet.
// I couldn't measure any performance impact, for sending 64 bytes instead of 1.
// Of-course, this cannot be done for printing, but it's fully okay for the '574 latch.

// The Prolific PL-2305H (VID_06A9&PID_1991) doesn't have this bug.

static void OutFlush() {
 int fill=OutFill;
 if (!fill) return;
 OutFill=0;	// auch im Fehlerfall Puffer leeren
 switch (iodevice) {
  case USB2PRN:
#ifdef USBPRN_MINFLUSHSIZE
  if (fill<USBPRN_MINFLUSHSIZE) {
   memset(OutBuf+fill,OutBuf[fill-1],USBPRN_MINFLUSHSIZE-fill);
   fill=USBPRN_MINFLUSHSIZE;	// Auf volle USB-Paketlänge aufblasen
  }
#endif
  {
#ifdef WIN32
   DWORD bw,ec;
   if (!WriteFile(hIO,OutBuf,fill,&bw,&o)
   && ((ec=GetLastError())!=ERROR_IO_PENDING
   || WaitForSingleObject(o.hEvent,500))) {	// Timeout wenn die Verbindung STB->ACK fehlt
    CancelIo(hIO);		// !! Nicht unter Win95 verfügbar !!
    DoExit(50,ec);
   }
   SetLastError(0);
#else
   BYTE *buf = OutBuf;
   while (fill) {
    int r = write(hIO,buf,fill);
    if (r<=0) DoExit(50,errno);	// dauert ohne O_NONBLOCK elend lange!
    buf+=r;
    fill-=r;
   }
   fsync(hIO);
#endif
  }break;
  case USB2LPT: {
   BYTE ins[8];
   int i;
#ifdef WIN32
   DWORD bw,ec;
   if (DeviceIoControl(hIO,IOCTL_VLPT_OutIn,OutBuf,fill,ins,Collected.InFF,&bw,&o)
   && ((ec=GetLastError())!=ERROR_IO_PENDING
   || WaitForSingleObject(o.hEvent,500))) {	// Timeout wenn die Verbindung STB->ACK fehlt
    CancelIo(hIO);
    DoExit(50,ec);
   }
   SetLastError(0);
#else
   struct {
    const BYTE *OutBuf;
    int OutLen;
    BYTE *InBuf;
    int InLen;
   }vlpt_outin={OutBuf,fill,ins,Collected.InFF};	// Der Linux-Treiber muss erst noch geschrieben werden!
   if (ioctl(hIO,12345,&vlpt_outin)<0) DoExit(50,errno);
#endif
   for (i=0; i<Collected.InFF; i++) {
    Collected.Bits<<=1;
    Collected.Bits|=ins[i]>>7;		// BUSY-Bit eines der in OutBuf enthaltenen IN-Befehle
   }
   Collected.InFF=0;
  }break;
  default:;				// kein Ausgabepuffer aktiv
 }
}

// Byte in den Puffer schreiben und ggf. Flush auslösen
static void OutPut(BYTE b) {
 OutBuf[OutFill++]=b;
 if (OutFill==elemof(OutBuf)) OutFlush();
}

static void OutByte(BYTE wert) {
 if (!(KnownStates&1) || wert!=LastByte) {
  if (debug) fprintf(stderr," %02X", wert);
  switch (iodevice) {
   case PARPORT: {
    BYTE i = outcycles;
    do _outp(PPort,wert); while (--i);
	// Anfängliches Rücklesen muss gleiches Byte liefern, sonst ist hier kein Parallelport
    if (!(KnownStates&1) && _inp(PPort)!=wert) DoExit(30,PPort);
   }break;
#ifdef WIN32
   case INPOUT32: {
    BYTE i = outcycles;
    do Out32(PPort,wert); while (--i);
    if (!(KnownStates&1) && Inp32(PPort)!=wert) DoExit(30,PPort);
   }break;
#else
   case INPOUT32: {
    BYTE i = outcycles;
    do if (ioctl(hIO,PPWDATA,&wert)<0) DoExit(50,errno); while(--i);
    if (!(KnownStates&1)) {
     BYTE r;
     ioctl(hIO,PPRDATA,&r);
     if (r!=wert) DoExit(30,PPort);	// könnte passieren im ECP-Modus, bei defektem Port usw.
    }
   }break;
#endif
   case USB2PRN: {
    BYTE i = outcycles;
    do OutPut(wert); while (--i);
   }break;
   case USB2LPT: {
    BYTE i = outcycles;
    do {
     OutPut(0x00);		// Schreiben Datenport
     OutPut(wert);		// Datenbyte
    }while (--i);
   }break;
  }
  LastByte=wert;
  KnownStates|=1;
 }
}

static BYTE InByte() {
 BYTE r=0xFF;
 switch (iodevice) {
  case PARPORT: r=_inp((WORD)(PPort+1)); break;
#ifdef WIN32
  case INPOUT32: r=Inp32((WORD)(PPort+1)); break;
  case USB2PRN: {
   DWORD br,ec;
   OutFlush();
   if (!DeviceIoControl(hIO,IOCTL_USBPRINT_GET_LPT_STATUS,NULL,0,&r,1,&br,&o)
   && ((ec=GetLastError())!=ERROR_IO_PENDING
   || WaitForSingleObject(o.hEvent,500))) {	// Timeout sollte hier nicht passieren
    CancelIo(hIO);
    DoExit(50,ec);	// Nur Bits 3,4,5 interessant
   }
   SetLastError(0);
  }break;
#else
  case INPOUT32: {
   if (ioctl(hIO,PPRSTATUS,&r)<0) DoExit(50,errno);	// Bit 7 interessant
  }break;
  case USB2PRN: {
   int i;	// LPGETSTATUS erwartet wirklich int*
   OutFlush();
   if (ioctl(hIO,LPGETSTATUS,&i)<0) DoExit(50,errno);
   r=i;		// Dauert ziemlich lange.
  }break;	// Bits 3,4,5 interessant
#endif
  case USB2LPT: OutPut(0x11); Collected.InFF++; break;
 }
 if (debug) fprintf(stderr," %02X\n",r);	// IN-Befehle schließen eine stderr-Zeile ab
 return r;
}

// Liest das Datenbit des PEPS ein (BUSY, Bit 7) nach Bit 0
// Es entspricht dem Bit am Schieberegister-Ausgang (74HC299)
// Bei USB2PRN und USB2LPT ist dies eine Dummy-Routine, die das Bit-Lesen nur anschiebt
static void ReadBit() {
 Collected.Bits<<=1;
 switch (iodevice) {
  case USB2PRN: {	// Hier kommt zumeist kein Bit; stattdessen wird das Bit in die Flipflop-Kette geschoben
   if (Cen_Clk&0x80) {	// Bei vier PEPSen muss jedes Bit einzeln gelesen werden! (Lahm!)
    Collected.Bits|=(InByte()>>3)&1;
   }else if (Collected.InFF==2) {
    Collected.Bits|=(InByte()>>3)&7;	// 3 Bits (immerhin)
    Collected.InFF=0;
   }else{
    OutB(LastByte|0x80);	// Übernahme-Flanke generieren und Bit in Flipflop-Kette retten lassen
    Collected.InFF++;
   }
  }break;
  case USB2LPT: InByte(); break;
  default: Collected.Bits|=InByte()>>7;	// Bit 7 ist invertierend, das PEPS invertiert auch: stimmt so!
 }
}

// Liefert die 8 inzwischen aufgesammelten Bits
// USB2PRN: Sonderfall wenn !(Cen_Clk&0x80)! InFF sollte hier == 2 sein!
static BYTE ReadByte() {
 Collected.InFF=0;
 switch (iodevice) {
  case USB2PRN: if (!(Cen_Clk&0x80)) {
   Collected.Bits|=(InByte()>>4)&3;	// letzten 2 Bits eines Bytes einsammeln
  }return ~Collected.Bits;		// Nur hier: Bits invertieren
  case USB2LPT: OutFlush(); break;	// generiert Collected.Bits
  default:;
 }
 return Collected.Bits;			// die doppelt invertierten Bits nicht invertieren
}

#ifdef WIN32

#ifdef _M_AMD64
# define INPOUT32DLL T("inpoutx64.dll")
#else
# define INPOUT32DLL T("inpout32.dll")
#endif

static HANDLE OpenUsbPrn(int n, DWORD flags) {
 static const GUID GUID_DEVINTERFACE_USBPRINT = {
  0x28d78fad,0x5a12,0x11D1,0xae,0x5b,0x00,0x00,0xf8,0x03,0xa8,0xc2};
 HANDLE ret = 0;
 HDEVINFO devs = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USBPRINT,0,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
 if (devs != INVALID_HANDLE_VALUE) {
  SP_DEVICE_INTERFACE_DATA devinterface;
  devinterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
  if (SetupDiEnumDeviceInterfaces(devs, NULL, &GUID_DEVINTERFACE_USBPRINT, n, &devinterface)) {
   SP_DEVINFO_DATA devinfo;
   struct{
    SP_DEVICE_INTERFACE_DETAIL_DATA id;
    TCHAR space[MAX_PATH];
   }id;
   devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
   id.id.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
   if (SetupDiGetDeviceInterfaceDetail(devs, &devinterface, &id.id, sizeof(id), NULL, &devinfo)) {
    ret = CreateFile(id.id.DevicePath,GENERIC_WRITE,FILE_SHARE_READ,
      NULL,OPEN_EXISTING,flags,NULL);
    if (ret == INVALID_HANDLE_VALUE) ret = 0;
   }
  }
  SetupDiDestroyDeviceInfoList(devs);
 }
 return ret;
}
#endif

void iodeviceInit() {
 if (hIO) return;
 switch (iodevice) {
  case PARPORT: setioperm(); break;
#ifdef WIN32
  case INPOUT32: {
   hIO = LoadLibrary(INPOUT32DLL);
   if (!hIO) DoExit(49,INPOUT32DLL);	// DLL nicht gefunden; nicht neben EXE oder im Pfad liegend
   Inp32 = (BYTE(_stdcall*)(WORD))GetProcAddress(hIO,"Inp32");
   Out32 = (void(_stdcall*)(WORD,BYTE))GetProcAddress(hIO,"Out32");
   if (!Inp32 || !Out32) DoExit(49,INPOUT32DLL);	// fehlerhafte DLL (keine Einsprungpunkte)
  }break;
  case USB2PRN: {	// PROBLEM: Funktioniert unsicher, besonders die Erkennung
   hIO = OpenUsbPrn(PPort,FILE_FLAG_OVERLAPPED);
   if (!hIO) DoExit(51,PPort);
   o.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
  }break;
  case USB2LPT: {
   TCHAR name[16];
   _sntprintf(name,elemof(name),T("\\\\.\\LPT%u"),PPort+1);
   hIO = CreateFile(name,0,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
   if (hIO==INVALID_HANDLE_VALUE) hIO=0;
   if (!hIO) DoExit(52,PPort);
   o.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
  }break;
#else
  case INPOUT32: {	// Datenrate: R: 18 kByte/s, W: 24 kByte/s
   char buf[16];
   int mode=0;
   snprintf(buf,elemof(buf),"/dev/parport%d",PPort);
   hIO = open(buf,O_RDWR|O_NONBLOCK);
   if (hIO<0) DoExit(49,buf,errno);
   if (ioctl(hIO,PPCLAIM)<0) DoExit(49,buf,errno);
   ioctl(hIO, PPDATADIR, &mode);	// Ausgabe
  }break;
  case USB2PRN: {	// PROBLEM: Funktioniert unsicher / extrem langsam
   char buf[16];
   snprintf(buf,elemof(buf),"/dev/usb/lp%d",PPort);
   hIO = open(buf,O_WRONLY/*|O_NONBLOCK*/);
   if (hIO<0) DoExit(51,buf,errno);
   ioctl(hIO,LPABORT,1);
   ioctl(hIO,LPRESET);
  }break;
  case USB2LPT: DoExit(52);
#endif
 }
 Collected.InFF=0;
 if (!hIO) hIO = (HANDLE)1;
}

void iodeviceDone() {
 if (hIO<=0) return;
 OutB(LastByte&0x0F);
 OutB(SIM_MODE);		// 3: PEPS im Emulationsmodus hinterlassen
 OutFlush();
 switch (iodevice) {
#ifdef WIN32
  case INPOUT32: FreeLibrary(hIO); break;
  case USB2PRN:
  case USB2LPT: {
   CloseHandle(o.hEvent);
   CloseHandle(hIO);
  }break;
#else
  case INPOUT32: {
   ioctl(hIO,PPRELEASE);
   close(hIO);
  }break;
  case USB2PRN: close(hIO); break;
#endif
  default:;
 }
 hIO=0;
}

//═════════════════════════════════════════════════════════════════════

/*---------------------------------------------------------------------
 ResetAddress setzt die Zählerbausteine des PEPS-III auf 0.
 nach /CLR muß noch ein Clock erfolgen, um die Outputregister zu laden
 --------------------------------------------------------------------*/
static void ResetAddress() {
 OutC(RES_MODE);   /* Zählerstand auf 0 */
 OutC(SND_MODE);   /* Ausgänge der Zähler übernehmen Zählerstand */
 CurrentAddr = 0;
}

/*---------------------------------------------------------------------
 Es können gleichzeitig bis zu 4 PEPS-III Geräte an den PC angeschlossen
 werden. Dazu gibt es auf den Leitungen D4-D7 4 unterschiedliche
 Clock-Impulse.
 Wenn mehrere Units mit -b Option definiert sind, werden mehrere Clock-
 Impulse gleichzeitig bei folgenden Befehlen angewendet.

 CountTo()
 ResetAddress();
 HoldReset()
 Simulate();

 Damit wirken die Befehle auf alle PEPSe. Cen_Clk wird zu Beginn einmal
 gesetzt je nach Datenbreite (Units).

 Die Funktionen SendData(), ReadData() jedoch senden/lesen immer nur von
 jeweils einem PEPS (die adressmäßig verschachtelt sind).
 Ausnahme: SendData kann bei gleichen zu schreibenden Bytes in mehrere
 PEPSe gleichzeitig schreiben.
 --------------------------------------------------------------------*/

// Aus Taktbitmaske (nur 1 Bit erlaubt) den Index für ShiftRegs[] ermitteln
static int Clk2Index(BYTE Clk) {
 switch (Clk) {
  case CEN_CLK0: return 0;
  case CEN_CLK0<<1: return 1;
  case CEN_CLK0<<2: return 2;
  case CEN_CLK0<<3: return 3;
 }
 return -1;	// unerlaubte Bitmaske!
}
 
// Ein Datenbit ins Schieberegister schieben
// <bit> != 0 -> ein Eins-Bit, sonst ein Null-Bit
// Hinterlässt das Ausgabeport mit gesetzten Clk-Bits
static void ShiftBit(BYTE Clk, BYTE bit) {
 if (bit) {
  OutB(SND_MODE);		// Dabei muss jedes Bit invers 
  OutB(SND_MODE|Clk);		// gesendet werden.
 }else{
  OutB(SND_MODE|CEN_DATA);
  OutB(SND_MODE|CEN_DATA|Clk);
 }
}

// Ein oder mehrere Schieberegister 74HC299 (parallel) mit 8 Bits füllen
// Ist das Schieberegister bereits mit dem passenden Wert gesetzt, nichts tun
// Ist es mit einigen passenden Bits gesetzt, weniger Schiebetakte als 8 generieren
// Denn die Hardware-E/A ist der Flaschenhals heutiger (2012) Computer,
// nicht wie damals (1993) die Rechenleistung und der C-Compiler.
// Beim Verlassen der Funktion ist die Taktleitung zwecks Rücklesen noch aktiv.
static void SetShiftRegister(BYTE Clk, BYTE b) {
 int i;
 BYTE m;
 char needshift = ~KnownStates&Clk ? 8 : 0;	// Anzahl einzuschiebender Bits
 for (i=0, m=CEN_CLK0; m; i++, m<<=1) {		// Vier Indizes durchgehen
  if (Clk & m) {
// Schon mal im voraus die bis zu 8 Schiebeoperationen des 74HC299 durchsimulieren
   if (needshift!=8) {
    BYTE alt=ShiftRegs[i];
    char j=0;
    BYTE m=0xFF;
    while (alt != (b&m)) {
     j++;	// j wird auf diese Weise maximal 8
     m<<=1;	// wird 0xFE, 0xFC usw.
     alt<<=1;	// gewissermaßen don't-care-Bits einschieben
    }
// Hier haben wir in <j> die Anzahl notwendiger Schiebeoperationen für dieses Byte
    if (!needshift) needshift=j;
    else if (needshift!=j) needshift=8;	// vereinfachend alle Bits erzwingen
   }
   ShiftRegs[i]=b;
  }
 }
 if (needshift--) {
  for (m = 1<<needshift; m; m>>=1) {	// Byte bitweise an PEPS(e)
   ShiftBit(Clk,(BYTE)(b&m));
  }
  KnownStates|=Clk;		// Inhalte nun bekannt
 }
}

/*──────────────────────────────────────────────────────────────┐
│TestByte()							│
├───────────────────────────────────────────────────────────────┤
│Testen, ob ein PEPS-Gerät angeschlossen ist.			│
│Es wird ein Byte in das Schieberegister eingeschoben und	│
│zurückgelesen.							│
│Beim Zurücklesen wird das wahrscheinlich nächste Prüfbyte	│
│eingeschrieben, um dann Takte zu sparen (SPI-Prinzip).		│
├───────────────────────────────────────────────────────────────┤
│ Parameter:							│
│  Clk: Takt-Maske (nur 1 Bit erlaubt)				│
│  b: Testbyte							│
│  next: nachfolgendes Byte (zur Abkürzung später)		│
│ Return:							│
│  Rückgelesenes Testbyte					│
├───────────────────────────────────────────────────────────────┤
│ Created: 25.11.92  by: AM					│
└──────────────────────────────────────────────────────────────*/
static BYTE TestByte(BYTE Clk, BYTE b, BYTE next) {
 BYTE m;
 SetShiftRegister(Clk,b);
// Nach dem achten Bit unbedingt die DOUT-Leitung lesen,
// solange CLOCK noch aktiv ist, das Bit kann sonst nicht mehr
// rückgelesen werden.
 for (m = 0x80; m; m>>=1) {
  ReadBit();			// MSB rücklesen lassen
  ShiftBit(Clk,(BYTE)(next&m));
 }
 ShiftRegs[Clk2Index(Clk)]=next;	// bekannter Schieberegister-Stand
 return ReadByte();		// Leseergebnis gebündelt abholen
}

// Liest von bis zu 4 PEPSen den RAM-Inhalt von 19-bit-Adresse <addr>
static DWORD ReadAddress(long addr) {
 DWORD ret=0;
 ReadData((BYTE*)&ret,Units,addr*Units,false);
 return ret;
}

// Schreibt auf bis zu 4 PEPSe auf 19-bit-Adresse <addr>
static void SendAddress(long addr, DWORD data) {
 SendData((BYTE*)&data,Units,addr*Units);
}

// Prüft ein RAM-Byte auf Schreiben und Lesen
static void CheckPepsRam(long addr) {
 DWORD v,c,m;
 v = ReadAddress(addr);
 SendAddress(addr,~v);	// Komplement schreiben
 m = (Units==4 ? 0 : 1UL<<(Units<<3))-1;
 c = ReadAddress(addr)^m;	// Komplement lesen
 SendAddress(addr,v);
 if (v!=c) DoExit(32,addr,v,c);
}

// BUG: Findet den GRÖSSTEN Peps!
static long DetectPepsRamSize() {
 DWORD v128k = ReadAddress(0x10000);
 DWORD v512k = ReadAddress(0x40000);
 if (v128k == v512k) {
  SendAddress(0x10000,~v128k);	// Bits drehen
  v512k = ReadAddress(0x40000);
  SendAddress(0x10000,v128k);	// zurückstellen
 }
 if (v128k != v512k) return 0x80000;	// 512K
 return 0x20000;			// 128K
}

/*──────────────────────────────────────────────────────────────┐
│TestByteOk()							│
├───────────────────────────────────────────────────────────────┤
│Diese Funktion ruft TestByte() mit diversen Testmustern auf	│
├───────────────────────────────────────────────────────────────┤
│ Parameter: Clk = Takt-Maske (nur 1 Bit erlaubt)		│
│ Zurück: 1 falls ok, 0 bei Fehler				│
├───────────────────────────────────────────────────────────────┤
│ Created: 15.10.93  by: AM					│
└──────────────────────────────────────────────────────────────*/
static bool TestByteOk(BYTE Clk) {
 BYTE c=1;
 do{ /* 51 verschiedene Testbytes an Peps senden. */
  if (TestByte(Clk,c,(BYTE)(c+5))!=c) return false;
 }while(c+=5);
 return true;
}

/*──────────────────────────────────────────────────────────────┐
│IteratePortDelay()						│
├───────────────────────────────────────────────────────────────┤
│Wird aufgerufen bei Programmstart.				│
│Falls mittels -b oder -u die Taktbitmaske <Cen_Clk> gesetzt	│
│wurde, werden genau diese auf Präsenz geprüft.			│
│Ansonsten Auto-Detect der angeschlossenen PEPSe.		│
│Diese Funktion bestimmt die notwendige Portverzögerung		│
│und speichert sie in <outcycles>				│
├───────────────────────────────────────────────────────────────┤
│ Created: 15.10.93  by: AM					│
└──────────────────────────────────────────────────────────────*/
static int IteratePortDelay() {
 BYTE Clk, okay=0;
 int units=0;
// Ohne PEPS-Masken-Vorgabe alle PEPSe suchen
 if (!Cen_Clk) {
  BYTE savecycles = outcycles;
  Cen_Clk^=0xF0;		// temporär: Funktion von 4 PEPS an Usb2Prn sicherstellen
  if (!outcycles) outcycles = MAX_PORTDELAY+1;
  for (Clk=CEN_CLK0;Clk;Clk<<=1) {
   if (TestByte(Clk,0x5A,0x5A)==0x5A			// 2 Versuche
   ||  TestByte(Clk,0x5A,0x5A)==0x5A) Cen_Clk^=Clk;	// ein PEPS gefunden!
  }
  Cen_Clk^=0xF0;		// tatsächliche Bitmaske
  outcycles = savecycles;
 }
// Alle PEPSe werden getrennt geprüft.
 for (Clk=CEN_CLK0;Clk;Clk<<=1) if (Clk&Cen_Clk) {
  units++;
  TestByte(Clk,Clk,0);				// Dummy-Zugriff
// Portdelay wurde als Parameter {-z} angegeben oder vom anderen PEPS gefunden
  if (outcycles) {
   if (TestByteOk(Clk)) okay|=Clk;
  }else{
// Wenn es mit outcycles=1 geht, wirds wohl so sein!
// Da sollte man die Datenübertragung nicht unnötig ausbremsen
   outcycles = 1;
   if (TestByteOk(Clk)) okay|=Clk;
   else{
// Wenn es ganz langsam nicht geht, wirds wohl nie gehen!
// In diesem Fall wird ohne -c0 das Programm später abgebrochen
    outcycles = MAX_PORTDELAY+1;
    if (TestByte(Clk,0xA5,0x55)!=0xA5) break;	// Fehler festgestellt
// Ansonsten ein funktionierendes Delay finden.
// Zu bevorzugen ist aber ein gutes, reflexionsarmes und kurzes Parallelportkabel!
    for (outcycles=2;outcycles<=MAX_PORTDELAY;outcycles++) {
     if (TestByte(Clk,0x55,0x55)==0x55 && TestByteOk(Clk)) {
      okay|=Clk;
      break;
     }
    }
   }
  } 
 }
 if (!units || okay!=Cen_Clk) DoExit(iodevice>=IOLOGICAL ? 2 : 3, PPort);
 return units;
}

extern void InitPeps3(CHECK_FLAGS check) {
 rprintf(0);
 KnownStates = 0;
 Units = IteratePortDelay();	// sucht alle Units wenn nicht vorgegeben (Cen_Clk = 0)
				// zur Adressumrechnung linear (21 bit) → PEPSe (19 bit)
 ResetAddress();
 if (check&CHECK_RAM) CheckPepsRam(0);
 PepsSize = check&CHECK_SIZE ? DetectPepsRamSize() : 0x80000;
 rprintf(1,outcycles-1,Units,PepsSize/1024);
 newline();
}

/*---------------------------------------------------------------------
 HoldReset und Simulate werden von DebugModus -o1 und nach dem Laden
 aufgerufen
 --------------------------------------------------------------------*/
extern void HoldReset() {
 OutC(CNT_MODE);
 OutFlush();
}

extern void Simulate() {
 OutC(SIM_MODE/*|Cen_Clk*/); /* AM 20.7.94 */
 OutFlush();
}

/*---------------------------------------------------------------------
 Zählt die 19-bit Zählerkaskade aller PEPSe bis zu der angegebenen Adresse.
 Falls die Zieladresse niedriger als die momentane Adresse liegt, werden
 die Zähler auf 0 gesetzt und von dort bis zur Zieladresse gezählt.
 Man könnte auch „oben herum“ zählen, aber das würde länger dauern …
--------------------------------------------------------------------*/
static void CountTo(long addr) {
 if (addr>=0 && addr != CurrentAddr ) {	/* current - for what PEPS? */
  if (CurrentAddr > addr) ResetAddress();
  while (CurrentAddr < addr) {
   OutC(CNT_MODE);
   CurrentAddr++;
  }
  OutC(SND_MODE);    /* Register Clock Pin 13 HCT299 latcht den Zählerstand */
 }
}

// Adresszähler der PEPSe und CurrentAddr heraufzählen.
// Die Adresse ist noch nicht an den Ausgängen der 74HC590-Kette
// wirksam, sondern eine Nummer kleiner!
// Dazu bedarf es irgendeines weiteren Taktes.
// Alle PEPSe führen stets den gleichen Adresszählerstand!
static void CountUp() {
 OutC(CNT_MODE);
 CurrentAddr++;
}

/*──────────────────────────────────────────────────────────────┐
│ SendData()							│
├───────────────────────────────────────────────────────────────┤
│ Sendet eine Anzahl von Bytes ab Adress an eine oder		│
│ mehrere Peps Geräte. Falls mehrere Units definiert sind,	│
│ werden die Bytes zyklisch auf die Geräte verteilt.		│
├───────────────────────────────────────────────────────────────┤
│ Parameter:							│
│  data:	Zeiger auf Anfang des Datenblocks		│
│  Size:	Größe des Datenblocks in Bytes			│
│  addr:	Lineare Daten-Adresse über die PEPSe (21 bit)	│
│ Return:	-						│
├───────────────────────────────────────────────────────────────┤
│ 921117	AM						│
└──────────────────────────────────────────────────────────────*/
void SendData(const BYTE *data, long Size, long addr) {
 long Pos;
 ldiv_t d = ldiv(addr,Units);

 CountTo(d.quot);

 if (Units>1) {			// langsamere Routine
  for (Pos=-d.rem; Pos<Size; ) {
   BYTE clka = 0;		// akkumuliertes Takt-Byte
   BYTE byta;			// akkumuliertes Daten-Byte
   BYTE Clk_Mask;		// aktuell betrachtetes Takt-Bit
   for (Clk_Mask = CEN_CLK0; Clk_Mask; Clk_Mask<<=1) {
    BYTE Clk = Cen_Clk & Clk_Mask;
    if (Clk) {			// Bytes zyklisch an PEPSe verteilen.
     if ((unsigned long)Pos<(unsigned long)Size) {	// Cast erschlägt Pos<0
      BYTE b=*data++;		// Byte holen
      if (clka && b!=byta) {	// gleich dem vorher gelesenen Byte
       SetShiftRegister(clka,byta);	// nein, Flush (mittendrin)
       clka=0;
      }
      clka|=Clk;		// Takt-Bits aufsammeln
      byta=b;			// für nächsten Vergleich
     }
     Pos++;
    }				// hier ist clka != 0
    SetShiftRegister(clka,byta);	// Flush (am Ende)
   }

   OutB(SND_MODE);		// CLOCK inaktivieren
   OutC(LOD_MODE);		// Schieberegisterinhalte in RAMs schreiben
   
   if (Pos<Size) CountUp();	// Nicht am Ende, um Byte-Verify zu erleichtern
// Besser als das Rücklesen des Schieberegisters erscheint mir
// ein stichprobenartiges Verify nach dem Beschreiben des RAMs
// unter Einschluss des ersten und letzten Bytes (kritisch)
   Fortschritt(Pos,Size);
  }  /* for (Pos=0...) */
 
 }else{	/* Units==1 */
  for (Pos=0; Pos<Size; ) {
   SetShiftRegister(Cen_Clk,*data++);

   OutB(SND_MODE);		// Takt weg, Glitches vermeiden
   OutC(LOD_MODE);		// Schieberegisterinhalt in RAM schreiben

   Pos++;
   if (Pos<Size) CountUp();	// Adresszähler und CurrentAddr heraufzählen.
   Fortschritt(Pos,Size);
  }
 }
 OutFlush();
}

/*──────────────────────────────────────────────────────────────┐
│ ReadData() liest aus den RAM des PEPS in einen Puffer *data.	│
├───────────────────────────────────────────────────────────────┤
│ Parameter:							│
│  data:	Zeiger auf Anfang des Datenblocks		│
│  Size:	Größe des Datenblocks in Bytes			│
│  addr:	Lineare Daten-Adresse (21 bit)			│
│  verify:	Daten vergleichen, nicht lesen			│
│ Return:	- (Terminierung bei Vergleichsfehler)		│
├───────────────────────────────────────────────────────────────┤
│ 921117	AM						│
└──────────────────────────────────────────────────────────────*/
void ReadData(BYTE *data, long Size, long addr, bool verify) {
 long Pos;
 int k;
 ldiv_t d = ldiv(addr,Units);

 CountTo(d.quot);

 KnownStates&=~Cen_Clk;		// Optimierung bringt beim Lesen nichts
 for (Pos=-d.rem; Pos<Size;) {
  BYTE Clk_Mask;
  CountUp();	// Adresszähler weiterzählen (aber am Ausgang steht noch die alte Adresse)
  for (Clk_Mask=CEN_CLK0; Clk_Mask; Clk_Mask<<=1) {
   BYTE Clk = Cen_Clk & Clk_Mask; /* Bytes zyklisch von PEPSen holen.                    */
   if (Clk) {
    if ((unsigned long)Pos<(unsigned long)Size) {
     BYTE b;
     OutB(RD_MODE);	// RAM ins Schieberegister laden.
     OutB(RD_MODE|Clk);	// Bit 7 lesen, solange CLOCK aktiv ist.
     ReadBit();		// Nun ist am Adresszähler bereits die nächste Adresse
     OutB(RD_MODE);	// Glitches vermeiden, daher Modus und Takt nicht gleichzeitig schalten
     OutB(SND_MODE);	// Schiebemodus aktivieren
     for (k=0; k<7; k++) {	// Einlesen der restlichen 7 Bit
      OutB(SND_MODE|Clk);
      ReadBit();
      OutB(SND_MODE);
     }
     b=ReadByte();	// aufgesammelte Bits abholen
     if (verify) {
      if (*data != b) newline(),errno=0,DoExit(32,addr,b,*data);
      data++;
      addr++;
     }else *data++ = b;
    }
    Pos++;
   }
  }
  Fortschritt(Pos,Size);
 }
 OutFlush();
}

// liefert verfügbare Speichergröße (aus zwei lokalen Variablen) in Byte
long GetPepsSize() {
 return PepsSize*Units;
}
Detected encoding: UTF-80