program FPGA;
(*

(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

29.04.2009 #2.61 Checkser bereinigt, PanelTimer eingeführt
                 Panel-LED ist jetzt nur Seriell-Indikator (Panel abgeschaltet)
27.04.2009 #2.60 Latenzzeiten FPGA-Core <-> AVR stark verringert
                 Interrupt-Handling verbessert
                 INI/CFG-Default-Filename jetzt auch in Klartext möglich
20.03.2008 #0.10 Übernahme aus SQG-Version

*)

//Defines aktivieren durch Entfernen des 1. Leerzeichens!

{$OPTI SMARTLINK_ONLY}

{$NOSHADOW}
{$W- Warnings}            {Warnings off}
{$TYPEDCONST OFF}
{$DEBDELAY}
{$OPTI_QUICK}
{ $DEFINE debug}         // Fehler-Anzeige auf LCD
{$DEFINE Panel}         // WICHTIG: Bedien-Panel vorhanden, bindet LCD mit ein
{$DEFINE Parser}         // für Testkompilierung auskommentieren

Device = mega644, VCC = 5;


Import SysTick, FAT16, TWImaster, SerPort;  // ADCport,
{$IFDEF Panel}
Import IncrPort4, LCDmultiPort; //, LCDGraphic;
{$ENDIF}

From System Import LongWord, LongInt, float;
// From LCDGraphic Import CharSet;     {block CharSet, pixels}

Define
  ProcClock      = 16000000;        {Hertz}
  TWIpresc       = TWI_BR400;

{$IFDEF Panel}
  LCDmultiPort   = I2C_TWI;
  LCDTYPE_M      = 44780; // 44780 oder 66712;
  LCDrows_M      = 4;              //rows
  LCDcolumns_M   = 20;             //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       = 32, iData;

  StackSize      = $0100, iData;
  FrameSize      = $0180, iData;

//  ADCchans = [3,4], iData, int2;
//  ADCpresc       = 16;
  FAT16          = MMC_SPI, iData;
  F16_MMCspeed   = fast;
  F16_FileHandles   = 2;
  F16_Dirlevels     = 1;


USES
  UFAT16;


Implementation

type
  tModify   = (pulse_low, pulse_high, pulse_hz, pulse_pwm, dds_hz, dds_os, filesel);
  tError = (NoErr, UserReq, BusyErr, FileErr, SyntaxErr, ParamErr, LockedErr,ChecksumErr);

{--------------------------------------------------------------}
const


  DDRBinit:byte                   = %10111011;            {PortB dir }
  DDRBinitRdRTC:byte               = %10011011;            {PortB dir wenn RTC gelesen wird}
  PortBinit:byte                   = %10111010;            {PortB }

  DDRCinit:byte                    = %01110000;            {PortC dir, 0..1=Incr4 }
  PortCinit:byte                   = %11001111;            {PortC }

{ Jumper und LEDs PortBits }
  DDRDinit:byte                    = %00001100;            {PortD dir, 0..1=Serial }
  PortDinit:byte                   = %11111100;            {PortD }


  BtnInPortReg:byte                = @PinD;   {Taster-Eingangsport}
{$TYPEDCONST OFF}
  LEDOutPort                 = @PortD;   {LED-Port}
  FPGAport                   = @PortB;   {FPGA-SPI-Port}
  FPGApin                    = @PinB;    {FPGA-SPI-Port}
  ConFPGABitPort             = @PortC;   {FPGA-Config-Port}
  ConFPGABitPin              = @PinC;   {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; //INT2 ausgelöst vom FPGA
  b_REGSEL                   = 1; //FPGA Registerauswahl
  b_AUX                      = 0; //Testpin Trigger LA

  c_CDSW                     = 3; //Card insert switch, low = inserted
  c_DATA                     = 4; //Daten FPGA Configuration
  c_CCLK                     = 5; //Takt FPGA Configuration
  c_PROG                     = 6; //Prog-Leitung FPGA Configuration
  c_DONE                     = 7; //Done-Leitung FPGA Configuration


//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = '1.01 [PULSEGEN by CM/c''t 5/2016]';    //Vers1
  WelcomeStr1                = 'VPG 1.01 cm 5/2016';
  WelcomeStr2                = 'Pulse and DDS Frequ';
  WelcomeStr3                = 'Generator';

  ErrStrArr      : array[0..7] of String[8] = (
    '[OK]','[SRQUSR]','[BUSY]','[FILERR]','[CMDERR]','[PARERR]',
    '[LOCKED]','[CHKSUM]');

  FaultStrArr : array[0..3] of String[10] = (
    '[NOTFOUND]',
    '[NOCARD]',
    '[CONFERR]',
    '[WRITERR]');

  EEnotProgrammedStr         = 'EEPROM EMPTY! ';
  AdrStr                     = 'Adr ';
  ParamHintStr               = 'Param  ';
  InputHintStr               = 'Value  ';
  EvalHintStr                = 'DataAdr';
  RunStr1                    = 'BTN:STOP';
  RunStr2                    = 'INI exec';
  ErrSubCh:Integer           = 255;

  CmdStrArr: array[0..15] of String[3] = (
  'STR', // 255
  'IDN', // 254
  'VAL', // 0..9999

  'CFG', // 240 File Auswahl
  'LST', // 241 Dir Listing
  'DIR', // 241 Dir Listing
  'FNM', // 242 FileNumber, Anzahl der Dateien
  'FNA', // 243 Data FileName (alpha)
  'FDL', // 244 Delete DataFileName (alpha)
  'FQU', // 249 File Query, OK/Exists
  'HEX', // 88  Hex-Mode bei Zahlenausgeben seriell [$12345678]
  'WEN', // 250 Write enable
  'ERC', // 251 ErrCount seit letztem Reset
  'SBD', // 252 SerBaud UBRR-Register mit U2X=1
  'REM', // 253
  'NOP');

  Cmd2SubChArr: array[0..15] of integer = (
  255, 254,
  0,
  240, 241, 241, 242, 243, 244, 249,
  88, 250, 251, 252, 253, 253);

  cmdAnzahl=15; // letzter Eintrag, statt tCmdwhich
  cmdErr=cmdAnzahl+1; // Error, Statt tCmdwhich

  high: boolean                       = true;
  low: boolean                        = 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..9] of LongInt = (
    1000000000,
    100000000,
    10000000,
    1000000,
    100000,
    10000,
    1000,
    100,
    10,
    5 );


structconst

//Default-EEPROM-Werte:
{$EEPROM}
  dummy:LongInt=0;
  EEinitialised:word=$AA55;
  InitIncRast:Integer = 4;
  EESerBaudReg:byte=51;
  InitHexMode:boolean=false;

  OptionArray:Array[0..19] of Integer =  (
  255,  // 0 BIN-File Default, 255=keine
  255,  // 1 INI-File Default
  0,    // 2 AutoIncSel, default 0
  128,  // 3 AutoIncReg, default 128
  0,    // 4 Result MainCh
  10,   // 5 Result SubCh
  7,    // 6 InitDigits
  255,  // 7 InitNachkomma, 255=auto
  9,    // 8 EEMainCh
  500,  // 9 EEgetTimeOut
  64,    // 10 Core Rx FPGA to AVR intern
  65,    // 11 Core Tx AVR to FPGA intern
  0,    // 12
  0,
  0,
  0,
  0,
  0,
  0,
  0);

  InitParamLongArray: array[0..7] of LongInt = (
  900000,   // Pulse Low in ns = 900 us
  100000,   // Pulse High in ns = 100 us
  100000,   // Frequ in 1/100 Hz = 1 kHz
  50,       // Duty %
  100000,   // DDS Frequ in 1/100 Hz = 1 kHz
  100000,   // DDS Oneshot Pulse High in ns = 100 us
  0,
  0);

{--------------------------------------------------------------}

var
{$EEPROM}
  EEbinFileNum[@OptionArray+0]:integer;      // BIN-FileNum
  EEiniFileNum[@OptionArray+2]:integer;      // INI-FileNum -1 (kein INI-File)
  EEAutoIncSel[@OptionArray+4]:integer;      // Default AutoIncSel
  EEAutoIncReg[@OptionArray+6]:integer;      // Default AutoIncReg
  InitDigits[@OptionArray+12]:integer;
  InitNachkomma[@OptionArray+14]:integer;
  EEMainCh[@OptionArray+16]:integer;
  EEgetTimeOut[@OptionArray+18]:integer;

{$DATA} {Schnelle Register-Variablen}
  i, k, m  : Byte;  // k in HW-Prozess benutzt
  s        : Int8;

{$PDATA}  {Langsamere SRAM-Variablen}
  LEDactivity[LEDOutPort, 2]    : bit; {Bit 2 LED Remote-Activity}
  LEDswitch[LEDOutPort, 3]      : bit; {Bit 3 LED über dem Display}
  FPGA_PROG[ConFPGABitPort, c_PROG]: bit;  // Prog-Leitung FPGA Configuration
  FPGA_DONE[ConFPGABitPin,  c_DONE]: bit;  // Done-Leitung FPGA Configuration
  SD_CDSW[ConFPGABitPin,  c_CDSW]: bit;    // Card Insertion Switch, low = inserted
  F_Aux[FPGAport, b_AUX]:bit;    // Testpin fü LA-Trigger
  F_Int[FPGAport, b_INT2]:bit;   // Daten-abholen-Interrupt

{$IDATA}  {Langsamere SRAM-Variablen}
  aChar:char;
  MainCh: byte;
  SwitchState: Byte; //Zwischenspeicher für 4094 SR

  ButtonTemp,RangeTemp: Byte; // invertiert - low=on!
  ButtonUp[@ButtonTemp, 5]  : bit;
  ButtonDown[@ButtonTemp, 4]  : bit;
  ButtonEnter[@ButtonTemp, 3]  : bit;

  FPGAreg: byte;   // FPGA-Registerauswahl
  FPGAsendLong,FPGAreceiveLong : LongInt;  // FPGA-Registerwert Langwort
  FPGAsendLong0[@FPGAsendLong+0]:byte;
  FPGAsendLong1[@FPGAsendLong+1]:byte;
  FPGAsendLong2[@FPGAsendLong+2]:byte;
  FPGAsendLong3[@FPGAsendLong+3]:byte;
  FPGAsendWord, FPGAreceiveWord:Word;
  FPGAsendWord0[@FPGAsendWord+0]:byte;
  FPGAsendWord1[@FPGAsendWord+1]:byte;
  FPGAsendByte,FPGAreceiveByte: byte;  // FPGA-Registerwert Byte
  DisplayTimer:Systimer8;
  SelectTimer, PanelTimer:Systimer8;

  CmdWhich: byte; // tcmdwhich nicht mehr benutzt
  CmdStr: string[3];
  SubCh: Integer;
  CurrentCh       : byte;
  OmniFlag,verbose: boolean;
  Param          : LongInt;
  ParamInt:Integer;
  ParamAlpha     : boolean; // Flag für Zeichenfolge hinter "="
  ParamLong:LongInt;
  ParamByte :Byte;

  DigitSelectFreq, DigitSelectPulse: Int8;
  DispDigitArray: array[0..15] of Int8;
  DispDigitStr: String[15];
  CursorOffset: Byte;
  ChangeFac: LongInt;

  ParamLongArray: array[0..7] of LongInt;
  PulseLowTimeLong[@ParamLongArray+0]:LongInt;
  PulseHighTimeLong[@ParamLongArray+4]:LongInt;
  PulseFrequLong[@ParamLongArray+8]:LongInt;
  PulseWidthLong[@ParamLongArray+12]:LongInt;
  DDSfreqLong[@ParamLongArray+16]:LongInt;
  DDSosLong[@ParamLongArray+20]:LongInt;

  PulseTimeLong: LongInt;

  InputLongArray: array[0..7] of LongInt;
  SerInpStr : String[63];
  SerInpPtr:byte;
  
  LCDpresent: boolean;
  Modify: tModify;
  IncrValue,OldIncrValue: LongInt;
  SelectDigit,FirstTurn:Boolean;
  IncrDiff,IncrAccInt10, IncRast:Integer;
  IncrDiffShort: Int8;
  digits, nachkomma:byte;
  ChangedFlag:Boolean;


//für Parser
  ParamStr:string[40]; // auch für Display
  isInteger,Request,hexmode:boolean;
  Status:byte;  // 0..3 Fehlernummer
  BusyFlag[@Status, 7]  : bit;
  UserSRQFlag[@Status, 6]  : bit;
  FileErrorFlag[@Status, 5]  : bit;  //
  EEUnlocked[@Status, 4]  : bit; // EEPROM-unlocked-Flag

{  FaultStrArr : array[0..3] of String[10] = (
    '[READERR]',
    '[NOCARD]',
    '[CONFERR]',
    '[WRITERR]');  }

  FaultFlags, ButtonNumber:byte;
  ReadErr[@FaultFlags, 0]  : bit;
  NoCard[@FaultFlags, 1]  : bit;
  ConfErr[@FaultFlags, 2]  : bit;
  WriteErr[@FaultFlags, 3]  : bit;
  CardOK:boolean;
  ErrCount:integer;
  ErrFlag:boolean;
  FaultTimerByte:Byte;
  NoINIfound:Boolean;  // Flag für INI-Dateien
  byteCount: LongInt;
  byteCount3[@byteCount+3]:byte;
  byteCount2[@byteCount+2]:byte;
  byteCount1[@byteCount+1]:byte;
  byteCount0[@byteCount]:byte;

  DirectoryArray:array[0..31] of string[12];
  DirectoryDone, FlashOK:Boolean;
  BinFile : file of byte;
  InitFile,MemFile,DataFile : file of text;
  DatFileName: string[12];
  INITfileIsOpen:Boolean;
  LastBinFileNum, FileNum, FileAnzahl,FileNumTemp : Int8;
  AutoIncReg:byte;         // Auto-Increment-Register, hierhin werden .DAT-Datenblöcke geschrieben
  AutoIncSel:byte;         // Auto-Increment-Select
  AutoIncWidth:byte;       // AI Breite in Bytes, 1, 2 oder 4

  DOStime, DOSdate:Word;
  FPGAbyte, FlashByte: byte;
  pDummy:byte;

  SPIsema:Boolean;
  CoreRxSubCh: byte;  // Daten vom FPGA
  CoreTxSubCh: byte;  // Daten zum FPGA
  IRQrBufPtr,rBufPtr, IRQtemp:byte;
  AutoIncBlockEnd,AutoIncBlockStart: LongInt;
  IRQrBuf[$e00]:Array[0..255] of char;
  BlockTable[$f00]:Table[0..255] of byte;
  BlockArray[@BlockTable]:Array[0..255] of byte; // für FAT16
  BlockArray2[@BlockTable]:Array[0..127] of Word; // für FAT16
  BlockArray4[@BlockTable]:Array[0..63] of LongInt; // für FAT16
  BlockStr[@BlockTable]:String[255];



{###########################################################################}
{$I FPGA-HW.pas}
{$IFDEF LCDGRAPH}
{$I FPGA-LCDG.pas}
{$ENDIF}
procedure serDummy;
begin
  serout('#');
end;



{###########################################################################}

//allg. Menü-Prozeduren

procedure SerCRLF;
begin
  SerOut(#$0D);
  SerOut(#$0A);
end;

procedure WriteChPrefix;
begin
  SerOut('#');
  SerOut(char(MainCh+48));
  SerOut(':');
  write(SerOut, IntToStr(SubCh));
  SerOut('=');
end;

procedure WriteSerInp;
begin
  write(SerOut, SerInpStr);   // Befehl weiterreichen
  SerCRLF;
end;

procedure SerPrompt(myErr:tError);
var myFault, myStatus:Byte;
//Error-Meldung und Status-Request-Antwort,
//Status Bit 7=Busy, 6=UserSRQ, 5=OverLoad, 4=WriteEnable, 3..0=Fault/Error
begin
  SubCh:=ErrSubCh; // Fehler-Kanal
  WriteChPrefix;
  FileErrorFlag:=(FaultFlags<>0);
  if myErr = UserReq then
    myStatus:=Status or ButtonNumber or 64;
  else
    if FileErrorFlag then
      myStatus:=Status or FaultFlags;
    else
      myStatus:=Status or ord(myErr);
      if myErr <> noErr then
        inc(ErrCount);
      endif;
    endif;
  endif;
  write(SerOut, ByteToStr(myStatus));
  if FileErrorFlag then
    myFault:=FaultFlags;
    for i := 0 to 3 do
      if (myFault and 1) = 1 then
        SerOut(#32);
        write(SerOut,FaultStrArr[i]);
      endif;
      myFault:=myFault shr 1;
    endfor;
  endif;
  SerOut(#32);
  write(SerOut,ErrStrArr[ord(myErr)]);
  SerCRLF;
end;

{###########################################################################}

function ButtonPressed:Boolean;
begin
  ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
  if ButtonTemp=$ff then
    return(false);
  else
    return(true);
  endif;
end;


{$I pulsegen_files.pas}

{###########################################################################}

//EEPROM-Routinen

procedure InitVars;
//Frequenz und Settings aus EEPROM holen
begin
  hexmode:=InitHexmode;
  for i :=0 to 7 do
    ParamLongArray[i]:=InitParamLongArray[i];
  endfor;
  IncRast:=InitIncRast;
  digits:=byte(InitDigits);
  nachkomma:=byte(InitNachkomma);
  ParamStr:='0.0';
  ClearDirectory;
  AutoIncReg:=byte(EEAutoIncReg);
  AutoIncSel:=byte(EEAutoIncSel);
  AutoIncWidth:=4;
  IRQrBufPtr:=0;
  rBufPtr:=0;
end;

{###########################################################################}
//Ausgabe-Prozeduren

{###########################################################################}
// Regelmäßig aufgerufen, aber nicht im Interrupt

procedure Chores;
// Regelmäßig außerhalb des Interrupts aus CheckSer heraus aufgerufen:
begin
  if SD_CDSW and DirectoryDone then
    ClearDirectory;
  endif;
  if (not SD_CDSW) and (not DirectoryDone) then
    mdelay(200);
    GetDirectory;
  endif;
end;

procedure CheckSer;
var
myChar: char;
begin
  if serStat then //
    myChar:=serInp;
    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
      LEDactivity:=low; // LED aus
      FaultFlags:=0;
      ParseSubCh;               // Befehl interpretieren, wenn für uns
      SerInpStr:='';
      setSystimer(PanelTimer,10);    // keine Panel-Abfrage während der nächsten 100 ms
      SetSystimer(DisplayTimer, 25); // kein Display-Refresh während der nächsten 250 ms
    endif;
  endif;
  if isSystimerZero(PanelTimer) then
    Chores;
    mdelay(1); // hier treffen max. 10 Zeichen seriell ein
    LEDactivity:=high; // LED aus
  endif;
end;

procedure CheckDelay(myDelay:byte);
// Delay mit serieller Abfrage in 1-ms-Schritten
var mycount:byte;
begin
  for mycount:=1 to mydelay do
    CheckSer;
  endfor;
end;

// #############################################################################
// #############################################################################

procedure SendCorrectedLongToFPGA(my_reg: byte);
var my_long: LongInt;
begin
  FPGAreg:=my_reg;
  if FPGAsendLong <= 0 then
    FPGAsendLong:= 1;
  endif;
  SendFPGAreg;
  SendFPGA32;
end;

procedure SendFrequPWM;
var my_long: LongInt; my_ns, my_f: float;
begin
  my_f:= float(PulseFrequLong) / 100;
  my_ns:=  2000000 * (100 - float(PulseWidthLong)) / my_f;
  my_long:= round(my_ns);
  FPGAsendLong:= my_long  - 1;
  SendCorrectedLongToFPGA(22);

  my_f:= float(PulseFrequLong) / 100;
  my_ns:=  2000000 * float(PulseWidthLong) / my_f;
  my_long:= round(my_ns);
  FPGAsendLong:= my_long  - 1;
  SendCorrectedLongToFPGA(23);

  FPGAsendLong:= 1; // Pulsegen to Mux Out
  FPGAreg:=25;
  SendFPGAreg;
  SendFPGA32;
end;

procedure SendDDS;
var my_long: LongInt; my_f: float;
begin
  // DDS-Faktor 21,47483648  = 1/0,04656612873077392578125
  my_f:= float(DDSfreqLong) * 0.2147483648;  // * 1/100
  if DDSfreqLong < 1000000 then // 10000 Hz
    FPGAsendLong:= 3; // div 8 Prescaler
    my_f:= my_f * 8;
  else
    FPGAsendLong:= 0; // no Prescaler
  endif;
  FPGAreg:=21;  // DDS-Prescaler
  SendFPGAreg;
  SendFPGA32;
  
  FPGAsendLong:= round(my_f);
  FPGAreg:=20;  // DDS
  SendFPGAreg;
  SendFPGA32;
  FPGAsendLong:= DDSosLong div 5 - 1; // in 5-ns-Schritten
  SendCorrectedLongToFPGA(24);    // Oneshot-Timer

  if Modify = dds_os then
     FPGAsendLong:= 2; // OneShot to Mux Out
  else
     FPGAsendLong:= 0; // DDS to Mux Out
  endif;
  FPGAreg:=25;
  SendFPGAreg;
  SendFPGA32;
end;


procedure SendPulseParams;
var my_long: LongInt;
begin
  FPGAsendLong:= PulseLowTimeLong div 5 - 1; // in 5-ns-Schritten
  SendCorrectedLongToFPGA(22);
  FPGAsendLong:= PulseHighTimeLong div 5 - 1;
  SendCorrectedLongToFPGA(23);

  FPGAsendLong:= 1; // Pulsegen to Mux Out
  FPGAreg:=25;
  SendFPGAreg;
  SendFPGA32;
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}
  EIMSK := EIMSK or %00000100;  // external IRQ F_INT auf INT2
  EICRA := EICRA or %00100000;  // Bit 4 und 5 = INT2 falling Edge

  InitVars;       // Optionen-Defaults
  
  udelay(1);
  MainCh:=(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, $00, $04, $04, $0E, $0E, $1F, $1f, $00); // Up Arrow
    LCDCharSet_M(LCD_m1, #1, $1F, $1F, $0E, $0E, $04, $04, $00, $00); // Down Arrow
    LCDCharSet_M(LCD_m1, #2, $01, $02, $05, $0A, $05, $02, $01, $00); {"<" Cursor IncrValeinstellung}
    LCDCharSet_M(LCD_m1, #3, $01, $03, $05, $09, $05, $03, $01, $00); {"<" Cursor hohl}
    LCDCharSet_M(LCD_m1, #4, $15, $00, $11, $00, $11, $00, $15, $00); {Kästchen hohl}
    LCDCharSet_M(LCD_m1, #5, $10, $18, $1C, $1E, $1C, $18, $10, $00); // ">" Cursor
    LCDCharSet_M(LCD_m1, #6, $01, $03, $07, $0F, $07, $03, $01, $00); {"<" Cursor}

    LCDpresent:=true;
    write(lcdout_m,WelcomeStr1);
    LCDxy_M(LCD_m1, 0, 1);
    write(lcdout_m,WelcomeStr2);
    LCDxy_M(LCD_m1, 0, 2);
    write(lcdout_m,WelcomeStr3);
    LCDxy_M(LCD_m1, 0, 3);
    if EEinitialised <> $AA55 then
      write(lcdout_m, EEnotProgrammedStr);
    else
      write(lcdout_m,AdrStr);
      lcdout_m(char(MainCh+48));
    endif;
  endif;
{$ENDIF}  //Panel

  LEDactivity:= low;
  mdelay(1500);
  LEDactivity:= high;
  Status:=0;
  EnableInts;
  while serstat do
    i:=SerInp;
  endwhile;
  IncrCount4start;
  SetIncrVal4(0,0);
  IncrValue:=GetIncrVal4(0);
  OldIncrValue:= IncrValue;
  IncrDiff:=0;
  SelectDigit:=false;
  Modify:=filesel;
  FirstTurn:=true;
  SubCh:=254;
  WriteChPrefix;
  write(SerOut,Vers1Str);
  if EEinitialised <> $AA55 then
    write(SerOut, EEnotProgrammedStr);
  endif;
  SerCRLF;

  ClearDirectory;
  Chores; // Files ggf. laden

  CurrentCh:=255; // erstmal keine Reaktion
  errcount:=0;
  ChangedFlag:=true;
  ButtonTemp := $FF;
  GetDirectory;
  if CardOK then
    FileLoad('MAIN.BIT');
    if FaultFlags<>0 then
      FileNum:=0;
      serprompt(FileErr);
    else
      WriteDirEntry;
    endif;
  endif;
  setSystimer(PanelTimer,1);
  SetSystimer(DisplayTimer, 1);
  SetSystimer(SelectTimer, 1);
  DigitSelectFreq:=4;  // 1kHz-Stelle
  DigitSelectPulse:=4; // 10us-Stelle

  SendDDS;
  SendPulseParams;
  Modify:= pulse_low;
  ChangedFlag:=true;
end;

// #############################################################################
// #############################################################################

procedure PulseLongToDispStr(insert_selbox: boolean);
begin
  ParamStr:= LongToStr(PulseTimeLong:10:'0');
  if insert_selbox then
    ParamStr[DigitSelectPulse + 1]:= #4;  // Kästchen hohl
  endif;
  DispDigitStr:= copy(ParamStr,1,1) + #39 + copy(ParamStr,2,3) + #39 + copy(ParamStr,5,3) + #39 + copy(ParamStr,8,3);
  CursorOffset:= byte(DigitSelectPulse);
  if DigitSelectPulse > 0 then
    inc(CursorOffset);
  endif;
  if DigitSelectPulse > 3 then
    inc(CursorOffset);
  endif;
  if DigitSelectPulse > 6 then
    inc(CursorOffset);
  endif;
end;

procedure PulseChange;
var my_limit: Boolean;
begin
  LCDcursor_m(LCD_m1, false, false);
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,'Pulse Gen (time)');
  LCDclrEOL_M(LCD_m1);

  LCDxy_M(LCD_m1, 0, 1);
  write(lcdout_m,'Hi: ');
  PulseTimeLong:= PulseHighTimeLong;
  my_limit:= PulseTimeLong <= 0;
  if Modify = pulse_high then
    lcdout_m(#5);
    PulseLongToDispStr(SelectDigit);
  else
    lcdout_m(#32);
    PulseLongToDispStr(false);
  endif;
  write(lcdout_m, DispDigitStr);

  LCDxy_M(LCD_m1, 0, 2);
  write(lcdout_m,'Lo: ');
  PulseTimeLong:= PulseLowTimeLong;
  my_limit:= my_limit or (PulseTimeLong <= 0);
  if Modify = pulse_low then
    lcdout_m(#5);
    PulseLongToDispStr(SelectDigit);
  else
    lcdout_m(#32);
    PulseLongToDispStr(false);
  endif;
  write(lcdout_m, DispDigitStr);
  

  LCDxy_M(LCD_m1, 0, 3);
  if my_limit then
    write(lcdout_m,'Pulse Range Limit!');
  else
    write(lcdout_m,'Time s  ms  us  ns');
  endif;
  LCDclrEOL_M(LCD_m1);

  LCDcursor_m(LCD_m1, false, true);
  LCDxy_M(LCD_m1, CursorOffset + 5, 2- ord(Modify));
end;

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

procedure FrequLongToDispStr(insert_selbox: boolean);
begin
  ParamStr:= LongToStr(ParamLong:10:'0');
  if insert_selbox then
    ParamStr[DigitSelectFreq + 1]:= #4;  // Kästchen hohl
  endif;
  if Modify = pulse_hz then
    if ParamLong >= 100000 then
      ParamStr[9]:= '*';
    endif;
    if ParamLong >= 1000000 then
      ParamStr[8]:= '*';
    endif;
    if ParamLong >= 10000000 then
      ParamStr[7]:= '*';
    endif;
    if ParamLong >= 100000000 then
      ParamStr[6]:= '*';
      ParamStr[5]:= '*';
    endif;
    if ParamLong >= 1000000000 then
      ParamStr[4]:= '*';
      ParamStr[3]:= '*';
    endif;
  endif;
  DispDigitStr:= copy(ParamStr,1,2) + #39 + copy(ParamStr,3,3) + #39 + copy(ParamStr,6,3) + '.' + copy(ParamStr,9,1);
  CursorOffset:= byte(DigitSelectFreq);
  if DigitSelectFreq > 1 then
    inc(CursorOffset);
  endif;
  if DigitSelectFreq > 4 then
    inc(CursorOffset);
  endif;
  if DigitSelectFreq > 7 then
    inc(CursorOffset);
  endif;
end;

procedure FrequDutyChange;
var my_limit: Boolean;
begin
  LCDcursor_m(LCD_m1, false, false);
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,'Pulse Gen (freq)');
  LCDclrEOL_M(LCD_m1);

  LCDxy_M(LCD_m1, 0, 1);
  write(lcdout_m,'PW: ');
  if Modify = pulse_pwm then
    lcdout_m(#5);
  else
    lcdout_m(#32);
  endif;
  write(lcdout_m,ByteToStr(lo(PulseWidthLong)));
  lcdout_m('%');
  LCDclrEOL_M(LCD_m1);

  LCDxy_M(LCD_m1, 0, 2);
  write(lcdout_m,'Hz: ');
  if Modify = pulse_hz then
    LCDcursor_m(LCD_m1, false, true);
    lcdout_m(#5);
  else
    lcdout_m(#32);
  endif;
  Paramlong:= PulseFrequLong;
  my_limit:= Paramlong <= 0;
  FrequLongToDispStr(SelectDigit);
  write(lcdout_m, DispDigitStr);
  LCDclrEOL_M(LCD_m1);

  LCDxy_M(LCD_m1, 0, 3);
  if my_limit then
    write(lcdout_m,'Frequ Range Limit!');
  else
    write(lcdout_m,'Frq MHz kHz  Hz');
  endif;
  LCDclrEOL_M(LCD_m1);
  LCDxy_M(LCD_m1, CursorOffset + 5, 2);
end;

procedure DDSchange;
var my_line, my_cursor_offset: byte;
    my_long: LongInt; my_f: float;
    my_limit: Boolean;
begin
  LCDcursor_m(LCD_m1, false, false);
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,'DDS Gen ');
  if Modify = dds_os then
     write(lcdout_m,'(pulse)');
  else
     write(lcdout_m,'(square)');
  endif;
  LCDclrEOL_M(LCD_m1);

  LCDxy_M(LCD_m1, 0, 1);
  write(lcdout_m,'Tp: ');

  my_f:= 100000 / float(DDSfreqLong);
  my_long:= round(my_f * 1000000);
  PulseTimeLong:= DDSosLong;

  if Modify = dds_os then
    my_limit:= (PulseTimeLong >= my_long) or (PulseTimeLong = 0);
    lcdout_m(#5);
    PulseLongToDispStr(SelectDigit);
    my_cursor_offset:= CursorOffset;
    my_line:= 1;
  else
    my_limit:= false;
    lcdout_m(#32);
    PulseLongToDispStr(false);
  endif;
  write(lcdout_m, DispDigitStr);

  LCDxy_M(LCD_m1, 0, 2);
  write(lcdout_m,'Hz: ');
  Paramlong:= DDSfreqLong;
  my_limit:= my_limit or (DDSfreqLong <= 0);
  if Modify = dds_hz then
    lcdout_m(#5);
    FrequLongToDispStr(SelectDigit);
    my_cursor_offset:= CursorOffset;
    my_line:= 2;
  else
    lcdout_m(#32);
    FrequLongToDispStr(false);
  endif;
  write(lcdout_m, DispDigitStr);
  LCDclrEOL_M(LCD_m1);

  LCDxy_M(LCD_m1, 0, 3);
  if my_limit then
    if Modify = dds_os then
      write(lcdout_m,'Pulse');
    else
      write(lcdout_m,'Frequ');
    endif;
    write(lcdout_m,' Range Limit!');
  else
    if Modify = dds_os then
      write(lcdout_m,'Time s  ms  us  ns');
    else
      write(lcdout_m,'Frq MHz kHz  Hz');
    endif;
  endif;
  LCDclrEOL_M(LCD_m1);
  LCDcursor_m(LCD_m1, false, true);
  LCDxy_M(LCD_m1, my_cursor_offset + 5, my_line);
end;

// #############################################################################

procedure ApplyChanges;
var my_digit: Int8; my_long: LongInt;
begin
  case Modify of
    pulse_low, pulse_high:
      PulseChange;
      SendPulseParams;
      |
    pulse_pwm, pulse_hz:
      FrequDutyChange;
      SendFrequPWM;
      |
    dds_hz, dds_os:
      DDSchange;
      SendDDS;
      |
    filesel:
      LCDcursor_m(LCD_m1, false, false);
      LCDxy_M(LCD_m1, 0, 0);
      write(lcdout_m,'File Menu');
      LCDclrEOL_M(LCD_m1);
      LCDxy_M(LCD_m1, 0, 1);
      lcdout_m('#');
      write(lcdout_m,BytetoStr(FileNum + 1));
      write(lcdout_m,' of ');
      write(lcdout_m,BytetoStr(FileAnzahl));
      write(lcdout_m,' files');
      LCDclrEOL_M(LCD_m1);
      SelectDigit:=false;
      LCDxy_M(LCD_m1, 0, 2);
      write(lcdout_m,DirectoryArray[FileNum]);
      LCDclrEOL_m(LCD_m1);
      LCDxy_M(LCD_m1, 12, 2);
      lcdout_m(#6);
      LCDxy_M(LCD_m1, 0, 3);
      write(lcdout_m,'Select BIT/INI File');
      LCDclrEOL_M(LCD_m1);
    |
  endcase;
  ChangedFlag:=false;
end;

// #############################################################################

procedure ChangeFrequOrPulse(my_fac_index: Int8);
// ändert PulseWidthLong mit IncrDiff-Werten an Stelle ParamDigit
var my_val, my_fac: LongInt; my_short: Int8;
begin
  my_fac:= IncrDigitArray[my_fac_index];
  my_val:= ParamLongArray[ord(Modify)] + my_fac * LongInt(IncrDiffShort);
  if my_val < 0 then
    ParamLongArray[ord(Modify)]:= 0;
  else
    ParamLongArray[ord(Modify)]:= my_val;
  endif;
end;

// #############################################################################

begin
  Initall;
//Hauptschleife
  loop
    CheckSer;  // Serinp parsen
    if isSystimerZero(PanelTimer) then
      IncrValue:=GetIncrVal4(0);
// Inkrementalgeber löst in Zweierschritten auf, deshalb der Umstand
      if (IncrValue<>OldIncrValue) then
        LEDactivity:=low;
        IncrDiff:=IncrDiff + integer(IncrValue-OldIncrValue);
        OldIncrValue:= IncrValue;
        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
          IncrDiffShort:=Int8(IncrDiff);    // Byte ohne Beschleunigung
          IncrDiff:=IncrAccInt10 * IncrAccArray[byte(abs(IncrDiff))];
          IncrAccInt10:=IncrDiff*10; // 40x Differenz mit Beschleunigung
          if SelectDigit then
            SetSystimer(SelectTimer, 150); // Auswahl-Kästchen stehenlassen
            if (Modify = pulse_hz) or (Modify = dds_hz) then
              DigitSelectFreq:= DigitSelectFreq + IncrDiffShort;
              DigitSelectFreq:= valueTrimLimit(DigitSelectFreq, 1, 8);
            else
              DigitSelectPulse:= DigitSelectPulse + IncrDiffShort;
              DigitSelectPulse:= valueTrimLimit(DigitSelectPulse, 0, 9);
            endif;
          else
            case Modify of
              pulse_low, pulse_high:
                ChangeFrequOrPulse(DigitSelectPulse);
                |
              pulse_hz:
                ChangeFrequOrPulse(DigitSelectFreq);
                |
              pulse_pwm:
                PulseWidthLong:= PulseWidthLong + LongInt(IncrDiffShort);
                PulseWidthLong:= valueTrimLimit(PulseWidthLong, 1, 99);
                |
              dds_hz:
                ChangeFrequOrPulse(DigitSelectFreq);
                |
              dds_os:
                ChangeFrequOrPulse(DigitSelectPulse);
                |
              filesel:
                SelectDigit:=false;
                FileNum:= FileNum + IncrDiffShort;
                FileNum:= valueTrimLimit(FileNum, 0, 31);
                |
            endcase;
          endif;
          IncrDiff:=0; // für nächsten Durchlauf
          ApplyChanges;
          FirstTurn:=false;
        endif;
      endif;
      Checkdelay(40); // wichtig für Beschleunigungsfunktion
      if ButtonPressed then
        Checkdelay(10);
        if ButtonPressed then
          ChangedFlag:=true;
          BusyFlag:=true;
          if (not ButtonEnter) then
            ButtonNumber:=3; // Button Enter/Fine
            serprompt(UserReq);
            if Modify=filesel then
              LEDactivity:=low;
              if CardOK then
                FileLoad(DirectoryArray[FileNum]);
                SendDDS;
                SendPulseParams;
              endif;
            else
              SelectDigit:=not SelectDigit;
              SetSystimer(DisplayTimer, 200);
              if SelectDigit then
                SetSystimer(SelectTimer, 150); // Auswahl-Kästchen stehenlassen
              endif;
            endif;
            ApplyChanges;
          endif;
          if not ButtonUp then
            SelectDigit:=false;
            inc(Modify);
            ApplyChanges;
          endif;
          if not ButtonDown then
            SelectDigit:=false;
            dec(Modify);
            ApplyChanges;
          endif;
          repeat
            Checkdelay(10);
          until not ButtonPressed;
          FirstTurn:=false;
        endif;
      endif;
    endif;

    if SelectDigit and isSystimerZero(SelectTimer) then
    // Auswahl nach einiger Zeit löschen
      SelectDigit:= false;
      ApplyChanges;
    endif;

    if isSystimerZero(DisplayTimer) then
      SetSystimer(DisplayTimer, 25);
      BusyFlag:=false;
      if ChangedFlag then
        ApplyChanges;
      endif;
      ChangedFlag:=false;
    endif;
  endloop;
end FPGA.

