program RTCDemo;
{$C- } {Sonst funktioniert 'keypressed' nicht}
{$I clock.inc}

{---------------------------------------------------------------------
        AVRCPM
        Test und Demonstration des I2C-Interface
        I2C Uhrenchip PCF8583
        Zeit und Datum lesen und schreiben

        Moegliche Erweiterung (TODO:):
        - Control/Status-Register und Alarmregister lesen/schreiben
        - RAM lesen/schreiben

        $Id: $
----------------------------------------------------------------------}

const
  I2CCMD    = 5;        {adr of I2C Command Port (1=read, 2=write)}
  I2CMSGLEN = 6;        {Transferpuffergroesse}
  I2CADRL   = 7;        {Transferpufferadresse low/high}
  I2CADRH   = 8;

  I2C_CMD_Read  = 1;    {I2C Read Command}
  I2C_CMD_Write = 2;    {I2C Write Command}

  { TP Delay Loop }
  T100ms = 100;         {20MHz AVR + Config fuer 3 MHz Z80 (TINST)}
  {T100ms = 75;}        {20MHz AVR + Config fuer 4 MHz Z80 (TINST default)}

type
  I2CBufLen = 0..16;

  CalTime = record
    sec: 0..59;
    min: 0..59;
    hrs: 0..23;
    day: 1..31;
    month: 1..12;
    year: integer;
  end;

  DTinput = array [1..3] of integer;

var
  msgbuf : array[0..16] of Byte;        {TODO: Transferbuffer nicht global}

  Time : CalTime;
  Done: boolean;

{---------------------------------------------------------------------
        Debugging: Print 16 byte RAM ab adr
----------------------------------------------------------------------}
procedure hexdump(adr: integer);
var
  i: integer;
  c: byte;

  function hexdigit(c: byte): char;
  begin
    if c < 10 then
      hexdigit := char(c + $30)
    else
      hexdigit := char(c - 10 + $41);
  end;

{TODO: print adr }
begin
  for i := 0 to 15 do
  begin
    c := Mem[adr+i];
    write(hexdigit(c shr 4), hexdigit(c and $f), ' ');
  end;
  writeln;
end;

{---------------------------------------------------------------------
        I2C - Routinen
----------------------------------------------------------------------}
procedure i2c_init;
var
  i: integer;
begin
  msgbuf[0] := $A2;     {I2C-Adresse des RTC-Chips}

  Port[I2CADRH] := Hi(Addr(msgbuf));
  Port[I2CADRL] := Lo(Addr(msgbuf));
end;

procedure i2c_write(len:Byte);
begin
  Port[I2CMSGLEN] := len;
  Port[I2CCMD] := I2C_CMD_Write;
end;

procedure i2c_read(len:Byte);
begin
  Port[I2CMSGLEN] := len;
  Port[I2CCMD] := I2C_CMD_Read;
end;


function BCDtoBINbyte(i: byte): byte;
begin
  BCDtoBINbyte := (i div 16) * 10 + (i and $F);
end;

function BINtoBCDbyte(i: byte): byte;
begin
  BINtoBCDbyte := (i div 10) * 16 + (i mod 10);
end;

procedure ReadRTC(var t: CalTime);
begin
  msgbuf[1] := 2;
  i2c_write(2);
  i2c_read(6);
  with t do
  begin
    sec  := BCDtoBINbyte(msgbuf[1]);
    min  := BCDtoBINbyte(msgbuf[2]);
    hrs  := BCDtoBINbyte(msgbuf[3] and $3F);
  end;
  msgbuf[1] := $10;
  i2c_write(2);
  i2c_read(3);
  with t do
  begin
    day  := BCDtoBINbyte(msgbuf[4] and $3F);
    month:= BCDtoBINbyte(msgbuf[5] and $1F);
    year := msgbuf[1] + 256*msgbuf[2];
    while Lo(year) and $3 <> (msgbuf[4] shr 6) do
      year := year + 1;
  end;
end;


procedure WriteRTC(t: CalTime);
begin
  with t do begin
    msgbuf[1] := 1;                   {register address}
    msgbuf[2] := 0;                   {hundredth of sec}
    msgbuf[3] := BINtoBCDbyte(sec);
    msgbuf[4] := BINtoBCDbyte(min);
    msgbuf[5] := BINtoBCDbyte(hrs);
    msgbuf[6] := BINtoBCDbyte(day) + (Lo(year) shl 6);
    msgbuf[7] := BINtoBCDbyte(month); {TODO: weekdays}
  end;
  i2c_write(8);

  msgbuf[1] := $10;                  {register address}
  with t do begin
    msgbuf[2] := Lo(year);
    msgbuf[3] := Hi(year);
  end;
  i2c_write(4);
end;

{---------------------------------------------------------------------
        Dialog Routinen
----------------------------------------------------------------------}

procedure PrintWithLeadingZero( number: byte);
begin
  if number < 10 then
    write( '0');
  write( number);
end;

procedure PrintTime(t: CalTime);
begin
  with t do
  begin
    write('Zeit: ');
    PrintWithLeadingZero( hrs);
    write( ':');
    PrintWithLeadingZero( min);
    write( ':');
    PrintWithLeadingZero( sec);
    write( '  ',
           'Datum: ', day, '.', month, '.', year);
  end;
end;

procedure PrintMenu;
begin
  ClrScr;
  writeln('Menu:');
  writeln(' T) Zeit setzen');
  writeln(' D) Datum setzen');
  writeln(' U) Uhr anzeigen');
  writeln(' Q) Quit');
  writeln('> ');
end;

function GetKey: char;
var
  c: char;
begin
  Read(Kbd, c);
  GetKey := c;
end;

{Daemliche input routine ohne Fehlerpruefung (TODO:)}
procedure GetDateTime(var a: DTinput; sep: char);
var
  line: String[80];
  s: String[4];
  i,j,k: integer;
  rc: integer;

begin
  readln(line);
  line := line + sep;
  j := 1;
  for i := 1 to 3 do
  begin
    k := 1;
    while line[k+j] in ['0'..'9'] do
      k := k + 1;
    s := Copy(line, j, k);
    val(s, a[i], rc);
    j := j + k + 1;
  end;
end;

procedure SetTime;
var
  a: DTinput;

begin
  write('Zeit (hh:mm:ss): ');
  a[1] := -1;
  GetDateTime(a, ':');
{  writeln('intime:', a[1], ':', a[2], ':', a[3]);
}
  if (a[1] in [0..24]) and (a[2] in [0..59]) and (a[3] in [0..59]) then
  begin
    ReadRTC(Time);
    Time.hrs  := a[1];
    Time.min  := a[2];
    Time.sec  := a[3];
    WriteRTC(Time);
  end else
  begin
    writeln('Fehler in Eingabe.');
  end;
end;

procedure SetDate;
var
  a: DTinput;

begin
  write('Datum (TT.MM.JJJJ): ');
  a[1] := -1;
  GetDateTime(a, '.');
{  writeln('indate:', a[1], '.', a[2], '.', a[3]);
}
  if (a[1] in [1..31]) and (a[2] in [1..12]) and (a[3] > 0) and (a[3] <= 2076) then
  begin
    ReadRTC(Time);
    Time.day  := a[1];
    Time.month:= a[2];
    Time.year := a[3];
    WriteRTC(Time);
  end else
  begin
    writeln('Fehler in Eingabe.');
  end;
end;

procedure CleanConsolebuffer;
var
  c: char;
begin
  while keypressed do c := GetKey;
end;

procedure ShowClock;
var
  old : CalTime;
begin
  ReadRTC( old);
  clock_init( old.sec, old.min, old.hrs);
  repeat
    Delay( T100ms);
    ReadRTC( Time);
    if Time.sec <> old.sec then
    begin
      clock_clear_sec( old.sec);
      if Time.min <> old.min then
      begin
        clock_clear_hands( old.min, old.hrs);
        clock_draw_hands( Time.min, Time.hrs);
      end;
      clock_draw_sec( Time.sec);
      old := Time;
    end;
  until keypressed;
  PrintMenu;
end;

{---------------------------------------------------------------------
        main
----------------------------------------------------------------------}

begin
  PrintMenu;
  i2c_init;

  writeln;
  Done := False;
  repeat
    Delay(T100ms);
    ReadRTC(Time);
    GotoXY(35,1); PrintTime(Time); ClrEol;
    gotoXY(2,5);  ClrEol;

    if keypressed then
    begin
      gotoXY(1,7); ClrEol;
      gotoXY(1,6); ClrEol;
      case UpCase(GetKey) of
       'T': SetTime;
       'D': SetDate;
       'U': ShowClock;
       'Q': Done := True;
      end;
    end;
  until Done;
  CleanConsolebuffer;
end.
