program EDL;
(*
Elektronische Last EDL, c't-Version mit LTC1257 oder LTC1655
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

28.01.2008 #0.10 Übernahme aus ADA-C DDS und DCG

*)

{$DEFINE LCDistSpannung} // IstSpannung auf LCD statt Sollwert
{$DEFINE ADA16}          // 16-Bit-AD- und DA-Wandler
{$DEFINE DEBUG}          // Debug-Modus mit zusätzlichen ser. Ausgaben


{ $NOSHADOW}
{$W- Warnings}            {Warnings off}
{$TYPEDCONST OFF}
{$DEBDELAY}

Device = mega32, VCC = 5;

Define_Fuses
//Override_Fuses; // optional, always replaces fuses in ISPE
LockBits0 = [];
FuseBits0 = [BODEN,BODLEVEL];  //4,5V Brown-Out
FuseBits1 = [SPIEN,CKOPT];
ProgMode = SPI;

Import SysTick, TWImaster, SerPort, IncrPort4, LCDmultiPort;   // ADCport,

from System import float;

Define
  ProcClock      = 16000000;        {Hertz}

  TWIpresc       = TWI_BR400;

  LCDmultiPort   = I2C_TWI;
  LCDTYPE_M      = 44780; // 44780 oder 66712;
  LCDrows_M      = 2;              //rows
  LCDcolumns_M   = 8;             //chars per line
  IncrPort4      = PinA, 1, 32; // pin-reg used, channels, 16 or 32bit integer
  IncrScan4      = Timer1, 4; // timer used, scan rate 1kHz (1..100)

  SysTick        = 1;              //msec
  SerPort        = 38400, Stop1, timeout;    {Baud, StopBits|Parity}
  RxBuffer       = 255, iData;
  TxBuffer       = 255, iData;

  StackSize      = $0080, iData;
  FrameSize      = $0100, iData;

//  ADCchans = [3..5], iData;   // Fehlverhalten wg. "überfahrenem" SysTick
//  ADCpresc       = 128;

Implementation

type
  tCmdWhich      = (str, idn, chn, val, dcv, dca, msv, msa, pcv, pca, raw,
                    dsp, ofs, scl, opt, all, wen, erc, sbd,nop, err);
  tMode          = (Iconst,Rconst);
  tModify        = (Ampere,Ohm,T_on,T_off,I_off,TrackCh);   //0..2
  tError = (NoErr, UserReq, BusyErr, OvlErr, SyntaxErr, ParamErr, LockedErr,ChecksumErr,FuseErr,FaultErr);
  tMeas = (Ioff,Uoff,Ion,Uon);

{--------------------------------------------------------------}
const


//                              ----SFDC             Strobe,FSync,Data,Clk
  DDRBinit                   = %10111011;            {PortB dir }
  PortBinit                  = %11111101;            {PortB, b_STROBE=0 }

  DDRCinit                   = %11111100;            {PortC dir, 2..3=MPX, 4=MPXENA, 5=RSWITCH }
  PortCinit                  = %11100011;            {PortC }

{ Jumper und LEDs PortBits }
  DDRDinit                   = %00001100;            {PortD dir, 0..1=Serial }
  PortDinit                  = %11111111;            {PortD }

  BtnInPortReg               = @PinD;   {Taster-Eingangsport}
  LEDOutPort                 = @PortD;   {LED-Port}
  ControlBitPort             = @PortB;   {DDS-Port}
  ControlBitPin              = @PinB;   {DDS-Port}
  MPXPort                    = @PortC;   {OE/Multiplexer-Port}
  
  b_SCLK                     = 7; //Takt für alle
  b_SDATAOUT                 = 5; //Daten für alle
  b_SDATAIN1                 = 6; //Daten vom ADC LTC2400
  b_STRAD16                  = 4; //LTC2400 Strobe
  b_STRDAC                   = 3; //DAC8501 Load
  b_ADC16sw                  = 1; //ADC16 MPX U9


//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = '0.51 [EDL by CM/c''t 01/2008]';    //Vers1
  Vers3Str                   = 'EDL 0.51';    // Kurzform von Vers1


  ErrStrArr      : array[0..9] of String[8] = (
    '[OK]','[SRQUSR]','[BUSY]','[OVERLD]','[CMDERR]','[PARERR]',
    '[LOCKED]','[CHKSUM]','[FUSE]','[FAULT]');
  MenuStrArr      : array[0..5] of String[8] = (
    '','','T on  ms','T off ms','I off % ','TrackChn');

    
  AdrStr                     = 'Adr ';

  ErrSubCh                   = 255;

  CmdStrArr      : array[0..19] of String[3] = (
  'STR', 'IDN', 'CHN',
  'VAL',
  'DCR',  // Soll-Lastwiderstand
  'DCA',  // Soll-Klemmenstrom
  'MSV',  // Messwert Klemmenspannung IST
  'MSA',  // Messwert Klemmenstrom IST
  'PCV',  // Prozentwert Ausgangsspannung Soll
  'PCA',  // Prozentwert Ausgangsstrom Soll
  'RAW',  // 50=Rohdaten 2xADC16, 3xADC10
  'DSP',
  'OFS',
  'SCL',
  'OPT',
  'ALL',
  'WEN', // 250 = Write Enable
  'ERC', // 251 = ErrCount seit letztem Reset
  'SBD', // 252 = SerBaud UBRR-Register mit U2X=1
  'NOP');
  Cmd2SubChArr   : array[0..19] of byte = (
  255, 254, 253,
  0,0,1,10,11,20,21,50,80,100,200,150,99,250,251,252,0);   // Offsets bei SubCh 100, Skalierungen bei 200

  ShuntA                   = 0;
  ShuntB                   = 1;
  ShuntC                   = 2;
  ShuntD                   = 3;   //0..3
  ShuntE                   = 4;   // gleich ShuntD, aber ohne U-Vorteiler für R<1 Ohm

  high                     = true;
  low                      = false;

//Inkrementalgeber-Beschleunigungstabelle
  IncrAccArray: array[0..15] of integer = (0,1,2,5,10,25,50,100,250,
  500,1000,2500,5000,10000,10000,10000);

//Default-EEPROM-Werte:
{$EEPROM}
structconst
  dummy:LongInt=0;        // diese Speicherstelle wird oft korrumpiert

// Werte für LTC1655, LTC1874

  DACIoffsets:Array[0..3] of Integer = (0, 0, 0, 0);  // 2mA, 20mA, 200mA, 2A
  DACIscales:Array[0..3] of Float = (1.00, 1.00, 1.00, 1.00); // 4 Shunts low I bis high I
  DACRscales:Array[0..3] of Float = (1.00, 1.00, 1.00, 1.00); // 4 Shunts high R bis low R

  ADCUoffset: Integer = 0;   // 25V
  ADCUscales:Array[0..3] of Float = (
  1.005,                   // ADC16UscaleLow
  1.005,                   // ADC16UscaleHigh
  1.0,                     // ADC10UscaleLow
  1.0);                    // ADC10UscaleHigh

  ADCIoffsets:Array[0..3] of Integer = (0, 0, 0, 0);  // 2mA, 20mA, 200mA, 2A
  ADCIscales: Array[0..3] of Float = (1, 1, 1, 1);  // 2mA, 20mA, 200mA, 2A

  OptionArray:Array[0..21] of Float =  (
  0.0,                    // InitVolt = 5.0;
  0.02,                   // InitAmp = 20mA;
  1,                      // InitNoDividerU, U-Gain wenn Spannungsteiler aus
  10,                     // InitDividerU, Istspannungs-Vorteiler
  10,                     // InitGainI, Gain Sense-OpAmp
  2.5,                    // Uref LT1019 = 2.5 V;
  30,                     // Umax maximale Klemmenspannung
  20,                     // R sense 1. Bereich nom. 2mA, 100 Ohm
  2.2,                    // R sense 2. Bereich nom. 20mA, 10 Ohm
  0.2190,                 // R sense 3. Bereich nom. 200mA, 1 Ohm
  0.0208,                 // R sense 4. Bereich nom. 2A, 0.1 Ohm

  0.010,                  // Imax A 1. Bereich, Umschaltpunkte
  0.100,                  // Imax A 2. Bereich
  1.000,                  // Imax A 3. Bereich
  10.00,                  // Imax A 4. Bereich für CheckLimits

  0,                      // nn
  0,                      // nn
  4,                      // InitOptions, Bit 2 = DCP present
  0,                      // InitIpercent
  10,                     // PWonTime
  0,                      // PWoffTime
  50);                    // InitFanOnTemp

  InitIncRast:Float =4;
  EESerBaudReg:byte=51;
  TrackChSave:byte = 255;
  EEinitialised:word=$AA55;

{--------------------------------------------------------------}

var
{$EEPROM}
  ADC16UscaleLow[@ADCUscales]:     Float;
  ADC16UscaleHigh[@ADCUscales+4]:  Float;
  ADC10UscaleLow[@ADCUscales+8]:   Float;
  ADC10UscaleHigh[@ADCUscales+12]: Float;

  InitVolt[@OptionArray]: Float;
  InitAmp[@OptionArray+4]: Float;

  InitNoDividerU[@OptionArray+8]: Float;
  InitDividerU[@OptionArray+12]: Float;
  InitGainI[@OptionArray+16]: Float;

  Uref[@OptionArray+20]: Float;
  Umax[@OptionArray+24]: Float; // für CheckLimits
  RSenseArray[@OptionArray+28]: Array[0..3] of Float;
  ImaxArray[@OptionArray+44]: Array[0..3] of Float;
  Imax[@OptionArray+56]: Float;  // für CheckLimits

  InitTonTime[@OptionArray+76]: Float;
  InitToffTime[@OptionArray+80]: Float;
  InitOptions[@OptionArray+68]: Float;
  InitIpercent[@OptionArray+72]: Float;
  InitFanOnTemp[@OptionArray+84]: Float;

{$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;
  LED_CurrentLimit[LEDOutPort, 3]    : bit;

  SCLK[@PortB, b_SCLK]       : bit; //Takt für alle
  STRAD16[@PortB, b_STRAD16] : bit; //Strobe für LTC2400
  SDATAIN1[@PinB, b_SDATAIN1]: bit; //Dateneingang vom LTC2400
  AD16MPX[@PortB, b_ADC16sw]       : bit; //Auswahl U oder I an ADC16
  MODEMPX[@PortB, 0]       : bit; //Auswahl DAC-Mode R (low) oder I (high)
  ButtonTemp,RangeTemp: Byte; // invertiert - low=on!
  ButtonDown[@ButtonTemp, 5]  : bit;
  ButtonUp[@ButtonTemp, 4]  : bit;
  ButtonEnter[@ButtonTemp, 3]  : bit;  // Inkrementalgeber-Druck

  MPXA0[@PortC, 2]  : bit;
  MPXA1[@PortC, 3]  : bit;
  MPXENA[@PortC,4]  : bit; //Enable Output-MPX
  RSWITCH[@PortC,5] : bit; //R-Switch BS170
  TRGOUT[@PortC, 7] : bit;

  LastMeas, NextMeas: tMeas;
  DACchanged:Boolean;
  ADCrawU,ADCrawI:Word;
//  AD16IntegrateMode:byte; //0=Off, 1=Fast, 2=Slow, (3=Auto)
  AD16, AD16temp, AD16tempIon,AD16tempIoff,AD16tempUon,AD16tempUoff : Word;
  AD16select,AD10ready: boolean;
  AD10           : Integer;
  AD16long:LongInt;
  Temperature,Switchpoint:Float;   // Umschaltpunkt U Low/ High
  DACtemp,DACtempOn,DACtempOff,ADCtemp:word; // für Interrupt-IO
  RIMode:tMode;  // Iconst, Rconst
  I2CslaveAdr:byte; // I2C IO Slave Adresse

//Parameter
  DCAmp, DCohm, DCAmpMod, DCohmMin, DCohmMax, Ipercent: float; // Strom in A, 0..2,000
  DividerU:Float;
  IntegrateCounter: Byte;
  PWonTime, PWoffTime, PWcounter: Integer;
  PWonOff:Boolean;

  DACLSBIarray: Array [0..3] of Float;  // Strom eines LSBits, 4 Bereiche, Output
  DACLSBRarray: Array [0..3] of Float;  // n = DividerU*Rshunt*DACmax/Rgew.

  ADC16LSBU: Float;  // Spannung eines LSBits LTC1684
  ADC10LSBU: Float;  // Spannung eines LSBits interner ADC
  ADC16LSBIarray: Array [0..3] of Float;  // Strom eines LSBits, 4 Bereiche, Input
  ADC10LSBIarray: Array [0..3] of Float;  // Strom eines LSBits, 4 Bereiche, Input

  Options:byte;  //
  DCPpresent[@Options, 2]  : bit;
  DACmax:LongInt;
  
  CmdWhich: tCmdWhich;
  SubCh, CurrentCh       : byte;
  NegativeFlag, OmniFlag, verbose: boolean;
  
  Param :float;
  ParamInt       : LongInt;
  ParamByte:Byte;
  SerInpStr      : String[63];
  SerInpPtr:byte;
  Prefix:char;

  ShuntSelect, OldShuntSelect, IAutoRange: byte;

  DisplayTimer:Systimer;
  ActivityTimer:Systimer8;
  IncrTimer:Systimer8;
  IntegrateTimer:Systimer8;
  FaultTimer:byte;
  LCDpresent: boolean;
  Modify:tModify;
  TrackChannel:byte;
  IncrValue,OldIncrValue: LongInt;
  IncrFine,FirstTurn:Boolean;
  IncrAccFloat,IncFineDiv, IncCoarseDiv:Float;
  IncrDiff,IncrAccInt10, IncRast:Integer;
  IncrDiffByte:Byte;
  digits, nachkomma:byte;
  ChangedFlag:Boolean;

  TempF:Float;
  TempRI:Integer;
//für Parser
  ParamStr:string[40]; // auch für Display

  Status:byte;
  BusyFlag[@Status, 7]  : bit;
  UserSRQFlag[@Status, 6]  : bit;
  OverloadFlag[@Status, 5]  : bit;
  EEUnlocked[@Status, 4]  : bit; // EEPROM-unlocked-Flag
  OverTemp[@Status, 3]  : bit;
  OverVolt[@Status, 2]  : bit;
  FuseBlown[@Status, 1]  : bit;
  ErrCount:integer;
  ErrFlag:boolean;

  ADSC10[@ADCSRA, 6]:bit;

{$NOSAVE}

{###########################################################################}

{$NOSAVE}
{$I EDL-HW.pas}

procedure SetLM75Temp;
// LM75 Lüfter-Schalt-Temperatur °C in Param
var aInt: Integer;
begin
   TWIout(I2CslaveAdr, 1, 4); // Optionsregister invertierter Ausgang

   aInt:=integer(Param*2);
   aInt:= swap(aInt shl 7);
   TWIout(I2CslaveAdr, 3, aInt);  // Tos-Register wählen

   aInt:=integer(Param*2)-6; // Hysteresis 3°C
   aInt:= swap(aInt shl 7);
   TWIout(I2CslaveAdr, 2, aInt);  // Thyst-Register wählen
   TWIout(I2CslaveAdr, 0);
end;

procedure GetLM75Temp;
// liefert LM75 Ist-Temperatur °C in Param
var aInt: Integer;
begin
  TWIout(I2CslaveAdr, 0);
  TWIinp(I2CslaveAdr, aInt);
  aInt:= swap(aInt) shr 7;
  Param:=float(aInt)/2;
end;

procedure InitScaleU(myHiRangeU:boolean);
begin
  if myHiRangeU then
    DividerU:=InitDividerU;
    ADC16LSBU:=ADC16UscaleHigh*Uref*DividerU/65535.0; // ADC neu berechnen
    ADC10LSBU:=ADC10UscaleHigh*Uref*DividerU/1023.0;
  else
    DividerU:=InitNoDividerU;
    ADC16LSBU:=ADC16UscaleLow*Uref*DividerU/65535.0;
    ADC10LSBU:=ADC10UscaleLow*Uref*DividerU/1023.0;
  endif;
end;

procedure InitScales;
var aInt:Integer; Ufac, myDACmax:Float;
//Frequenz und Settings aus EEPROM holen
begin
  aInt:=integer(InitOptions); // Bit 0=1: LTC1655 statt 1257, Bit 1=1: LTC1864 statt interner DAC
  Options:=byte(aInt);
  DividerU:=InitDividerU;
  Ufac:=Uref;       // DAC8501 intern 1fache Verstärkung
  InitScaleU(high);
  DACmax:=65535; // für SetLevelDAC_I
  myDACmax:=float(DACmax);
  for i:=0 to 3 do
    // mA pro LSBit für 4 Bereiche, Output:
    DACLSBIarray[i]:=(Ufac/RSenseArray[i])/(myDACmax*DACIscales[i]*InitGainI);
    DACLSBRarray[i]:=(InitGainI*RSenseArray[i]*myDACmax*DACRscales[i]);
    // mA pro LSBit für 4 Bereiche, Input:
    ADC16LSBIarray[i]:=(ADCIscales[i]*Uref/(RSenseArray[i]))/myDACmax/InitGainI; //
    ADC10LSBIarray[i]:=(ADCIscales[i]*Uref/(RSenseArray[i]))/1024.0/InitGainI; //;  // Strom eines LSBits, 4 Bereiche, Input
  endfor;
  TrackChannel:=TrackChSave;
  Param:=InitFanOnTemp;
  I2CslaveAdr:=$48;
  SetLM75Temp;
  PWonTime:=integer(InitTonTime);
  PWoffTime:=integer(InitToffTime);
  Ipercent:=InitIpercent;
  DCohmMin:=(RSenseArray[3]*InitGainI*1.1);  // min. Einstellbarer Widerstand inkl 10% Abw.
  DCohmMax:=(RSenseArray[0]*DividerU*100);  // max. sinnvoll einstellbarer Widerstand
end;


{###########################################################################}

// auch für DEBUG-mode gebraucht

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;

//DCG-Funktionen

procedure SetShunt(myShunt:Byte);
begin
  DividerU:=InitDividerU;
  case myShunt of
    ShuntA:
      MPXA0:= high;  //Shunt-Umschalter 20/100R
      MPXA1:= high;
      RSWITCH:=high;
      InitScaleU(high);
      |
    ShuntB:
      MPXA0:= low;  //Shunt-Umschalter 2/10R
      MPXA1:= high;
      RSWITCH:=high;
      InitScaleU(high);
      |
    ShuntC:
      MPXA0:= high;  //Shunt-Umschalter 0,2/1R
      MPXA1:= low;
      RSWITCH:=high;
      InitScaleU(high);
      |
    ShuntD:
      MPXA0:= low;  //Shunt-Umschalter 0,02/0,1R
      MPXA1:= low;
      RSWITCH:=high;
      InitScaleU(high);
      |
    ShuntE:
      MPXA0:= low;  //Shunt-Umschalter 0,02/0,1R
      MPXA1:= low;
      RSWITCH:=low;
      InitScaleU(low);
      |
  endcase;
end;

procedure CalcRangeI;
begin
  ShuntSelect:=ShuntA;
  for i:=0 to 3 do
    if DCAmp >= ImaxArray[i] then
      inctolim(ShuntSelect,ShuntD);
    endif;
  endfor;
end;

procedure CalcRangeR;
begin
  ShuntSelect:=ShuntA;
  for i:=0 to 3 do
    if DCohm < (RSenseArray[i]*InitGainI*InitDividerU*1.1) then
      inctolim(ShuntSelect,ShuntD);
    endif;
  endfor;
  if DCohm < (RSenseArray[3]*InitGainI*InitDividerU*1.1) then
    ShuntSelect:=ShuntE;
  endif;
end;


procedure SetLevelDAC_I;
//Spannungs- und Stromeinstellung nach DCVolt und DCAmp
var
  myDAC:longInt; myOffset:Integer; myPercent: Float;
  myShunt: byte;
begin
//Berechnung des Range-Wertes für Strom
  MPXENA:=low;
  RSWITCH:=high;
  CalcRangeI;
  if ShuntSelect<>OldShuntSelect then
    DCAmpMod:=1; // Prozent-Faktor rücksetzen
    DACtemp:=0;  // kurz abschalten
// DACrawI wird vom Interrupt übernommen, Wandlung abwarten
    mdelay(2);  // Wandlungszeit auf 0
  endif;
  SetShunt(ShuntSelect);
  OldShuntSelect:=ShuntSelect;
{$IFDEF DEBUG}
  write(Serout, 'Shunt=');
  write(Serout,ByteToStr(ShuntSelect));
{$ENDIF}
  myShunt:=ShuntSelect;
  if ShuntSelect > ShuntD then
    myShunt:=ShuntD;
  endif;
//Berechnung des DAC-Wertes für Strom, On-Zeit
  myDAC:=LongInt((DCAmp*DCAmpMod)/DACLSBIarray[myShunt]+0.5)+longInt(DACIoffsets[myShunt]);
  if myDAC>DACmax then
    myDAC:=DACmax;
  endif;
  if myDAC<0 then
    myDAC:=0;
  endif;
  disableInts;
  DACtempOn:=word(myDAC);
  enableInts;
  myPercent:=Ipercent/100;
  //DACtemp für Off-Zeit berechnen
  myDAC:=LongInt((DCAmp*DCAmpMod*myPercent)/DACLSBIarray[myShunt]+0.5)+longInt(DACIoffsets[myShunt]);
  if myDAC>DACmax then
    myDAC:=DACmax;
  endif;
  if myDAC<0 then
    myDAC:=0;
  endif;
  disableInts;
  DACtempOff:=word(myDAC);
  enableInts;
  MODEMPX:=high; // I-Mode
  MPXENA:=high;

{$IFDEF DEBUG}
  write(Serout, '  DACtempI=$');
  write(Serout,IntToHex(DACtempOn));
  write(Serout, ' dez.');
  write(Serout,IntToStr(DACtempOn));
  SerCRLF;
{$ENDIF}
end;


procedure SetLevelDAC_R;
//Spannungs- und Stromeinstellung nach DCVolt und DCAmp
var
  myDAC:longInt; myOffset:Integer; myPercent: Float;
  myShunt: byte;
begin
//Berechnung des Range-Wertes für Strom
  MPXENA:=low;
  CalcRangeR;
  DCAmpMod:=1; // Prozent-Faktor rücksetzen
  if ShuntSelect<>OldShuntSelect then
    DACtemp:=0;  // kurz abschalten
// DACrawI wird vom Interrupt übernommen, Wandlung abwarten
    mdelay(2);  // Wandlungszeit auf 0
  endif;
  SetShunt(ShuntSelect);
  OldShuntSelect:=ShuntSelect;
{$IFDEF DEBUG}
  write(Serout, 'Shunt=');
  write(Serout,ByteToStr(ShuntSelect));
{$ENDIF}
  myShunt:=ShuntSelect;
  if ShuntSelect > ShuntD then
    myShunt:=ShuntD;
  endif;
//Berechnung des DAC-Wertes für Strom, On-Zeit
  myDAC:=LongInt((DividerU*DACLSBRarray[myShunt]+0.5)/DCohm)+longInt(DACIoffsets[myShunt]);
  if myDAC>DACmax then
    myDAC:=DACmax;
  endif;
  if myDAC<0 then
    myDAC:=0;
  endif;
  disableInts;
  DACtempOn:=word(myDAC);
  DACtempOff:=word(myDAC);
  enableInts;
  MODEMPX:=low;  // R-Mode
  MPXENA:=high;
{$IFDEF DEBUG}
  write(Serout, '  DACtempR=$');
  write(Serout,IntToHex(DACtempOn));
  write(Serout, ' dez.');
  write(Serout,IntToStr(DACtempOn));
  SerCRLF;
{$ENDIF}


end;

procedure GetVoltage(offOn:boolean);
// Istwert Spannung vom ADC holen,
// wenn offOn=false, bei Off-Zeit des DAC-Timers (Low-Current-Wert)
// wenn offOn=true,  bei  On-Zeit des DAC-Timers (High-Current-Wert)
var
  myADC:longInt;
begin
  disableInts;
  if offOn then
    myADC:=LongInt(AD16tempUon);
  else
    myADC:=LongInt(AD16tempUoff);
  endif;
  enableInts;
  myADC:=myADC+longInt(ADCUoffset);
  Param:=float(myADC)*ADC16LSBU;
end;

procedure GetCurrent(offOn:boolean);
var
  myADC:longInt;
begin //Istwert Ausgangsstrom
  disableInts;
  if offOn then
    myADC:=LongInt(AD16tempIon);
  else
    myADC:=LongInt(AD16tempIoff);
  endif;
  enableInts;
  myADC:=myADC+longInt(ADCIoffsets[ShuntSelect]);
  Param:=float(myADC)*ADC16LSBIarray[ShuntSelect];
end;

procedure GetVoltage10;
//Istwert Spannung vom internen ADC holen
var
  myADC:word;
begin
  myADC:=GetADC10(3);
  Param:=float(myADC)*ADC10LSBU;
end;

procedure GetCurrent10;
//Istwert Strom vom internen ADC holen
var
  myADC:word;
 begin
  myADC:=GetADC10(4);
  Param:=float(myADC)*ADC10LSBIarray[ShuntSelect];
end;


{###########################################################################}

//allg. Menü-Prozeduren


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
  if verbose OR (myERR<>noErr) then
    SubCh:=ErrSubCh; // Fehler-Kanal
    WriteChPrefix;
    write(Serout, ByteToStr(ord(myErr)+myStatus));
    Serout(#32);
    write(serout,ErrStrArr[ord(myErr)]);
    SerCRLF;
  endif;
end;

{###########################################################################}

procedure IncFacI;
begin
  if Param >= 10 then
    IncCoarseDiv:=10;
  else
    IncCoarseDiv:=10000;
    if Param >= 0.01 then
      IncCoarseDiv:=1000;
    endif;
    if Param >= 0.1 then
      IncCoarseDiv:=100;
    endif;
    if Param >= 1 then
      IncCoarseDiv:=10;
    endif;
  endif;
  IncFineDiv:=IncCoarseDiv*100;
{$IFDEF DEBUG}
   write(Serout, 'IncFacI');
   SerCRLF;
{$ENDIF}
end;

procedure IncFacR;
begin
  IncCoarseDiv:=10;
  If Param>=10 then
     IncCoarseDiv:=1;
  endif;
  If Param>=100 then
     IncCoarseDiv:=0.1;
  endif;
  If Param>=1000 then
     IncCoarseDiv:=0.01;
  endif;
  IncFineDiv:=IncCoarseDiv*100;
{$IFDEF DEBUG}
   write(Serout, 'IncFacR');
   SerCRLF;
{$ENDIF}
end;

procedure RoundIncParam;
begin
  if not IncrFine then
    TempRI:=round(Param*IncCoarseDiv);  // einmalig runden
    Param:=float(TempRI)/IncCoarseDiv;
    FirstTurn:=false;

{$IFDEF DEBUG}
   write(Serout, 'RoundInc');
   SerCRLF;
{$ENDIF}
  endif;
end;

procedure SetAccParam;
begin
   if IncrFine then
     IncrAccFloat:=IncrAccFloat/IncFineDiv;
   else
     IncrAccFloat:=IncrAccFloat/IncCoarseDiv;
   endif;
   Param:=Param + IncrAccFloat;
end;

procedure ParamToStr;
begin
  if Param<=0 then
    ParamStr:='0.000';
  else
    ParamStr:=floatToStr(Param:digits:nachkomma:'0');
  endif;
end;

procedure SendTrackCmd;
// anderes Netzteil trackem
begin
  if TrackChannel<>255 then
    Serout(char(TrackChannel+48)); // Moduladresse Target
    Serout(':');
    write(Serout, '1'); // Target-Kanal mA
    Serout('=');
    Param:=DCAmp;
    ParamToStr;
    write(Serout, ParamStr);
    Serout('!');
    SerCRLF;
  endif;
end;

procedure Setcursor(fullCursor:boolean);
var myCursor: char;
begin
  if fullCursor then
    if IncrFine then
      myCursor:=#2; // schraffiert
    else
      myCursor:=#0; // voller Cursor, Sollwert
    endif;
  else
    myCursor:=#1; // hohler Cursor, Istwert
  endif;
  LCDxy_M(LCD_m1, 7, 0); // Cursor oben
  lcdout_m(myCursor);
end;

procedure Prefix_I;
begin
  Prefix:=#32;
  if Param < 1.0 then
    Param:=Param*1000;
    Prefix:='m';
  endif;
end;

procedure Prefix_R;
begin
  Prefix:=#32;
  if Param >= 1000 then
    Param:=Param/1000;
    Prefix:='k';
  endif;
end;

procedure SpannungOnLCD;
begin
  digits:=5;
  nachkomma:=3;
  if Param>=10.0 then
    nachkomma:=2;
  endif;
  ParamToStr;
  if OverTemp then
    ParamStr:='OVRTEMP';
  endif;
  if OverVolt then
    ParamStr:='OVRVOLT';
  endif;
  LCDxy_M(LCD_m1, 0, 1);
  write(lcdout_m,ParamStr);
  lcdout_m(#32);
  lcdout_m('V');
  lcdout_m(#32);
end;

procedure StromOnLCD;
begin
  digits:=5;
  nachkomma:=3;
  Prefix_I;
  ParamToStr;
  setlength(ParamStr,5);
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,ParamStr);
  lcdout_m(Prefix);
  lcdout_m('A');
end;

procedure WiderstandOnLCD;
begin
  digits:=5;
  nachkomma:=3;
  Param:=DCohm;
  Prefix_R;
  ParamToStr;
  setlength(ParamStr,5);
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,ParamStr);
  lcdout_m(Prefix);
  lcdout_m(#3);

end;

procedure IstStromOnLCD;
begin
  GetCurrent(true);
  StromOnLCD;
  GetVoltage(true);
  SpannungOnLCD;
end;

procedure SollStromOnLCD;
begin
  Param:=DCAmp;
  StromOnLCD;
  GetVoltage(true);
  SpannungOnLCD;
end;

procedure SollWiderstandOnLCD;
begin
  Param:=DCohm;
  WiderstandOnLCD;
  GetVoltage(true);
  SpannungOnLCD;
end;

procedure TrackChannelOnLCD;
begin
end;

procedure WerteOnLCD;
begin
  LCDxy_M(LCD_m1, 0, 1);
  write(lcdout_m,MenuStrArr[ord(Modify)]);
  LCDxy_M(LCD_m1, 0, 0);
  // (Ampere,Ohm,T_on,T_off,TrackCh);   //0..2
  case Modify of
    Ampere:
      if issystimerzero(DisplayTimer) then
        IstStromOnLCD;
        Setcursor(false);
      else
        SollStromOnLCD;
        Setcursor(true);
      endif;
      |
    Ohm:
      SollWiderstandOnLCD;
      Setcursor(true);
      |
    T_on:
      LCDclreol_M(LCD_m1);
      write(lcdout_m,intToStr(PWonTime));
      Setcursor(true);
      |
    T_off:
      LCDclreol_M(LCD_m1);
      write(lcdout_m,intToStr(PWoffTime));
      Setcursor(true);
      |
    I_off:
      LCDclreol_M(LCD_m1);
      Param:=Ipercent;
      digits:=3;
      nachkomma:=1;
      ParamToStr;
      write(lcdout_m,ParamStr);
      Setcursor(true);
      |
    TrackCh:
      LCDxy_M(LCD_m1, 0, 0);
      LCDclreol_M(LCD_m1);
      if TrackChannel=255 then
        write(lcdout_m,'OFF');
      else
        write(lcdout_m,byteToStr(TrackChannel));
      endif;
      Setcursor(true);
      |
  endcase;
end;

procedure WriteParamSer;
begin
  ParamToStr;
  WriteChPrefix;
  write(serout,ParamStr);
  SerCRLF;
end;

procedure WriteParamIntSer;
begin
  ParamStr:= longToStr(ParamInt);
  ParamStr:= trim(ParamStr);
  WriteChPrefix;
  write(Serout, ParamStr);
  SerCRLF;
end;

procedure CheckLimits;
begin
  if DCohm < DCohmMin then
    DCohm:=DCohmMin;
  endif;
  if DCohm > DCohmMax then
    DCohm:=DCohmMax;
  endif;
  if DCAmp < 0 then
    DCAmp:=0;
  endif;
  if DCAmp > Imax then
    DCAmp:=Imax;
  endif;
  if Ipercent < 0 then
    Ipercent:=0;
  endif;
  if Ipercent > 100 then
    Ipercent:=100;
  endif;
  if PWonTime < 1 then
    PWonTime:=1;
  endif;
  if PWoffTime < 0 then
    PWoffTime:=0;
  endif;
  if Trackchannel > 127 then
    TrackChannel:=255;
  else
    if Trackchannel > 7 then
      TrackChannel:=7;
    endif;
  endif;
end;


{###########################################################################}

{$I EDL-Parser.pas}

procedure FaultCheck;
begin
  if DCPpresent then
    I2CslaveAdr:=$48;
    GetLM75temp;
    Temperature:=Param;
  else
    Temperature:=0;
  endif;
  if Temperature>80 then
    if not OverTemp then
      SerPrompt(OvlErr,Status);
    endif;
    Overtemp:=true;
  else
    OverTemp:=false;
  endif;
  getVoltage10;
  if Param>Umax then // über 50V?
    if not OverVolt then
      SerPrompt(FaultErr,Status);
    endif;
    OverVolt:=true;
  else
    OverVolt:=false;
  endif;
end;

procedure Chores;
// Regelmäßig außerhalb des Interrupts aus CheckSer heraus aufgerufen:
begin
  if isSystimerZero(IntegrateTimer) then
    SetSystimer(IntegrateTimer, 40);
    FaultCheck;
  endif;
end;

procedure CheckSer;
var
myChar: char;
begin
  while SerInp_TO(myChar,20) do // 20 ms Timeout
//    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
      setlength(SerInpStr, length(SerInpStr)-1);
    endif;
    if myChar=#13 then
      Chores;
      ParseSubCh;           //Befehl interpretieren, wenn für uns
      SerInpStr:='';
    endif;
  endwhile;
  Chores;
end;

procedure CheckDelay(myDelay:byte);
// Delay mit serieller Abfrage in 20-ms-Schritten
var mycount:byte;
begin
  for mycount:=1 to mydelay do
    CheckSer;
  endfor;
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}
  
  DACtemp:=0;
  ShiftOut8501;

  i:=EESerBaudReg;
  if not i in [9..239] then
    EESerBaudReg:=51;
    i:=51;
  endif;
  ubrr1:=i;
  UCSRA:=UCSRA or 2; // Set U2X bit
//  ADMUX:= ADMUX OR %11000000;  {Internal ADC Reference}

//  serBaud(EESerbaud);        // nur mit 644

  udelay(1);
  SlaveCh:= (not PinD) shr 5;
  
  LEDactivity:= low;

  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 voll}
    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 schraffiert}
    LCDCharSet_M(LCD_m1, #3, $0E, $11, $11, $11, $0A, $0A, $1B, $00); // Ohm, Omega

    LCDpresent:=true;
    write(lcdout_m,Vers3Str);
    LCDxy_M(LCD_m1, 0, 1);
    write(lcdout_m,AdrStr);
    lcdout_m(char(SlaveCh+48));
  endif;

  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;
  
  Status:=0;
  EnableInts;
  while serstat do
    i:=SerInp;
  endwhile;
  InitScales;
  DCAmpMod:=1;
  DCAmp:=InitAmp;
  OldShuntSelect:=255;
  CalcRangeI;

  IncRast:=integer(InitIncRast);
  Modify:=Ohm;
  IncrCount4start;
  SetIncrVal4(0,0);
  IncrValue:=GetIncrVal4(0);
  OldIncrValue:= IncrValue;
  IncrFine:=false;
  IncrDiff:=0;
  FirstTurn:=true;
  SubCh:=254;
  WriteChPrefix;
  write(serout,Vers1Str);
  SerCRLF;
  CurrentCh:=255; // erstmal keine Reaktion
  mdelay(10);
  SetLevelDAC_I; // Anfangswerte
  ErrCount:=0;
  mdelay(200); // Interrupt-DAC stabilisieren
  FaultCheck; // in Param ist jetzt Eingangsspannung
  modify:=Ampere;
  RImode:=Iconst;
  SetLevelDAC_I; // Anfangswerte
  DCohm:=100.0;
end;

{###########################################################################}

begin
  Initall;
//Hauptschleife
  loop
    CheckSer;  // Serinp parsen, nicht busy
    if isSystimerZero(ActivityTimer) then
      LEDactivity:=high; // LED aus
    endif;
    if LCDpresent then
      IncrValue:=GetIncrVal4(0);
// Inkrementalgeber löst in Zweierschritten auf, deshalb der Umstand
      if (IncrValue<>OldIncrValue) then
        SetSystimer(ActivityTimer, 250);
        LEDactivity:=low;
        IncrDiff:=IncrDiff + integer(IncrValue-OldIncrValue);
        OldIncrValue:= IncrValue;
        SetSystimer(IncrTimer, 200);
        if abs(IncrDiff)>= IncRast then // erst wenn n Impulse gezählt
          ChangedFlag:=true;
          BusyFlag:=true;
          IncrDiff:=IncrDiff div IncRast;
// verbesserte Beschleunigungsfunktion, hier für 20 ms Schleifenzeit
          IncrAccInt10:=sgn(IncrDiff);     // hier als Sign benutzt
          IncrDiffByte:=byte(IncrDiff);    // Byte ohne Beschleunigung
          IncrDiff:=IncrAccInt10 * IncrAccArray[byte(abs(IncrDiff))];
{$IFDEF DEBUG}
   write(Serout, 'IncDiff=');
   write(Serout, IntToStr(IncrDiff));
   SerCRLF;
{$ENDIF}
          IncrAccInt10:=IncrDiff*10; // 40x Differenz mit Beschleunigung
          IncrAccFloat:=float(IncrDiff);
          SetSystimer(DisplayTimer, 1500); // für Feineinstellung
          case Modify of
            Ampere:
              Param:=DCamp;
              if FirstTurn then
                IncFacI;
                RoundIncParam;
              endif;
              SetAccParam;
              DCamp:=Param;
              DCAmpMod:=1;
              CheckLimits;
              RImode:=Iconst;
              SetLevelDAC_I;
              SubCh:=1;
              ParseGetParam;
              DCohm:=100;
              |
            Ohm:
              Param:=DCohm;
              if FirstTurn then
                IncFacR;
                RoundIncParam;
              endif;
              SetAccParam;
              DCohm:=Param;
              DCAmpMod:=1;
              CheckLimits;
              RImode:=Rconst;
              SetLevelDAC_R;
              SubCh:=5;
              ParseGetParam;
              DCamp:=0.02;
              |
            T_on:
              if PWonTime>=100 then
                if FirstTurn then
                  PWonTime:=PWonTime div 10;   // einmalig runden
                  PWonTime:=PWonTime * 10;
                  FirstTurn:=false;
                endif;
                PWonTime:=PWonTime+IncrAccInt10;
              else
                PWonTime:=PWonTime+IncrDiff;
              endif;
              CheckLimits;
              |
            T_off:
              if PWoffTime>=100 then
                if FirstTurn then
                  PWoffTime:=PWoffTime div 10;   // einmalig runden
                  PWoffTime:=PWoffTime * 10;
                  FirstTurn:=false;
                endif;
                PWoffTime:=PWoffTime+IncrAccInt10;
              else
                PWoffTime:=PWoffTime+IncrDiff;
              endif;
              CheckLimits;
              |
            I_off:
              Ipercent:=Ipercent+IncrAccFloat;
              CheckLimits;
              SetLevelDAC_I;
              |
            TrackCh:
              TrackChannel:=Trackchannel+byte(IncrDiff);
              CheckLimits;
              |
          endcase;
          WerteOnLCD;
          IncrDiff:=0; // für nächsten Durchlauf
        endif;
      endif;
      Checkdelay(1); // wichtig für Beschleunigungsfunktion
      ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
      if ButtonTemp <> $FF then
        Checkdelay(1);
        ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
        if ButtonTemp <> $FF then
          SetSystimer(DisplayTimer, 1000); // Zeit für Sollwerte
          if not ButtonEnter then
            IncrFine:=not IncrFine;
            SetSystimer(DisplayTimer, 1500); // etwas länger für Feineinstellung
            serprompt(noErr,Status+67); // UserSRQ-Flag=64, +3
          endif;
          if not ButtonDown then
            serprompt(noErr,65); // UserSRQ-Flag=64, +1
            if modify=TrackCh then
              TrackChSave:=TrackChannel;  // beim Verlassen in EEPROM merken
            endif;
            if modify>Ampere then
              dec(modify);
            endif;
          endif;
          if not ButtonUp then
            serprompt(noErr,66); // UserSRQ-Flag=64, +2
            if modify<TrackCh then
              inc(modify);
            endif;
          endif;

          if Modify=Ampere then
            RImode:=Iconst;
            SetLevelDAC_I;
          else
            RImode:=Rconst;
            SetLevelDAC_R;
          endif;

          WerteOnLCD;
          repeat
            Checkdelay(1);
            ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
          until ButtonTemp=$ff;
          serprompt(noErr,Status+64); // UserSRQ-Flag=64, freigegeben
        endif;
      endif;
    endif;
    if isSystimerZero(IncrTimer) and LCDpresent then
      SetSystimer(IncrTimer, 50);
      if not FirstTurn then
        SendTrackCmd;
        serprompt(noErr,Status+64); // UserSRQ-Flag=64, freigegeben
        BusyFlag:=false;
      endif;
      FirstTurn:=true;
    endif;
    if isSystimerZero(DisplayTimer) and LCDpresent then
      WerteOnLCD;
      SetSystimer(DisplayTimer, 200);
      IncrFine:=false;
      ChangedFlag:=false;
      BusyFlag:=false;
    endif;
  endloop;
end EDL.

