UNIT cct; {$project vt }
{ Programmierung des CCT-Chips (Computer Controlled Teletext) SAA5246 }

INTERFACE; FROM vt USES global;
{$incl "i2c.lib" }

CONST PF_SERMAG = $0800; { C11  offizielle Seitenflags }
      PF_RENEW  = $0100; { C8 }
      PF_STITLE = $0040; { C6 }
      PF_HEADLN = $0020; { C5 }
      PF_ISMAP  = $0004; { inoffizielle Flags für meine eigenen Zwecke, }
      PF_PSEUDO = $0002; { "C3 bis C0", existieren im }
      PF_LOCAL  = $0001; { Standard aber auch nicht }
CONST maxerror=9;
VAR i2c_error: ARRAY[0..maxerror] OF String[30];
VAR i2c_status, tv_status: Integer;

PROCEDURE busmaster(really: Boolean);
PROCEDURE display_select(speicher: Byte);
PROCEDURE TV_display(mode: Byte);
FUNCTION VTstat: Byte;
PROCEDURE sperren(speicher: Byte);
PROCEDURE anfordern(speicher: Byte; page, subpage: Integer; mask: Byte);
PROCEDURE init_CCT;
FUNCTION seite_da(speicher: Byte): Boolean;
PROCEDURE gettime(VAR zeit: Str80);
PROCEDURE gethead(VAR head: Str80);
PROCEDURE getpage(speicher: Byte; seite: p_onepage; ganz: Boolean);
PROCEDURE add_sp_number(seite: p_onepage);
PROCEDURE char_to_page(x,y: Integer; c: Char; seite: p_onepage);
PROCEDURE write_to_page(x,y: Integer; txt: Str80; vgf,hgf: Integer;
                       seite: p_onepage);
PROCEDURE init_page(seite: p_onepage);

{ ---------------------------------------------------------------------- }

IMPLEMENTATION;

{$opt q,s+,i+ } { keine Laufzeitprüfungen außer Stack und Feldindizes }

CONST SAA5246 = $22;
      PBLF = $20;
      DOCARE =  $10;
      NOTHOLD = $08;

VAR i2cdata: ARRAY[1..20] OF Byte;    { kleiner Puffer für Kommandos }
    aktspeicher: Integer;   { der Speicher, in dem die VT-Uhr läuft }

PROCEDURE busmaster{(really: Boolean)};
VAR l: Long;
BEGIN
  IF really THEN l := BringBackI2C
    ELSE ShutDownI2C;
END;

PROCEDURE send(addr: Byte; anz: Word; buffer: Ptr);
{ Dummy-Prozedur, um den Fehler "keine Library" aufzufangen }
BEGIN
  IF I2CBase=Nil THEN
    i2c_status := maxerror
  ELSE
    i2c_status := (SendI2C(addr,anz,buffer) SHR 8) AND $FF;
END;

PROCEDURE receive(addr: Byte; anz: Word; buffer: Ptr);
BEGIN
  IF I2CBase=Nil THEN
    i2c_status := maxerror
  ELSE
    i2c_status := (ReceiveI2C(addr,anz,buffer) SHR 8) AND $FF;
END;

PROCEDURE setregister(addr,reg,value: Byte);
{ Häufig benötigter Vorgang: ein einzelnes Register am I²C-Bus beschreiben. }
BEGIN
  i2cdata[1] := reg; i2cdata[2] := value;
  send(addr,2,^i2cdata);
END;

FUNCTION getregister(addr,reg: Byte): Byte;
{ Etwas umständlicher, wird auch seltener benötigt: ein einzelnes Register }
{ auslesen. NICHT schleifenweise aufrufen, um mehrere Bytes zu lesen! }
BEGIN
  send(addr,1,^reg);
  receive(addr,1,^i2cdata);
  getregister := i2cdata[1];
END;

PROCEDURE display_select{(speicher: Byte)};
{ Einen der 8 Seitenspeicher zur Anzeige auswählen }
BEGIN
  setregister(SAA5246,4,speicher);
  aktspeicher := speicher;
END;

PROCEDURE TV_display{(mode: Byte)};
{ Art der Anzeige am Fernseher auswählen. }
BEGIN
  i2cdata[1] := 5;
  CASE mode OF
    2: { Normalseiten bildfüllend, Schlagzeilen als Boxen }
      BEGIN  i2cdata[2] := %11001100; i2cdata[3] := %01001111; END;
    1: { Normalseiten als Boxen, Schlagzeilen transparent }
      BEGIN  i2cdata[2] := %11001111; i2cdata[3] := %00001111; END;
    OTHERWISE  { Anzeige aus }
      BEGIN  i2cdata[2] := %00000011; i2cdata[3] := %00000011; END;
  END;
  i2cdata[4] := %00000111;
  send(SAA5246,4,^i2cdata);
  tv_status := mode;
END;

FUNCTION VTstat{: Byte};
{ Liest aus dem CCT das Register R11B. Bedeutung: bit0 - Videosignal vorh., }
{ bit1 - Textsignal vorh., bit2-5 - ROM-Version }
BEGIN
  setregister(SAA5246,0,%00000001);   { R11B aktivieren }
  VTstat := getregister(SAA5246,11);    { Status lesen }
  setregister(SAA5246,0,%00000000);   { R11 aktivieren }
END;

PROCEDURE sperren{(speicher: Byte)};
{ Seitensuche eines Empfangskreises anhalten, damit nicht evtl. niederpriore }
{ Empfangskreise behindert werden. Die Sperre wird bei Programmierung einer }
{ neuen Seitenanforderung automatisch aufgehoben. }
{ Kreis 3 (der mit der niedrigsten Priorität) wird *nicht* gesperrt, da das }
{ unnötig ist. Stattdessen wird er auf Seite *** programmiert, so daß seine }
{ Titelzeile ständig durchläuft. }
BEGIN
  IF speicher=3 THEN
    anfordern(speicher, $100, 0, %000)
  ELSE BEGIN
    i2cdata[1] := 2;
    i2cdata[2] := speicher*16;
    i2cdata[3] := 0;
    send(SAA5246,3,^i2cdata);
  END;
END;

PROCEDURE anfordern{(speicher: Byte; page, subpage: Integer; mask: Byte)};
{ Einlesen einer Seite in einen Seitenspeicher 0..7 anfordern, Unterseite nur }
{ direkt anfordern, wenn subpage > 0 }
{ <mask> ist eine dreistellige Binärzahl, wo eine '0' steht, wird in der }
{ entsprechenden Ziffer der angeforderten Seitennummer das DOCARE-Bit }
{ gelöscht. }
VAR lauf: Integer;
BEGIN
  { Seitenanforderung beschreiben: }
  i2cdata[1] := 2;
  i2cdata[2] := speicher*16;
  i2cdata[3] := ((page SHR 8) AND $7) OR NOTHOLD;
  i2cdata[4] := (page SHR 4) AND $F;
  i2cdata[5] := page AND $F;
  IF page > 0 THEN
    FOR lauf := 3 TO 5 DO
      IF Odd(mask SHR (5-lauf)) THEN
        i2cdata[lauf] := i2cdata[lauf] OR DOCARE;
  i2cdata[6] := (subpage SHR 12) AND $3;
  i2cdata[7] := (subpage SHR 8) AND $F;
  i2cdata[8] := (subpage SHR 4) AND $7
  i2cdata[9] := subpage AND $F;
  IF subpage > 0 THEN
    FOR lauf := 6 TO 9 DO  i2cdata[lauf] := i2cdata[lauf] OR DOCARE;
  send(SAA5246,9,^i2cdata);
END;

PROCEDURE init_CCT;
VAR i: Byte;
BEGIN
  { Wichtig in R1: bit5 - ACQ OFF, Decoder-NotAus; bit2 - TCS ON, }
  { RGB-Ausgangssignal wird mit Fernsehbild synchronisiert. }
  setregister(SAA5246,1,%00000100);
  { Seitensuche sperren: }
  FOR i := 0 TO maxactive-1 DO BEGIN
    sperren(i); activejobs[i].pg := 0;
  END;
  { alle Seitenspeicher löschen: }
  FOR i := 0 TO 7 DO BEGIN
    setregister(SAA5246,8,%00001000 OR i); Delay(2);
    { Löschvorgang dauert bis zu 22 ms, darum mal jeweils 40 ms warten. }
  END;
  { R11 aktivieren }
  setregister(SAA5246,0,%00000000);
  { Fernsehdarstellung einschalten }
  display_select(aktspeicher);
  TV_display(tv_status);
END;

FUNCTION seite_da{(speicher: Byte): Boolean};
{ Überprüft, ob die für einen Speicher 0..7 angeforderte Seite bereits }
{ eingetroffen ist. Das Statusflag PBLF, das in diesem Fall gelöscht ist, }
{ muß danach wieder gesetzt werden. Das geschieht entweder implizit beim }
{ Programmieren einer neuen Seitenanforderung oder explizit durch }
{ Schreibzugriff, in diesem Programm in getpage(). }
{ Problem: das Statusflag PBLF bezieht sich auf den Empfang der Kopfzeile, }
{ nicht etwa den Empfang der kompletten Seite. Genauer gesagt: für letzteres }
{ existiert überhaupt kein Statusflag! }
{ Nun ist das nicht ganz so tragisch, nach Empfang der Kopfzeile kann man }
{ bereits bedenkenlos die Statuszeile 25 mit Steuerbits und Seitennummer }
{ lesen. Und in vielen Fällen geht es sogar gut, sofort mit dem Einlesen }
{ der gesamten Seite zu beginnen, da sich nämlich maximal 11 Seiten pro }
{ Sekunde über den I²C-Bus schaufeln lassen, während der Videotext selbst }
{ mit ca. 13 Seiten pro Sekunde einrollt: Die noch leere Seite wird }
{ schneller aufgefüllt, als sie ausgelesen werden kann. }
{ Probleme entstehen natürlich, wenn der Empfang der VT-Seite länger als }
{ ca. 1/10 s dauert. Das ist möglich, wenn der Sender nicht alle zur }
{ VT-Übertragung zulässigen Rasterzeilen ausnutzt, oder wenn er gar den }
{ Magazin-gemischten Übertragungsmodus verwendet. Letzteres ist aber an }
{ einem Statusflag (C11) vom Programm aus erkennbar, hm ... }
BEGIN
  seite_da := False;
  { Byte 9 der Statuszeile (Zeile 25) adressieren ... }
  i2cdata[1] := 8;
  i2cdata[2] := speicher;
  i2cdata[3] := 25; i2cdata[4] := 9;
  send(SAA5246,4,^i2cdata);
  IF i2c_status<>0 THEN Exit;
  { ... und auslesen: }
  receive(SAA5246,1,^i2cdata);
  IF i2c_status<>0 THEN Exit;
  seite_da := (i2cdata[1] AND PBLF)=0;
END;

PROCEDURE gettime{(VAR zeit: Str80)};
{ liest aus der zur Anzeige ausgewählten Speicherseite die Uhrzeit }
VAR i: Integer;
BEGIN
  zeit := '        ';
  { Uhrzeit aus der Seite im CCT lesen: Zeile 0, Spalte 32 }
  i2cdata[1] := 8;
  i2cdata[2] := aktspeicher;
  i2cdata[3] := 0; i2cdata[4] := 32;
  send(SAA5246,4,^i2cdata);
  IF i2c_status<>0 THEN Exit;
  { Uhrzeit lesen: }
  receive(SAA5246,8,^i2cdata);
  IF i2c_status<>0 THEN Exit;
  FOR i := 1 TO 8 DO
    IF i2cdata[i] IN [32..127] THEN zeit[i] := Chr(i2cdata[i]);
END;

PROCEDURE gethead{(VAR head: Str80)};
{ liest aus Speicher 3 die 24 Byte Titelzeile zwischen Statusfeld und }
{ Uhrzeit, Steuerzeichen werden entfernt }
VAR i: Integer;
BEGIN
  head := '';
  { Seitenspeicher addressieren: Zeile 0, Spalte 8 }
  i2cdata[1] := 8;
  i2cdata[2] := 3; { Speicher }
  i2cdata[3] := 0; i2cdata[4] := 8;
  send(SAA5246,4,^i2cdata);
  IF i2c_status=0 THEN
    receive(SAA5246,24,^head);  { Zeile lesen }
  FOR i := 1 TO 24 DO
    IF NOT ((i2c_status=0) AND (head[i] IN [' '..'~'])) THEN
      head[i] := ' ';
  head[25] := #0;
END;

PROCEDURE getpage{(speicher: Byte; seite: p_onepage; ganz: Boolean)};
{ Liest eine Speicherseite (0..7) aus dem CCT-Baustein aus und speichert }
{ diese Seite im Amiga. Für <ganz>=false werden nur die Statusinformationen }
{ aus Zeile 25 gelesen. }
VAR i,h: Integer;
    status: ARRAY [0..9] OF Byte;
    s: str80;
BEGIN
  seite^.pg := 0; seite^.sp := 0; seite^.cbits := 0;
  { Status aus der Seite im CCT lesen: Zeile 25, Spalte 0 }
  i2cdata[1] := 8;
  i2cdata[2] := speicher;
  i2cdata[3] := 25; i2cdata[4] := 0;
  send(SAA5246,4,^i2cdata);
  IF i2c_status<>0 THEN Exit;
  receive(SAA5246,10,^status);
  IF i2c_status<>0 THEN Exit;
  seite^.pg := (status[0] AND $F) + (status[1] AND $F) SHL 4
                                  + (status[8] AND $7) SHL 8;
  IF seite^.pg<$100 THEN seite^.pg := seite^.pg + $800;
  seite^.sp := (status[2] AND $F)       + (status[3] AND $7) SHL 4
             + (status[4] AND $F) SHL 8 + (status[5] AND $3) SHL 12;
  seite^.cbits := (status[3] AND $08) SHL 1   { C4 }
               OR (status[5] AND $0C) SHL 3   { C5, C6 }
               OR (status[6] AND $0F) SHL 7   { C7, C8, C9, C10 }
               OR (status[7] AND $0F) SHL 11  { C11, C12..C14 }
  IF make_bcd(get_bcd(seite^.pg))<>seite^.pg THEN
    seite^.cbits := seite^.cbits OR PF_PSEUDO;
  IF ganz THEN BEGIN
    seite^.dejavu := False; { als Neuigkeit kennzeichnen }
    { Seite einlesen. Richtigen Speicher, Zeile 0, Spalte 0 adressieren: }
    i2cdata[1] := 8;
    i2cdata[2] := speicher;
    i2cdata[3] := 0; i2cdata[4] := 0;
    send(SAA5246,4,^i2cdata);
    IF i2c_status<>0 THEN Exit;
    { Seite einlesen: }
    receive(SAA5246,960,^seite^.chars);
    IF i2c_status<>0 THEN Exit;
    { aus dem zur Anzeige aktivierten Seitenspeicher die Uhrzeit holen: }
    IF speicher<>aktspeicher THEN BEGIN
      gettime(s);
      FOR i := 1 TO 8 DO
        seite^.chars[31+i] := Ord(s[i]);
    END;
  END;
END;

{ Jetzt noch ein paar Routinen, die zwar nicht auf den I²C-Bus zugreifen, }
{ aber wie getpage() dem Ausfüllen von VT-Seiten dienen: }

PROCEDURE add_sp_number{(seite: p_onepage)};
{ In den freien Anfang der 1. Zeile die Unterseitennummer eintragen, }
VAR i: Integer;
    s: str80;
BEGIN
  s := '('+hexstr(seite^.sp,4)+') ';
  IF (seite^.cbits AND PF_SERMAG)=0 THEN BEGIN { Magazin-gemischte Übertragung, }
    s[1] := '<'; s[6] := '>';     { Modus für den Benutzer erkennbar machen }
  END;
  FOR i := 1 TO 7 DO
    seite^.chars[i] := Ord(s[i]);
END;

PROCEDURE char_to_page{(x,y: Integer; c: Char; seite: p_onepage)};
BEGIN
  IF (y IN [0..23]) AND (x IN [0..39]) THEN 
    seite^.chars[x+40*y] := Ord(c);
END;

PROCEDURE write_to_page{(x,y: Integer; txt: Str80; vgf,hgf: Integer;
                       seite: p_onepage)};
{ Text in eine Videotextseite schreiben. }
{ x und y werden ab 0 gezählt. Umlaute werden konvertiert. Wenn vgf oder }
{ hgf -1 sind, werden keine Farbsteuerzeichen erzeugt -> Platzersparnis. }
VAR i,j: Integer;
BEGIN
  IF y>23 THEN Exit;
  i := x + 40*y;
  IF hgf=0 THEN BEGIN
    seite^.chars[i] := 28; Inc(i);
  END;
  IF hgf>0 THEN BEGIN
    seite^.chars[i] := hgf; Inc(i);
    seite^.chars[i] := 29; Inc(i);
  END;
  IF vgf>=0 THEN BEGIN
    seite^.chars[i] := vgf; Inc(i);
  END;
  FOR j := 1 TO Length(txt) DO BEGIN
    CASE txt[j] OF
      'ä': seite^.chars[i] := Ord('{');
      'ö': seite^.chars[i] := Ord('|');
      'ü': seite^.chars[i] := Ord('}');
      'ß': seite^.chars[i] := Ord('~');
      'Ä': seite^.chars[i] := Ord('[');
      'Ö': seite^.chars[i] := Ord('\');
      'Ü': seite^.chars[i] := Ord(']');
      OTHERWISE seite^.chars[i] := Ord(txt[j]);
    END;
    IF i<959 THEN Inc(i);
  END;
END;

PROCEDURE init_page{(seite: p_onepage)};
{ gerade mit New() erzeugte Seite zum Selbstbeschreiben vorbereiten. }
{ Nur pg und sp müssen bereits eingetragen sein. }
VAR i: Integer;
    s: Str80;
BEGIN
  seite^.dejavu := False; { als Neuheit kennzeichnen }
  FOR i := 0 TO 959 DO seite^.chars[i] := 32;
  seite^.cbits := $4000 OR PF_SERMAG OR PF_LOCAL; { dt. Zeichensatz! }
  IF seite^.pg>0 THEN BEGIN
    s := '('+hexstr(seite^.sp,4)+') '+hexstr(seite^.pg,3);
    write_to_page(0,0,s,7,-1,seite);
    write_to_page(16,0,'lokal erzeugte VT-Seite',5,-1,seite);
  END;
END;

LIBRARY SysBase:
-414 : PROCEDURE CloseLibrary(A1: Ptr);
-552 : FUNCTION  OpenLibrary(A1: Str; D0: Long): Ptr;
END;

PROCEDURE retreat;
BEGIN
  IF I2CBase<>Nil THEN CloseLibrary(I2CBase);
END;
               
VAR i: Integer;

BEGIN  { Initialisierungsteil }
  aktspeicher := 0; tv_status := 2;
  AddExitServer(retreat); I2CBase := OpenLibrary('i2c.library',39);
  FOR i := 0 TO maxerror-1 DO
    IF I2CBase<>Nil THEN i2c_error[i] := I2CErrText(i SHL 8)
    ELSE i2c_error[i] := '';
  i2c_error[maxerror] := 'need i2c.library V39+';
END.
