program DCG;
(*
DC-Generator- mit 2xLTC1257
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

23.01.2007 #1.50 LCD-Anschlus 2x8 Zeichen für Messwertanzeige
05.12.2006 #0.90 Offset OFU und OFI eingefügt, für neg. Offset des LTC1257
05.12.2006 #0.10 Übernahme aus ADA-C DDS

*)


{$DEFINE Zweizeilig}         //wenn zweizeiliges Display eingesetzt ist


{ $NOSHADOW}
{$W- Warnings}            {Warnings off}
{$TYPEDCONST OFF}
{$DEBDELAY}


Device = mega168, VCC = 5;

Define_Fuses
//Override_Fuses; // optional, always replaces fuses in ISPE
LockBits0 = [];
//FuseBits0 = [BODEN,BODLEVEL];
ProgMode = SPI;

Import SysTick, TWImaster, LCDmultiPort, ADCport, IncrPort4, SerPort;

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

  IncrPort4      = PinC, 1, 32; // pin-reg used, channels, 16 or 32bit integer
  IncrScan4      = Timer1, 4; // timer used, scan rate 1kHz (1..100)

  SerPort        = 38400, Stop1;    {Baud, StopBits|Parity}
  RxBuffer       = 64, iData;
  TxBuffer       = 64, iData;

  SysTick        = 1;              //msec
  StackSize      = $0080, iData;
  FrameSize      = $0100, iData;

  ADCchans = [3,4], iData, int2;
  ADCpresc       = 32;

Implementation

type
  tCmdWhich      = (idn, chn, rng, val, ofs, scl, raw, nop, err);
  tRange         = (DC2mA, DC20mA, DC200mA, DC2A);   //0..3
  tModify        = (Volt,Ampere);   //0..1

{--------------------------------------------------------------}
const


//                              ----SFDC             Strobe,FSync,Data,Clk
  DDRBinit                   = %00111111;            {PortB dir }
  PortBinit                  = %00010011;            {PortB, b_STROBE=0 }

  DDRCinit                   = %11110000;            {PortC dir, 2..3=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}
  DDSOutPort                 = @PortB;   {DDS-Port}
  ControlBitPort             = @PortB;   {DDS-Port}

  b_SCLK                     = 0; //Takt für alle
  b_SDATA                    = 1; //Daten für alle
//  b_MPXA0                    = 2;
//  b_MPXA1                    = 3;
  b_STRDAC                   = 4; //LTC1257 Load



//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = 'ADA-C DCG V#1.50';    //Vers1
  Vers2Str                   = 'C.Meyer 12/2006';    //Vers2
  Vers3Str                   = 'DCG 1.50';    // 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;

  CmdStrArr      : array[0..7] of String[3] = (
  'IDN', 'CHN', 'RNG',
  'VAL', 'OFS', 'SCL', 'RAW', 'NOP');   //2 Parameter
  Cmd2SubChArr   : array[0..7] of byte = (
  254, 253, 252, 0, 100, 200, 50, 0);   // Offsets bei SubCh 100, Skalierungen bei 200

  high                       = true;
  low                        = false;

//Default-EEPROM-Werte:
{$EEPROM}
structconst
  DACUoffset:Integer = 5;
  DACIoffset:Integer = 5;
  DACUscale:float = 1;
  DACIscale:float = 1;
  ADCUoffset:Integer = 1;
  ADCIoffset:Integer = 0;
  ADCUscale:float = 1;
  ADCIscale:float = 1;
  InitVolt:float = 5.0;
  InitmA:float =20;

{--------------------------------------------------------------}

var
{$EEPROM}

{$DATA} {Schnelle Register-Variablen}
  i,
  k           : Byte;  // k in HW-Prozess benutzt
  m: Byte;

{$IDATA}  {Langsamere SRAM-Variablen}

  SlaveCh: byte;
  LEDactivity[LEDOutPort, 2]    : bit;

  ButtonTemp,RangeTemp: Byte; // invertiert - low=on!
  ButtonLeft[@ButtonTemp, 5]  : bit;
  ButtonRight[@ButtonTemp, 4]  : bit;
  ButtonEnter[@ButtonTemp, 3]  : bit;
  ButtonIncr[BtnInPortReg, 3]  : bit;

  b_MPXA0[@PortB, 2]  : bit;
  b_MPXA1[@PortB, 3]  : bit;
  b_URange[@PortB, 5]  : bit;

  DACUtemp,DACItemp:Integer;
  LevelByteHi, LevelByteLo: byte;


//Parameter
  DCVolt : float;  //Spannung in V, 0..20
  DCmA: float; // Strom in mA, 0..2000

  CmdWhich: tCmdWhich;
  CmdStr: string[3];
  SubCh, CurrentCh       : byte;
  OmniFlag: boolean;
  
  Param, ADCmAfac, ADCVoltfac:float;
  ParamInt       : Integer;
  ParamByte:Byte;
  ParamStr: String[15];
  SerInpStr:string[63];
  IRange: tRange;
  URangeHigh: Boolean;
  ActivityTimer:Systimer;
  DisplayTimer:Systimer;
  LCDpresent: boolean;
  Modify:tModify;
  IncrValue,OldIncrValue,IncrDiff: LongInt;
  IncrFine,FirstTurn:Boolean;
  TempF:Float;
  TempI:Integer;
{$NOSAVE}

{###########################################################################}

{$I DCG-HW.pas}


{$NOSAVE}

//EEPROM-Routinen

procedure PatchCopyFromEE;
//AktuelleFrequenz und Settings aus EEPROM holen
var myPatchAdr: Word;
begin
  DCVolt:=InitVolt;
  DCmA:=InitmA;

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('#');
  write(serout,ByteToStr(SlaveCh));
  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;
begin
  ParamStr:=floatToStr(Param:5:3:'0');
  setlength(ParamStr,5); // auf 4 Stellen genau
  if Param>=1000.0 then
    ParamStr[5]:=' '; // Dezimalpunkt weglassen
  endif;
end;

procedure IstwerteOnLCD;
var Prefix:char;
begin
  Param:=float(getadc(3)+ADCUoffset);
  Param:=Param*ADCVoltfac;     // ADCVoltfac von SetLevelDAC gesetzt
  Prefix:=' ';
  if Param<1 then
    Param:=Param*1000;
    Prefix:='m';
  endif;
  ParamToStr;
  LCDxy_M(LCD_m1, 0, 0);
  write(lcdout_m,ParamStr);
  lcdout_m(Prefix);
  lcdout_m('V');
  Param:=float(getadc(4)+ADCIoffset);
  Param:=Param*ADCmAfac;     // ADCmAfac von SetLevelDAC gesetzt
  ParamToStr;
{$IFDEF Zweizeilig}
    LCDxy_M(LCD_m1, 0, 1);
{$Else}
    LCDxy_M(LCD_m1, 0, 0);
{$ENDIF}
  write(lcdout_m,ParamStr);
  lcdout_m('m');
  lcdout_m('A');
  LCDxy_M(LCD_m1, 7, 0);
  If Modify = Volt then
    lcdout_m(#1);
    LCDxy_M(LCD_m1, 7, 1);
    lcdout_m(#32);
  else
    lcdout_m(#32);
    LCDxy_M(LCD_m1, 7, 1);
    lcdout_m(#1);
  endif;
end;

procedure SollwerteOnLCD;
var Prefix:char;
begin
  LCDxy_M(LCD_m1, 7, 0);
  If Modify = Volt then
    Param:=DCVolt;     // ADCVoltfac von SetLevelDAC gesetzt
    Prefix:=' ';
    if Param<1 then
      Param:=Param*1000;
      Prefix:='m';
    endif;
    ParamToStr;
    LCDxy_M(LCD_m1, 0, 0);
    write(lcdout_m,ParamStr);
    lcdout_m(Prefix);
    lcdout_m('V');
    LCDxy_M(LCD_m1, 7, 0);
    if IncrFine then
      lcdout_m(#2);
    else
      lcdout_m(#0);
    endif;
    LCDxy_M(LCD_m1, 7, 1);
    lcdout_m(#32);
  else
{$IFDEF Zweizeilig}
    LCDxy_M(LCD_m1, 0, 1);
{$Else}
    LCDxy_M(LCD_m1, 0, 0);
{$ENDIF}
    Param:=DCmA;
    ParamToStr;
    write(lcdout_m,ParamStr);
    lcdout_m('m');
    lcdout_m('A');
    LCDxy_M(LCD_m1, 7, 0);
    lcdout_m(#32);
    LCDxy_M(LCD_m1, 7, 1);
    if IncrFine then
      lcdout_m(#2);
    else
      lcdout_m(#0);
    endif;
  endif;
end;

procedure WriteParamSer;
var
  digits:byte;
begin
  ParamToStr;
  ParamStr:=trim(ParamStr);
  WriteChPrefix;
  write(serout,ParamStr);
  SerCRLF;
end;

procedure WriteParamIntSer;
begin
  ParamStr:= IntToStr(ParamInt);
  ParamStr:= trim(ParamStr);
  WriteChPrefix;
  write(Serout, ParamStr);
  SerCRLF;
end;

procedure CheckLimits;
begin
  if DCVolt < 0 then
    DCVolt:=0;
  endif;
  if DCVolt > 20 then
    DCVolt:=20;
  endif;
  if DCmA < 0 then
    DCmA:=0;
  endif;
  if DCmA > 2000 then
    DCmA:=2000;
  endif;
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:=Integer(Param);
    ParamByte:=byte(ParamInt);
  endif;

  if request then
    case SubCh of
      0:
        Param:=DCVolt;
        |
      1:
        Param:=DCmA;
        |
      2: //Istwert Ausgangsspannung
        Param:=float(getadc(3)+ADCUoffset);
        Param:=Param*ADCVoltfac;     // ADCmAfac von SetLevelDAC gesetzt
        |
      3: //Istwert Ausgangsstrom
        Param:=float(getadc(4)+ADCIoffset);
        Param:=Param*ADCmAfac;     // ADCmAfac von SetLevelDAC gesetzt
        |
      100:
        ParamInt:=DACUoffset;
        IntFlag:=true
        |
      101:
        ParamInt:=DACIoffset;
        IntFlag:=true
        |
      102:
        ParamInt:=ADCUoffset;
        IntFlag:=true
        |
      103:
        ParamInt:=ADCIoffset;
        IntFlag:=true
        |
      254: //Wert
        SubCh:=ErrSubCh;
        WriteChPrefix;
        write(serout,Vers1Str);
        SerCRLF;
        return;
        |
    else
      serprompt(ParamErr);
      return;
    endcase;
    if IntFlag then
      WriteParamIntSer;
    else
      WriteParamSer;
    endif;
  else  // kein Request, also Parameter setzen
    case SubCh of
      0:
        DCVolt:=Param;
        |
      1:
        DCmA:=Param;
        |
      100:
        DACUoffset:=ParamInt;
        |
      101:
        DACIoffset:=ParamInt;
        |
      102:
        ADCUoffset:=ParamInt;
        |
      103:
        ADCIoffset:=ParamInt;
        |
     else
      serprompt(ParamErr);
      return;
    endcase;
    CheckLimits;
    SetLevelDAC;
    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}

  PatchCopyFromEE;       // Optionen-Defaults
  
  udelay(1);
  SlaveCh:= (not PinD) shr 5;
  
  LEDactivity:= low;
  mdelay(1000);
  if LCDsetup_M(LCD_m1) then
    LCDcursor_M(LCD_m1, false, false);
    LCDCharSet_M(LCD_m1, #0, $01, $03, $07, $0F, $07, $03, $01, $00); {"<" Cursor}
    LCDCharSet_M(LCD_m1, #1, $01, $03, $05, $09, $05, $03, $01, $00); {"<" Cursor hohl}
    LCDCharSet_M(LCD_m1, #2, $01, $02, $05, $0A, $05, $02, $01, $00); {"<" Cursor Feineinstellung}

    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;
  while serstat do
    i:=SerInp;
  endwhile;
  DCVolt:=InitVolt;
  DCmA:=InitmA;
  SetLevelDAC;
  Modify:=Volt;
  IncrCount4start;
  SetIncrVal4(0,0);
  IncrValue:=GetIncrVal4(0);
  OldIncrValue:= IncrValue;
  IncrFine:=false;
end;

{###########################################################################}

begin
  Initall;
//Hauptschleife
  loop
    if serstat then
      readln(SerInp, SerInpStr);
      ParseInpStr;           //Befehl interpretieren
    endif;
    if isSystimerZero(ActivityTimer) then
      LEDactivity:=high; // LED aus
      if LCDpresent then
        IncrValue:=GetIncrVal4(0);
        if (not ButtonIncr) then
          IncrFine:=not IncrFine;
          SetSystimer(DisplayTimer, 1000);
          SollwerteOnLCD;
          repeat
            mdelay(20);
          until ButtonIncr;
        endif;
        if (IncrValue<>OldIncrValue) then
          SetSystimer(DisplayTimer, 1000);
          // Inkrementalgeber löst in Zweierschritten auf, deshalb skalieren
          IncrDiff:= (IncrValue-OldIncrValue);
          if abs(IncrDiff) > 2 then // Beschleunigung bei schneller Drehung
            IncrDiff:= IncrDiff*2;
          endif;
          if abs(IncrDiff) > 5 then // Beschleunigung bei sehr schneller Drehung
            IncrDiff:= IncrDiff*4;
          endif;
          If Modify = Volt then
            if IncrFine then
              DCVolt:=DCVolt + Float(IncrDiff)/400;
            else
              if FirstTurn then
                TempI:=round(DCVolt*10);  // einmalig auf 100mV-Stufen runden
                DCVolt:=float(TempI)/10;
                FirstTurn:=false;
              endif;
              DCVolt:=DCVolt + Float(IncrDiff)/20;
            endif;
          else
            if IncrFine then
              DCmA:=DCmA + Float(IncrDiff)/4;
            else
              if FirstTurn then
                TempI:=round(DCmA/10);  // einmalig auf 10mA-Stufen runden
                DCmA:=float(TempI*10);
                FirstTurn:=false;
              endif;
              DCmA:=DCmA + Float(IncrDiff)*5;
            endif;
          endif;
          CheckLimits;
          SollwerteOnLCD;
          OldIncrValue:= IncrValue;
          mdelay(15); // wichtig für Beschleunigungsfunktion
        endif;

        ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
        if ButtonTemp <> $FF then
          IncrFine:=false;
          if not ButtonLeft then
            Modify:=Ampere;
          endif;
          if not ButtonRight then
            Modify:=Volt;
          endif;
          SetSystimer(DisplayTimer, 1000);
          SollwerteOnLCD;
          repeat
            mdelay(20);
            ButtonTemp := (LCDportInp_M(LCD_m1) or %11000111);
          until ButtonTemp=$ff;
        endif;
        SetLevelDAC;
      endif;
    endif;
    if isSystimerZero(DisplayTimer) and LCDpresent then
      FirstTurn:=true;
      IstwerteOnLCD;
      SetSystimer(DisplayTimer, 250);
      IncrFine:=false;
    endif;
  endloop;
end DCG.

