program FPGA;
(*
FPGA-Funktionsgenerator mit FPGA XC3S500 an PortB

(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

29.08.2007 #0.10 Übernahme aus SQG-Version

*)

//Defines aktivieren durch Entfernen des 1. Leerzeichens!

{ $DEFINE debug}         // Fehler-Anzeige auf LCD
{$DEFINE EFM01}         // EFM01-Modul statt direktes FPGA
{$DEFINE Panel}         // WICHTIG: Bedien-Panel vorhanden, bindet LCD mit ein

{$DEFINE Parser}         // für Testkompilierung auskommentieren

{$NOSHADOW}
{$W- Warnings}            {Warnings off}
{$TYPEDCONST OFF}
{$DEBDELAY}

Device = mega644, VCC = 5;


Import SysTick, FAT16, TWImaster, ADCport, SerPort;
{$IFDEF Panel}
Import IncrPort4, LCDmultiPort;
{$ENDIF}

From System Import LongWord, LongInt;

Define
  ProcClock      = 16000000;        {Hertz}
  TWIpresc       = TWI_BR400;

{$IFDEF Panel}
  LCDmultiPort   = I2C_TWI;
  LCDTYPE_M      = 44780; // 44780 oder 66712;
  LCDrows_M      = 2;              //rows
  LCDcolumns_M   = 8;             //chars per line
  IncrPort4      = PinA, 1, 32; // pin-reg used, channels, 16 or 32bit integer
  IncrScan4      = Timer1, 4; // timer used, scan rate 1kHz (1..100)
{$ENDIF}


  SysTick        = 10;              //msec
  SerPort        = 38400, Stop1, timeout;    {Baud, StopBits|Parity}
  RxBuffer       = 255, iData;
  TxBuffer       = 255, iData;

  StackSize      = $0080, iData;
  FrameSize      = $0100, iData;

  ADCchans = [3,4], iData, int2;
  ADCpresc       = 16;
  FAT16          = MMC_SPI, iData;
  F16_MMCspeed   = standard;
  F16_FileHandles   = 1;
  F16_Dirlevels     = 1;

USES
  UFAT16;


Implementation

type
    tCmdWhich = (str,idn,trg,
                 val,frq,lvl,lvp,dbv,wav,bst,inl,rng,ofs,
                 dsp,all,scl,wen,erc, sbd,nop, err);
  tModify   = (param0, param1, param2, param3, input0, input1, input2, input3,
   eval0, eval1, eval2, eval3, filesel);
  tError = (NoErr, UserReq, BusyErr, OvlErr, SyntaxErr, ParamErr, LockedErr,ChecksumErr);

{--------------------------------------------------------------}
const


//                              ----SFDC             Strobe,DATASEL,Data,Clk
{$IFDEF EFM01}
  DDRBinit                   = %10111011;            {PortB dir }
  PortBinit                  = %10110111;            {PortB, b_STROBE=0 }
{$ELSE}
  DDRBinit                   = %10111111;            {PortB dir }
  PortBinit                  = %10110111;            {PortB, b_STROBE=0 }
{$ENDIF}

  DDRCinit                   = %11011100;            {PortC dir, 0..1=Incr4 }
  PortCinit                  = %11111111;            {PortC }

{ Jumper und LEDs PortBits }
  DDRDinit                   = %00001100;            {PortD dir, 0..1=Serial }
  PortDinit                  = %11111100;            {PortD }

  BtnInPortReg               = @PinD;   {Taster-Eingangsport}
  LEDOutPort                 = @PortD;   {LED-Port}
  ControlBitPort             = @PortB;   {FPGA-SPI-Port}
  ConFPGABitPort             = @PortC;   {FPGA-Config-Port}

  b_SCK                      = 7; //Takt für alle, SPI-Belegung!
  b_MISO                     = 6; //SPI Daten von allen
  b_MOSI                     = 5; //Daten an alle
  b_SS                       = 4; //für MMC-Karte benutzt, HW-SPI!
  b_DATASEL                  = 3; //FPGA 32-Bit-Register
  b_INT2                     = 2; //Testpin Trigger LA  oder INT2
  b_FLASHSEL                 = 1; //Flash-Speicher M24P40  4 MBit auf EFM01,
                                  //per FPGA durchgeschleift!
  b_REGSEL                   = 0; //FPGA Registerauswahl

  c_CCLK                     = 2; //Takt FPGA Configuration
  c_DATA                     = 3; //Daten FPGA Configuration
  c_PROG                     = 4; //Prog-Leitung FPGA Configuration
  c_DONE                     = 5; //Done-Leitung FPGA Configuration


//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = '0.20 [FPGA by CM/c''t 03/2007]';    //Vers1
  Vers3Str                   = 'FPGA 0.2';    // Kurzform von Vers1

  ErrStrArr      : array[0..7] of String[9] = (
    '[OK]','[SRQUSR]','[BUSY]','[OVERLD]','[CMDERR]','[PARERR]','[LOCKED]','[CHKSUM]');


  EEnotProgrammedStr         = 'EEPROM EMPTY! ';
  AdrStr                     = 'Adr ';
  ParamHintStr               = 'Param  ';
  InputHintStr               = 'Value  ';
  EvalHintStr                = 'DataAdr';
  FileNumStr                 = 'FPGAfile';
  ErrSubCh                   = 255;

  CmdStrArr: array[0..19] of String[3] = (
  'STR', // 255
  'IDN', // 254
  'TRG', // 249
  'VAL', // 0..255
  'FRQ', // 0
  'LVL', // 1
  'LVP', // 2
  'DBU', // 3
  'FIL', // 4
  'BST', // 5
  'INL', // 10..13
  'RNG', // 19
  'DCO', // 20
  'DSP', // 80
  'ALL', // 99
  'SCL', // 200..213
  'WEN', // 250
  'ERC', // 251 = ErrCount seit letztem Reset
  'SBD', // 252 = SerBaud UBRR-Register mit U2X=1
  'NOP');   //2 Parameter
  Cmd2SubChArr: array[0..19] of byte = (
  255, 254, 249,
  0, 0, 1, 2, 3, 4, 5, 10, 19, 20, 80, 99, 200, 250, 251, 252, 0);

  high                       = true;
  low                        = false;


//Inkrementalgeber-Beschleunigungstabelle
  IncrAccArray: array[0..15] of integer = (0,1,2,5,10,25,50,100,250,
  500,1000,2500,5000,10000,10000,10000);
  
  IncrDigitArray: array[0..7] of LongInt = (
  $10000000,$01000000,$00100000,$00010000,
  $00001000,$00000100,$00000010,$00000001);

structconst

//Default-EEPROM-Werte:
{$EEPROM}
  dummy:LongInt=0;
  EEinitialised:word=$AA55;
  InitFileNum:byte=0;      // FileNum 0
  InitIncRast:Integer =4;
  EESerBaudReg:byte=51;


{--------------------------------------------------------------}

var
{$DATA} {Schnelle Register-Variablen}
  i,
  k           : Byte;  // k in HW-Prozess benutzt
  m: Byte;

{$IDATA}  {Langsamere SRAM-Variablen}
  aChar:char;
  SlaveCh: byte;
  SwitchState: Byte; //Zwischenspeicher für 4094 SR

  LEDactivity[LEDOutPort, 2]    : bit; {Bit 2 LED Remote-Activity}
  LEDswitch[LEDOutPort, 3]      : bit; {Bit 3 LED über dem Display}

  ButtonTemp,RangeTemp: Byte; // invertiert - low=on!
  ButtonDown[@ButtonTemp, 5]  : bit;
  ButtonUp[@ButtonTemp, 4]  : bit;
  ButtonEnter[@ButtonTemp, 3]  : bit;

{
  c_CCLK                     = 2; //Takt FPGA Configuration
  c_DATA                     = 3; //Daten FPGA Configuration
  c_PROG                     = 4; //Prog-Leitung FPGA Configuration
  c_DONE                     = 5; //Done-Leitung FPGA Configuration
}

  FPGA_CCLK[@PortC, c_CCLK]: bit;  // Takt FPGA Configuration
  FPGA_DATA[@PortC, c_DATA]: bit;  // Daten FPGA Configuration
  FPGA_PROG[@PortC, c_PROG]: bit;  // Prog-Leitung FPGA Configuration
  FPGA_DONE[@PinC,  c_DONE]: bit;  // Done-Leitung FPGA Configuration
  FlashSel[@PortB, b_FLASHSEL]:bit; // CS-Leitung M25P40 auf EFM01
  TriggerLA[@PortB, b_INT2]:bit;    // Testpin fü LA-Trigger

  FPGAreg: byte;   // FPGA-Registerauswahl
  FPGAparam : LongInt;  // FPGA-Registerwert

  ActivityTimer:Systimer8;
  DisplayTimer:Systimer8;
  IncrTimer:Systimer8;

//ein paar Beispiel-Parameter
  FileNum : byte;
  freqStr        : String[10];

  CmdWhich: tCmdWhich;
  CmdStr: string[3];
  SubCh, CurrentCh       : byte;
  OmniFlag,verbose: boolean;
  ParamInt:Integer;
  ParamLong:LongInt;
  ParamLongArray: array[0..3] of LongInt;
  InputLongArray: array[0..3] of LongInt;
  ParamByte, ParamDigit :Byte;
  SerInpStr : String[63];
  SerInpPtr:byte;
  
  LCDpresent: boolean;
  Modify: tModify;
  IncrValue,OldIncrValue: LongInt;
  SelectDigit,FirstTurn:Boolean;
  IncrDiff,IncrAccInt10, IncRast:Integer;
  IncrDiffByte:Byte;
  digits, nachkomma:byte;
  ChangedFlag:Boolean;

//für Parser
  ParamStr:string[40]; // auch für Display
  isInteger,Request:boolean;

  Status:byte;
  BusyFlag[@Status, 7]  : bit;
  UserSRQFlag[@Status, 6]  : bit;
  OverloadFlag[@Status, 5]  : bit;
  EEUnlocked[@Status, 4]  : bit; // EEPROM-unlocked-Flag
  ErrCount:integer;
  ErrFlag:boolean;

  BlockArray:array[0..255] of byte;
  byteCount: LongInt;
  byteCount3[@byteCount+3]:byte;
  byteCount2[@byteCount+2]:byte;
  byteCount1[@byteCount+1]:byte;
  byteCount0[@byteCount]:byte;

  DirectoryArray:array[0..63] of string[8];
  DirectoryDone, CardOK, FlashOK:Boolean;
  fs : file of byte;
  FPGAbyte, FlashByte: byte;
{$NOSAVE}



{###########################################################################}
{$I FPGA-HW.pas}

{###########################################################################}
{$NOSAVE}

procedure ClearDirectory;
begin
  for i:=0 to 63 do
    DirectoryArray[i]:='<empty>';
  endfor;
  FileNum:=0;
  DirectoryDone:=false; // einlesen des Directory beim nächsten FileSel
end;

procedure GetDirectory;
var
SR : TSearchRec;
st : TfileName;
begin
// search for filename/directory entries, accept wildcards
  i:=0;
  CardOK:= false;
  if F16_DiskInit then
    CardOK:= F16_DiskReset;
  endif;
  if not CardOK then
    LCDxy_M(LCD_m1, 0, 0);
    Write(LCDOut_M, 'CardFail');
    FileNum:=0;
    mDelay(1000);
  else
    if (F16_FindFirst ('\', '*.BIN', [faFilesOnly], SR)) then
      repeat
        DirectoryArray[i]:= ExtractFileName(SR.Name);
        inc(i);
      until (i=64) or (not F16_FindNext (SR));
    endif;
  endif;
end;

procedure FlashActivity;
// Aktivitätsanzeige 1. Zeile, starten mit ParamDigit=8!
begin
  if isSystimerzero(DisplayTimer) then
    setsystimer(DisplayTimer,25);
    if ParamDigit > 7 then
      ParamDigit:=0; // Aktivitätsanzeige
      LCDxy_M(LCD_m1, ParamDigit, 0);
      LCDclrEOL_m(LCD_m1);
    endif;
    lcdout_m('>');
    inc(ParamDigit);
  endif;
end;

{$IFDEF EFM01}
procedure FlashWaitReady;
var myStatus:Byte;
begin
  repeat
    FlashByte:=$05; // Read Status
    FlashSel:=low;
    SendFlash;
    FlashByte:=$00;
    SendFlash;
    myStatus:=SPDR;
    FlashSel:=high;
    FlashActivity;
  until (myStatus AND 1) = 0;
end;


procedure TrigLA;
begin
  TriggerLA:=low;
  nop;
  nop;
  TriggerLA:=high;
end;


procedure FlashErase;
var signature:byte;
begin
  FlashSel:=low;  // Keeper im FPGA auf SPI-Clk Ruhezustand setzen
  nop;
  nop;
  FlashSel:=high;
  nop;
  nop;
  
  FlashSel:=low;
  FlashByte:=$AB; //Release from Power Down
  SendFlash;
  FlashByte:=$00; // 3 Dummy bytes
  SendFlash;
  SendFlash;
  SendFlash;
  SendFlash; // Read Signature
  signature:=SPDR;
  FlashSel:=high;
  LCDxy_M(LCD_m1, 0, 1);
  if signature=$12 then           // Signature vom M25P40
    FlashOK:=true;
    Write(LCDOut_M, 'FlashEra');  // gleichzeitig Wartezeit nach PowerDown
    FlashByte:=$06; //Write Enable
    SendFlashSel;
    FlashByte:=$C7; // Bulk Erase
    SendFlashSel;
    FlashWaitReady;
  else
    FlashOK:=false;
    Write(LCDOut_M, 'No Flash!');
    mdelay(1000);
  endif;
end;


procedure FlashPage;
begin
  if FlashOK then
    FlashByte:=$06; //Write Enable
    SendFlashSel;
    nop;
    nop;
    FlashSel:=low;
    FlashByte:=$02; //Page Program
    SendFlash;
    FlashByte:=ByteCount2; // 24-Bit-Adresse
    SendFlash;
    FlashByte:=ByteCount1;
    SendFlash;
    FlashByte:=ByteCount0;
    SendFlash;
    for i:=0 to 255 do
      FlashByte:= BlockArray[i];
      SendFlash;
    endfor;
    FlashSel:=high;
    FlashWaitReady;
  endif;
end;

procedure FPGAload;
// liest Datei "*.bin" von SD- oder MMC-Karte und programmiert
// SFlash M25P40 über SPI damit

var firstBlock, FPGAresponse:Boolean;
  BytesRead:word;
begin
  FPGAresponse:=false;
  setsystimer(DisplayTimer,1);
  byteCount:=0;
  if CardOK then
    ParamStr:=DirectoryArray[FileNum];
    append('.BIN',ParamStr);
    if F16_FileExist ('\', ParamStr, faFilesOnly) then
      ParamDigit:=8; // für FlashActivity
      FlashErase;
      if FlashOK then
        F16_FileAssign (fs, '',ParamStr);
        F16_FileReset (fs); // Datei zum Lesen öffnen
        firstBlock:=true;
        while not F16_EndOfFile (fs) do // read the entire file
          F16_BlockRead (fs, @BlockArray, 256, BytesRead);
          if firstBlock then
            firstBlock:=false;
            LCDxy_M(LCD_m1, 0, 1);
            ParamDigit:=8; // für FlashActivity
            Write(LCDOut_M, 'Loading ');
            FlashActivity;
          endif;
          FlashActivity;
          FlashPage;
          byteCount:=byteCount+256;
        endwhile; // until end of file
        ParamDigit:=7;
        TrigLA;
        LCDxy_M(LCD_m1, 0, 1);
        Write(LCDOut_M, 'BitCount');
        LCDxy_M(LCD_m1, 0, 0);
        LCDclrEOL_m(LCD_m1);
        Write(LCDOut_M, longtostr(byteCount shl 3));

        F16_FileClose (fs);
        FPGA_PROG:=low;
        udelay(1);
        FPGA_PROG:=high; // Startup FPGA mit Impuls auf PROG_B
        mDelay(1000);
      endif;
      InitFileNum:=FileNum;  // letztbenutzte Datei merken
    else
      LCDxy_M(LCD_m1, 0, 0);
      Write(LCDOut_M, 'NotFound');
      ClearDirectory;
      mDelay(1000);
    endif;
  endif;
end;
{$ELSE}
procedure FPGAload;
// liest Datei "FPGAn.bin" von SD- oder MMC-Karte und programmiert
// Spartan-2-FPGA über SPI damit

var firstBlock, FPGAresponse:Boolean;
  byteCount: LongInt;
  BytesRead:word;
begin
  FPGAresponse:=false;
  byteCount:=0;
  if CardOK then
    ParamStr:=DirectoryArray[FileNum];
    append('.BIN',ParamStr);
    if F16_FileExist ('\', ParamStr, faFilesOnly) then
      F16_FileAssign (fs, '',ParamStr);
      F16_FileReset (fs); // Datei zum Lesen öffnen
      firstBlock:=true;
      while not F16_EndOfFile (fs) do // read the entire file
        F16_BlockRead (fs, @BlockArray, 256, BytesRead);
        if firstBlock then
          FPGA_PROG:=low;
          udelay(1);
          FPGA_PROG:=high;
          udelay(10);     // 100 µs Setup- Zeit FPGA Spartan-2
          firstBlock:=false;
          if not FPGA_Done then
            FPGAresponse:=true;
          endif;
          LCDxy_M(LCD_m1, 0, 1);
          Write(LCDOut_M, 'BitCount');
          LCDxy_M(LCD_m1, 0, 0);
          LCDclrEOL_m(LCD_m1);
        endif;
        for i:=0 to 255 do
          FPGAbyte:= BlockArray[i];
          ShiftFPGAconf;
          if FPGA_Done then break; endif;
          inc(byteCount);
        endfor;
        LCDxy_M(LCD_m1, 0, 0);
        Write(LCDOut_M, longtostr(byteCount shl 3));
        if FPGA_Done then break; endif;
      endwhile; // until end of file
      FPGAbyte:= 255;  // Wichtig: noch ein paar CCLK-Impulse nachschieben
      ShiftFPGAconf;
      ShiftFPGAconf;
      F16_FileClose (fs);
      mDelay(1000);
      InitFileNum:=FileNum;  // letztbenutzte Datei merken
      if (not FPGAresponse) or (not FPGA_Done) then
        LCDxy_M(LCD_m1, 0, 0);
        Write(LCDOut_M, 'ConfFail');
        mDelay(1000);
      endif;
    else
      LCDxy_M(LCD_m1, 0, 0);
      Write(LCDOut_M, 'NotFound');
      ClearDirectory;
      mDelay(1000);
    endif;
  endif;
end;
{$ENDIF}

{###########################################################################}

//EEPROM-Routinen

procedure PatchCopyFromEE;
//Frequenz und Settings aus EEPROM holen
begin
  FileNum :=InitFileNum;
  IncRast:=InitIncRast;
end;

{###########################################################################}

//allg. Menü-Prozeduren

procedure SerCRLF;
begin
  Serout(#$0D);
  Serout(#$0A);
end;

procedure WriteChPrefix;
begin
  Serout('#');
  Serout(char(SlaveCh+48));
  Serout(':');
  write(Serout, ByteToStr(SubCh));
  Serout('=');
end;

procedure WriteSerInp;
begin
  write(Serout, SerInpStr);   // Befehl weiterreichen
  SerCRLF;
end;

procedure SerPrompt(myErr:tError;myStatus:byte);
//Error-Meldung und Status-Request-Antwort,
//Status Bit 7=Busy, 6=UserSRQ, 5=OverLoad, 4=!!!, 3..0=Fehler-Nummer
begin
  if verbose OR (myERR<>noErr) then
    SubCh:=ErrSubCh; // Fehler-Kanal
    WriteChPrefix;
    write(Serout, ByteToStr(ord(myErr)+myStatus));
    Serout(#32);
    write(serout,ErrStrArr[ord(myErr)]);
    SerCRLF;
  endif;
  if myERR<>noErr then
    inc(ErrCount);
    ErrFlag:=true;
  endif;
end;

{###########################################################################}

{###########################################################################}
//Menü-Prozeduren


procedure WriteParamStrSer;
begin
  WriteChPrefix;
  write(Serout, ParamStr);
  SerCRLF;
end;




procedure ParamLongToStr;
//mit einer Nachkommastelle in Strin wandeln
begin
  ParamStr:=LongToStr(ParamLong);
end;

procedure ParamLongToHex;
//Longin in Hex-String wandeln
begin
  ParamStr:=LongToHex(ParamLong);
end;

procedure ParamIntToHex;
//Longin in Hex-String wandeln
begin
  ParamStr:=IntToHex(ParamInt);
end;


procedure WriteParamByteSer;
begin
  ParamStr:= ByteToStr(ParamByte);
  WriteParamStrSer;
end;

procedure WriteParamSer;
begin
  ParamStr:= IntToStr(ParamInt);
  WriteParamStrSer;
end;

{$IFDEF Panel}
procedure ParamStrOnLCD;
begin
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,ParamStr);
  LCDclrEOL_m(LCD_m1);
  LCDxy_M(LCD_m1, 7, 0);
  if SelectDigit then
    lcdout_m(#2);
  else
    lcdout_m(#0);
  endif;
  LCDxy_M(LCD_m1, 0, 1);
end;

procedure SollWerteOnLCD;
begin
  if (not ChangedFlag) then
    return; // unverändert, nichts zu tun
  endif;
  digits:=2;
  nachkomma:=1;
  case Modify of
    param0..param3:
      ParamLong:=ParamLongArray[ord(Modify)];
      ParamLongToHex;
      LCDxy_M(LCD_m1, 0, 0);
      write(lcdout_m,ParamStr);
      LCDxy_M(LCD_m1, 0, 1);
      write(lcdout_m,ParamHintStr);
      lcdout_m(char(ord(modify)+48));
      lcdout_m(#32);
      DirectoryDone:=false;
      if SelectDigit then
        LCDxy_M(LCD_m1, ParamDigit, 0);
        LCDcursor_m(LCD_m1, false, false);
        lcdout_m(#3);
      else
        LCDcursor_m(LCD_m1, true, true);
      endif;
      LCDxy_M(LCD_m1, ParamDigit, 0);
      |
    input0..input3:
      LCDcursor_m(LCD_m1, false, false);
      ParamLong:=InputLongArray[ord(Modify)-ord(input0)];
      ParamLongToHex;
      LCDxy_M(LCD_m1, 0, 0);
      write(lcdout_m,ParamStr);
      LCDxy_M(LCD_m1, 0, 1);
      write(lcdout_m,InputHintStr);
      lcdout_m(char(ord(modify)+44));
      lcdout_m(#32);
      DirectoryDone:=false;
      |
    eval0..eval3:
      ExchangeFPGA;
      // Anzeige übermittelte "Adrresse"
      ParamLong:=ParamLongArray[ord(Modify)-ord(eval0)];
      ParamInt:=Integer(ParamLong);
      ParamIntToHex;
      LCDxy_M(LCD_m1, 0, 0);
      write(lcdout_m,ParamStr);
      write(lcdout_m,'adr');
      lcdout_m(char(ord(modify)+40));

      // Anzeige erhaltene "Daten"
      ParamLong:=InputLongArray[ord(Modify)-ord(eval0)];
      ParamInt:=Integer(ParamLong);
      ParamIntToHex;
      LCDxy_M(LCD_m1, 0, 1);
      write(lcdout_m,ParamStr);
      LCDxy_M(LCD_m1, 4, 1);
      write(lcdout_m,'data');

      LCDxy_M(LCD_m1, ParamDigit-4, 0);
      if SelectDigit then
        LCDcursor_m(LCD_m1, false, false);
        lcdout_m(#3);
      else
        LCDcursor_m(LCD_m1, true, true);
      endif;
      LCDxy_M(LCD_m1, ParamDigit-4, 0);
      DirectoryDone:=false;
      |
    filesel:
      SelectDigit:=false;
      LCDcursor_m(LCD_m1, false, false);
      if not DirectoryDone then GetDirectory; endif;
      ParamStr:=DirectoryArray[FileNum];
      ParamStrOnLCD;
      lcdout_m(#0);
      LCDxy_M(LCD_m1, 0, 1);
      write(lcdout_m,FileNumStr);
      DirectoryDone:=CardOK;
    |
  endcase;
end;
{$ENDIF} //Panel

function Checklimits:boolean;
// liefert TRUE wenn "Out of Range"
var myBool: Boolean;
begin
  myBool:=false;
  if FileNum>127 then  // Byte!
    FileNum:=0;
    myBool:=true;
  endif;
  if FileNum>63 then  // Byte!
    FileNum:=63;
    myBool:=true;
  endif;
  return(myBool);
end;


{###########################################################################}

// gerätespezifischer Parser-Teil

procedure ParseGetParam;
begin
  digits:=2;
  nachkomma:=1;
  case SubCh of
    0:
      |
    1:
      |
    4:
      ParamByte:=FileNum;
      WriteParamByteSer;
      return;
      |
    80:
      ParamByte:=ord(Modify);
      WriteParamByteSer;
      return;
      |
    89:
      ParamByte:=byte(IncRast);
      WriteParamByteSer;
      return;
      |
    251: // Fehlerzähler auslesen
      ParamInt:=integer(Errcount);
      |
    252: // Serielle Baudrate
      ParamByte:=EESerBaudReg;
      WriteParamByteSer;
      return;
      |
    253: // SerTest, gibt Input-String komplett und unverändert wieder aus
      write(Serout, SerInpStr);
      SerCRLF;
      return;
      |
    254: //Wert
      WriteChPrefix;
      write(serout,Vers1Str);
      SerCRLF;
      return;
      |
    250,255: //Status
      serprompt(NoErr,Status);
      return;
      |
  else
    serprompt(ParamErr,0);
    return;
  endcase;
  WriteParamSer;
end;

procedure ParseSetParam;
begin
  if Busyflag then
    serprompt(BusyErr,0);
    return;
  endif;
  ChangedFlag:=true;
  case SubCh of
    0:
      |
    1:
      |
    4:
      FileNum:=ParamByte;
      |
    80: // LCD Anzeige-Wert
      Modify:=tModify(ParamByte);
      |
    89: // Inkrementalgeber Impulse pro Rastung
      if EEunLocked then
        IncRast:=ParamInt;
        InitIncRast:=IncRast;
      else
        serprompt(LockedErr,0);
        return;
      endif;
      |
    251: //Error-Count
      ErrCount:=ParamInt;
      |
    252: // Serielle Baudrate, erst beim nächsten Reset aktiv
      if EEunLocked then
        EESerBaudReg:=ParamByte;
      else
        serprompt(LockedErr,0);
        return;
      endif;
      |
    250: // EEPROM Write Enable
      |
   else
    serprompt(ParamErr,0);
    return;
  endcase;
  EEunLocked:=false;
  if SubCh=250 then  // EEPROM Write Enable
    EEunLocked:=true;
  endif;

  if CheckLimits then
    serprompt(ParamErr,Status);
  else
    serprompt(NoErr,Status);
  endif;
end;

{###########################################################################}

// allgemeiner Parser-Teil

function Cmd2Index : tCmdWhich;
// Umsetzen eines Text-Befehls in Index-Eintrag der Befehlstabelle
var myCmdIndex: tCmdWhich;
begin
  ParamStr:= uppercase(ParamStr);
  for myCmdIndex := tError(0) to nop do
    if ParamStr = CmdStrArr[ord(myCmdIndex)] then
      return(myCmdIndex);
    endif;
  endfor;
  return(err);
end;

function ParseExtract:boolean;
//extrahiert ParamStr oder CmdStr aus SerInpStr,
//liefert true, wenn Parameter, sonst false, wenn Command
var myChar: char; myBool:boolean;
begin
  ParamStr:='';
  myBool:=false;
  while SerInpStr[SerInpPtr] =' ' do // Leerzeichen überspringen
    inc(serInpPtr);
  endwhile;
  if SerInpStr[SerInpPtr] in ['*'..'9'] then // Zahlen oder Wildcard, es wird ein Parameter
    myBool:=true;
    for i:=SerInpPtr to length(SerInpStr) do
      mychar:=SerInpStr[i];
      if myChar <='9' then
        append(mychar,ParamStr);
      else // Buchstabe oder sonstirgendwas, abbrechen
        SerInpPtr:=i;
        return(mybool);
      endif;
    endfor;
  else
    for i:=SerInpPtr to length(SerInpStr) do
      mychar:=SerInpStr[i];
      if myChar >= 'A' then
        append(mychar,ParamStr);
      else // Ziffer oder sonstirgendwas, abbrechen
        SerInpPtr:=i;
        return(mybool);
      endif;
    endfor;
  endif;
  return(mybool);
end;

procedure ParseSubCh;
//Überprüfen, ob Befehl oder Daten eingegangen und für uns, Parser-Vorbehandlung,
//wenn Busy => BusyErr
var
  GleichPos,
  CheckSpos, myCheckSum,CheckSumIn:byte;
  SubChOffset:byte;

  myChar:char;
  hasMainCh, hasCRC, isResult, isOmni, isRequest, isParam: Boolean;

begin
  if SerInpStr='' then
    serprompt(NoErr,0);
    return;
  endif;
  hasMainCh:=(pos(':',SerInpStr)>0); // Kanaltrenner-':'
  isRequest:=not (pos('=',SerInpStr)>0); // Abfrage
  myChar:=SerInpStr[1];
  isOmni:=(myChar='*');    // Omni-Flag
  isResult:=(myChar='#');  // Ergebnis-'#'
  if isResult then
    WriteSerInp;
    return;
  endif;
  SerInpPtr:=1;
  if hasMainCh then
    isParam:=ParseExtract;
    inc(SerInpPtr);
    if isOmni then // Omni-Befehl
      WriteSerInp; // weiterleiten
    else
      CurrentCh:=StrToInt(ParamStr);
    endif;
  endif;
  if (not isOmni) and (CurrentCh<>SlaveCh) then // nicht für uns
    WriteSerInp;
    return;
  endif;

// Befehl/Parameter ist für uns, ab hier eigentliche Behandlung
// Wenn vorhanden, XOR-Prüfsumme checken, Präfix-"$" zählt nicht mit!
  verbose:=(pos('!',SerInpStr)>0) OR (pos('?',SerInpStr)>0); // Ausführliche Antwort erwünscht
  CheckSpos:=pos('$',SerInpStr);
  hasCRC:=CheckSpos>0; // CheckSumIn-'$'
  if hasCRC then
    ParamStr:=copy(SerInpStr,CheckSpos+1, 2);
    CheckSumIn:=HexToInt(ParamStr); // erhaltene XOR-Checksumme
    myCheckSum:=0;
    for i:= 1 to CheckSpos-1 do
      myChar:=SerInpStr[i];
      myCheckSum:=myCheckSum xor ord(myChar);
    endfor;
    if myCheckSum <> CheckSumIn then
      serprompt(CheckSumErr,0);
      return;
    endif;
  endif;
//  SerInpStr:=trim(SerInpStr);
  SetSystimer(ActivityTimer, 25);
  LEDactivity:=low;
  
//Parse einzelnen Befehl

  if ParseExtract then
    SubChOffset:=0; // direkter SubCh-Aufruf
  else
    CmdWhich:=Cmd2Index; // Klartext übersetzen
    if CmdWhich = err then
      serprompt(SyntaxErr,0);
      return;
    endif;
    SubChOffset:=Cmd2SubChArr[ord(CmdWhich)];
    isParam:=ParseExtract; // SubCh-Parameter holen
  endif;
  SubCh:=StrToInt(ParamStr)+SubChOffset; //auf neuen SubCh umrechnen

  if isRequest then
    ParseGetParam;
  else
    SerInpPtr:=pos('=',SerInpStr)+1; // Set-'='
    if (SerInpPtr>1) and ParseExtract then
      ParamInt:=StrToInt(ParamStr);
      ParamByte:=byte(ParamInt);
    else
      serprompt(ParamErr,0);
      return;
    endif;
    ParseSetParam;
  endif;
end;

{###########################################################################}
// Regelmäßig aufgerufen, aber nicht im Interrupt

procedure Chores;
// Regelmäßig außerhalb des Interrupts aus CheckSer heraus aufgerufen:
begin
  ExchangeFPGA;
end;

procedure CheckSer;
var
myChar: char;
begin
  while SerInp_TO(myChar,2) do // 20 ms Timeout
//    serout(mychar); // Echo Character
    if myChar in [#32..#127] then // nur 7-Bit-ASCII ohne Controls
      append(myChar,SerInpStr);
    endif;
    if mychar=#8 then // Backspace
      setlength(SerInpStr, length(SerInpStr)-1);
    endif;
    if myChar=#13 then
      ParseSubCh;           //Befehl interpretieren, wenn für uns
{$IFDEF debug}
      if ErrFlag then
        ErrFlag:=false;
        LCDxy_M(LCD_m1, 0, 1);
        write(lcdout_m,inttostr(errcount));
      else
        WriteSerInp;
      endif;
{$ENDIF}
      SerInpStr:='';
    endif;
  endwhile;
  Chores;
end;

procedure CheckDelay(myDelay:byte);
// Delay mit serieller Abfrage in 20-ms-Schritten
var mycount:byte;
begin
  for mycount:=1 to mydelay do
    CheckSer;
  endfor;
end;

{###########################################################################}

procedure initall;
//nach Reset aufgerufen
begin
  DDRB:=  DDRBinit;            {PortB dir}
  PortB:= PortBinit;           {PortB}
  DDRC:=  DDRCinit;            {PortC dir}
  PortC:= PortCinit;           {PortC}
  DDRD:=  DDRDinit;            {PortD dir}
  PortD:= PortDinit;           {PortD}

//  serBaud(EESerbaud);        // nur mit 644

  ADMUX:= ADMUX OR %11000000;  {Internal ADC Reference}

  PatchCopyFromEE;       // Optionen-Defaults
  
  udelay(1);
  SlaveCh:=(not PinD) shr 5;
  LEDactivity:= low;

{$IFDEF Panel}
  if LCDsetup_M(LCD_m1) then
    LCDcursor_M(LCD_m1, false, false);
    LCDCharSet_M(LCD_m1, #0, $01, $03, $07, $0F, $07, $03, $01, $00); {"<" Cursor}
    LCDCharSet_M(LCD_m1, #1, $01, $03, $05, $09, $05, $03, $01, $00); {"<" Cursor hohl}
    LCDCharSet_M(LCD_m1, #2, $01, $02, $05, $0A, $05, $02, $01, $00); {"<" Cursor IncrValeinstellung}
    LCDCharSet_M(LCD_m1, #3, $15, $00, $11, $00, $11, $00, $15, $00); {Kästchen hohl}

    LCDpresent:=true;
    write(lcdout_m,Vers3Str);
    LCDxy_M(LCD_m1, 0, 1);
    if EEinitialised <> $AA55 then
      write(lcdout_m, EEnotProgrammedStr);
    else
      write(lcdout_m,AdrStr);
      lcdout_m(char(SlaveCh+48));
    endif;
  endif;
{$ENDIF}  //Panel

  mdelay(1000);
  if SlaveCh>0 then
    for i := 0 to SlaveCh-1 do
      LEDactivity:= not LEDactivity;
      mdelay(150);
      LEDactivity:= not LEDactivity;
      mdelay(150);
    endfor;
  endif;
  LEDactivity:= high;

  Status:=0;
  EnableInts;
//  MP3off;
  while serstat do
    i:=SerInp;
  endwhile;
{$IFDEF Panel}
  IncrCount4start;
  SetIncrVal4(0,0);
  IncrValue:=GetIncrVal4(0);
  OldIncrValue:= IncrValue;
  IncrDiff:=0;
  SelectDigit:=false;
{$ENDIF}  //Panel
  Modify:=filesel;
  FirstTurn:=true;
  SubCh:=254;
  WriteChPrefix;
  write(serout,Vers1Str);
  if EEinitialised <> $AA55 then
    write(Serout, EEnotProgrammedStr);
  endif;
  SerCRLF;
  ClearDirectory;
  GetDirectory;
  FileNum:=InitFileNum;
{$IFNDEF EFM01}
  if CardOK then FPGAload; endif;
  SetFrequFPGA;
{$ENDIF}
  CurrentCh:=255; // erstmal keine Reaktion
  errcount:=0;
  ChangedFlag:=true;
  ParamDigit:=7;
end;

{###########################################################################}

procedure ChangeParam(index:byte);
// ändert ParamLongArray[index] mit IncrDiff-Werten an Stelle ParamDigit
// oder ParamDigit selbst, wenn SelectDigit=true
begin
  if SelectDigit then
    ParamDigit:=ParamDigit+IncrDiffByte;
    if (Modify in [eval0..eval3]) and (ParamDigit < 4) then
      ParamDigit:=4; // nur letzte 4 Stellen änderbar
    endif;
    if ParamDigit>127 then
      ParamDigit:=0;
    endif;
    if ParamDigit>7 then
      ParamDigit:=7;
    endif;
  else
    FPGAparam:=ParamLongArray[index];
    FPGAparam:=FPGAparam+(LongInt(IncrDiff)*IncrDigitArray[ParamDigit]);
    ParamLongArray[index]:=FPGAparam;
// wird auch laufend in Chores oder SollwerteOnLCD ausgetauscht, hier sofort
    FPGAreg:=index;
    SendFPGA8;
    SendFPGA32;
  endif;
end;

begin
  Initall;
//Hauptschleife
  loop
    CheckSer;  // Serinp parsen
    if isSystimerZero(ActivityTimer) then
      LEDactivity:=high; // LED aus
    endif;
{$IFDEF Panel}
    if LCDpresent then
      IncrValue:=GetIncrVal4(0);
// Inkrementalgeber löst in Zweierschritten auf, deshalb der Umstand
      if (IncrValue<>OldIncrValue) then
        SetSystimer(ActivityTimer, 25);
        LEDactivity:=low;
        IncrDiff:=IncrDiff + integer(IncrValue-OldIncrValue);
        OldIncrValue:= IncrValue;
        SetSystimer(IncrTimer, 20);
        if abs(IncrDiff)>= IncRast then // erst wenn n Impulse gezählt
          ChangedFlag:=true;
          BusyFlag:=true;
          IncrDiff:=IncrDiff div IncRast;
// verbesserte Beschleunigungsfunktion, hier für 20 ms Schleifenzeit
          IncrAccInt10:=sgn(IncrDiff);     // hier als Sign benutzt
          IncrDiffByte:=byte(IncrDiff);    // Byte ohne Beschleunigung
          IncrDiff:=IncrAccInt10 * IncrAccArray[byte(abs(IncrDiff))];
{$IFDEF DEBUG}
   write(Serout, 'IncDiff=');
   write(Serout, IntToStr(IncrDiff));
   SerCRLF;
{$ENDIF}
          IncrAccInt10:=IncrDiff*10; // 40x Differenz mit Beschleunigung
          if SelectDigit then
            SetSystimer(DisplayTimer, 75); // Auswahl-Kästchen stehenlassen
          endif;
          case Modify of
            param0..param3:
              ChangeParam(ord(Modify));
              |
            input0..input3:
              ChangeParam(ord(Modify)-ord(input0));
              ChangedFlag:=true;   // Anzeige erzwingen
              |
            eval0..eval3:
              if ParamDigit < 4 then
                ParamDigit:=4; // nur letzte 4 Stellen änderbar
              endif;
              ChangeParam(ord(Modify)-ord(eval0));
              ChangedFlag:=true;   // Anzeige erzwingen
              |
            filesel:
              SelectDigit:=false;
              FileNum:=FileNum+IncrDiffByte;
              CheckLimits;
              SubCh:=3;
              ParseGetParam;
              |
          endcase;
          IncrDiff:=0; // für nächsten Durchlauf
          SollwerteOnLCD;
          FirstTurn:=false;
        endif;
      endif;
      Checkdelay(2); // wichtig für Beschleunigungsfunktion
      ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
      if ButtonTemp <> $FF then
        Checkdelay(1);
        ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
        if ButtonTemp <> $FF then
          ChangedFlag:=true;
          BusyFlag:=true;
          if (not ButtonEnter) then
            serprompt(noErr,Status+67); // UserSRQ-Flag=64, +3
            if Modify=filesel then
              if CardOK then
                FPGAload;
                SendParamArrayFPGA;
              endif;
            else
              SelectDigit:=not SelectDigit;
              SetSystimer(DisplayTimer, 75);
            endif;
          endif;
          if not ButtonUp then
            serprompt(noErr,Status+65); // UserSRQ-Flag=64, +1
            inc(Modify);
          endif;
          if not ButtonDown then
            serprompt(noErr,Status+66); // UserSRQ-Flag=64, +2
            dec(Modify);
          endif;
          SollwerteOnLCD;
          repeat
            Checkdelay(1);
            ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
          until ButtonTemp=$ff;
          FirstTurn:=false;
        endif;
      endif;
    endif;
    if isSystimerZero(IncrTimer) then
      SetSystimer(IncrTimer, 20);
      if not FirstTurn then
        serprompt(noErr,Status+64); // UserSRQ-Flag=64, freigegeben
      endif;
      FirstTurn:=true;
    endif;
{$ENDIF}  //Panel

    if isSystimerZero(DisplayTimer) and LCDpresent then
      SetSystimer(DisplayTimer, 25);
{$IFDEF Panel}
      SelectDigit:=false;
      BusyFlag:=false;
      SollwerteOnLCD;
//      ChangedFlag:=false;
{$ENDIF}  //Panel
    endif;
  endloop;
end FPGA.

