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
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


{ $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      = (idn, trg, rng, itg, rmo, val, ofs, scl, nop, err);
  
  tRange  = (X100mV, X1V, X10V, X100V, X1000V);     // 0..4

  tRangeMode =(DCmanual, DCauto, ACmanual, ACauto);
  tError = (NoErr, SyntaxErr, ParamErr, RangeErr,BusyErr);

{--------------------------------------------------------------}
const


//                              ----SFDC             Strobe,FSync,Data,Clk
  DDRBinit                   = %00011111;            {PortB dir }
  PortBinit                  = %00010111;            {PortB, b_STROBE=0 }

  DDRCinit                   = %11110000;            {PortC dir, 0..1=ADC }
  PortCinit                  = %11110011;            {PortC }

{ Jumper und LEDs PortBits }
  DDRDinit                   = %00000100;            {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                   = 'ADA-C DIV V#2.05';    //Vers1
  Vers2Str                   = 'C.Meyer 12/2006';    //Vers2
  Vers3Str                   = 'DIV 2.05';    // Kurzform von Vers1

  ErrStr1                    = 'ERR SYNTAX';
  ErrStr2                    = 'ERR PARAMETER';
  ErrStr3                    = 'ERR RANGE';
  BusyStr                    = 'BUSY';
  OKStr                      = 'OK';
  AdrStr                     = 'Adr ';
  OVLStr                     = 'OVL';
  ErrSubCh                   = 255;

  CmdStrArr      : array[0..8] of String[3] = (
  'IDN','TRG','RNG','ITG','RMO',
  'VAL','OFS','SCL','NOP');   //2 Parameter
  Cmd2SubChArr   : array[0..8] of byte = (
  254, 253, 252, 251, 250,
  0, 100, 200, 255);   // 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/82400.0,1.0/82400000,1.0/8240000,1.0/824000,1.0/82400,    // 0..4  AD24 für 5 Ranges DC 100mV..1000V
  1.0/163840,1.0/163840000,1.0/16384000,1.0/1638400,1.0/163840,    // 5..9  AD24 für 5 Ranges AC 100mV..1000V
  1.0/5,1.0/5000,1.0/500,1.0/50,1.0/5,            // 10..14  AD10 für 5 Ranges DC 100mV..1000V
  1.0/10,1.0/10000,1.0/1000,1.0/100,1.0/10);      // 15..19  AD10 für 5 Ranges AC 100mV..1000V
  
  OffsetArray    : array[0..19] of LongInt =
 (0, 0, 0, 0, 0,    // 0..4 Offset AD24 für 5 Ranges DC 100mV..1000V
  0, 0, 0, 0, 0,    // 5..9 Offset AD24 für 5 Ranges AC 100mV..1000V
  0, 0, 0, 0, 0,    // 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; {Bit 2 ist Memory-LED, separat}

  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; // Multiplexer
  LED1V[@LEDTemp, 6]    : bit; // Multiplexer
  LED10V[@LEDTemp, 5]   : bit; // Multiplexer
  LED100V[@LEDTemp, 4]  : bit; // Multiplexer
  LED1000V[@LEDTemp, 3] : bit; // Multiplexer
  LEDDC[@LEDTemp, 2]    : bit; // Multiplexer
  LEDAC[@LEDTemp, 1]    : bit; // Multiplexer

  LEDauto[@LEDTemp, 0]   : bit; // Overload-Flag

  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;
  CmdStr         : String[3];
  SubCh, CurrentCh     : byte;
  OmniFlag, OverloadFlag, NegativeFlag: boolean;

  Param          : Float;
  Parambyte      : Byte;
  ParamInt       : LongInt;
  SerInpStr      : String[63];
  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;
  RangeMode, OldRangeMode: tRangeMode;
//  RangeCommand, RangeReturn :byte;
  ActivityTimer:Systimer;
  DisplayTimer:Systimer8;
  LCDpresent: boolean;

//für Parser
  ParamStr:string[40]; // auch für Display
  isInteger,Request:boolean;
{$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 RangeMode >= ACmanual then
    Param:= abs(Param);
  endif;
end;

procedure ParamScale10;
begin
  if RangeMode>=ACmanual then
    m:=ord(Range)+15;
  else
    m:=ord(Range)+10;
  endif;
  Param:= Float(LongInt(AD10) + OffsetArray[m]);
  RangeScale;
end;

procedure ParamScale24;
begin
  if RangeMode>=ACmanual then
    m:=ord(Range)+5;
  else
    m:=ord(Range);
  endif;
  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<X100mV 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
//OverloadFlag ggf. vom IRQ gesetzt
var myAD24:LongInt;
begin
  case myIntMode of
    1:
      myAD24:=AD24tempFI;
      |
    2:
      myAD24:=AD24tempSI;
      |
    3:
      case Range of
        X100mV :
          myAD24:=AD24tempSI;
          |
        X1V :
          myAD24:=AD24tempFI;
          |
      else
        myAD24:=AD24temp;
      endcase;
      |
  else
    myAD24:=AD24temp;
  endcase;
  if Range < X100mV then // DC-Bereiche
    AD24:= myAD24 - $800000; // FS/2 abziehen bei DC
  else
    AD24:= myAD24;
  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 in [X100mV..X1000V] 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 RANGEMODE einschalten
begin
  if (Range=OldRange) and (RangeMode=OldRangeMode) then
    return;
  endif;
  OldRange:=Range;
  OldRangeMode:=RangeMode;
  RelaisTemp:= $0;  //Grundstellung
  LEDtemp:=$FF;
  if RangeMode in [DCauto,ACauto] then
    LEDauto := low;  // AutoRange-LED an
  endif;
  if RangeMode < ACmanual then  //DC-Bereiche
    LEDDC:=low;
    case Range of
{$IFDEF DIV22}     // Neue Platine
      X100mV :
                SWAC200:= true;
                PGA1:= true; //X100
                LED100mV:=low;
                
              |
      X1V    :
                SWAC200:= true;
                PGA0:= true; //X10
                LED1V:=low;
              |
      X10V   :
                SWAC200:= true;
                LED10V:=low;
              |
      X100V  :
                SW3:= true;
                SW1:= true;
                LED100V:=low;
              |
      X1000V :
                SW3:= true;
                SW2:= true;
                LED1000V:=low;
              |
{$ELSE} //Alte Platine mit DG509 MPX
      X100mV :
                SWAC200:= true;
                PGA1:= true; // X100
                MPX1:=true;  // MPX S3, offen
                LED100mV:=low;
              |
      X1V    :
                SWAC200:= true;
                PGA0:= true; // X10
                MPX1:=true;  // MPX S3, offen
                LED1V:=low;
              |
      X10V   :
                SWAC200:= true;
                MPX1:=true;  // MPX S3, offen
                LED10V:=low;
              |
      X100V  :
                SW2:= true;
                SW1:= true;
                MPX1:=true;  // MPX S3, offen
                LED100V:=low;
              |
      X1000V :
                SW2:= true;
                MPX0:=true;  // MPX S2
                LED1000V:=low;
              |
{$ENDIF}
    endcase;
  else
    LEDAC:=low;
    case Range of
{$IFDEF DIV22}     // Neue Platine
      X100mV :
                SW4:=true;  // AC-Zweig, AC X10 (SW3=false)
                PGA0:= true; // X10 DC Preamp
                LED100mV:=low;
              |
      X1V    :
                SW4:=true;  // AC-Zweig, AC X10 (SW3=false)
                LED1V:=low;
              |
      X10V   :
                SW4:=true;  // AC-Zweig
                SW3:= true; // AC X1
                LED10V:=low;
              |
      X100V  :  //Abschwächer 200, Verstärkung 10 = /20
                SWAC200:= true;
                SW4:=true;   // AC-Zweig
                SW3:= true;  // AC X1
                LED100V:=low;
              |
      X1000V :  //Abschwächer 200
                SWAC200:= true;
                SW4:=true;   // AC-Zweig
                SW3:= true;  // AC X1
                LED1000V:=low;
              |
{$ELSE} //Alte Platine mit DG509 MPX
      X100mV :
                MPX2:=true;  // MPX S5..8
                MPX1:=true;  // X10 AC Preamp
                PGA0:= true; // X10 DC Preamp
                LED100mV:=low;
              |
      X1V    :
                MPX2:=true;  // MPX S5..8
                MPX1:=true;  // X10 AC Preamp
                LED1V:=low;
              |
      X10V   :
                MPX2:=true;  // MPX S5..8
                MPX0:=true;  // ohne AC Preamp
                LED10V:=low;
              |
      X100V  :  //Abschwächer 200, Verstärkung 10 = /20
                MPX2:=true;  // MPX S5..8
                MPX1:=true;  // X10 AC Preamp
                SWAC200:= true;
                LED100V:=low;
              |
      X1000V :  //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;

{###########################################################################}
//Menü-Prozeduren

procedure SerCRLF;
begin
  Serout(#$0D);
  Serout(#$0A);
end;

function Cmd2Index : tCmdWhich;
var myCmdIndex: tCmdWhich;
begin
  CmdStr:= uppercase(CmdStr);
  for myCmdIndex := idn to nop do
    if CmdStr = CmdStrArr[ord(myCmdIndex)] then
      return(myCmdIndex);
    endif;
  endfor;
  return(err);
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);
begin
  SubCh:=ErrSubCh; // Fehler-Kanal
  WriteChPrefix;
  case myErr of
  NoErr:
    write(serout,OKstr);
    |
  SyntaxErr:
    write(serout,ERRstr1);
    |
  ParamErr:
    write(serout,ERRstr2);
    |
  BusyErr:
    write(serout,BusyStr);
    |
  endcase;
  SerCRLF;
end;

procedure ParamToStr;
// Param in ParamStr ASCII-Strin wandeln
begin
  ParamStr:=FloatToStr(Param);
end;


procedure WriteParamLCD;
begin
  ParamToStr;
  setlength(ParamStr,8);
  if Range in [X100mV, X100mV] then
    ParamStr[8]:=#109;  // "m"
  endif;
  LCDxy_M(LCD_m1, 0, 0);
  if OverloadFlag then
    write(lcdout_m,'OVERLOAD');
  else
    write(lcdout_m,ParamStr);
  endif;
{$IFDEF Zweizeilig}
  LCDxy_M(LCD_m1, 0, 1);
  if RangeMode in [DCauto,DCmanual] then
    lcdout_m('D');
  else
    lcdout_m('A');
  endif;
  lcdout_m('C');
  
  write(lcdout_m,' Volt');
{$ENDIF}
end;

procedure WriteParamSer(Ovl : boolean);
begin
  WriteChPrefix;
  if Ovl then
    write(serout,'-99999');
  else
    ParamToStr;
    ParamStr:=trim(ParamStr);
    write(serout,ParamStr);
    if Range in [X100mV, X100mV] then
      write(serout,'E-3');
    endif;
  endif;
  SerCRLF;
end;

procedure WriteParamIntSer(Ovl : boolean);
var ParamStr: String[15];
begin
  WriteChPrefix;
  ParamStr:= LongToStr(ParamInt);
  ParamStr:= trim(ParamStr);
  write(Serout, ParamStr);
  SerCRLF;
end;

function ParseCheck(imbusy:boolean):boolean;
//Überprüfen, ob Befehl oder Daten eingegangen und für uns, Parser-Vorbehandlung,
//wenn Busy => BusyErr
var
  Laenge, ParamStartPos, GleichPos, FragePos,DpunktPos:byte;
  SubChOffset:byte;
  firstChar:char;

begin
  Request:=false;
  SerInpStr:=trim(SerInpStr);
  FirstChar:=SerInpStr[1];
  if FirstChar='#' then // Messwert durchreichen
    WriteSerInp;
    return (false);
  endif;
  if FirstChar='*' then // Omni-Befehl
    WriteSerInp; // weiterleiten
    OmniFlag:=true;
    delete(serInpStr,1,1);
  else
    OmniFlag:=false;
  endif;
  DpunktPos:=pos(':',SerInpStr); // Kanaltrenner-':'
  if DpunktPos > 0 then
    CurrentCh:=byte(FirstChar)-48;  // erste Ziffer = neuer Kanal
  endif;
  if (not OmniFlag) and (CurrentCh<>SlaveCh) then // nicht für uns
    WriteSerInp;
    return (false);
  endif;
  if length(SerInpStr) = 0 then
    serprompt(NoErr);
    return (false);
  endif;
  if imbusy then  //wenn Busy => BusyErr
    serprompt(BusyErr);
    return (false);
  endif;

  inc(DpunktPos);
  FirstChar:=SerInpStr[DpunktPos];
  SetSystimer(ActivityTimer, 500);
  LEDactivity:=low;
//Parse einzelnen Befehl
  if FirstChar <':' then // also Ziffer, Kurzbefehl statt "VAL"
    ParamStartPos:=DpunktPos;
    CmdWhich:=val;
  else
    ParamStartPos:=pos(' ',SerInpStr)+1; // Trenner-' '
    CmdStr:=copy(SerInpStr,DpunktPos,3);
    CmdWhich:=Cmd2Index;
    if CmdWhich = err then
      serprompt(SyntaxErr);
      return (false);
    endif;
  endif;

  FragePos:=pos('?',SerInpStr); // Request-'?'
  Gleichpos:=pos('=',SerInpStr); // Set-'='
  Laenge:=length(SerInpStr);

  SubChOffset:=Cmd2SubChArr[ord(CmdWhich)];
  if FragePos > 0 then
    Request:=true;
    dec(Laenge); // ohne Fragezeichen
    SerInpStr:=copy(SerInpStr,1,Laenge);
    ParamStr:=copy(SerInpStr,ParamStartPos,Laenge);
    SubCh:=StrToInt(ParamStr)+SubChOffset; //auf neuen SubCh umrechnen
  else
    if CmdWhich >= val then // Befehle mit 2 Parametern auswerten
      if (GleichPos=0) or (GleichPos=Laenge) then // '=' fehlt
        serprompt(SyntaxErr);
        return (false);
      endif;
      ParamStr:=copy(SerInpStr,ParamStartPos,GleichPos-ParamStartPos);
      SubCh:=StrToInt(ParamStr)+SubChOffset; //auf neuen SubCh umrechnen
      ParamStr:=copy(SerInpStr,GleichPos+1,Laenge);
    else // einen Byte- oder keinen Parameter
      SubCh:=SubChOffset;  //auf neuen SubCh umrechnen
      ParamStr:=copy(SerInpStr,ParamStartPos,Laenge); //ganzen Rest-String
    endif;
    Param:=StrToFloat(ParamStr);
    ParamInt:=LongInt(Param);
    ParamByte:=byte(ParamInt);
  endif;
  return (true); //gültig und für uns
end;


procedure ParseSubCh;
{
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

}


begin
  if Request then
    isInteger:= false;
    if (SubCh < 2) and TriggerSetFlag then
      waitAD24;
    endif;
    case SubCh of
      0 : //VAL Eingangsspannung AD24
                       GetAD24(AD24IntegrateMode);
                       ParamScale24;
                       TriggerSetFlag:=false;
                     |
      1 :  //VAL Eingangsspannung AD10
                       GetAD10;
                       ParamScale10;
                     |
      3 :  //VAL Eingangsspannung AD24 roh
                       ParamInt:= LongInt(AD24);
                       isInteger:= true;
                       TriggerSetFlag:=false;
                     |
      4 :  //VAL Eingangsspannung AD10 roh
                       GetAD10;
                       ParamInt:= LongInt(AD10);
                       isInteger:= true;
                     |
      100..119 : // OFS AD24 Offset je nach eingestellter Range
                       ParamInt:= OffsetArray[ord(Range)];
                       isInteger:= true;
                     |
      200..219       : //SCL Skalierung DC (5 Ranges) und AC (5 Ranges)
                       Param:= ScaleArray[SubCh - 200];
                     |
      250            : //RMO
                       ParamInt:=longInt(RangeMode);
                       isInteger:= true;
                    |
      251            : //ITG
                       ParamInt:=longInt(AD24IntegrateMode);
                       isInteger:= true;
                    |
      252            : // RNG, bei AutoRange gewählten Range liefern
                       ParamInt:= ord(Range);
                       isInteger:= true;
                     |
      254            : //IDN
                       SubCh:=ErrSubCh;
                       WriteChPrefix;
                       write(Serout, Vers1Str);
                       SerCRLF;
                       return;
                     |
    else
      serprompt(ParamErr);
      return;
    endcase;
    if isInteger then
      WriteParamIntSer(OverloadFlag);
    else
      WriteParamSer(OverloadFlag);
    endif;
  else  // kein Request, also Parameter setzen
    case SubCh of
      100..119 :  //OFS
                 OffsetArray[SubCh - 100]:= ParamInt;
               |
      200..219 :  //SCL
                 ScaleArray[SubCh - 200]:= Param;
               |
      250 :       //RMO
                 RangeMode:=tRangeMode(ParamByte AND 3);
                 SwitchRange;
               |
      251 :       //ITG
                 AD24integrateMode:=(ParamByte AND 3);
               |
      252      :  //RNG
                 if RangeMode in [DCmanual, ACmanual] then
                   Range:=tRange(ParamByte mod 10);
                   SwitchRange;
                 endif;
               |
      253      :  //TRG
                 AbortFlag:=true;
                 repeat
                 until not AbortFlag;
                 AD24ready := false;
                 TriggerSetFlag:=true;
               |
    else
      serprompt(ParamErr);
      return;
    endcase;
    serprompt(NoErr);
  endif;
end;

procedure CheckSer(imbusy:boolean);
begin
  if serstat then
    readln(SerInp, SerInpStr);
    if ParseCheck(imbusy)then
      ParseSubCh;           //Befehl interpretieren, wenn für uns
    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 RangeMode = DCauto then
    if not (Range in [X100mV..X1000V]) then
      Range:= X10V;
      SwitchRange;
    endif;
    WaitAD10; // vom IRQ gesetzt
    GetAD10;
    repeat
      withinLimits:=true;
      if OverloadFlag then
        withinLimits:=false;
        if Range=X1000V then
          withinLimits:=true; // nicht steigerungsfähig
        else
          inc(Range);
          SwitchRange;
          CheckDelay(50);
        endif;
      endif;
      if (abs(AD10) < 50) then
        withinLimits:=false;
        if Range=X100mV 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 RangeMode = ACauto then
    if not (Range in [X100mV..X1000V]) then
      Range:= X10V;
      SwitchRange;
    endif;
    WaitAD10; // vom IRQ gesetzt
    GetAD10;
    repeat
      withinLimits:=true;
      if OverloadFlag then
        withinLimits:=false;
        if Range=X1000V then
          withinLimits:=true; // nicht steigerungsfähig
        else
          inc(Range);
          SwitchRange;
          CheckDelay(200);
        endif;
      endif;
      if (abs(AD10) < 95) then
        withinLimits:=false;
        if Range=X100mV 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;
  mdelay(1000);
  if LCDsetup_M(LCD_m1) then
    LCDcursor_M(LCD_m1, false, false);
    LCDpresent:=true;
    write(lcdout_m,Vers3Str);
    mdelay(1000);
    LCDclr_M(LCD_m1);
    write(lcdout_m,AdrStr);
    lcdout_m(char(SlaveCh+48));
  endif;
  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:=3; //Auto-Integrate je nach Bereich
  OldRange:= tRange(255);
  OldRangeMode:= tRangeMode(255);
  Range:=X10V;
  RangeMode:=DCmanual;
  SwitchRange;
  while serstat do
    i:= SerInp;
  endwhile;
end;

{###########################################################################}


begin
  Initall;
//Hauptschleife
  loop
    CheckSer(false);  // Serinp parsen, nicht busy
//    LEDOVL:=not OverloadFlag;
    if RangeMode in [ACauto,DCauto] then
      AutoRange;
    endif;
    if isSystimerZero(ActivityTimer) then
      LEDactivity:=high; // LED aus
      if LCDpresent then
        ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
        if ButtonTemp <> $FF then
          if not ButtonEnter then
            if RangeMode in [DCmanual, ACauto] then
              RangeMode:=DCAuto;
            else
              RangeMode:=ACAuto;
            endif;
            AutoRange; // Auto-LED setzen
          else
            RangeTemp:=ord(Range);
            if not ButtonLeft then
              dectolim(RangeTemp,0);
              RangeMode:=DCmanual;
            endif;
            if not ButtonRight then
              inctolim(RangeTemp,14);
              if RangeTemp = 5 then  // überspringen
                RangeTemp := 10;
              endif;
              RangeMode:=DCmanual;
            endif;
            Range:=tRange(RangeTemp);
            SwitchRange;
          endif;
          repeat
            ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
            Checkdelay(30);
          until ButtonTemp = $FF;
        endif;
      endif;
    endif;
    if isSystimerZero(DisplayTimer) and LCDpresent then
      GetAD24(3); // höhere DC-Bereiche ohne Integration
      ParamScale24;
      WriteParamLCD;
      SetSystimer(DisplayTimer, 255);
    endif;
  endloop;
end DIV.

