{###########################################################################}
// UNIC-Version!
// je nach MainChInternFlag entweder auf OptoBus oder FPGA-CoreTx
{$NOFRAME}
procedure mySerOut(const b: char);
begin
  asm;
    lds  _ACCB, UNIC.EDIPoutFlag
    cpi _ACCB,0
    brne UNIC.mySerOutEDIP
    call SYSTEM.SerOut  ; normale Ausgabe wenn EDIPoutFlag=false
    ADIW      _FRAMEPTR, 1 ; FramePtr korrigieren
    ret

    UNIC.mySerOutEDIP:
  endasm;
  // String um b verlängern, brute force
  c:=EDIPtempStr[0];
  inc(c);
  EDIPtempStr[0]:=c;
  EDIPtempStr[byte(c)]:=b;
end;

procedure SPIbytetransfer;
//Sende und empfange ein Daten-Byte an den UNIC-Chip über SPI
begin
  asm;
    cli ; Disable interrupts
//    sbi  UNIC.ControlBitPort, UNIC.b_slavesel; SLAVESEL, immer benutzter Testport für LA
    lds  _ACCA, UNIC.SPIsendbyte
    out SPDR, _ACCA    ; SPI wurde von FAT16-Treiber eingeschaltet!
  SPIwait8_1:
    in _ACCA, SPSR
    sbrs _ACCA,7
    rjmp SPIwait8_1; Warte bis gesendet und empfangen
    in _ACCA, SPDR
    sts  UNIC.SPIreceivebyte, _ACCA  ; Lesewert zurück ins Datenbyte
//    cbi  UNIC.ControlBitPort, UNIC.b_slavesel
    sei ; Enable interrupts
  endasm;
end;


procedure ShiftOutSR;
//Sende PortArray-Bytes an 4094-SR

begin
// SPI Control Register: SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0
  SPCR:= %01010001; // SPI enable, Master, MSB first, CPOL 0.0, fclk/16
  STRSHIFT:= low;
  SPIsendbyte:=PortSR3;
  SPIbytetransfer;
  SPIsendbyte:=PortSR2;
  SPIbytetransfer;
  SPIsendbyte:=PortSR1;
  SPIbytetransfer;
  SPIsendbyte:=PortSR0;
  SPIbytetransfer;
  STRSHIFT:= high;
  nop; nop; nop; nop;
  STRSHIFT:= low;
end;

procedure ShiftOutDAC(DACraw:Integer; DACchannel:byte; HalfGain,InvPolarity:Boolean);
//Sende DACraw an MCP4822
var DACctrl: byte;
begin
  if ((DACchannel and $01) = 1) then // Kanal B wenn 1 oder 3
    DACctrl:=%10010000;
  else
    DACctrl:=%00010000;
  endif;
  if HalfGain then
    DACctrl := DACctrl or %00100000;
  endif;

// SPI Control Register: SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0
  SPCR:= %01010000; // SPI enable, Master, MSB first, CPOL 0.0, fclk/4
  if DACchannel > 1 then
    STRDAC1:=low;
  else
    STRDAC0:=low;
  endif;
  SPIsendbyte:=(hi(DACraw) and $0F) or DACctrl;
  SPIbytetransfer;
  SPIsendbyte:=lo(DACraw);
  SPIbytetransfer;
  STRDAC0:=high;
  STRDAC1:=high;
  nop; nop;
  STRDACLOAD:= low;
  nop; nop;
  STRDACLOAD:= high;
  udelay(1);
  case DACchannel  of
    0:
      INVERT0:=InvPolarity;
    |
    1:
      INVERT1:=InvPolarity;
    |
    2:
      INVERT2:=InvPolarity;
    |
    3:
      INVERT3:=InvPolarity;
    |
  endcase;
end;


function ShiftInADC(ADCchannel:byte):Integer;
//Sende Channel an MCP3208, erhalte Raw-Wert
//Achtung: kehrt Reihenfolge 0..7 um wg. Layout
var myCh, myCtrl: byte;
begin
  myCh:=7-ADCchannel;      // Reihenfolge umkehren!
  if ((myCh and $04) = 4) then
    myCtrl:=%00000111;
  else
    myCtrl:=%00000110;
  endif;
// SPI Control Register: SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0
  SPCR:= %01010001; // SPI enable, Master, MSB first, CPOL 0.0, fclk/16
  STRADC:=low;
  SPIsendbyte:=myCtrl;
  SPIbytetransfer;
  SPIsendbyte:=myCh shl 6;
  SPIbytetransfer;
  TempW_high:=SPIreceivebyte and $0f;
  SPIsendbyte:=0;
  SPIbytetransfer;
  TempW_low:=SPIreceivebyte;
  nop;
  STRADC:=high;
  return(TempI);
end;

{###########################################################################}

procedure ReadADC(myADC:byte);    // liefert Param!
var ADCtemp:Integer;
begin
{
      if IntegrateADC then
        ADCtemp:=ShiftInADC(myIndex);
        ADCtemp:=ADCtemp+ShiftInADC(myIndex);
        ADCtemp:=ADCtemp+ShiftInADC(myIndex);
        ADCtemp:=ADCtemp+ShiftInADC(myIndex);
        ADCtemp:=ADCtemp shr 2;
      else
      endif;
      }
   ADCtemp:=ShiftInADC(myADC);
   ADCrawArray[myADC]:=ADCtemp;
   ADCtemp:=ADCtemp+ADCoffsets[myADC];
   Param:=Float(ADCtemp)*ADCscales[myADC]*ADCbaseScale; //FS/2 Grundskalierung 12 Bit
end;

procedure SetDACfrac(myDAC:Byte;myFrac:LongInt);
var
myOffset,myIntVal:Integer;
myInvert,mybool: Boolean;
myIndex:Byte;
myScale:LongInt;

begin
  DACValues[myDAC]:=myFrac;
  myBool:= (myFrac > FracScale); // Für PortB Bit-Ports 24..27
  case myDAC of
  0..3:
    myIndex:=myDAC;
    if myFrac < 0 then  // Skalierung und Offset für negative Ausgangsspannung
      myIndex:=myIndex+4;
    endif;
    myOffset:=DACoffsets[myIndex];
    myScale:=DACScales[myIndex];
// Grundskalierung Eingangswert -10V .. + 10V für MCP4822
    myIntVal:= integer(myFrac*myScale div DACbaseScale)+myOffset;
    if myIntVal>4095 then
      myIntVal:=4095;
    endif;
    if myIntVal<-4095 then
      myIntVal:=-4095;
    endif;
    myInvert:=not(sign(myIntVal));
// Nulldurchgang? Dann vor dem Polaritätswechsel Nullwert ausgeben
    if sign(DACrawArray[myDAC]) = myInvert then  // mit letztem Wert vergleichen
      ShiftOutDAC(0,myDAC,false,myInvert);       // Raw-Wert, Channel, HalfGain, Invert
    endif;
    ShiftOutDAC(abs(myIntVal),myDAC,false,myInvert);   // Raw-Wert, Channel, HalfGain, Invert
    DACrawArray[myDAC]:= myIntVal;
  |
// Bits statt DAC
  4:
// PA4 := myBool;
    PA4:=myBool;
  |
  5:
    PA5:=myBool;
  |
  6:
    PA6:=myBool;
  |
  7:
    PA7:=myBool;
  |
  endcase;
end;

{###########################################################################}

procedure RTCtickSecond;      // CallBack from RTCclock
begin
  tickSeconds:=true;
  if INITfileIsOpen then
    LEDactivity:= not LEDactivity;
  endif;
end;

procedure RTCtickMinute;      // CallBack from RTCclock
begin
  tickMinutes:=true;
end;

procedure RTCtickHour;        // CallBack from RTCclock
begin
  tickHours:=true;
end;

{$IFDEF EXTRTC}
procedure ShiftRTCout;  // für externe RTC DS1302 oder HT1381 an PortB
var SPItemp:byte;
begin
  SPItemp:=SPCR;
  SPCR:=0; // SPI abklemmen
  asm;
    cbi  PortB, UNIC.b_RTCclk
    sbi  PortA, UNIC.a_RTCrst

    lds  _ACCA, UNIC.RTCcmd
    lsl  _ACCA        // wg. RW-Bit
    andi  _ACCA,$FE   // RW-Bit auf 0
    ori  _ACCA,$80    // MSB auf 1
    ldi  _ACCB, 8

    UNIC.cmdloop1:    ; Cmd-Byte rausschieben
    sbrs _ACCA,0 // Bit low?
    cbi  PortB, UNIC.b_RTCio
    sbrc _ACCA,0 // Bit high?
    sbi  PortB, UNIC.b_RTCio
    sbi  PortB, UNIC.b_RTCclk
    lsr  _ACCA
    nop
    nop
    cbi  PortB, UNIC.b_RTCclk
    dec _ACCB
    brne  UNIC.cmdloop1

    lds  _ACCA, UNIC.RTCdata
    ldi  _ACCB, 8

    UNIC.dataloop1:    ; Data-Byte rausschieben
    sbrs _ACCA,0 // Bit low?
    cbi  PortB, UNIC.b_RTCio
    sbrc _ACCA,0 // Bit high?
    sbi  PortB, UNIC.b_RTCio
    sbi  PortB, UNIC.b_RTCclk
    lsr  _ACCA
    nop
    nop
    cbi  PortB, UNIC.b_RTCclk
    dec  _ACCB
    brne UNIC.dataloop1

    cbi  PortA, UNIC.a_RTCrst
  endasm;
  SPCR:=SPItemp;       // SPI wiederherstellen
end;

procedure ShiftRTCin;  // für externe RTC DS1302 oder HT1381
var SPItemp:byte;
begin
  SPItemp:=SPCR;
  SPCR:=0; // SPI abklemmen
  asm;
    cbi  PortB, UNIC.b_RTCclk
    sbi  PortA, UNIC.a_RTCrst
    lds  _ACCA, UNIC.RTCcmd
    lsl  _ACCA        // wg. RW-Bit Adresse eins nach links
    ori  _ACCA,$81    // MSB auf 1
    ldi  _ACCB, 8
    UNIC.cmdloop2:    ; Cmd-Byte rausschieben
    sbrs _ACCA,0 // Bit low?
    cbi  PortB, UNIC.b_RTCio
    sbrc _ACCA,0 // Bit high?
    sbi  PortB, UNIC.b_RTCio
    sbi  PortB, UNIC.b_RTCclk
    lsr  _ACCA
    nop
    nop
    cbi  PortB, UNIC.b_RTCclk
    dec _ACCB
    brne  UNIC.cmdloop2
  endasm;
  DDRB:=DDRBinitRdRTC;       {PortB dir}
  asm;
    ldi  _ACCB, 7
    UNIC.dataloop2:    ; Data-Byte reinschieben
    clc
    sbic PinB, UNIC.b_RTCio // Bit gesetzt?
    sec
    sbi  PortB, UNIC.b_RTCclk
    nop
    nop
    ror _ACCA  // Carry-Bit einschieben
    cbi  PortB, UNIC.b_RTCclk
    dec _ACCB
    brne  UNIC.dataloop2
    cbi  PortA, UNIC.a_RTCrst
    lsr _ACCA
    sts UNIC.RTCdata,_ACCA ; Daten-Byte
  endasm;
  DDRB:=DDRBinit;       {PortB dir}
  PortB:=PortBinit;           {PortB}
  SPCR:=SPItemp;       // SPI wiederherstellen
end;

procedure GetExtRTC;
begin
  RTCcmd:=2;
  ShiftRTCin;
  Hour:=BCDtoByte(RTCdata);
  RTCcmd:=1;
  ShiftRTCin;
  Minute:=BCDtoByte(RTCdata);
  RTCcmd:=0;
  ShiftRTCin;
  Second:=BCDtoByte(RTCdata);
  RTCcmd:=3;
  ShiftRTCin;
  Day:=BCDtoByte(RTCdata);
  RTCcmd:=4;
  ShiftRTCin;
  Month:=BCDtoByte(RTCdata);
  RTCcmd:=6;
  ShiftRTCin;
  Year:=BCDtoByte(RTCdata);
end;

procedure SetExtRTC;
begin
  RTCcmd:=7;
  RTCdata:=0;
  ShiftRTCout;
  RTCcmd:=2;
  RTCdata:=ByteToBCD(Hour);
  ShiftRTCout;
  RTCcmd:=1;
  RTCdata:=ByteToBCD(Minute);
  ShiftRTCout;
  RTCcmd:=0;
  RTCdata:=ByteToBCD(Second);
  ShiftRTCout;
  RTCcmd:=3;
  RTCdata:=ByteToBCD(Day);
  ShiftRTCout;
  RTCcmd:=4;
  RTCdata:=ByteToBCD(Month);
  ShiftRTCout;
  RTCcmd:=6;
  RTCdata:=ByteToBCD(Year);
  ShiftRTCout;
  RTCcmd:=7;
  RTCdata:=128;
  ShiftRTCout;
end;
{$ENDIF}

procedure GetRTC;
begin
  disableints;
  Hour:=RTCgethour;
  Minute:=RTCgetminute;
  Second:=RTCgetsecond;
  Day:=RTCgetday;
  Month:=RTCgetmonth;
  Year:=RTCgetyear;
  enableints;
end;

procedure SetRTC;
begin
  disableints;
  RTCsethour(Hour);
  RTCsetminute(Minute);
  RTCsetsecond(Second);
  RTCsetday(Day);
  RTCsetmonth(Month);
  RTCsetyear(Year);
  enableints;
{$IFDEF EXTRTC}
  SetExtRTC;
{$ENDIF}
end;

procedure DateTimeToDOS;
{ DOS time format bits:
DOStime: 15..11=Hours (0-23), 10..5=Minutes (0-59), 4..0=Seconds/2 (0-29)
DOSdate: 15..9=Year (0=1980, 127=2107), 8..5=Month (1=January, 12=December),
4..0=Day (1-31) }
begin
  GetRTC;
  DOStime:=(word(Hour) shl 11) or (word(Minute) shl 5) or (word(Second) shr 1);
  DOSdate:=(word(Year+20) shl 9) or (word(Month) shl 5) or (word(Day));
end;

procedure TimeToParamStr;
begin
  ParamStr:=byteToStr(Hour:2:'0')+':'+byteToStr(Minute:2:'0')+':'+byteToStr(Second:2:'0');
end;

procedure DateToParamStr;
begin
  ParamStr:=byteToStr(Day:2:'0')+'.'+byteToStr(Month:2:'0')+'.'+byteToStr(Year:2:'0');
end;

{###########################################################################}



function GetPort(myPort:byte): byte; //IO-Pin-Register setzen
begin
  if IOpresent then
    case myPort of
    4:
    return(IO4Pin);
    |
    5:
    return(IO5Pin);
    |
    6:
    return(IO6Pin);
    |
    7:
    return(IO7Pin);
    |
    endcase;
  else // Geschriebene OUT-Werte
    return(PortArray[myPort]);
  endif;
end;

procedure SetPort(myPort,myVal:byte); //IO-Port-Register lt. SubCh setzen
begin
  PortArray[myPort]:= myVal;  // für SR
  if IOpresent then
    I2CslaveAdr:=myPort+$38;   // Port0 auf I2C-Adresse $38
    ParamInt:=$100 + integer(myval);
    TWIout(I2CslaveAdr, ParamInt);
  else
    ShiftOutSR;
    ENASHIFT:=high;
  endif;
end;

procedure SetDir(myPort,myVal:byte); //IO-Pin-Directions
begin
  DDRArray[myPort]:=myVal;
  if IOpresent then
    case myPort of
    4:
      IO4DDR:=myVal;
      |
    5:
      IO5DDR:=myVal;
      |
    6:
      IO6DDR:=myVal;
      |
    7:
      IO7DDR:=myVal;
      |
    endcase;
  endif;
end;


{###########################################################################}

{$IFDEF EDIP}

function EDIPsend(request:byte):boolean;
// request = DC1 oder DC2
// liefert true wenn ACK empfangen
var myByte, mybcc, mylen, mytry:byte; mybool:boolean;
begin
  mylen:=length(EDIPstr);
  if mylen=0 then
    return(true);
  endif;
  EDIPstart:=request; // DC1
  mybcc:=EDIPstart;
  for i:=0 to mylen do  // Checksumme berechnen
    myByte:=byte(EDIPstr[i]);
    mybcc:=mybcc+myByte;
  endfor;
  mytry:=0;
  repeat
    inc(mytry);
    mybool:=TWIout(EDIPadr,EDIPstart,EDIPstr); // Buffer senden
    mybool:=TWIout(EDIPadr,mybcc); // Checksumme hinterher
    udelay(20);
    mylen:=0; // Hier Timeout-Zähler
    repeat // Timeout-Schleife
      inc(mylen);
      TWIinp(EDIPadr, mybyte); // ACK or NAK; Display liefert danach nur noch $FF
      checkdelay(1);
    until (myByte=ACK) or (myByte=NAK) or (mylen>100);
{
    if not (myByte=ACK) then
      serout(0);   // Error-TEST
    endif;
}
  until (myByte=ACK) or (mytry>10);
  return(myByte=ACK);   // ACK erfolgt?
end;

function EDIPgetraw: byte;
// Bufferinhalt anfordern, falls vorhanden, und in EDIPstr speichern
// liefert Länge des Strings als Ergebnis
var mybcc, mylen:byte;
begin
  if EDIPempty then
    return(0);
  endif;
  EDIPstr:='S'+#0; // Buffer anfordern
  EDIPsend(DC2);
  EDIPstr:=''; // String löschen
  TWIinp(EDIPadr,EDIPstart);
  TWIinp(EDIPadr,EDIPstr);
  TWIinp(EDIPadr,mybcc);
  mylen:=length(EDIPstr);
  return(mylen);
end;

procedure EDIPupdateBar(myBarId:byte;myBarInt:Integer);
begin
  if myBarInt<0 then  // wenn negativ
    myBarInt:=0;
  endif;
  EDIPstr:=#27+'BA'+char(myBarID)+char(lo(myBarInt));
  EDIPsend(DC1);
end;

function EDIPgetcmd: char;
// hängt empfangene Zeichen an EDIPinpStr an,
// liefert aber auch letztes empfangenes Zeichen (CR?)
var myindex, mycount, myptr, mystart, mylen, mycmdidx:byte; mycode,mychar: char;
  myInt:Integer;
begin
  mylen:=EDIPgetraw;
  if mylen=0 then
    return(#0);
  endif;
  myptr:=0;
  mychar:=#0;
  repeat
    inc(myptr);
    if (EDIPstr[myptr]=#27) then
      mycode:=EDIPstr[myptr+1];
      if mycode='A' then  // Auto-Button Msg
        mycount:=byte(EDIPstr[myptr+2])-1;   // Länge der Nutzdaten
        mystart:=myptr+3;
        if mycount<=20 then  // sonst Überlauf-Fehler
          for myindex:=mystart to (mystart+mycount) do
            // Zeichen(folge) anhängen
            mychar:=EDIPstr[myindex];
            mycmdidx:=length(EDIPinpStr)+1;
            EDIPinpStr[mycmdidx]:=mychar;
            setlength(EDIPinpStr,mycmdidx);
          endfor;
        endif;
        myptr:=mystart+mycount;
      elsif mycode='B' then  // Auto-Bar Msg
        myptr:=myptr+3;  // Start der Nutzdaten, hier feste Länge
        BarUpdated:=byte(EDIPstr[myptr]);
        inc(myptr);
        if BarUpdated=BARC then   // Main Display Bar 18
          if Bipolar then
            myInt:=integer(EDIPstr[myptr])-(BARL shr 1);
            BarValueC:=myInt*(200 div BARL);     // von -100 bis 100
          else
            BarValueC:=integer(EDIPstr[myptr]);  // von 0 bis 100
          endif;
        endif;
        inc(myptr);
        mychar:=EDIPstr[myptr];
        BarUpdatedSema:=true;
      endif;
    endif;
  until (mychar=#13) or (myptr>=mylen);
  return(mychar);
end;

{$ENDIF}

{###########################################################################}
//Ausgabe-Prozeduren
{$IFDEF EDIP}

procedure EDIPtermxy;
// mit offset für Terminal-Fenster
begin
  EDIPstr:=#27+'TP'+char(x+1)+char(y+1);
  EDIPsend(DC1);
end;

procedure EDIPtermX0Y0;
begin
  x:= 0;
  y:= 0;
  EDIPtermxy;
end;

procedure EDIPtermX0Y1;
begin
  x:= 0;
  y:= 1;
  EDIPtermxy;
end;

procedure EDIPtermclr;
begin
  x:=0;
  y:=0;
  EDIPstr:=#12;
  EDIPsend(DC1);
  mdelay(10);
end;

procedure EDIPparamStrTermXY;
// Anzeige von ParamStr im Terminal-Fenster an X,Y
var myptr, mylen:byte; mychar:char;
begin
  EDIPstr:=#27+'TP'+char(x+1)+char(y+1);
  EDIPsend(DC1);
  mylen:=length(ParamStr);
  if mylen>0 then
    for myptr:= 1 to mylen do
      mychar:=ParamStr[myptr];
      if mychar=#13 then      // CR
        EDIPtermX0Y0;
      elsif mychar='#' then
        EDIPstr:='##';
        EDIPsend(DC1);
      elsif mychar>=#32 then
        EDIPstr:=mychar;
        EDIPsend(DC1);
      endif;
    endfor;
  endif;
end;

procedure EDIPparamStrOut(myX,myY:byte; mymode:char);
// Anzeige von ParamStr als Grafik an X,Y

begin
  EDIPstr:=#27+'Z'+mymode+char(myX)+char(myY)+ParamStr+#0; // mit Abschluss-0!
  EDIPsend(DC1);
end;


procedure EDIPsetfont(myfont:byte);
// Anzeige von ParamStr als Grafik an X,Y

begin
  EDIPstr:=#27+'ZF'+char(myfont)+#0; // mit Abschluss-0!
  EDIPsend(DC1);
end;

{$ENDIF}


