/*--------------------------------------------------------------
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-8 | 0
|