program UNIC;
(*

(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de
12.04.2010 #1.012  ADC-Reihenfolge umgekehrt, Param/Frac-Routine aufgeräumt
10.03.2010 #0.91  Angepasst auf EDIP 240-7 Display von Electronic Assembly, I2C
20.02.2010 #0.901 Reimport aus FPGA, nicht Benötigtes gestrichen
15.02.2010 #0.900 erste lauffähige Version
22.01.2010 #0.100 Import von ADA-C, HW-Routinen angepasst

*)

//Defines aktivieren durch Entfernen des 1. Leerzeichens!


{$NOSHADOW}
{$W+ Warnings}            {Warnings off}
{$TYPEDCONST OFF}
{$DEBDELAY}
{$OPTI_QUICK}
{$DEFINE EXTRTC}          // externe RTC DS1302 an Port B
{ $DEFINE EXTDCF}          // externer DCF-Empfänger an Port D4
{ $DEFINE EDIP}        // LCD-Grafik EA eDIP240-7

Device = mega644p, VCC = 5;


Import SysTick, FAT16, IncrPort4, TWImaster,
       SerPort, I2CExpand, FreqCount, RTclock;  // ADCport,
{$IFDEF EXTDCF}
Import DCFclock;
{$ENDIF}

From System Import float, LongWord, LongInt;

Define
  ProcClock      = 16000000;        {Hertz}
{$IFDEF EDIP}
  TWIpresc       = TWI_BR100;
{$ELSE}
  TWIpresc       = TWI_BR400;
{$ENDIF}

  IncrPort4      = PinA, 1, 16; // pin-reg used, channels, 16 or 32bit integer
  IncrScan4      = Timer2, 10; // timer used, scan rate 10 kHz (1..100)


  SysTick        = 10;              //msec
  SerPort        = 38400, Stop1, timeout;    {Baud, StopBits|Parity}
  RxBuffer       = 255, iData;
  TxBuffer       = 32, iData;

  StackSize      = $0100, iData;
  FrameSize      = $0180, iData;

  FAT16          = MMC_SPI, iData;
  F16_MMCspeed   = fast;
  F16_FileHandles   = 2;
  F16_Dirlevels     = 1;
  
  I2Cexpand = I2C_TWI, $38; {use TWIport, 9554A}
  I2CexpPorts = {Port0,Port1,Port2,Port3,} Port4,Port5,Port6,Port7;

{$IFDEF EXTDCF}
  DCFclock    = iData;
  DCFport     = PinD, 4, negative;        {Port, Pin#}
{$ENDIF}
  RTclock     = iData, DateTime;{Time, DateTime}
  RTCsource   = SysTick;

  FreqTimer = Timer1;
{
  ADCchans = [1,2], iData;
  ADCpresc       = 16;
}
USES
  UFAT16;


Implementation

type
  tError = (NoErr, UserReq, BusyErr, FileErr, SyntaxErr, ParamErr, LockedErr,ChecksumErr);
  tMenu = (adjust,adjfine,noadj,select);
  tPage = (display,terminal,files,all,none);
  tresponseType = (rNone, rByte, rInt, rFloat, rString, rStringInBrackets);
{--------------------------------------------------------------}
const

  DDRAinit                   = %11110100;     {PortA dir}
  PortAinit                  = %00001011;     {PortA CDSW, RTC-Reset, IncrGeber}

// SCLK, MISO, MOSI, SD_SS, (PB3), INT2, COUNT T1, AUX_SS
  DDRBinit                   = %10111001;     {PortB dir }
  DDRBinitRdRTC              = %10011001;     {PortB dir wenn RTC gelesen wird}
  PortBinit                  = %10111101;     {PortB SPI, Trigger und FrequCount}

//JTAG-Fuses disablen, sonst PortC nur teilweise Funktion!
  DDRCinit                   = %11111100;     {PortC dir}
  PortCinit                  = %10011100;     {PortC Strobes DAC und SR, I2C}

{ Jumper und LEDs PortBits }
{$IFDEF EDIP}
  DDRDinit                   = %11110100;     {PortD dir, PD3=PanelBuf}
  PortDinit                  = %00001100;     {PortD Enables/Invert-Bits, LED}
{$ELSE}
  DDRDinit                   = %11111100;     {PortD dir}
  PortDinit                  = %00001100;     {PortD Enables/Invert-Bits, LED}
{$ENDIF}

//  ControlBitPort             = @PortB;   {DDS-Port}
//  ControlBitPin              = @PinB;   {DDS-Port}
  StrobeBitPort              = @PortC;   {DDS-Port}
  StrobeBitPin               = @PinC;   {DDS-Port}
  InvertBitPort              = @PortD;   {DDS-Port}
  InvertBitPin               = @PinD;   {DDS-Port}

  b_RTCclk                   = 7; // RTC an Port B, RST = F_AUX!
  b_RTCio                    = 5; // RTC IO-Pin
  a_RTCrst                   = 2; // RTC-Ena/Reset
  a_CDSW                     = 3; // SD-Kartenslot Switch

  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_INT2                     = 2; //INT2
  b_counter                  = 1; //Counter-Input
  b_AUX                      = 0; //Testpin Trigger LA

//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = '1.012 [UNIC by CM/c''t 03/2010]';    //Vers1

  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]');

  HeaderStr = 'VAL'+#9+'REG'+#9+'TIME'+#9+'DATE';

  EEnotProgrammedStr         = 'EEPROM EMPTY! ';
  NoneStr                    = '--- ';
  ErrSubCh                   = 255;

  CmdStrArr: array[0..59] of String[3] = (
  'STR', // 255
  'VAL', // 0..9999
  
{--------------------------------------------------------------}
// Script-Befehle
  'REG', // 300..309  Load Register 0..9 immediately mit Wert, auch Abfrage
  'ACC', // 300  Load Akku A (Register 0) immediately mit Wert, auch Abfrage
  'MOV', // 310..319  Move Register 0..9 := 0..9
  'DEC', // 320  Decrement Register (0..9), Ergebnis für Branches merken
  'INC', // 330  Increment Register (0..9), Ergebnis für Branches merken
  'CPZ', // 340  Compare Register (0..9) mit "0", Inhalt für Branches merken
  'XCH', // 350  Exchange 0..9 :=: 0..9, ohne Argument: Akku A mit Register (0..9)


  'GET', // 400..409  Warte auf Ergebnis, speichere in Registerinhalt 0..9
  'PUT', // 500..509  Sende Wert in Akku A auf MCM/SCM

  'MUL', // 600  Multiplikation Akku A = Akku A * Register (0..9)
  'DIV', // 610  Division Akku A = Akku A / Register (0..9)
  'ADD', // 620  Addition Akku A = Akku A + Register (0..9)
  'SUB', // 630  Subtraktion Akku A = Akku A - Register (0..9)
  'SQR', // 640  Quadratwurzel Register (0..9) = sqr(Register (0..9))
  'SQU', // 650  Quadrat Register (0..9) = Register (0..9) * Register (0..9)
  'NEG', // 660  Negiere Register (*-1)

  'LBL', // 1000  Label 0..99 setzen (hier begrenzt auf 0..31)
  'GTO', // 1100  Goto <labelnr>
  'BRA', // 1100  Branch always to <labelnr>, wie GTO
  'BRG', // 1200  Branch if greater 0 to Label <labelnr>, wenn Akku A größer als B
  'BGE', // 1300  Branch if greater or equal 0 to Label <labelnr>, wenn Akku A größer oder gleich B
  'BEQ', // 1400  Branch if equal 0 to Label <labelnr>, wenn Akku A gleich B
  'BLE', // 1500  Branch if lower or equal 0 to Label <labelnr>, wenn Akku A kleiner oder gleich B
  'BRL', // 1600  Branch if lower 0 to Label <labelnr>, wenn Akku A kleiner als B

  'INP', // 2000..2255 Input Akku mit Wert von moduleigener SubCh-Abfrage (SPI-Reg.)
  'OUT', // 2000..2255 Register Output,
         // Ausgabe von Akku oder Reg. 0..9 in moduleigenen SubCh (SPI-Reg.) 0..63

{--------------------------------------------------------------}
  'PIO', // 30 = Ports I/O
  'DDR', // 40 = Ports Datenrichtungen
  'RAW', // 50 = Roh-AD-Werte
  'DSP', // 80 = Display-Parameter
  'DOK', // 89 = Display "OK" und warte auf Drücken
  'DEF', // 100..159 = Defines Offsets, Saklierungen
  'DPC', // 160..169 = Anzeige-Kanal auf Display 0..9
  'DMX', // 170..179 = Display Bargraph-Maximalwert auf Display 0..9; negativ=bipolar
  'DPT', // 180..189 = Display-Beschriftung
  'DPU', // 190..199 = Display-Units

  'CLK', // 90..95, 97 RTC-Register und Befehle
  'ALL', // 98,99 = Alle Werte Ports, ADC, als Liste
  'OPT', // 200..209  Options Init-Werte 0..9
         // 210  IniFileName (INI oder CFG)
         // 211  DataFileName (INI oder CFG)
         // 212  ADCBaseScale
         // 213  DACBaseScale
  'FWR', // 260 FileWriteRegister, FWR=<register0..9> in Datei DatFileName schreiben
  'FWV', // 270 FileWriteValue, FWV <index>=<wert> in Datei DatFileName schreiben

  'WTH', // 290  Wait Tick Hour
  'WTM', // 291  Wait Tick Minute
  'WTS', // 292  Wait Tick Second
  'DLY', // 299  Delay ms

  '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
  'WEN', // 250 Write enable
  'ERC', // 251 ErrCount seit letztem Reset
  'SBD', // 252 SerBaud UBRR-Register mit U2X=1
  'REM', // 253
  'IDN', // 254
  'NOP');

  Cmd2SubChArr: array[0..59] of integer = (
  255,
  0,
  300,300,
  310,320,330,340,350,
  400,500,
  600,610,620,630,640,650,660,
  1000,1100,1100,1200,1300,1400,1500,1600,
  2000, 2000,

  30,40,50,80,89,      // Ports, Raw, Display
  100,          // Defines Offsets und Skalierungen,
  160,170,180,190,  // Display-Kanal, Subtitel

  90,   //RTC
  98,
  200,
  260,
  270,
  290,291,292,299,
  240, 241, 241, 242, 243, 244,249,
  250, 251, 252, 253, 254, 253);

  cmdAnzahl=59; // letzter Eintrag, statt tCmdwhich
  cmdErr=cmdAnzahl+1; // Error, Statt tCmdwhich

  high                       = true;
  low                        = false;

  filesel=10;

  EDIPadr=%01101111 ; // $DE div 2
  DC1=$11;
  DC2=$12;
  ACK=$06;
  NAK=$15;
  
  BARC=18;          // Coarse-Bargraph
  BARF=19;          // Fine-Bargraph
  BARL=100;         // Länge in Pixeln
  
  FracScale = 10000;
  
structconst

//Default-EEPROM-Werte:
{$EEPROM}
  dummy:LongInt=0;
  
  InitADCBaseScale: Float  = 1/409.5;      // 2442 für FracScale=1000
  InitDACBaseScale: LongInt  = 269541; // berücksichtigt Grundverstärkung

  InitDACvalueArray      : array[0..7] of Float =
  (0,0,0,0,0,0,0,0);

// ADCoffsets, 10..17 AD12  (-2145 für bipolar Rgnd=10k/Rref=6k8/Rv=30k)
  InitADCOffsetArray      : array[0..7] of Integer =
  (0, 0, 0, 0,
  -2145, -2145, 0, 0);
  
// ADCscales 10..17 (2.1 für bipolar 10V, Rgnd=10k/Rref=6k8/Rv=30k)
  InitADCscaleArray      : array[0..7] of Float =
  (1, 1, 30, 30,
  0.21, 10.51, 1.0, 1.0);       // RPG Ug ist: 12.37

// DAC 0 bis 3 Offsets 20..23, 20..23 negativ
  InitDACOffsetArray      : array[0..7] of Integer =
  (0, 0, 0, 0,
  0, 0, 0, 0);

// DACscales 20..23, 20..23 negativ,
  InitDACscaleArray      : array[0..7] of Float =
  (0.03333, 0.03333, 0.2, 0.4,       // RPG: Ua, Us, Ug, Uf
   0, 0, 0.2, 0);                    // für negative Ausgangsspannungen

// IO-Port DDR Datenrichtungsregister; 0..3 immer Ausgang
  InitDDRArray: array [0..7] of byte = ($FF,$FF,$FF,$FF,$FF,$FF,0,0);
  InitPortArray: array [0..7] of byte = (0,0,0,0,$AB,$CD,0,0);

  EEinitialised:word=$AA55;
  InitIncRast:Integer =4;
  EESerBaudReg:byte=51;
  InitIntegrateADC:boolean=false;

  OptionArray:Array[0..9] of Integer =  (
  255,  // 0 BIN-File Default, 255=keine
  255,  // 1 INI-File Default
  0,    // 2 --
  0,    // 3 --
  0,    // 4 Result MainCh
  0,    // 5 Result SubCh
  6,    // 6 InitDigits
  0,    // 7 (war InitNachkomma, obsolete)
  8,    // 8 EEMainCh
  500   // 9 EEgetTimeOut
  );

  EEInitFileName:String[15] = 'TEST.INI';
  EEDatFileName:String[15] = 'DATAFILE.XLS';


//Diese Tabellen setzen Display-Inhalt auf bestimmten SubCh
// Übersetzungstabelle Display-Modify -> SubCh:
  InitSubChArray: Array[0..9] of Integer =
  (300,10,11,14,15,
   20,21,22,23,30
   );

// Wenn Wert negativ, wird ein Bargraph mit Nullpunkt in der Mitte angezeigt
// Wenn Wert null, wird kein Bargraph angezeigt
  InitBarMaxArray: Array[0..9] of Integer =
  (20,200,200,-1,-50,
   300,300,-50,25,255
   );


//SBT Subtitel-Textarray:
  InitParamTextArray: Array[0..9] of String[15] =
 ('Result','I anode','I screen','I grid','U grid is',
  'U anode','U screen','U grid','U filament','Port 0'
  );

// Unit-Tabelle Display-Modify -> SubCh:
  InitUnitArray: Array[0..9] of String[4] =
  ('mA/V','mA','mA','mA','V',
   'V','V','V','V','Port'
   );

{--------------------------------------------------------------}

var
{$EEPROM}
  EEbinFileNum[@OptionArray+0]:integer;      // BIN-FileNum
  EEiniFileNum[@OptionArray+2]:integer;      // INI-FileNum -1 (kein INI-File)

  InitDigits[@OptionArray+12]:integer;
  EEMainCh[@OptionArray+16]:integer;
  EEgettimeout[@OptionArray+18]:integer;


{$DATA} {Schnelle Register-Variablen}
  i,j: Byte;
  TempW: Word;
  TempI[@TempW]: Integer;
  TempW_low[@TempW+0]: byte;
  TempW_high[@TempW+1]: byte;
  c:char; // in mySerOut benutzt
{$IDATA}  {Langsamere SRAM-Variablen}
  MainCh: byte;

  LEDactivity[@PortD, 2]    : bit; {Bit 2 LED Remote-Activity}
//  LEDswitch[@PortD, 3]      : bit; {Bit 3 LED über dem Display}
  SD_CDSW[@PinA, a_CDSW]: bit;    // Card Insertion Switch, low = inserted

  PA4[@PortA, 4]: bit;    // AUX Portbits
  PA5[@PortA, 5]: bit;    // AUX Portbits
  PA6[@PortA, 6]: bit;    // AUX Portbits
  PA7[@PortA, 7]: bit;    // AUX Portbits

  PD3[@PinD, 3]: bit;    // PanelBuf Portbits

  I2CslaveAdr:byte; // I2C IO Slave Adresse
  SPIsendbyte, SPIreceivebyte: byte;

  DisplayTimer:Systimer8;

  CmdWhich: byte; // tcmdwhich nicht mehr benutzt
  SubCh: Integer;
  x,y, CurrentCh       : byte;
  verbose: boolean;
  Param          : float;
  ParamFrac: LongInt;
  ParamInt:Integer;
  ParamAlpha     : boolean; // Flag für Zeichenfolge hinter "="
  ParamLong:LongInt;
  ParamByte :Byte;

  SerInpStr : String[63];
  SerInpPtr:byte;

  LCDpresent: boolean;
  Modify: byte;
  digits:byte;
  ChangedFlag:Boolean;
  MenuSelect: tMenu;
//für Parser
  ParamStr:string[40]; // auch für Display
  Request: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;
  byteCount: LongInt;
  byteCount3[@byteCount+3]:byte;
  byteCount2[@byteCount+2]:byte;
  byteCount1[@byteCount+1]:byte;
  byteCount0[@byteCount]:byte;


  Pause:word;

// Script-Register 0..9
  RegisterArray:array[0..9] of Float; // REG-Werte
  AccA[@RegisterArray+0]: Float;      // Akku A (0)

  GetTimeout: Integer;
  CompareParam:Float;
  GETwaitFlag:Boolean;
  LabelArray:array[0..31] of Word;
  LabelValidArray:array[0..31] of boolean;
  DirectoryArray:array[0..63] of string[12];
  DirectoryDone:Boolean;
  InitFile,DataFile : file of text;
  FileName,DatFileName: string[12];
  INITfileIsOpen, LabelSeek:Boolean;
  CurrentGotoLabel:byte;
  LastBinFileNum, FileNum, FileAnzahl,FileNumTemp : byte;
// Real Time Clock RTC
  tickSeconds, tickMinutes, tickHours: Boolean;
  RTCarray:array[0..5] of byte;
  Hour[@RTCarray+0]:byte;
  Minute[@RTCarray+1]:byte;
  Second[@RTCarray+2]:byte;
  Day[@RTCarray+3]:byte;
  Month[@RTCarray+4]:byte;
  Year[@RTCarray+5]:byte;
  RTCcmd, RTCdata:Byte;    // für externe RTC DS1302 oder HT1381

  DOStime, DOSdate:Word;

  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];

{###########################################################################}

  TRIGIN[@PinB, 0]        : bit; //Trigger-Eingang
  SLAVESEL[@PortB, 4]        : bit;
  STRDAC0[@PortC, 2]      : bit; //Strobe für MCP4822 0/1
  STRDAC1[@PortC, 3]      : bit; //Strobe für MCP4822 2/3
  STRDACLOAD[@PortC, 4]      : bit; //LOAD für beide MCP4822
  STRADC[@PortC, 7]    : bit; //Strobe für MCP3208
  STRSHIFT[@PortC, 5]   : bit; //Strobe 4094 SR
  ENASHIFT[@PortC, 6]   : bit; //Output Enable 4094 SR

  INVERT0[@PortD, 5]   : bit; //Invert Output DAC 0
  INVERT1[@PortD, 4]   : bit; //Invert Output DAC 1
  INVERT2[@PortD, 7]   : bit; //Invert Output DAC 2
  INVERT3[@PortD, 6]   : bit; //Invert Output DAC 3


      {
  IOPin[@PIn0] : TI2Cport;     // In-Port 9554A auf I/O-Platine
  IOPort[@Port0] : TI2Cport;   // Out-Port 9554A auf I/O-Platine
  IODDR[@DDR0] : TI2Cport;     // Datenrichtung 9554A auf I/O-Platine

  IO0Pin[@PIn0] : TI2Cport;     // Port 9554A auf I/O-Platine
  IO1Pin[@PIn1] : TI2Cport;     // Port 9554A auf I/O-Platine
  IO2Pin[@PIn2] : TI2Cport;     // Port 9554A auf I/O-Platine
  IO3Pin[@PIn3] : TI2Cport;     // Port 9554A auf I/O-Platine
}
  IO4Pin[@PIn4] : TI2Cport;     // Port 9554A auf I/O-Platine
  IO5Pin[@PIn5] : TI2Cport;     // Port 9554A auf I/O-Platine
  IO6Pin[@PIn6] : TI2Cport;     // Port 9554A auf I/O-Platine
  IO7Pin[@PIn7] : TI2Cport;     // Port 9554A auf I/O-Platine
{
  IO0Port[@Port0] : TI2Cport;   // Port 9554A auf I/O-Platine
  IO1Port[@Port1] : TI2Cport;   // Port 9554A auf I/O-Platine
  IO2Port[@Port2] : TI2Cport;   // Port 9554A auf I/O-Platine
  IO3Port[@Port3] : TI2Cport;   // Port 9554A auf I/O-Platine
}
  IO4Port[@Port4] : TI2Cport;   // Port 9554A auf I/O-Platine
  IO6Port[@Port6] : TI2Cport;   // Port 9554A auf I/O-Platine
  IO5Port[@Port5] : TI2Cport;   // Port 9554A auf I/O-Platine
  IO7Port[@Port7] : TI2Cport;   // Port 9554A auf I/O-Platine
{
  IO0DDR[@DDR0] : TI2Cport;
  IO1DDR[@DDR1] : TI2Cport;
  IO2DDR[@DDR2] : TI2Cport;
  IO3DDR[@DDR3] : TI2Cport;
}
  IO4DDR[@DDR4] : TI2Cport;
  IO5DDR[@DDR5] : TI2Cport;
  IO6DDR[@DDR6] : TI2Cport;
  IO7DDR[@DDR7] : TI2Cport;

  PortArray, PinArray, DDRArray: array [0..7] of byte;
  PortSR0[@PortArray+0]: byte;
  PortSR1[@PortArray+1]: byte;
  PortSR2[@PortArray+2]: byte;
  PortSR3[@PortArray+3]: byte;
  PinSR0[@PinArray+0]: byte;
  PinSR1[@PinArray+1]: byte;
  PinSR2[@PinArray+2]: byte;
  PinSR3[@PinArray+3]: byte;

  IOpresent: Boolean;

// SubCh 100..119
  ADCoffsets:array[0..7] of Integer;
  DACoffsets:array[0..7] of Integer;
// SubCh 120..149
  ADCscales:array[0..7] of Float;
  DACscales:array[0..7] of LongInt;


//Arrays mit Parametern für 32 SubChannels
  DACvalues     : Array[0..7] of LongInt;  // in Frac-Darstellung Festkomma
  DACrawArray      : Array[0..7] of Integer;
  ADCrawArray      : Array[0..7] of Integer;
  ParamTextArray: Array[0..9] of String[15];
  UnitArray: Array[0..9] of String[4];
  BarMaxArray:array[0..9] of Integer;
  SubChArray: Array[0..9] of Integer;
  ADCbaseScale   :Float;
  DACbaseScale:   LongInt;
  IntegrateADC: Boolean;

  FreqMode : tFreqCountMode;
  Freq     : word;
  FreqLo[@Freq+0]:byte;
  FreqHi[@Freq+1]:byte;
  FreqLong : LongWord;
  FreqMeasureMode : Byte;
  
  DisplaySwitchSema, SubSelectSema, ModifySema, ButtonSelectSema,
  IncrSema, CardChangeSema, FileSelectSema,
  BarUpdatedSema:  Boolean;
  BarUpdated : byte;  // letzter Auto-Touchbar Nummer und Wert
  ButtonSelect: Byte;
  MainPage,LastMainPage: tPage;
  SubSelect: Byte;
  SanduhrIcon: Byte;
  EDIPempty[@PinD, 3]: bit;    // PanelBuf Portbits
  EDIPoutFlag: boolean; // Ausgabe-Flag für mySerout
  EDIPinpStr: String[31];
  EDIPtempStr: String[63];
  EDIPstart: byte;  // DC1
  EDIPstr: String[63];
  BarMax, BarMid, BarMin:Integer;
  Bipolar: Boolean;
  BarValueC: Integer;    // geht normiert von 0 bis 100
  IncrDiff:Integer;                // Für Up/Down-Buttons
  OsziCount,EncStep:byte;  // EncRast-Zähler
  EncDiff,EncValue,OldEncValue: Integer;   // Für Dreh-Encoder
  EncSema:Boolean;  // Drehknopf betätigt

{###########################################################################}


procedure CheckSer;  forward;
procedure CheckDelay(myDelay:byte);  forward;
procedure ParseSubCh;  forward;
procedure FileRun(myfileName:String[12]);  forward;


function FracToFloat(myFrac:LongInt):Float;
begin
  return(Float(myFrac)/FracScale);
end;

Procedure ParamFracDivFracScale;
begin
  ParamFrac:=ParamFrac div FracScale;
end;

Procedure ParamToParamFrac;
begin
  ParamFrac:=LongInt(Param*FracScale);
end;

Procedure ParamFracToParam;
begin
  Param:=Float(ParamFrac)/FracScale;
end;

{$I UNIC-HW.pas}

procedure EncZero;
begin
  SetIncrVal4(0,0);
  OldEncValue:=GetIncrVal4(0);
  EncValue:=GetIncrVal4(0);
  EncStep:=0;
end;


procedure InitVars;
//Frequenz und Settings aus EEPROM holen
begin
  digits:=byte(InitDigits);
  ParamStr:='';
  GetTimeout:=EEgetTimeOut;
  DatFileName:=EEDatFileName;
  IntegrateADC:=InitIntegrateADC;
  for i:=0 to 9 do
    ParamTextArray[i]:=InitParamTextArray[i];
    SubChArray[i]:=InitSubChArray[i];
    UnitArray[i]:=InitUnitArray[i];
    BarMaxArray[i]:=InitBarMaxArray[i];
  endfor;
  ADCbaseScale:=InitADCBaseScale;
  DACbaseScale:=InitDACBaseScale;
  for i:=0 to 7 do
    ADCScales[i]:=InitADCScaleArray[i];
    ADCOffsets[i]:=InitADCOffsetArray[i];
    
    Param:=InitDACscaleArray[i];
    ParamToParamFrac;
    DACScales[i]:=ParamFrac;
    DACoffsets[i]:=InitDACoffsetArray[i];
    
    Param:=InitDACvalueArray[i];
    ParamToParamFrac;
    SetDACfrac(i,ParamFrac);
  endfor;
  EncZero;
end;


{###########################################################################}

procedure serDummy;
begin
  SerOut(#0);
end;

procedure CheckCard;
begin
  if F16_DiskInit then
    F16_DiskReset;
    CardOK:=true;
    NoCard:=false;
  else
    CardOK:=false;
    NoCard:=true;
  endif;
end;

procedure ClearDirectory;
begin
  for i:=0 to 63 do
    DirectoryArray[i]:='<empty>';
  endfor;
  FileNum:=0;
  FileAnzahl:=0;
  LastBinFileNum:=0;
  DirectoryDone:=false; // einlesen des Directory beim nächsten FileSel
  CardChangeSema:=true;
end;

procedure GetDirectory;
var
SR : TSearchRec;
begin
// search for filename/directory entries, accept wildcards
  i:=0;
  CheckCard;
  FileAnzahl:=0;
  if not CardOK then
    ClearDirectory;
  else
    if (F16_FindFirst ('\', '*.*', [faArchive], SR)) then
      repeat
        DirectoryArray[i]:= SR.Name;
        inc(i);
      until (i=64) or (not F16_FindNext (SR));
    endif;
    FileAnzahl:=i;
    DirectoryDone:=true;
    CardChangeSema:=true;
  endif;
end;

{###########################################################################}

//allg. Menü-Prozeduren und Hilfsroutinen

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;
  if Modify>127 then  // Byte!
    Modify:=0;
    myBool:=true;
  endif;
  if Modify>filesel then  // Byte!
    Modify:=filesel;
    myBool:=true;
  endif;
  return(myBool);
end;

procedure ParamToStr;
begin
  ParamStr:=floatToStr(Param);
// workaround für fehlerhafte Umwandlung "gerader" Zahlen in AVRco
  ParamStr:=trimleft(ParamStr);
  if pos('.',ParamStr)=0 then
    ParamStr:=ParamStr+'.0';
  endif;
  ParamStr:=padright(ParamStr,10,'0');
  setLength(ParamStr,digits+1); // Dezimalpunkt zählt nicht, daher +1
end;

procedure IntToParamStr(myInt:Integer);
begin
  ParamStr:=IntToStr(myInt);
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 ParamFracToPMStr; // String-Wandlung mit Vorzeichen
// var myString:String[10];
begin
{
  if ParamFrac=0 then
    ParamStr:= '0';
  else
    ParamStr:=LongToStr(abs(ParamFrac));
  endif;
  mylen:=length(ParamStr);
  if myLen<5 then
    myString:=padleft(ParamStr,5,'0');
    myLen:=5;
    ParamStr:=myString;
  endif;
  insert('.',ParamStr,mylen-3);
  if sign(ParamFrac) then
    mysign:='+';
  else
    mysign:='-';
  endif;
  insert(mysign,ParamStr,1);
  }
  if sign(ParamFrac) then
    Param:=FracToFloat(ParamFrac+1);
    ParamToStr;
    insert('+',ParamStr,1);
  else
    Param:=FracToFloat(ParamFrac-1);
    ParamToStr;
  endif;
  setlength(ParamStr,6);
end;

procedure SerCRLF;
begin
  mySerOut(#$0D);
  mySerOut(#$0A);
end;

procedure WriteChPrefix;
begin
  mySerOut('#');
  mySerOut(char(MainCh+48));
  mySerOut(':');
  write(mySerOut, IntToStr(SubCh));
  mySerOut('=');
end;

procedure WriteSerInp;
begin
  write(mySerOut, 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(mySerOut, ByteToStr(myStatus));
  if FileErrorFlag then
    myFault:=FaultFlags;
    for i := 0 to 3 do
      if (myFault and 1) = 1 then
        mySerOut(#32);
        write(mySerOut,FaultStrArr[i]);
      endif;
      myFault:=myFault shr 1;
    endfor;
  endif;
  mySerOut(#32);
  write(mySerOut,ErrStrArr[ord(myErr)]);
  SerCRLF;
end;


{###########################################################################}

procedure SetFreqMeasureMode(myVal:byte); // Frequenzzähler-Modus
begin
  FreqMeasureMode:=myVal;
  SetSema(FreqCountSema,0);
  case myVal of
    0  : SetFreqCountMode(TFreqBase1MHz);
         FreqCountRestart;
          |
    1  : SetFreqCountMode(TFreqBase100kHz);
         FreqCountRestart;
          |
    2  : SetFreqCountMode(TFreqBase10kHz);
         FreqCountRestart;
          |
    3  : SetFreqCountMode(TFreqBase1kHz);
         FreqCountRestart;
          |
    4  : SetFreqCountMode(TFreqBase100Hz);
         FreqCountRestart;
          |
    5  : SetFreqCountMode(TFreqBaseNone);
// Timer/Counter manuell setzen
         TCCR1A:=0;
         TCCR1B:=%00000111; // Rising edge on T1, falling: TCCR1B:=%00000110
         TCNT1H:=0;   // Hi zuerst!
         TCNT1L:=0;
          |
    6  : SetFreqCountMode(TFreqBaseNone);
// Timer/Counter manuell setzen
         TCCR1A:=0;
         TCCR1B:=%00000110; // Falling edge on T1
         TCNT1H:=0;   // Hi zuerst!
         TCNT1L:=0;
          |
  endcase;
end;

procedure FreqToParamStr(myLong:LongWord; myFac:Float; isPulses:boolean);
begin
  Param:=float(myLong)*myFac;
  ParamToStr;
  if isPulses then
    ParamStr:=ParamStr+' [Pulses]';
  else
    ParamStr:=ParamStr+' [Hz]';
  endif;
end;

procedure GetFreqTimerString;
begin
  if GetFreqCountOvrFlow <> 0 then
    ParamStr:= '-9999 [OVERLD]';
    Param:=-9999;
  else
    // wait for new result
    if (SemaStat(FreqCountSema) <> 0) or (FreqMode=TFreqBaseNone) then
      FreqMode:= GetFreqCountMode;
      FreqLong:= GetFreqCounterL;
      case FreqMode of
        TFreqBaseNone   :
                        FreqLo:=TCNT1L;  // Low zuerst!
                        FreqHi:=TCNT1H;
                        FreqToParamStr(LongWord(Freq),1,true);
                        |
        TFreqBase100Hz  :
                        FreqToParamStr(FreqLong,0.01,false);
                         |
        TFreqBase1kHz   :
                        FreqToParamStr(FreqLong,0.1,false);
                        |
        TFreqBase10kHz  :
                        FreqToParamStr(FreqLong,1,false);
                        |
        TFreqBase100kHz :
                        FreqToParamStr(FreqLong,10,false);
                        |
        TFreqBase1MHz   :
                        FreqToParamStr(FreqLong,100,false);
                        |
       endcase;
     else
       ParamStr:= '-9999 [INVALID]';
       Param:=-9999;
     endif;
  endif;
end;

function GetNewValue(mySubCh:Integer):boolean;
//Werte in Param holen, wenn in SubCh; Raw-Werte in ParamInt
//return-Wert True, wenn Integer
var myResultIsInteger:boolean;
    myIndex:Byte;
begin
  ParamInt:=0;
  Param:=0;
  myResultIsInteger:=true;
  myIndex:=byte(mySubCh mod 10);  // angespr. Register 0..9 errechnen aus SubCh-Rest
  case mySubCh of
    8:
      GetFreqTimerString; // Liefert auch Param
      |
    10..17, 50..57: // aktuellen Wert holen
      ReadADC(myIndex);
      if mySubCh >=50 then
        ParamInt:=ADCrawArray[myIndex];
      else
        ParamToParamFrac;
        myResultIsInteger:=false;
      endif;
      |
    20..27: // aktuellen Wert holen
      ParamFrac:=DACValues[myindex];
      ParamFracToParam;
      myResultIsInteger:=false;
      |
    30..37:
      ParamInt:=Integer(GetPort(myIndex));
      |
    40..47: //DIR Port direction
      ParamInt:=integer(DDRArray[myIndex]);
      |
    70..77:
      ParamInt:=Integer(DACrawArray[myIndex]);
      |
    300..309: // Registerwert holen
      Param:=RegisterArray[myIndex];
      ParamToParamFrac;
      myResultIsInteger:=false;
      |
  endcase;
  ParamByte:=lo(ParamInt);
  if myResultIsInteger then
    ParamFrac:=LongInt(ParamInt)*FracScale;  // für Display
  endif;
  return (myResultIsInteger);
end;


procedure SetNewValue(mySubCh:Integer);
//Werte in ParamFrac setzen, für Panel-Änderungen
var myIndex:Byte; myNewValueInt:Integer;
begin
  myIndex:=byte(mySubCh mod 10);  // angespr. Register 0..9 errechnen aus SubCh-Rest
  case mySubCh of
    20..29:
      SetDACfrac(myIndex,ParamFrac);
      |
    30..37:
      ParamFracDivFracScale;
      myNewValueInt:=Integer(ParamFrac);
      SetPort(myIndex,byte(myNewValueInt));
      |
    300..309: // Registerwert setzen
      ParamFracToParam;
      RegisterArray[myIndex]:=Param;
      |
  endcase;
end;

function SubChValueToPMstr(myDisplaySubCh:Integer):boolean;
// wie GetNexValue, wandelt aber gleich in passenden String um
begin
  if GetnewValue(myDisplaySubCh) then // Integer geliefert
    ParamStr:='$'+IntToHex(ParamInt)+' ';
    if myDisplaySubCh in [30..47] then  // Portbits
      ParamStr:='$'+ByteToHex(ParamByte)+' ';
    endif;
    return(true);
  else                                // Float geliefert
    //ParamRound1000;
    ParamFracToPMStr;
    return(false);
  endif;
end;

{###########################################################################}

procedure WriteParamStrSer;
begin
  WriteChPrefix;
  write(mySerOut, ParamStr);
  SerCRLF;
end;


procedure WriteParamSer;
begin
  ParamToStr;
  WriteParamStrSer;
end;


procedure WriteParamIntSer;
begin
  IntToParamStr(ParamInt);
  WriteParamStrSer;
end;

procedure WriteParamByteSer;
begin
  ParamStr:= ByteToStr(ParamByte);
  WriteParamStrSer;
end;

procedure WriteParamStrInBrackets(myParamByte:Byte);
begin
  WriteChPrefix;
  write(mySerOut, ByteToStr(myParamByte)+' ['+ParamStr+']');
  SerCRLF;
end;

procedure WriteDirEntry;
//Directory-Eintrag FileNum ausgeben
begin
  SubCh:=240;
  ParamStr:=DirectoryArray[FileNum];
  WriteParamStrInBrackets(FileNum);
  Checkdelay(10);
end;


{###########################################################################}

{$IFDEF EDIP}
procedure EDIPparse;
//EDIP-Button-Befahl behandeln
begin
  SerInpStr:=EDIPinpStr;
  FaultFlags:=0;
  EDIPtempStr:=''; // mySerout sammelt Ausgaben in EDIPtempStr
  EDIPoutFlag:=true;
  ParseSubCh;   // verändert ggf. Paramstr
  EDIPoutFlag:=false;
  SerInpStr:='';
  x:=0;
  y:=1;
  ParamStr:=EDIPtempStr;
//  checkdelay(5);
  EDIPparamStrTermXY;   // TEST
  EDIPinpStr:='';
end;

procedure EDIPterminal(myPrompt:String[15];minimal:boolean);
// Terminal-Seite aufbauen und Terminal-Schleife ausführen
var mylastchar:char; mylen:byte;
begin
  if LastMainPage <> terminal then
    if minimal then
      EDIPstr:=#27+'MN'+char(121); // Minimalversion
    else
      EDIPstr:=#27+'MN'+char(120);
    endif;
    EDIPsend(DC1);
    EDIPtermclr;
    EDIPinpStr:=myPrompt;
    ParamStr:=myPrompt;
    EDIPparamStrTermXY;
    LastMainPage:=terminal;
  endif;
  repeat
    Checkdelay(10);
    mylastchar:=EDIPgetcmd;
    mylen:=length(EDIPinpStr);
    y:=0; // Eingaben immer oben
    if mylastchar=#8 then
      if (mylen>1) then  // String kürzen und neu ausgeben
        dec(mylen);
        dec(mylen);
        setlength(EDIPinpStr,mylen);
        EDIPtermclr;
        ParamStr:=EDIPinpStr;
        EDIPparamStrTermXY;
      endif;
    elsif mylastchar=#13 then // Befehl vollständig, abarbeiten
      EDIPtermclr;
      y:=1;
      EDIPparse;
      if minimal then
        DisplaySwitchSema:=true;
        MainPage:=Display;
      endif;
    elsif mylastchar>#32 then
      ParamStr:=EDIPinpStr;
      EDIPparamStrTermXY;
    endif;
  until DisplaySwitchSema;
  DisplaySwitchSema:=false;
end;

{###########################################################################}

procedure EDIPdisplayBackGnd;
// Display-Seite Hintergrund aufbauen
begin
  EDIPstr:=#27+'MN'+#140+#27+'MN'+#142+#27+'MN'+#143;
  EDIPsend(DC1);
  EDIPgetraw;
  EDIPinpStr:='';
  ModifySema:=true;
end;

procedure EDIPdisplayUpdate;
// Sollwerte anzeigen, falls Display-Timer abgelaufen
// Beschriftung Y für Bar = 54, Font 4   Länge max = 170
var myDisplaySubCh:Integer;
    myParamAdjust:Boolean;  // Parameter änderbar?
    myFracFac, myFrac, myBarMax, myBarMin: LongInt;
    myOsziVal:Byte;
    myOsziX0,myOsziX1:char;
begin
  myDisplaySubCh:=SubChArray[Modify];
  if isSysTimerzero(displayTimer) or BarUpdatedSema or EncSema then
    myParamAdjust:= (myDisplaySubCh >= 20); // SEHR einfache Lösung
    if ModifySema then  // Legende zum Messwert anzeigen
      EncZero;
      ParamStr:=ParamTextArray[Modify];
      if length(ParamStr)=0 then
        ParamStr:=NoneStr+ByteToStr(Modify);
      endif;
      // Rect löschen, Oszi löschen
      EDIPstr:=#27+'RL'+#0+#0+#239+#33+#27+'RL'+#0+#34+#125+#62+#27+'MN'+#143;
      EDIPsend(DC1);
      //ParamStr:=padLeft(ParamStr,8); // mit Leerzeichen auffüllen
      EDIPsetfont(5);
      EDIPparamStrOut(0,34,'R');

      BarMax:=BarMaxArray[Modify];
      if BarMax<0 then
        BarMin:=BarMax;
        BarMid:=0;
        Bipolar:=true;
      else
        Bipolar:=false;
        BarMin:=0;
        BarMid:=abs(BarMax) shra 1;    //
      endif;
      BarMax:=abs(BarMax);

      EDIPsetfont(4);
      IntToParamStr(BarMin);
      EDIPparamStrOut(0,54,'L');
      IntToParamStr(BarMid);
      EDIPparamStrOut(60,54,'C');
      IntToParamStr(BarMax);
      EDIPparamStrOut(124,54,'R');

      EDIPstr:=#27+'AP'+char(150+Modify)+#1;  // Auswahl-Button enablen
      EDIPsend(DC1);
      EDIPsetfont(6);
      ModifySema:=false;
      OsziCount:=0;
    endif;
    if BarMax=0 then
      BarUpdatedSema:=false;  // nicht änderbar
    endif;
    myFracFac:=LongInt(BarMax)*50;
    
    // Messwert ermitteln und anzeigen
    if myParamAdjust and (BarUpdatedSema or EncSema) then
      myBarMax:=LongInt(BarMax)*FracScale;
      myBarMin:=LongInt(BarMin)*FracScale;
      if BarUpdatedSema and (BarUpdated=BARC) then
        ParamFrac:=LongInt(BarValueC)*myBarMax div BARL;
        SetNewValue(myDisplaySubCh);
      endif;
      if EncSema then
        if GetNewValue(myDisplaySubCh) then  // Integer-Wert
          ParamFrac:=ParamFrac+(LongInt(EncDiff)*FracScale);
        else
          ParamFrac:=ParamFrac+(LongInt(EncDiff)*myFracFac);
        endif;
        // auf Bargraph-Bereich limitieren
        ParamFrac:=ValueTrimLimit(ParamFrac, myBarMin, myBarMax);
        SetNewValue(myDisplaySubCh);
        EncSema:=false;
        EncDiff:=0;
      endif;
    elsif isSysTimerzero(displayTimer) then
      SetSystimer(DisplayTimer, 25); // kein Display-Refresh während der nächsten x10 ms
      SubChValueToPMstr(myDisplaySubCh); // Integer geliefert
      BarValueC:=Integer(ParamFrac div LongInt(BarMax) div 100);
      if Bipolar then
        BarValueC:=(BarValueC shra 1)+(BARL shr 1)+1; // BarLänge
      endif;
      EDIPupdateBar(BARC,BarValueC);

      ParamStr:=copy(ParamStr,1,7);   // und begrenzen
      EDIPparamStrOut(0,2,'L');
      ParamStr:=padRight(UnitArray[Modify],5);   // Unit anfügen
      EDIPparamStrOut(115,2,'L');

      // Oszillogramm-Schrieb aktualisieren
      myOsziVal:=byte(BarValueC) shr 1;
      if myOsziVal>50 then
        myOsziVal:=50;
      endif;
      myOsziX0:=char(133+OsziCount);
      myOsziX1:=succ(myOsziX0);
      EDIPstr:=#27+'GD'+myOsziX1+#42+myOsziX1+#91+
               #27+'RL'+myOsziX0+#42+myOsziX0+#91+
               #27+'GP'+myOsziX0+char(91-myOsziVal);
      EDIPsend(DC1);
      if odd(OsziCount) then
        EDIPstr:=#27+'GP'+myOsziX0+#66;   // Mittellinie erneuern
        EDIPsend(DC1);
      endif;
      inctolimwrap(OsziCount,49,0);
    endif;
  endif;
  if isSysTimerzero(displayTimer) then
    BarUpdatedSema:=false;
  endif;
end;


procedure EDIPdisplay;
// Display-Seite anzeigen und Schleife
var mylastchar:char;
begin
  if LastMainPage <> display then
    EDIPdisplayBackGnd;
    LastMainPage:=display;
  endif;
  repeat
    mylastchar:=EDIPgetcmd;
    if mylastchar=#13 then      // Befehl vollständig, abarbeiten
      EDIPparse;
    endif;
    EDIPdisplayUpdate;
    CheckDelay(1);
    if ButtonSelectSema and (ButtonSelect=1) then   // Auf Messwert geklickt
      EDIPterminal(IntToStr(SubChArray[Modify])+'=',true);
      Checkdelay(250);      // evt. Fehler kurz sichtbar
      MainPage:=display;
      DisplaySwitchSema:=true;
    endif;
    ButtonSelectSema:=false;
  until DisplaySwitchSema;
  DisplaySwitchSema:=false;
end;

{###########################################################################}

procedure EDIPall;
// Alle Mess- und Einstellwerte anzeigen und Schleife
var mylastchar:char;
  myXPos,myYpos,myindex,myrow, myPage,mycount: byte; mybool:boolean;
  myDisplaySubCh:Integer;
begin
  if LastMainPage <> all then
    EDIPstr:=#27+'MN'+#180;
    EDIPsend(DC1);
    EDIPinpStr:='';
    LastMainPage:=all;
    IncrDiff:=0;
    checkdelay(250); // sonst Übertragungsfehler wg. vorherigem Makro
  endif;
  myPage:=0;
  IncrSema:=true; // Neuaufbau erzwingen
  SubSelectSema:=false;
  repeat
    if IncrSema then // Seite komplett aufbauen
      myPage:=myPage+byte(IncrDiff);
      if myPage>1 then
        myPage:=0;
      endif;
      EDIPstr:=#27+'RL'+#0+#0+#172+#139;
      EDIPsend(DC1);
      EDIPinpStr:='';
      checkdelay(250);
      EDIPsetfont(3);
      if myPage=0 then
        ParamStr:='10-17  20-27  30-47';
      else
        ParamStr:='300-309   Frequ/RTC';
      endif;
      EDIPparamStrOut(0,0,'L');
      checkdelay(10);
      EDIPsetfont(2);
      IncrDiff:=0;
      mycount:=0;
      IncrSema:=false;
    endif;
    if myPage = 0 then
      myXPos:=0;
      for myrow:=10 to 40 by 10 do
        for myindex:=0 to 7 do
          myDisplaySubCh:=integer(myrow+myIndex);
          mybool:=SubChValueToPMstr(myDisplaySubCh); // liefert fertigen ParamStr
          myYpos:=(myindex*12)+16;
          EDIPparamStrOut(myXPos,myYpos,'L');
        endfor;
        if myrow<30 then
          myXPos:=myXPos+50;
        else
          myXPos:=myXPos+25;
        endif;
      endfor;
    else  // MainPage=1
      for myindex:=0 to 9 do
        myDisplaySubCh:=integer(myIndex)+300;
        mybool:=SubChValueToPMstr(myDisplaySubCh); // liefert fertigen ParamStr
        myYpos:=(myindex*10)+16;
        EDIPparamStrOut(0,myYpos,'L');
      endfor;
      inc(mycount);
      if mycount>4 then // min. 1 Sekunde warten bevor Freq gültig
        GetFreqTimerString;
        EDIPparamStrOut(70,16,'L');
        mycount:=0;
      endif;
      GetRTC;
      TimeToParamStr;
      EDIPparamStrOut(70,26,'L');
      DateToParamStr;
      EDIPparamStrOut(70,36,'L');
    endif;
    mylastchar:=EDIPgetcmd;
    if mylastchar=#13 then // Befehl vollständig, abarbeiten
      EDIPparse;
    endif;
    checkdelay(250);
  until DisplaySwitchSema or SubSelectSema;
  DisplaySwitchSema:=false;
  SubSelectSema:=false;
end;

{###########################################################################}

procedure EDIPfiles;
// Directory anzeigen und Schleife
var mylastchar:char;
  mystart,myYpos,myindex, myPage, myFileNum: byte;
begin
  if LastMainPage <> files then
    EDIPstr:=#27+'MN'+char(160);
    EDIPsend(DC1);
    EDIPinpStr:='';
    mystart:=0;
    EDIPsetfont(3);
    LastMainPage:=files;
    IncrDiff:=0;
    checkdelay(250); // sonst Übertragungsfehler wg. vorherigem Makro
    checkdelay(250); // sonst Übertragungsfehler wg. vorherigem Makro
  endif;
  myPage:=0;
  IncrSema:=true; // Neuaufbau erzwingen
  SubSelectSema:=false;
  repeat
    if IncrSema or CardChangeSema then
      myPage:=myPage+byte(IncrDiff);
      if myPage>7 then
        myPage:=0;
      endif;
      IncrDiff:=0;
      myStart:=myPage shl 3; // *8 Einträge
      for myindex:= 0 to 7 do
        myYpos:=(myindex*16)+2;
        myFileNum:=myindex+mystart;
        ParamStr:=ByteToStr(myFileNum)+' ';
        EDIPparamStrOut(4,myYpos,'L');
        ParamStr:=padRight(DirectoryArray[myFileNum],18);
        EDIPparamStrOut(24,myYpos,'L');
      endfor;
      IncrSema:=false;
      CardChangeSema:=false;
    endif;
    mylastchar:=EDIPgetcmd;
    if mylastchar=#13 then // Befehl vollständig, abarbeiten
      EDIPparse;
    endif;
    checkdelay(50);
  until DisplaySwitchSema or SubSelectSema;
  FileNum:=SubSelect+myStart;
  DisplaySwitchSema:=false;
  FileSelectSema:=SubSelectSema;
  SubSelectSema:=false;
end;

{###########################################################################}

procedure EDIPmainPageSelect;
begin
  case MainPage of
    terminal:
      EDIPterminal(ByteToStr(MainCh)+':',false);
      |
    display:
      EDIPdisplay;
      |
    files:
      EDIPfiles;
      |
    all:
      EDIPall;
      |
  endcase;
end;

procedure RunningMsg;
var mychar:char;
begin
  inc(SanduhrIcon);
  SanduhrIcon:=SanduhrIcon and 7;
  myChar:=char(SanduhrIcon+231);
  EDIPstr:=#27+'MN'+myChar;
  EDIPsend(DC1);
end;

procedure RunningMsgOff;
begin
  SanduhrIcon:=0;
  EDIPstr:=#27+'MN'+#203;
  EDIPsend(DC1);
end;

function ButtonPressed:Boolean;
begin
  return(not EDIPempty);
end;

{$ELSE} //EDIP

function ButtonPressed:Boolean;
begin
  return(false);
end;

procedure RunningMsg;
var mychar:char;
begin
  LEDactivity:= not LEDactivity;
end;

procedure RunningMsgOff;
var mychar:char;
begin
  LEDactivity:= high;
end;

{$ENDIF} //EDIP

{###########################################################################}

procedure DataWrite(myIndex:byte;isReg:boolean);
// schreibt Messwert aus Reg 0..9, Transfer(Sub)Ch und Zeitpunkt in Datei
var myfileName:String[12];
begin
  WriteErr:=true;
  if CardOK then
    myfileName:=DatFileName;
    if not F16_FileExist ('\', myfileName, faFilesOnly) then
      DateTimeToDOS;
      F16_FileAssign (DataFile, '',myfileName);
      F16_FileReWrite (DataFile,[faArchive],DOStime,DOSdate); // Datei erstellen
      if F16_CheckHandle(Datafile)<>faNone then
        Writeln(Datafile,HeaderStr);
      endif;
      DirectoryDone:=false;
    else
       F16_FileAssign (DataFile, '',myfileName);
       F16_FileAppend (DataFile); // Datei zum Schreiben/Anhängen öffnen
    endif;
    if F16_CheckHandle(Datafile)<>faNone then
      if isReg then
        Param:=RegisterArray[myIndex];
      endif;
      ParamToStr;
      Write(Datafile,ParamStr);
      ParamStr:=#9+ByteToStr(myIndex);
      Write(Datafile,ParamStr);
      GetRTC;
      TimeToParamStr;
      Write(Datafile,#9+ParamStr);
      DateToParamStr;
      WriteLn(Datafile,#9+ParamStr);
      WriteErr:=false;
    endif;
    F16_FileClose (Datafile);
  endif;
  NoCard:=not CardOK;
end;

{$I UNIC-Parser.pas}

procedure FileRun(myfileName:String[12]);
// liest Datei von SD- oder MMC-Karte und interpretiert Befehle nach
// c't-Lab-Syntax
var
  WaitLoops:Integer; verboseSave:Boolean;
  myFileExt:String[3];
begin
  myFileExt:=ExtractFileExt(myfileName);
  if myFileExt<>'INI' then
    return;
  endif;
  RunningMsg;
  for i:=0 to 31 do
    LabelValidArray[i]:=false;
  endfor;
  LEDactivity:=low;
  verboseSave:=verbose;
  ReadErr:=true;
  LabelSeek:=false;
  GetWaitFlag:=false;
  if CardOK then
    BusyFlag:=true;
    if F16_FileExist ('\', myfileName, faFilesOnly) then
      F16_FileAssign (InitFile, '',myfileName);
      F16_FileReset (InitFile); // Datei zum Lesen öffnen
      INITfileIsOpen:=true;
      while (not ButtonPressed) and (not F16_EndOfFile(InitFile)) do // read the entire file
        ReadLn(InitFile,SerInpStr);
        if SerInpStr[1]<>'/' then
          ParseSubCh;
{$IFDEF EDIP} //EDIP
          if LastMainPage <> display then
            EDIPdisplayBackGnd;
            LastMainPage:=display;
          endif;
          EDIPdisplayUpdate;
{$ENDIF} //EDIP
          RunningMsg;
        endif;
        SerInpStr:='';
        if serstat then
          CheckSer;
        endif;
        Waitloops:=GetTimeout;
        while ((not ButtonPressed) and GetWaitFlag and (Waitloops>0)) do // auf Antwort warten
          CheckSer;
          dec(Waitloops);
        endwhile;
        GetWaitFlag:=false; // nach Timeout
      endwhile; // until end of file
      F16_FileClose (InitFile);
      SerInpStr:='';
      ReadErr:=false;
    endif;
  endif;
  INITfileIsOpen:=false;
  GetWaitFlag:=false; // nach Timeout
  LabelSeek:=false;
  BusyFlag:=false;
  NoCard:=not CardOK;
  verbose:=verboseSave;
  RunningMsgOff;
end;

{###########################################################################}


procedure Chores;
// Regelmäßig nach Ablauf von DisplayTimer aufgerufen
begin
  if SD_CDSW and DirectoryDone then
    ClearDirectory;
  endif;
  if (not SD_CDSW) and (not DirectoryDone) then
    mdelay(100);
    GetDirectory;
    //Modify:=filesel;
    //MainPage:=files;
    //DisplaySwitchSema:=true;
  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
      EDIPoutFlag:=false;
      LEDactivity:=low; // LED aus
      FaultFlags:=0;
      ParseSubCh;               // Befehl interpretieren, wenn für uns
      SerInpStr:='';
      SetSystimer(DisplayTimer, 20); // kein Display-Refresh während der nächsten 200 ms
    endif;
  endif;
{$IFDEF EDIP}
  mdelay(1); // hier treffen max. 10 Zeichen seriell ein
  if isSystimerZero(DisplayTimer) then
    Chores;
    LEDactivity:=high; // LED aus
  endif;
{$ELSE}
  if isSystimerZero(DisplayTimer) then
    mdelay(1); // hier treffen max. 10 Zeichen seriell ein
    Chores;
    LEDactivity:=high; // LED aus
  endif;
{$ENDIF}
end;

procedure CheckEncoder;
// Dreh-Encoder auswerten
begin
  EncValue:=GetIncrVal4(0);
  while EncValue > OldEncValue do
    inc(OldEncValue);
    inc(EncStep);
    if EncStep = 4 then      // = EncRast
      EncStep:=0;
      inc(EncDiff);
      EncSema:=true;
    endif;
    // serout(lo(EncDiff));
  endwhile;
  while EncValue < OldEncValue do
    dec(OldEncValue);
    dec(EncStep);
    if EncStep = 252 then     // -EncRast, 256 - 4
      EncStep:=0;
      dec(EncDiff);
      EncSema:=true;
    endif;
    // serout(lo(EncDiff));
  endwhile;
end;

procedure CheckDelay(myDelay:byte);
// Delay mit serieller Abfrage in 1-ms-Schritten
var mycount:byte;
begin
  for mycount:=1 to mydelay do
    CheckSer;
    CheckEncoder;
  endfor;
end;


{###########################################################################}

procedure initall;
//nach Reset aufgerufen
begin
  DDRA:=  DDRAinit;            {PortA dir}
  PortA:= PortAinit;           {PortA}
  DDRB:=  DDRBinit;            {PortB dir}
  PortB:= PortBinit;           {PortB}
  DDRC:=  DDRCinit;            {PortC dir}
  PortC:= PortCinit;           {PortC}
  DDRD:=  DDRDinit;            {PortD dir}
  PortD:= PortDinit;           {PortD}

  IOpresent:=I2CexpStat(IO4DDR);

//  SetFreqCountMode(TPulseBase100ms);
  SetFreqMeasureMode(2);  //  SetFreqCountMode(TFreqBase10kHz);

// SPI Control Register: SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0
  SPCR:= %01010001; // SPI enable, Master, MSB first, CPOL 0.0, fclk/16

// Ports initialisieren
  for i:=0 to 7 do
    j:=InitDDRArray[i];
    SetDir(i,j);
    j:=InitPortArray[i];
    SetPort(i,j);
  endfor;
  ENASHIFT:=high;

//  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
  MainCh:=byte(EEMainCh);

  LEDactivity:= low;
  mdelay(1000);
  LEDactivity:= high;

{$IFDEF EXTRTC}
  GetExtRTC;
  SetRTC;
{$ENDIF}
  Status:=0;
  EnableInts;
  while serstat do
    i:=SerInp;
  endwhile;

  SubCh:=0;
  CurrentCh:=MainCh; //
  errcount:=0;
  ChangedFlag:=true;

  EDIPoutFlag:=false;
  WriteChPrefix;
  write(mySerOut,Vers1Str);
  if EEinitialised <> $AA55 then
    write(mySerOut, EEnotProgrammedStr);
  endif;
  SerCRLF;

  ClearDirectory;
  Chores; // Files ggf. laden

  IncrCount4start;
  SetIncrVal4(0,0);
  SetSystimer(DisplayTimer, 10);
  Modify:=0;
  MenuSelect:=select;
{$IFDEF EDIP}
  EDIPgetraw;
  EDIPtermclr;
  MainPage:=display;
  LastMainPage:=none;
{$ENDIF}
  GetDirectory;
  if CardOK and (EEinitFileName<>'') then
    FileRun(EEinitFileName);
    if FaultFlags<>0 then
      FileNum:=0;
      serprompt(FileErr);
    else
      WriteDirEntry;
    endif;
  endif;
end;

{###########################################################################}


begin
  Initall;
//Hauptschleife
  loop
{$IFDEF EDIP}
    EDIPmainPageSelect;
    if FileSelectSema then
      FileSelectSema:=false;
      FileRun(DirectoryArray[FileNum]);
      checkdelay(250); // sonst Übertragungsfehler wg. vorherigem Makro
    endif;
{$ENDIF}
    Checkdelay(1);
  endloop;
end UNIC.

