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

05.02.2007 Define für ein- oder zweizeiliges Display
23.01.2007 LCD-Anschlus 1x8 Zeichen für Messwertanzeige
12.01.2007 neuer Parser
18.09.2006 erste Implementierung aus Labor-DDS
08.10.2006 Parser Q&D

*)

{ $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, chn, rng, zer, itg, val, ofs, scl, raw, nop, err);
  tRange  = (DC30mV, DC300mV, DC3V, DC30V, DC300V, DCauto,    //0..5
             zero1, zero10, zero100, zero1000,
             AC30mV, AC300mV, AC3V, AC30V, AC300V, ACauto,
             zeroAC,noRange);  //10..15
  tRangeMode =(RangeManual, RangeDCAuto, RangeACAuto);

{--------------------------------------------------------------}
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#1.42';    //Vers1
  Vers2Str                   = 'C.Meyer 12/2006';    //Vers2
  Vers3Str                   = 'DIV 1.42';    // Kurzform von Vers1

  ErrStr1                    = 'ERR SYNTAX';
  ErrStr2                    = 'ERR PARAMETER';
  ErrStr3                    = 'ERR RANGE';
  OKStr                      = 'OK';
  AdrStr                     = 'Adr ';
  OVLStr                     = 'OVL';
  NoErr                      = 0;
  SyntaxErr                  = 1;
  ParamErr                   = 2;
  RangeErr                   = 3;
  ErrSubCh                   = 255;

// tCmdWhich = (idn,chn,val,rng,ofs,scl,nop, err);
  CmdStrArr      : array[0..9] of String[3] = (
  'IDN','CHN','RNG','ZER','ITG',
  'VAL','OFS','SCL','RAW','NOP');   //2 Parameter
  Cmd2SubChArr   : array[0..9] of byte = (
  254, 253, 252, 251, 250,
  0, 100, 200, 50, 0);   // Offsets bei SubCh 100, Skalierungen bei 200

  high                       = true;
  low                        = false;
//Basis-Offsets und Skalierungen,
//bei DC durch ADC-Spannungsteiler 2k/2k67/7k14 bestimmt

  BaseScale24DC:Float              = 25171.495;  // ca. 1/3 * 2^24 /20, korrigiert Fehler durch ADC-Spannungsteiler
  BaseScale24AC:Float              = 54633.0; // ca. 1/3 * 163840, wg. FS = 1000 statt 1024

  BaseScale10DC:Float              = 1.54;
  BaseScale10AC:Float              = 3.333;

//Default-EEPROM-Werte:
{$EEPROM}
structconst

  OffsetArray    : array[0..35] of LongInt =
  (0, 0, 0, 0, 0, 0,   // Offset AD24 für 5 Ranges DC 100mV..1000V (+ auto, nicht benutzt)
  0, 0, 0, 0,          // (nicht benutzt)
  0, 0, 0, 0, 0, 0,    // Offset AD24 für 5 Ranges AC 100mV..1000V (+ auto, nicht benutzt)
  0, 0, 0, 0,          // (nicht benutzt)
  0, 0, 0, 0, 0, 0,    // Offset AD10 für 5 Ranges DC 100mV..1000V (+ auto, nicht benutzt)
  0, 0, 0, 0,          // (nicht benutzt)
  0, 0, 0, 0, 0, 0);   // Offset AD10 für 5 Ranges AC 100mV..1000V (+ auto, nicht benutzt)

  ScaleArray     : array[0..35] of Float =
  (1, 1, 1, 1, 1, 1, // AD24 Skalierung für 5 Ranges DC 100mV..1000V (+ auto, nicht benutzt)
  1, 1, 1, 1,
  1, 1, 1, 1, 1, 1,  // AD24 Skalierung für 5 Ranges AC 100mV..1000V
  1, 1, 1, 1,
  1, 1, 1, 1, 1, 1,  // AD10 Skalierung für 5 Ranges DC 100mV..1000V (+ auto, nicht benutzt)
  1, 1, 1, 1,
  1, 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;
  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
  
  LEDTemp        : byte; // invertiert - low=on!
  LED30mV[@LEDTemp, 7] : bit; // Multiplexer
  LED300mV[@LEDTemp, 6]    : bit; // Multiplexer
  LED3V[@LEDTemp, 5]   : bit; // Multiplexer
  LED30V[@LEDTemp, 4]  : bit; // Multiplexer
  LED300V[@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;
  ParamStr       : String[15];
  SerInpStr      : String[63];
  AD24IntegrateMode:byte; //0=Off, 1=Fast, 2=Slow, (3=Auto)
  AD24, AD24temp, AD24tempSI, AD24tempFI,
  AD24Integrate0, AD24Integrate1,AD24Integrate2,AD24Integrate3 : LongInt;
  AD24flag, AD10flag    : boolean;
  AD10           : Integer;
  Range, OldRange: tRange;
  RangeMode: tRangeMode;
//  RangeCommand, RangeReturn :byte;
  ActivityTimer:Systimer;
  DisplayTimer:Systimer8;
  LCDpresent: 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;
  nop; nop; nop;
  if (SDATAIN1 = low) then // EOC LTC2400, Wandlung fertig?
    STRAD24:= high;
    ShiftIn2400;
    AD24flag:= true; // AD24 aktualisiert
    //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;
  endif;
  STRAD24:= high;
  AD10flag:= true;
end;

procedure ParamScale10;
begin
  m:=ord(Range)+20;
  Param:= Float(LongInt(AD10) + OffsetArray[m]);
  if Range<AC30mV then
    Param:= Param / BaseScale10DC; // Grundskalierung 10 Bit
  else
    Param:= abs(Param / BaseScale10AC); // Grundskalierung 10 Bit
  endif;
  Param:= Param*ScaleArray[m]; // Grundskalierung je nach Range
end;

procedure ParamScale24;
begin
  m:=ord(Range);
  Param:= Float(AD24 + OffsetArray[ord(Range)]);
  if Range<AC30mV then
    Param:= Param / BaseScale24DC; // Grundskalierung 10 Bit
  else
    Param:= abs(Param / BaseScale24AC); // Grundskalierung 10 Bit
  endif;
  Param:= Param*ScaleArray[m]; // Grundskalierung je nach Range
end;

procedure GetAD10;  // <Param> vom internen ADC holen, je nach Range AC oder DC
var ADtemp: Integer;
begin
  OverloadFlag:= false;
  if Range<AC30mV 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
        AC30mV :
          myAD24:=AD24tempSI;
          |
        DC30mV,DC300mV,AC300mV :
          myAD24:=AD24tempFI;
          |
      else
        myAD24:=AD24temp;
      endcase;
      |
  else
    myAD24:=AD24temp;
  endcase;
  if Range < AC30mV 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
  AD24flag := false;
  repeat
  until AD24flag; // vom IRQ gesetzt
end;

procedure IntegrateReset;  // Warte auf Ergebnis von ADC24
begin
  WaitAD24; // verwerfen
  WaitAD24; // und neu anfordern
  if Range in [AC30mV..AC300V] then
    AD24Integrate0:=0; //Integrationswerte vorbelegen
    AD24Integrate1:=0;
    AD24Integrate2:=0;
    AD24Integrate3:=0;
  else
    AD24Integrate0:=AD24temp; //Integrationswerte vorbelegen
    AD24Integrate1:=AD24temp;
    AD24Integrate2:=AD24temp;
    AD24Integrate3:=AD24temp;
  endif;
end;


procedure SwitchRange;
// Range-Relais und LEDs anhand RANGE und RANGEMODE einschalten
begin
  RelaisTemp:= %10000000;  //Grundstellung mit AC/200
  LEDtemp:=$FF;
  if RangeMode <> RangeManual then  // keine AutoRanges und LockRange
    LEDauto := low;  // Auto-Bereiche
  endif;
  if Range >=AC30mV then
    LEDAC:=low;
  else
    LEDDC:=low;
  endif;
  case Range of
    DC30mV :
              PGA1:= true; //*100
              MPX1:=true;  // MPX S3, offen
              LED30mV:=low;
            |
    DC300mV    :
              PGA0:= true; //*10
              MPX1:=true;  // MPX S3, offen
              LED300mV:=low;
            |
    DC3V   :
              MPX1:=true;  // MPX S3, offen
              LED3V:=low;
            |
    DC30V  :
              SW2:= true;
              SW1:= true;
              MPX1:=true;  // MPX S3, offen
              LED30V:=low;
            |
    DC300V :
              SW2:= true;
              MPX0:=true;  // MPX S2
              LED300V:=low;
            |
    zero10:    //Auto-Zero 10fache Verstärkung DC
              PGA0:=true;
            |
    zero100:     //Auto-Zero 10fache Verstärkung DC
              PGA1:=true;
            |
    zero1000:     //Auto-Zero 10fache Verstärkung DC
              PGA0:=true;
              PGA1:=true;
            |
    AC30mV :
              MPX2:=true;  // MPX S5..8
              MPX1:=true;  // *10 AC Preamp
              SWAC200:= false;
              PGA0:= true; // *10 DC Preamp
              LED30mV:=low;
            |
    AC300mV    :
              MPX2:=true;  // MPX S5..8
              MPX1:=true;  // *10 AC Preamp
              SWAC200:= false;
              LED300mV:=low;
            |
    AC3V   :
              MPX2:=true;  // MPX S5..8
              MPX0:=true;  // ohne AC Preamp
              SWAC200:= false;
              LED3V:=low;
            |
    AC30V  :  //Abschwächer 200, Verstärkung 10 = /20
              MPX2:=true;  // MPX S5..8
              MPX1:=true;  // *10 AC Preamp
              LED30V:=low;
            |
    AC300V :  //Abschwächer 200
              MPX2:=true;  // MPX S5..8
              MPX0:=true;  // *10 AC Preamp
              LED300V:=low;
            |
    zeroAC:     //Auto-Zero 10fache Verstärkung DC
              MPX2:=true;  // MPX S5..8
            |
  endcase;
  RelaisPort:= RelaisTemp;
  LEDPort:=LEDtemp;
end;

procedure AutoRange;
var withinLimits:boolean;
begin
  if RangeMode = RangeDCauto then
    if not (Range in [DC30mV..DC300V]) then
      Range:= DC3V;
      SwitchRange;
    endif;
    WaitAD10; // vom IRQ gesetzt
    GetAD10;
    repeat
      withinLimits:=true;
      if OverloadFlag then
        withinLimits:=false;
        if Range=DC300V then
          withinLimits:=true; // nicht steigerungsfähig
        else
          inc(Range);
          SwitchRange;
          mdelay(50);
        endif;
      endif;
      if (abs(AD10) < 50) then
        withinLimits:=false;
        if Range=DC30mV then
          withinLimits:=true; // nicht steigerungsfähig
        else
          dec(Range);
          SwitchRange;
          mdelay(50);
        endif;
      endif;
      OverLoadFlag:=false;
      WaitAD10; // vom IRQ gesetzt
      GetAD10;
    until withinLimits;
  endif;

  if RangeMode = RangeACauto then
    if not (Range in [AC30mV..AC300V]) then
      Range:= AC3V;
      SwitchRange;
    endif;
    WaitAD10; // vom IRQ gesetzt
    GetAD10;
    repeat
      withinLimits:=true;
      if OverloadFlag then
        withinLimits:=false;
        if Range=AC300V then
          withinLimits:=true; // nicht steigerungsfähig
        else
          inc(Range);
          SwitchRange;
          mdelay(200);
        endif;
      endif;
      if (abs(AD10) < 95) then
        withinLimits:=false;
        if Range=AC30mV then
          withinLimits:=true; // nicht steigerungsfähig
        else
          dec(Range);
          SwitchRange;
          mdelay(200);
        endif;
      endif;
      OverLoadFlag:=false;
      WaitAD10; // vom IRQ gesetzt
      GetAD10;
    until withinLimits;
  endif;
  
  if Range <> OldRange then
    SwitchRange;
    IntegrateReset;
    OldRange:=Range;
  endif;
end;

procedure SetRange(myRange:byte);
//Mit AutoRange-Funktion
begin
  case myRange of
    ord(DCauto):
      RangeMode:= RangeDCauto;
      |
    ord(ACauto):
      RangeMode:= RangeACauto;
      |
  else
    RangeMode:= RangeManual;
    Range:=tRange(myRange);
  endcase;
  AutoRange;
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 : byte);
begin
  SubCh:= ErrSubCh; // Fehler-Kanal
  WriteChPrefix;
  case myErr of
    NoErr     :
                write(Serout, OKStr);
              |
    SyntaxErr :
                write(Serout, ErrStr1);
              |
    ParamErr  :
                write(Serout, ErrStr2);
              |
  endcase;
  SerCRLF;
end;

procedure ParamToStr;
//Festkomma-Umwandlung je nach Range
var
  ParamLong: LongInt;
  dezPos:byte;
  negative:Boolean;
begin
    dezPos:=4; // für 300mV, 300V
    case Range of
    DC300mV,AC300mV:
      dezPos:=2;
      |
    DC3V,AC3V:
      dezPos:=3;
      |
    DC300V,AC300V:
      dezPos:=5;
      |
    endcase;
    ParamLong:=LongInt(Param*10000.0+0.5); // auf Integer runden
    negative:=(ParamLong<0);
    ParamStr:=LongToStr(abs(ParamLong):8:'0');
    insert(#46,ParamStr,dezPos);
    if negative then
      ParamStr[1]:=#45; // Minus-Zeichen
    else
      ParamStr[1]:=#32;
    endif;
end;

procedure WriteScale;
//Festkomma-Umwandlung 1.0000000
var
  ParamLong: LongInt;
begin
  ParamLong:=LongInt(Param*10000000); // auf Integer runden
  ParamStr:=LongToStr(abs(ParamLong):8:'0');
  insert(#46,ParamStr,2);
  WriteChPrefix;
  write(serout,ParamStr);
  SerCRLF;
end;


procedure WriteParamLCD;
begin
  ParamToStr;
  setlength(ParamStr,8);
  if Range in [DC30mV, AC30mV] 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 = RangeManual then
    write(lcdout_m,'    ');
  else
    write(lcdout_m,'auto');
  endif;
  if Range < AC30mV then
    write(lcdout_m,' VDC');
  else
    write(lcdout_m,' VAC');
  endif;
{$ENDIF}
end;

procedure WriteParamSer(Ovl : boolean);
begin
  WriteChPrefix;
  if Ovl then
    write(serout,OVLStr);
  else
    ParamToStr;
    write(serout,ParamStr);
    if Range in [DC30mV, AC30mV] then
      write(serout,'E-3');
    endif;
  endif;
  SerCRLF;
end;

procedure WriteParamIntSer(Ovl : boolean);
var ParamStr: String[15];
begin
  WriteChPrefix;
  if Ovl then
    write(Serout,OVLStr);
  else
    ParamStr:= LongToStr(ParamInt);
    ParamStr:= trim(ParamStr);
    write(Serout, ParamStr);
  endif;
  SerCRLF;
end;

procedure ParseInpStr;
{
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

}

var
  ParamStr:string[40]; // auch für Display
  Request,IntFlag:boolean;
  Laenge, ParamStartPos, GleichPos, FragePos,DpunktPos,
  SubChOffset:byte;
  firstChar:char;

begin
  SerInpStr:=trim(SerInpStr);
  FirstChar:=SerInpStr[1];
  if FirstChar='#' then // Messwert durchreichen
    WriteSerInp;
    return;
  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;
  endif;
  if length(SerInpStr) = 0 then
    serprompt(NoErr);
    return;
  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;
    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
    Request:=false;
    if CmdWhich >= val then // Befehle mit 2 Parametern auswerten
      if (GleichPos=0) or (GleichPos=Laenge) then // '=' fehlt
        serprompt(SyntaxErr);
        return;
      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;

  if Request then
    IntFlag:= false;
    case SubCh of
      0..19          : //Eingangsspannung AD24
                       SetRange(SubCh);
                       waitAD24;
                       GetAD24(AD24IntegrateMode);
                       ParamScale24;
                       // bei AutoRange gewählten Range liefern:
                       SubCh:=ord(Range);
                     |
      20..39         :  //Eingangsspannung AD10 DC
                       SetRange(SubCh-20);
                       WaitAD10; // vom IRQ gesetzt
                       GetAD10;
                       ParamScale10;
                       SubCh:=ord(Range)+20;
                     |
      50..69         : //Eingangsspannung AD24 roh
                       SetRange(SubCh-50);
                       ParamInt:= LongInt(AD24);
                       IntFlag:= true;
                     |
      70..89         : //Eingangsspannung AD10 roh
                       SetRange(SubCh - 70);
                       WaitAD10; // vom IRQ gesetzt
                       GetAD10;
                       ParamInt:= LongInt(AD10);
                       IntFlag:= true;
                     |
      100..135       : // Offset DC (5 Ranges) und AC (5 Ranges)
                       ParamInt:= OffsetArray[SubCh - 100];
                       IntFlag:= true;
                     |
      200..235       : // Skalierung DC (5 Ranges) und AC (5 Ranges)
                       Param:= ScaleArray[SubCh - 200];
                       WriteScale;
                       return;
                     |
      250            :
                       ParamInt:=longInt(AD24IntegrateMode);
                       IntFlag:= true;
                    |
      252            :
                       ParamInt:= ord(Range);
                       IntFlag:= true;
                     |
      254            : //Wert
                       SubCh:= ErrSubCh;
                       WriteChPrefix;
                       write(Serout, Vers1Str);
                       SerCRLF;
                       return;
                     |
    else
      serprompt(ParamErr);
      return;
    endcase;
    if IntFlag then
      WriteParamIntSer(OverloadFlag);
    else
      WriteParamSer(OverloadFlag);
    endif;
  else  // kein Request, also Parameter setzen
    case SubCh of
      100..134 :
                 OffsetArray[SubCh - 100]:= ParamInt;
               |
      200..234 :
                 ScaleArray[SubCh - 200]:= Param;
               |
      250 :
                 AD24integrateMode:= ParamByte;
               |
      252      :
                 SetRange(ParamByte);
               |
    else
      serprompt(ParamErr);
      return;
    endcase;
    serprompt(NoErr);
  endif;
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}

(*
      SerInpStr:='ofs 3?';
      ParseInpStr;           //Befehl interpretieren
*)


  PatchCopyFromEE;       // Optionen-Defaults
  RangeDDR:= $FF;
  LEDDDR:= $FF;
  LEDPort:= $FF;
//  udelay(1);
  
  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:= noRange;
  SetRange(ord(DC30V));
  while serstat do
    i:= SerInp;
  endwhile;
end;

{###########################################################################}


begin
  Initall;
//Hauptschleife
  loop
    if serstat then
      readln(SerInp, SerInpStr);
      ParseInpStr;           //Befehl interpretieren
    endif;
//    LEDOVL:=not OverloadFlag;
    if RangeMode <> RangeManual 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 [RangeManual, RangeACAuto] then
              RangeMode:=RangeDCAuto;
            else
              RangeMode:=RangeACAuto;
            endif;
            AutoRange; // Auto-LED setzen
          else
            RangeTemp:=ord(Range);
            if not ButtonLeft then
              dectolim(RangeTemp,0);
              if RangeTemp = 9 then  // überspringen
                RangeTemp := 4;
              endif;
              RangeMode:=RangeManual;
            endif;
            if not ButtonRight then
              inctolim(RangeTemp,14);
              if RangeTemp = 5 then  // überspringen
                RangeTemp := 10;
              endif;
              RangeMode:=RangeManual;
            endif;
            Range:=tRange(RangeTemp);
            SwitchRange;
          endif;
          repeat
            ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
            mdelay(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.

