program DIV;
(*
AD/DA-Messystem mit ATmega16, 4 10-Bit-Analog-In, Analog-In,
I2C-I/O
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

27.03.2007 #2.08 Status-Codes eingeführt, Overload, Übermittlung der Bedienelemente
19.03.2007 #2.07 Parser-Syntax #!?, Error-Codes, ALL-Request
06.03.2007 #2.06 Status-Byte-Ausgabe Error, Overload
26.02.2007 #2.05 neue Platine, bis 10V ohne Vorteiler
26.02.2007 #2.03 Busy-Meldung eingeführt, Parser zweigeteilt
23.02.2007 #2.02 Neue SubCh-Belegung, TRG, RNG, VAL ohne Warten
05.02.2007 #1.42 Define für ein- oder zweizeiliges Display
23.01.2007 #1.40 LCD-Anschlus 1x8 Zeichen für Messwertanzeige
12.01.2007 #1.00 neuer Parser
08.10.2006 #0.90 Parser Q&D
18.09.2006 #0.10 erste Implementierung aus Labor-DDS

*)

{$DEFINE DIV22}         //Platine ab Version 2.2
{$DEFINE Zweizeilig}         //wenn zweizeiliges Display eingesetzt ist
{$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 = mega168, VCC = 5;

Import SysTick, TWImaster, LCDmultiPort, ADCport, SerPort, I2CExpand;

from System import float;

Define
  ProcClock      = 16000000;        {Hertz}

  TWIpresc       = TWI_BR400;
  LCDmultiPort   = I2C_TWI;
  LCDTYPE_M      = 44780; // 44780 oder 66712;
{$IFDEF Zweizeilig}
  LCDrows_M      = 2;              //rows
{$ELSE}
  LCDrows_M      = 1;              //rows
{$ENDIF}
  LCDcolumns_M   = 8;             //chars per line

  SerPort        = 38400, Stop1;    {Baud, StopBits|Parity}
  RxBuffer       = 64, iData;
  TxBuffer       = 64, iData;

  SysTick        = 1;              //msec

  StackSize      = $0080, iData;
  FrameSize      = $0120, iData;

  ADCchans       = [3,4], iData, int2;
  ADCpresc       = 32;

  I2Cexpand      = I2C_TWI, $38; {use TWIport, 9554A}
  I2CexpPorts    = Port0, Port1; {use Port0}

Implementation

type
  tCmdWhich      = (str, idn, trg, rng, itg, rmo, val, ofs, scl, all, nop, err);
  
  tRange  = (DC100mV, DC1V, DC10V, DC100V, DC1000V,      // 0..4
             AC100mV, AC1V, AC10V, AC100V, AC1000V);     // 5..9

  tError = (NoErr, UserReq, BusyErr, OvlErr, SyntaxErr, ParamErr);

{--------------------------------------------------------------}
const


//                              ----SFDC             Strobe,FSync,Data,Clk
  DDRBinit                   = %00011111;            {PortB dir }
  PortBinit                  = %00010111;            {PortB, b_STROBE=0 }

  DDRCinit                   = %11110000;  {PortC dir, 0+1=Incr, 2+3=ADC }
  PortCinit                  = %11110011;  {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;   {DDS-Port}

  b_SCLK                     = 0; //Port B, Takt für alle
  b_STRAD24                  = 2; //Strobe-CS für LTC2400 AD 24 Bit
  b_STRAD12                  = 3; //Strobe-CS für LTC1286
  b_SDATAIN1                 = 5; //Daten vom LTC2400-1


//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = '2.08 [DIV by CM/c''t 03/2007]';     //Vers1
  Vers3Str                   = 'DIV 2.08';    // Kurzform von Vers1

  ErrStrArr      : array[0..5] of String[9] = (
    '[OK]','[SRQUSR]','[BUSY]','[OVERLD]','[CMDERR]','[PARERR]');

  AdrStr                     = 'Adr ';
  ErrSubCh                   = 255;  // Error- und Status-Ausgabekanal

  CmdStrArr      : array[0..10] of String[3] = (
  'STR',
  'IDN',
  'TRG',
  'RNG',
  'ITG',
  'RMO',
  'VAL',
  'OFS',
  'SCL',
  'ALL',
  'NOP');   //2 Parameter
  Cmd2SubChArr   : array[0..10] of byte = (
  255, 254, 249, 252, 251, 250,
  0, 100, 200, 99, 0);   // Offsets bei SubCh 100, Skalierungen bei 200

  high                       = true;
  low                        = false;
//Basis-Offsets und Skalierungen,
//bei DC durch ADC-Spannungsteiler 2k/2k67/8k06 bestimmt

//  BaseScale24DC:Float              = 1.0/824000;  // 1/2^24, korrigiert Fehler durch ADC-Spannungsteiler
//  BaseScale24AC:Float              = 1.0/1638400; // wg. FS = 1000 statt 1024

//  BaseScale10DC:Float              = 1/50;
//  BaseScale10AC:Float              = 1/100;

//Default-EEPROM-Werte:
{$EEPROM}
structconst
  // 1/2^24, korrigiert bei DC Fehler durch ADC-Spannungsteiler
  RangeArray: array[0..19] of float =
 (1.0/83350,        // DC 100mV AD24
  1.0/8335000,      // DC 1V
  1.0/833500,       // DC 10V
  1.0/83350,        // DC 100V
  1.0/8335,         // DC 1000V
  1.0/163840,       // AC 100mV AD24
  1.0/16384000,     // AC 1V
  1.0/1638400,      // AC 10V
  1.0/163840,       // AC 100V
  1.0/16384,        // AC 1000V
  0.197,              // DC 100mV AD10
  0.00197,            // DC 1V
  0.0197,             // DC 10V
  0.197,              // DC 100V
  1.97,              // DC 1000V
  0.1,              // AC 100mV AD10
  0.001,            // AC 1V
  0.01,             // AC 10V
  0.1,              // AC 100V
  1.0);             // AC 1000V AD10
  
  OffsetArray    : array[0..19] of LongInt =
 (22900, -9000, -11400, -11500, -11500,    // 0..4 Offset AD24 für 5 Ranges DC 100mV..1000V
  -2000, 0, 0, 0, 0,    // 5..9 Offset AD24 für 5 Ranges AC 100mV..1000V
  3, 2, 1, 1, 1,    // 10..14 Offset AD10 für 5 Ranges DC 100mV..1000V
  0, 0, 0, 0, 0);   // 15..19 Offset AD10 für 5 Ranges AC 100mV..1000V

  ScaleArray     : array[0..19] of Float =
 (1, 1, 1, 1, 1,  // AD24 Skalierung für 5 Ranges DC 100mV..1000V
  1, 1, 1, 1, 1,  // AD24 Skalierung für 5 Ranges AC 100mV..1000V
  1, 1, 1, 1, 1,  // AD10 Skalierung für 5 Ranges DC 100mV..1000V
  1, 1, 1, 1, 1); // AD10 Skalierung für 5 Ranges AC 100mV..1000V

{--------------------------------------------------------------}

var
{$EEPROM}

{$DATA} {Schnelle Register-Variablen}
  i, j   : byte;
  k              : byte;  // k in HW-Prozess benutzt
  m              : byte;

{$IDATA}  {Langsamere SRAM-Variablen}

  SlaveCh        : byte;
  LEDactivity[LEDOutPort, 2]      : bit;
  LEDaux[LEDOutPort, 3]      : bit;

  SCLK[@PortB, b_SCLK]       : bit; //Takt für alle
  STRAD24[@PortB, b_STRAD24] : bit; //Strobe für LTC2400
  SDATAIN1[@PinB, b_SDATAIN1]: bit; //Dateneingang vom LTC2400

  RelaisPort[@Port0] : TI2Cport;    // Port 9554A auf I/O-Platine
  LEDPort[@Port1]    : TI2Cport;    // Port 9554A auf I/O-Platine
  RangeDDR[@DDR0]    : TI2Cport;
  LEDDDR[@DDR1]      : TI2Cport;

  RelaisTemp      : byte;
{$IFDEF DIV22}
  SW3[@RelaisTemp, 0]    : bit;  // DC-Vorteiler HV, 1=on
  SW4[@RelaisTemp, 1]    : bit;  // AC/DC-Umschalter, 1=AC
  SWAC200[@RelaisTemp, 2]: bit;  // AC-Vorteiler /200
  PGA0[@RelaisTemp, 3]   : bit;  // PGA202
  PGA1[@RelaisTemp, 4]   : bit;  // PGA202
  SW1[@RelaisTemp, 5]    : bit;  // DC/10, 1=on
  SW2[@RelaisTemp, 6]    : bit;  // DC/100, 1=on
{$ELSE}
  MPX0[@RelaisTemp, 0]   : bit; // Multiplexer
  MPX1[@RelaisTemp, 1]   : bit; //
  MPX2[@RelaisTemp, 2]   : bit; // Multiplexer + SW3, 1=on
  PGA0[@RelaisTemp, 3]   : bit; // PGA202
  PGA1[@RelaisTemp, 4]   : bit; // PGA202
  SW1[@RelaisTemp, 5]    : bit; // DC/100, 1=on
  SW2[@RelaisTemp, 6]    : bit; // DC-Vorteiler HV, 1=on
  SWAC200[@RelaisTemp, 7]  : bit; // AC Vorteiler 1=AC/200, 0=AC/2
{$ENDIF}

  LEDTemp        : byte; // invertiert - low=on!
  LED100mV[@LEDTemp, 7] : bit; // Leuchtdioden am 2. PCA9554A
  LED1V[@LEDTemp, 6]    : bit; //
  LED10V[@LEDTemp, 5]   : bit; //
  LED100V[@LEDTemp, 4]  : bit; //
  LED1000V[@LEDTemp, 3] : bit; //
  LEDDC[@LEDTemp, 2]    : bit; //
  LEDAC[@LEDTemp, 1]    : bit; //
  LEDauto[@LEDTemp, 0]   : bit; //



  ButtonTemp,RangeTemp: Byte; // invertiert - low=on!
  ButtonLeft[@ButtonTemp, 5]  : bit;
  ButtonRight[@ButtonTemp, 4]  : bit;
  ButtonEnter[@ButtonTemp, 3]  : bit;
//Parametern für externen und internen ADC
//  ValueAD24,ValueAD10: Float;

  CmdWhich       : tCmdWhich;
  SubCh, CurrentCh     : byte;
  OmniFlag, NegativeFlag: boolean;

  Param          : Float;
  Parambyte      : Byte;
  ParamInt       : LongInt;
  SerInpStr      : String[63];
  SerInpPtr:byte;
  
  AD24IntegrateMode:byte; //0=Off, 1=Fast, 2=Slow, (3=Auto)
  AD24, AD24temp, AD24tempSI, AD24tempFI,
  AD24Integrate0, AD24Integrate1,AD24Integrate2,AD24Integrate3 : LongInt;
  AD24ready, AD10flag, TriggerSetFlag, AbortFlag    : boolean;
  AD10           : Integer;
  Range, OldRange: tRange;
  AutoRangeMode, OldRangeMode: Boolean;
//  RangeCommand, RangeReturn :byte;
  ActivityTimer:Systimer;
  DisplayTimer,ScanTimer:Systimer8;
  LCDpresent: boolean;

//für Parser
  ParamStr:string[40]; // auch für Display

// ausgegebene Status-Meldung, Bits 0..3 = Fehler oder Overload-Kanal
  Status        : byte; // für Status-Meldung
  OverloadFlag[@Status, 5]  : bit; // Overload/Overrange-Flag
  UserSRQFlag[@Status, 6]  : bit;  // User Service Request
  BusyFlag[@Status, 7]  : bit;     // Device busy

{$NOSAVE}

{###########################################################################}
//EEPROM-Routinen und sonstige

procedure PatchCopyFromEE;
//AktuelleFrequenz und Settings aus EEPROM holen
var myPatchAdr: Word;
begin

end;


{###########################################################################}

{$I DIV-HW.pas}
procedure OnSysTick(saveallregs);
begin
  SCLK:= low;
  STRAD24:= low;
  if AbortFlag then
    Abort2400;
    AbortFlag:=false;
  else
    if (SDATAIN1 = low) then // EOC LTC2400, Wandlung fertig?
      STRAD24:= high;
      ShiftIn2400;
      //Ergebnis der Wandlung steht in AD24temp, zusätzlich integrierte Werte:
      AD24tempFI:=(AD24temp+AD24Integrate0) div 2;
      AD24Integrate0:=AD24tempFI;

      AD24tempSI:=(AD24temp+AD24Integrate1+AD24Integrate2+AD24Integrate3) div 4;
      AD24Integrate3:=AD24Integrate2;
      AD24Integrate2:=AD24Integrate1;
      AD24Integrate1:=AD24tempSI;
      
      AD24ready:= true; // AD24 aktualisiert
    endif;
  endif;
  STRAD24:= high;
  AD10flag:= true;
end;

procedure RangeScale;
//Param mit Skalenfaktoren 0.1 .. 100 versehen
begin
  Param:= Param*RangeArray[m]*ScaleArray[m];   // Achtung, m ist hier ord(Range)
  if Range >= AC100mV then
    Param:= abs(Param);
  endif;
end;

procedure ParamScale10;
begin
  m:=ord(Range)+10;
  Param:= Float(LongInt(AD10) + OffsetArray[m]);
  RangeScale;
end;

procedure ParamScale24;
begin
  m:=ord(Range);
  Param:= Float(AD24 + OffsetArray[m]);
  RangeScale;
end;

procedure GetAD10;  // <Param> vom internen ADC holen, je nach Range AC oder DC
var ADtemp: Integer;
begin
  OverloadFlag:= false;
  if Range < AC100mV then
    AD10:= Integer(getADC(3));  //DC-Kanal mit bipolar-Bias
    if AD10 >= 1022 then
      OverloadFlag:= true;
    endif;
    if AD10 = 0 then
      OverloadFlag:= true;
    endif;
    AD10:= AD10 - 512; // FS/2 bipolar-Bias abziehen
  else
    AD10:= Integer(getADC(4)); // AC-Kanal
    if AD10 >= 1022 then
      OverloadFlag:= true;
    endif;
  endif;
end;

procedure GetAD24(myIntMode:Byte);  // <Param> vom externen ADC LTC2400 holen
//Negative- und OverloadFlag ggf. vom IRQ gesetzt
var myAD24:LongInt;
begin
  case myIntMode of
    1:
      myAD24:=AD24tempFI;
      |
    2:
      myAD24:=AD24tempSI;
      |
    3:
      case Range of
        DC100mV :
          myAD24:=AD24tempSI;
          |
        DC1V :
          myAD24:=AD24tempFI;
          |
      else
        myAD24:=AD24temp;
      endcase;
      |
  else
    myAD24:=AD24temp;
  endcase;
  if Range < AC100mV then // DC-Bereiche
    AD24:= myAD24 - $800000; // FS/2 abziehen bei DC
  else
    AD24:= myAD24;
    if NegativeFlag then
      OverloadFlag:=false;
      AD24:=0;
    endif;
  endif;
end;


procedure WaitAD10;  // Warte auf Ergebnis von ADC3
begin
  AD10flag := false;
  repeat
  until AD10flag; // vom IRQ gesetzt
end;

procedure WaitAD24;  // Warte auf Ergebnis von ADC24
begin
  repeat
  until AD24ready; // vom IRQ gesetzt
end;

procedure IntegrateReset;  //Integrationswerte zurücksetzen
begin
  if Range < AC100mV then
    AD24Integrate0:=0;
    AD24Integrate1:=0;
    AD24Integrate2:=0;
    AD24Integrate3:=0;
  else
    AD24temp:=$800000;
    AD24Integrate0:=AD24temp;
    AD24Integrate1:=AD24temp;
    AD24Integrate2:=AD24temp;
    AD24Integrate3:=AD24temp;
  endif;
end;


procedure SwitchRange;
// Range-Relais und LEDs anhand RANGE und AutoRangeMode einschalten
begin
  if (Range=OldRange) and (AutoRangeMode=OldRangeMode) then
    return;
  endif;
  OldRange:=Range;
  OldRangeMode:=AutoRangeMode;
  RelaisTemp:= $0;  //Grundstellung
  LEDtemp:=$FF;
  LEDaux := not AutoRangeMode;   // Mini-Panel-LED an
  LEDauto:= not AutoRangeMode;
  if Range < AC100mV then  //DC-Bereiche
    LEDDC:=low;
    case Range of
{$IFDEF DIV22}     // Neue Platine
      DC100mV :
                SWAC200:= true;
                PGA1:= true; //X100
                LED100mV:=low;
              |
      DC1V    :
                SWAC200:= true;
                PGA0:= true; //X10
                LED1V:=low;
              |
      DC10V   :
                SWAC200:= true;
                LED10V:=low;
              |
      DC100V  :
                SW3:= true;
                SW1:= true;
                LED100V:=low;
              |
      DC1000V :
                SW3:= true;
                SW2:= true;
                LED1000V:=low;
              |
{$ELSE} //Alte Platine mit DG509 MPX
      DC100mV :
                SWAC200:= true;
                PGA1:= true; // X100
                MPX1:=true;  // MPX S3, offen
                LED100mV:=low;
              |
      DC1V    :
                SWAC200:= true;
                PGA0:= true; // X10
                MPX1:=true;  // MPX S3, offen
                LED1V:=low;
              |
      DC10V   :
                SWAC200:= true;
                MPX1:=true;  // MPX S3, offen
                LED10V:=low;
              |
      DC100V  :
                SW2:= true;
                SW1:= true;
                MPX1:=true;  // MPX S3, offen
                LED100V:=low;
              |
      DC1000V :
                SW2:= true;
                MPX0:=true;  // MPX S2
                LED1000V:=low;
              |
{$ENDIF}
    endcase;
  else
    LEDAC:=low;    // AC-Bereiche
    case Range of
{$IFDEF DIV22}     // Neue Platine
      AC100mV :
                SW4:=true;  // AC-Zweig, AC X10 (SW3=false)
                PGA0:= true; // X10 DC Preamp
                LED100mV:=low;
              |
      AC1V    :
                SW4:=true;  // AC-Zweig, AC X10 (SW3=false)
                LED1V:=low;
              |
      AC10V   :
                SW4:=true;  // AC-Zweig
                SW3:= true; // AC X1
                LED10V:=low;
              |
      AC100V  :  //Abschwächer 200, Verstärkung 10 = /20
                SWAC200:= true;
                SW4:=true;   // AC-Zweig
                SW3:= true;  // AC X1
                LED100V:=low;
              |
      AC1000V :  //Abschwächer 200
                SWAC200:= true;
                SW4:=true;   // AC-Zweig
                SW3:= true;  // AC X1
                LED1000V:=low;
              |
{$ELSE} //Alte Platine mit DG509 MPX
      AC100mV :
                MPX2:=true;  // MPX S5..8
                MPX1:=true;  // X10 AC Preamp
                PGA0:= true; // X10 DC Preamp
                LED100mV:=low;
              |
      AC1V    :
                MPX2:=true;  // MPX S5..8
                MPX1:=true;  // X10 AC Preamp
                LED1V:=low;
              |
      AC10V   :
                MPX2:=true;  // MPX S5..8
                MPX0:=true;  // ohne AC Preamp
                LED10V:=low;
              |
      AC100V  :  //Abschwächer 200, Verstärkung 10 = /20
                MPX2:=true;  // MPX S5..8
                MPX1:=true;  // X10 AC Preamp
                SWAC200:= true;
                LED100V:=low;
              |
      AC1000V :  //Abschwächer 200
                MPX2:=true;  // MPX S5..8
                MPX0:=true;  // X10 AC Preamp
                SWAC200:= true;
                LED1000V:=low;
              |
{$ENDIF}
    endcase;
  endif;
  RelaisPort:= RelaisTemp;
  LEDPort:=LEDtemp;
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
  SubCh:=ErrSubCh; // Fehler-Kanal
  WriteChPrefix;
  write(Serout, ByteToStr(ord(myErr)+myStatus));
  Serout(#32);
  write(serout,ErrStrArr[ord(myErr)]);
  SerCRLF;
end;

{###########################################################################}

procedure ParamToStr;
// Param in ParamStr ASCII-String wandeln
begin
  if Param=0 then
    ParamStr:='0.000000';
  else
    ParamStr:=FloatToStr(Param);
  endif;
  ParamStr:=trim(ParamStr);
end;


procedure ShowRange;
begin
{$IFDEF Zweizeilig}
  LCDxy_M(LCD_m1, 0, 1);
  if Range < AC100mV then
    lcdout_m('D');
  else
    lcdout_m('A');
  endif;
  lcdout_m('C');
  case Range of
    DC100mV,AC100mV:
      write(lcdout_m,' 100mV');
      |
    DC1V,AC1V:
      write(lcdout_m,'    1V');
            |
    DC10V,AC10V:
      write(lcdout_m,'   10V');
            |
    DC100V,AC100V:
      write(lcdout_m,'  100V');
            |
    DC1000V,AC1000V:
      write(lcdout_m,'   1kV');
            |
  endcase;
{$ENDIF}
end;

procedure WriteParamLCD;
begin
  ParamToStr;
  append('0000',ParamStr);
  if ParamStr[1] <> '-' then
    insert('+',ParamStr,1);
  endif;
  setlength(ParamStr,8);
  LCDxy_M(LCD_m1, 0, 0);
  if OverloadFlag then
    write(lcdout_m,'OVERLOAD');
  else
    write(lcdout_m,ParamStr);
  endif;
end;

procedure WriteParamSer(Ovl : boolean);
begin
  if Ovl then
    SerPrompt(OvlErr,0);
  else
    WriteChPrefix;
    ParamToStr;
    write(serout,ParamStr);
    if Range in [DC100mV, AC100mV] then
      write(serout,'E-3');
    endif;
    SerCRLF;
  endif;
end;

procedure WriteParamIntSer(Ovl : boolean);
var ParamStr: String[15];
begin
  WriteChPrefix;
  ParamStr:= LongToStr(ParamInt);
  ParamStr:= trim(ParamStr);
  write(Serout, ParamStr);
  SerCRLF;
end;


{###########################################################################}
{
normale Syntax: (alle auch mit <*> statt <MainCh> als Omni-Befehl)
<MainCh> <:> CMD <SPACE> <SubCh> <=> <Wert> <CRLF>
CMD <SPACE> <SubCh> <?> <CRLF>
Ein-Paramater-Befehle:
CMD <SPACE> <SubCh> <CRLF>
Null-Paramater-Befehle:
CMD <?> <CRLF>

Kurzform: VAL <SPACE> und <MainCh> <:> können weggelassen werden,
es wird dann der zuletzt angesprochene Kanal verwendet
Beispiel:
0:VAL 5?
0:5?
5?
0:VAL 2=1.00
0:2=1.00
2=1.00
}

// gerätespezifischer Parser-Teil

procedure ParseGetParam;
var isInteger:Boolean;
begin
  isInteger:= false;
  if (SubCh < 100) and TriggerSetFlag then
    waitAD24;
    TriggerSetFlag:=false;
  endif;
  case SubCh of
    0 : //VAL Eingangsspannung AD24
                   GetAD24(AD24IntegrateMode);
                   ParamScale24;
                   |
    1 :  //VAL Eingangsspannung AD10
                   GetAD10;
                   ParamScale10;
                   |
    2 :  //VAL Eingangsspannung AD24 roh
                   ParamInt:= LongInt(AD24);
                   isInteger:= true;
                   |
    3 :  //VAL Eingangsspannung AD10 roh
                   GetAD10;
                   ParamInt:= LongInt(AD10);
                   isInteger:= true;
                   |
    99:  //ALL alle Eingangsspannungen
                   GetAD24(AD24IntegrateMode);
                   ParamScale24;
                   SubCh:=0;
                   WriteParamSer(OverloadFlag);
                   GetAD10;
                   ParamScale10;
                   SubCh:=1;
                   |
    100..119 : // OFS AD24 Offset je nach eingestellter Range
                   ParamInt:= OffsetArray[SubCh - 100];
                   isInteger:= true;
                   |
    200..219       : //SCL Skalierung DC (5 Ranges) und AC (5 Ranges)
                    Param:= ScaleArray[SubCh - 200];
                   |
    250            : //RMO
                   ParamInt:=longInt(AutoRangeMode);
                   isInteger:= true;
                   |
    251            : //ITG
                   ParamInt:=longInt(AD24IntegrateMode);
                   isInteger:= true;
                   |
    252            : // RNG, bei AutoRange gewählten Range liefern
                   ParamInt:= ord(Range);
                   isInteger:= true;
                   |
    254            : //IDN
                   WriteChPrefix;
                   write(Serout, Vers1Str);
                   SerCRLF;
                   return;
                   |
    255: //Status
                   serprompt(NoErr,Status);
                   return;
                   |
  else
    serprompt(ParamErr,0);
    return;
  endcase;
  if isInteger then
    WriteParamIntSer(OverloadFlag);
  else
    WriteParamSer(OverloadFlag);
  endif;
end;


procedure ParseSetParam;
begin // kein Request, also Parameter setzen
  case SubCh of
    100..119 :  //OFS
               OffsetArray[SubCh - 100]:= ParamInt;
             |
    200..219 :  //SCL
               ScaleArray[SubCh - 200]:= Param;
             |
    250 :       //RMO
               if ParamByte=0 then
                 AutoRangeMode:=false;
               else
                 AutoRangeMode:=true;
               endif;
               SwitchRange;
             |
    251 :       //ITG
               AD24integrateMode:=(ParamByte AND 3);
             |
    252      :  //RNG
               if AutoRangeMode then
                 if (Range < AC100mV) and (ParamByte >= byte(AC100mV)) then
                   Range:=tRange(ParamByte);
                   SwitchRange;
                 endif;
                 if (Range >= AC100mV) and (ParamByte < byte(AC100mV)) then
                   Range:=tRange(ParamByte);
                   SwitchRange;
                 endif;
               else
                 Range:=tRange(ParamByte);
                 SwitchRange;
               endif;
             |
    249      :  //TRG
               AbortFlag:=true;
               repeat
               until not AbortFlag;
               AD24ready := false;
               TriggerSetFlag:=true;
             |
  else
    serprompt(ParamErr,0);
    return;
  endcase;
  serprompt(NoErr,Status);
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(imbusy:boolean);
//Überprüfen, ob Befehl oder Daten eingegangen und für uns, Parser-Vorbehandlung,
//wenn Busy => BusyErr
var
  GleichPos:byte;
  SubChOffset:byte;

  myChar:char;
  hasMainCh, isResult, isOmni, isRequest, isParam: Boolean;

begin
  SerInpStr:=trim(SerInpStr);
  if SerInpStr='' then
    serprompt(NoErr,0);
    return;
  endif;
  hasMainCh:=(pos(':',SerInpStr)>0); // Kanaltrenner-':'
  isRequest:=(pos('?',SerInpStr)>0); // Abfrage
  isResult:=(pos('#',SerInpStr)>0);  // Ergebnis-'='
  isOmni:=(pos('*',SerInpStr)>0);    // Omni-Flag
  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;
  if imbusy then  //wenn selbst Busy => BusyErr
    serprompt(BusyErr,0);
    return;
  endif;
// Befehl/Parameter ist für uns, ab hier eigentliche Behandlung
  SetSystimer(ActivityTimer, 500);
  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 ParseExtract then
      Param:=StrToFloat(ParamStr);
      ParamInt:=longInt(Param);
      ParamByte:=byte(ParamInt);
    else
      serprompt(ParamErr,0);
      return;
    endif;
    ParseSetParam;
  endif;
end;

procedure CheckSer(imbusy:boolean);
var
myChar: char;
begin
  if serstat then
    myChar:=SerInp;
//    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
      delete(SerInpStr, length(SerInpStr), 1);
    endif;
    if myChar=#13 then
{$IFDEF Parser}
      ParseSubCh(imbusy);           //Befehl interpretieren, wenn für uns
{$ENDIF}
      SerInpStr:='';
    endif;
  endif;
end;


procedure CheckDelay(myDelay:byte);
var mycount:byte;
begin
  for mycount:=0 to mydelay do
    CheckSer(true);
    mdelay(1);
  endfor;
end;

{###########################################################################}

procedure AutoRange;
var withinLimits:boolean;
begin
  if not AutoRangeMode then
    return;
  endif;
  
  if Range in [DC100mV..DC1000V] then
    WaitAD10; // vom IRQ gesetzt
    GetAD10;
    repeat
      withinLimits:=true;
      if OverloadFlag then
        withinLimits:=false;
        if Range=DC1000V then
          withinLimits:=true; // nicht steigerungsfähig
        else
          inc(Range);
          SwitchRange;
          CheckDelay(50);
        endif;
      endif;
      if (abs(AD10) < 46) then
        withinLimits:=false;
        if Range=DC100mV then
          withinLimits:=true; // nicht steigerungsfähig
        else
          dec(Range);
          SwitchRange;
          CheckDelay(50);
        endif;
      endif;
      OverLoadFlag:=false;
      WaitAD10; // vom IRQ gesetzt
      GetAD10;
    until withinLimits;
  endif;

  if Range in [AC100mV..AC1000V] then
    WaitAD10; // vom IRQ gesetzt
    GetAD10;
    repeat
      withinLimits:=true;
      if OverloadFlag then
        withinLimits:=false;
        if Range=AC1000V then
          withinLimits:=true; // nicht steigerungsfähig
        else
          inc(Range);
          SwitchRange;
          CheckDelay(200);
        endif;
      endif;
      if (abs(AD10) < 91) then
        withinLimits:=false;
        if Range=AC100mV then
          withinLimits:=true; // nicht steigerungsfähig
        else
          dec(Range);
          SwitchRange;
          CheckDelay(200);
        endif;
      endif;
      OverLoadFlag:=false;
      WaitAD10; // vom IRQ gesetzt
      GetAD10;
    until withinLimits;
  endif;
  
  SwitchRange; // Messbereichsumschaltung
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}

//  ADMUX:= ADMUX OR %11000000;  {Internal ADC Reference}

  PatchCopyFromEE;       // Optionen-Defaults
  RangeDDR:= $FF;
  LEDDDR:= $FF;
  LEDPort:= $FF;

  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}

    LCDpresent:=true;
    write(lcdout_m,Vers3Str);
    LCDxy_M(LCD_m1, 0, 1);
    write(lcdout_m,AdrStr);
    lcdout_m(char(SlaveCh+48));
  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;
  
  EnableInts;
  AD24integrateMode:=0; //Auto-Integrate abgeschaltet
  OldRange:= tRange(255);
  Range:=DC10V;
  AutoRangeMode:=false;
  OldRangeMode:= true;
  SwitchRange;
  while serstat do
    i:= SerInp;
  endwhile;
end;

{###########################################################################}


begin
  Initall;
//Hauptschleife
  loop
    CheckSer(false);  // Serinp parsen, nicht busy
//    LEDOVL:=not OverloadFlag;
    AutoRange;
    if isSystimerZero(ActivityTimer) then
      LEDactivity:=high; // LED aus
    endif;
    if LCDpresent and isSystimerZero(ScanTimer) then
      SetSystimer(ScanTimer, 20);
      ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
      if ButtonTemp <> $FF then
        if not ButtonEnter then
          AutoRangeMode := not AutoRangeMode;
          SubCh:=250;
        else
          RangeTemp:=ord(Range);
          if not ButtonLeft then
            dectolim(RangeTemp,0);
            AutoRangeMode:=false;
          endif;
          if not ButtonRight then
            inctolim(RangeTemp,9);
            AutoRangeMode:=false;
          endif;
          Range:=tRange(RangeTemp);
          SubCh:=252;
        endif;
        ParseGetParam; // Parameter am Panel geändert, ausgeben
        SwitchRange;
        ShowRange;
        repeat
          ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
          Checkdelay(30);
        until ButtonTemp = $FF;
      endif;
    endif;
    if isSystimerZero(DisplayTimer) then
      SetSystimer(DisplayTimer, 255);
      if LCDpresent then
        GetAD24(0); // untere DC-Bereiche mit Integration
        ParamScale24;
        WriteParamLCD;
        ShowRange;
      endif;
    endif;
  endloop;
end DIV.

