program DDS;
(*
DDS-Funktionsgenerator mit 2x16-Display, AD98833 und Yampp-I3-MP3-Spieler
als universelle Audio-Quelle
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

09.09.2007 #3.63 ADC-Wandlung verbessert, OnSysTick angepasst
29.08.2007 #2.43 Moduladresse SlaveCh über MCH/VAL 199 einstellbar
26.08.2007 #2.40 Aktueller c't-Lab-Parser, aktuelle HW-Routinen
01.10.2006 #2.32 Umbelegung auf ATmega32, neue Prozessor-Platine
14.09.2006 Terz und Tonleiter auf Integer verkürzt, Drehgeber wieder absolut
12.09.2006 Einfachen SCPI-Parser implementiert
19.08.2006 Pegel-Anzeige, Bargraph für MP3-Player
18.08.2006 Neue Memory-Verwaltung und -User-Interface
15.08.2006 Routinen umbenannt und umsortiert, Sweep implementiert
ShowXXX = Absolute Display-Position, WriteXXX = an Cursor-Position
13.08.2006 neue Routinen für Drehgeber, jetzt relativ
10.08.2006 Frequenz-Ziffern mit Überlauf auf nächste Stelle

LCD mit 2x16 Zeichen am I2C, für Preset- und Default-Anzeige
5 Taster zur Bedienung, Up/Down, Left/Right, Aux
Steuert MP3-Spieler, die per Widerstands-Netz fernbedienbar sind
Vorliegende Version angepasst für
Yampp Industrial III von Jesper Hansen, www.jelu.se

Incrementalgeber Panasonic am Port A0/A1 des ATmega32, Pushbutton an A2
*)

{$NOSHADOW}
{$W- Warnings}            {Warnings off}
{$TYPEDCONST OFF}
{$DEBDELAY}

//Defines aktivieren durch Entfernen des 1. Leerzeichens!

{$DEFINE yi3}         //MP3-Player Yampp Industrial an serieller Schnittstelle
{ $DEFINE bournsincr}  //Bourns-Incrementalgeber mit doppelter Frequenz, sonst ALPS

Device = mega32, VCC = 5;

Define_Fuses
  Override_Fuses; // optional, always replaces fuses in ISPE
  LockBits0      = [];
  FuseBits0      = [BODEN, BODLEVEL];
  ProgMode       = SPI;

Import SysTick, RTClock, TWImaster, LCDmultiPort, IncrPort4, SerPort, I2CExpand;  // ADCport,

From System Import LongInt, Float;

Define
  ProcClock      = 16000000;        {Hertz}

  TWIpresc       = TWI_BR400;
  LCDmultiPort   = I2C_TWI;
  LCDTYPE_M      = 44780;          // 66712;
  LCDrows_M      = 2;              //2 rows
  LCDcolumns_M   = 16;             //16 chars per line
  IncrPort4      = PinA, 1, 16; // pin-reg used, channels, 16 or 32bit integer
  IncrScan4      = Timer1, 4; // timer used, scan rate 1kHz (1..100)

  SysTick        = 10;              //msec
  SerPort        = 38400, Stop1, timeout;    {Baud, StopBits|Parity}
  RxBuffer       = 255, iData;
  TxBuffer       = 255, iData;

  StackSize      = $0080, iData;
  FrameSize      = $0100, iData;

//  ADCchans       = [7, 8], iData, int2;
//  ADCpresc       = 128;

  I2Cexpand      = I2C_TWI, $38; {use TWIport, 9554A}
  I2CexpPorts    = Port0, Port1; {use Port0}

Implementation

{--------------------------------------------------------------}
type
  TEditWhich     = (m_desc0, m_desc1, m_desc2, m_desc3, m_desc4, m_desc5, m_desc6, m_desc7,
  m_desc8, m_desc9, m_desc10, m_desc11, m_desc12, m_desc13, m_desc14, m_desc15,
  m_rtime, m_audb, m_out, m_swoct, m_sweep, m_brst, m_wav,
  m_patch, m_db, m_fr1, m_fr2, m_fr3, m_fr4, m_fr5, m_fr6, m_frdec);

  tCmdWhich      = (str, idn, trg,
  val, frq, lvl, lvp, dbv, wav, bst, pwr, inl, ofs,
  dsp, mch, scl, wen, erc, sbd, nop, err);
  tError         = (NoErr, UserReq, BusyErr, OvlErr, SyntaxErr, ParamErr, LockedErr, ChecksumErr);

{--------------------------------------------------------------}
const
  m_lastmenu                 = m_frdec;

//Wave = Sinus, Dreieck/Sägezahn, Rechteck, Audio 1..99
  c_off                      = 0;
  c_sinw                     = 1;
  c_triw                     = 2;
  c_squw                     = 3;
  c_logic                    = 4;
//  c_dc                       = 5;

//OutputMode Normal,
  c_nrmo                     = 0;
  c_symo                     = 1;
  c_pwro                     = 2;
  c_psymo                    = 3;

{ Keyboard-Interface, Taster und LEDs PortBits }

//                              A--SPBII             MP3level,-,-,SymLED,PwrLED,IncBtn,Inc1,Inc2
  DDRAinit                   = %00011000;            {PortA dir }
  PortAinit                  = %00011111;            {PortA, IncrEnc on 0+1, IncPB 2 }

//                              --Y-SFDC             -,-,YAMPP,Strobe,FSync,Data,Clk
  DDRBinit                   = %00101111;            {PortB dir }
  PortBinit                  = %00100111;            {PortB, b_STROBE=0 }

  DDRCinit                   = %11111100;            {PortC dir, 0..1=I2C }
  PortCinit                  = %11111111;            {PortC }

  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_SDATAOUT                 = 1; //Daten für alle
  b_FSYNC                    = 2; //DSS-Frame
  b_STROBE                   = 3; //4094 SR Strobe
  b_SerAux                   = 5; //PB5 ist SerAux zweiter serieller Ausgang

//für AD9833-DDS
//                       Bit    5432109876543210
  c_ddsresetcmd              = %0010000100000000;
//                       Bit              O D M
  c_ddssinecmd               = %0010000000000000;
  c_ddstrianglecmd           = %0010000000000010;
  c_ddssquarecmd             = %0010000000101000;

//Preset: Terzfrequenzen
//20, 25, 31,5, 40, 50, 63, 80, 100, 125, 160, 200 usw.
  {$I DDSL-Terz-16.pas}
  {$I DDSL-Tonleiter-64.pas}

{AD9833-DDS-Konstanten für Frequenzen, addiert für DDS, abhängig von CLK, hier 4.194304 Mhz}
  fhz            : Table[0..7] of LongInt = (64000000, 6400000, 640000, 64000, 6400, 640, 64, 6);

  SweepFak       : Table[0..15] of Word = (1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 0);

//LCDXY-Positionen der Menüs und der zugehörigen Cursor-Position auf dem Display
// m_rtime,m_audb,m_out, m_swoct,m_sweep,m_brst,m_wav, m_patch,m_db, m_frvari,m_fr1,m_fr2,m_fr3,m_fr4,m_fr5,m_fr6,m_frdec, m_frequ,m_mv
  cursx          : Table[0..31] of byte = (5, 11, 15, 3, 7, 11, 15, 7, 13, 1, 3, 5, 7, 9, 11, 13, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

  posy           : Table[0..31] of byte = (1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 );

//Lineal     .----.----.----.----.----.----.----.----.----

  Vers1Str                   = '3.63 [DDSAudio by CM/c''t 03/2006]';    //Vers1
  Vers1LCDStr                = 'DDSAudio V#3.63';    //Vers1
  Vers2LCDStr                = 'C.Meyer 09/2006';    //Vers2

  HzStr                      = 'Hz';
  dBStr                      = 'dB';
  mVStr                      = 'mV';
  SinStr                     = 'Sin';
  SquStr                     = 'Sqr';
  TriStr                     = 'Tri';
  LogicStr                   = 'Lgc';
  DCStr                      = 'DC ';

  NrmStr                     = 'Nrm';
  SymStr                     = 'Sym';
  PwrStr                     = 'Pwr';
  SymPwrStr                  = 'PSy';

  EffStr                     = 'RMS';
  VssStr                     = 'P-P';

  PlayStr                    = '  Time ';

  StopStr                    = 'STOP';
  OKStr                      = 'OK';
  AdrStr                     = 'MainCh Adr ';
  RemoteStr                  = 'Remote Control''d';

  OverloadStr                = 'Input Overload! ';
  CopyOKStr                  = 'Copied to Memory';
  MemoryStr                  = 'Mem         P<>M';
  OverwriteStr               = 'Overwrite?  N<>Y';
  PresetStr                  = 'Set  ';
  TerzStr                    = 'Terz ';
  AudioStr                   = 'Audio  ';
  FrequDispStr               = 'Frequency       ';
  DescDispStr                = 'Description     ';
  AudioOptionsStr            = 'RepTime +dB Out ';
  WaveOptionsStr             = 'Rng Swp Brs Wav ';
  TonlStr                    = 'C C#D D#E F F#G G#A A#B ';
  NoteStr                    = 'Nte ';
  EmptyStr                   = 'off';
  InLevelStr                 = 'Input Level';

  high                       = true;
  low                        = false;

  ErrSubCh                   = 255;

  ErrStrArr      : array[0..7] of String[9] = (
  '[OK]', '[SRQUSR]', '[BUSY]', '[OVERLD]', '[CMDERR]', '[PARERR]', '[LOCKED]', '[CHKSUM]');

  EEnotProgrammedStr         = 'EEPROM EMPTY! ';

  CmdStrArr      : array[0..19] of String[3] = (
  'STR', // 255
  'IDN', // 254
  'TRG', // 249
  'VAL', // 0..255
  'FRQ', // 0
  'LVL', // 1
  'LVP', // 2
  'DBU', // 3
  'WAV', // 4
  'BST', // 5
  'PWR', // 6
  'INL', // 10..13
  'DCO', // 20
  'DSP', // 80
  'MCH', // 199
  'SCL', // 200..213
  'WEN', // 250
  'ERC', // 251              = ErrCount seit letztem Reset
  'SBD', // 252              = SerBaud UBRR-Register mit U2X=1
  'NOP');   //2 Parameter
  Cmd2SubChArr   : array[0..19] of byte = (
  255, 254, 249,
  0, 0, 1, 2, 3, 4, 5, 6, 10, 20, 80, 199, 200, 250, 251, 252, 0);

//Default-EEPROM-Werte:
{$EEPROM}
structconst
  dummy          : LongInt   = 0;
  EEinitialised  : Word      = $AA55;

  EEPatchArray   : array[0..159] of byte =
  (
  $10, $27, 0, 0, 0, 2, 8, 0,  //Frequ 1000 Hz (LongInt), MP3sec, MP3min, dBkorr, Out
  0, 10, 0, 1, $67, $A6, $41, $44,     //SweepOct SweepTime Brst Wav Level (mV, Float) 774.6= $4441A666
  200, 0, 0, 0, 30, 0, 8, 0,     //Frequ 10 Hz Sweep 1000x
  9, 10, 0, 1, $67, $A6, $41, $44,
  $10, $27, 0, 0, 10, 0, 8, 0,    //White Noise @0dB Abgleichpunkt
  0, 0, 0, 4, $67, $A6, $41, $44,
  $10, $27, 0, 0, 30, 2, 14, 0,    //Groovin
  0, 0, 0, 5, $67, $A6, $41, $44,
  $10, $27, 0, 0, 20, 3, 14, 0,
  0, 0, 0, 6, $67, $A6, $41, $44,
  $10, $27, 0, 0, 30, 3, 14, 0,
  0, 0, 0, 7, $67, $A6, $41, $44,
  $10, $27, 0, 0, 30, 8, 14, 0,
  0, 0, 0, 8, $67, $A6, $41, $44,
  $10, $27, 0, 0, 30, 3, 14, 0,
  0, 0, 0, 9, $67, $A6, $41, $44,
  $10, $27, 0, 0, 30, 3, 14, 0,
  0, 0, 0, 10, $67, $A6, $41, $44,
  $10, $27, 0, 0, 30, 3, 12, 0,
  0, 0, 0, 11, $67, $A6, $41, $44
  );


  EEStringArray  : array[0..10] of String[16] =
  (
  'PowerOn Default ',
  'Audio Sweep     ',
  'White Noise     ',
  'Booker T.       ',
  'Classic         ',
  'Rock Organ B3   ',
  'Jazz            ',
  'Pop             ',
  'Rock Independent',
  'Jazz Sax        ',
  'Jazz Folk       ');

  EEPwrGaindB    : byte      = 20; // 20dB PowerAmp-Nachverstärkung
  EEPwrGainFac   : byte      = 10; // 20dB = 10fach
  EESerBaudReg   : byte      = 51;

  SlaveCh        : byte      = 2;    //eigene Kanalnummer

{--------------------------------------------------------------}

var
{$EEPROM}

{$DATA} {Schnelle Register-Variablen}
  i,
  k              : byte;  // k in HW-Prozess benutzt
  m              : byte;

{$IDATA}  {Langsamere SRAM-Variablen}

  dss_cmd        : Word;
  wave_cmd       : Word;
  MP3switchTemp, MP3State, SwitchState: byte; //Zwischenspeicher für 4094 SR oder PortB miniDDS
  BtnInState     : byte;  //Sample-Wert der Panel-Tasten
  b_TasteRechts[@BtnInState, 4]: bit; {Bit 0 ist Taste Enter, ganz rechts}
  b_TasteAb[@BtnInState, 6]  : bit; {Bit 6 ist Taste Down}
  b_TasteAuf[@BtnInState, 5] : bit; {Bit 5 ist Taste Up}
  b_TasteLinks[@BtnInState, 3]: bit; {Bit 3 ist Taste Esc, ganz links}
  b_TasteAux[@BtnInState, 7] : bit; {Bit 7 ist Taste Aux, separat}

  b_IncPB[@PinA, 2]    : bit; {Bit 2 ist Incrementalgeber-Pushbutton}

  b_LEDpwr[@PortA, 3]  : bit; {Bit 3 ist "Power-Amp" LED, separat}
  b_LEDsym[@PortA, 4]  : bit; {Bit 4 ist "Symmetry"-LED, separat}

  b_LEDaux[LEDOutPort, 2]    : bit; {Bit 2 ist Memory-LED, separat}

  b_SquareSw[@SwitchState, 0]: bit;
  b_AttnSw[@SwitchState, 1]  : bit;
  b_ExtOnMP3Power[@SwitchState, 2]: bit;
  b_PwrSw[@SwitchState, 3]   : bit;
  b_LogicSw[@SwitchState, 4] : bit;

  IncrValueRel,             // Vergleichswert für Incremental-Encoder
  EditValue,
  ItemUpperLimit, ItemLowerLimit: Integer;
  SweepStarted,
  MemoryOn,                 // Abruf der Memory-Werte, Status Mem-Taste
  IncrVerdreht, IncrFine     : Boolean;
  EditWrap       : Boolean; // Überlauf pro Stelle zulässig?
  DDSfrequ,                       //Frequenzwert für DSS
  AktuelleFrequenz, FrequFak : LongInt;    //Absolutfrequenz real, Skalierungsfaktor

  LevelByteHi, LevelByteLo   : byte;
  dB             : Float;

  EditWhich, LastMenu  : TEditWhich;
  x, y, {x,y Koordinaten LCD}
  BtnTime, BtnTime2,
//  SweepCount,
  BurstCount     : byte;
  BurstPrime, Modified, ShowAnalyzeflag,
  RemoteFlag, RemoteEnded, RemoteDisplayFlag,
  aBool, MP3isOn : Boolean;
  MP3currenttrack      : byte;
  OldInLevel:integer;
  MP3elapsed, MP3SecondChanged: Boolean;      // Repeat-Punkt B gesetzt?
  SweepUpper     : LongInt;
  PwrGain        : byte; //Nachverstärkung extern Default (kein Edit-Wert!)
  MemoryNum      : byte;
  PresetNum,
  MP3Track       : byte;

//Reihenfolge wichtig nur für Preset-Datei
  EditArray      : Table[0..15] of byte;
  Edit1[@EditArray]    : byte;

  Frequenz[@EditArray]       : LongInt;      // Absolutwert in 1/10 Hz

  MP3sec[@EditArray+4]       : byte;      // Sekunden Spielzeit
  MP3min[@EditArray+5]       : byte;      // Sekunden Spielzeit

// Korrekturwert für MP3-Player 0..16, 8=Normal, in 2-dB-Schritten bei Yampp I3
  MP3dBKorr[@EditArray+6]    : byte;

  OutputMode[@EditArray+7]   : byte;
  SweepRange[@EditArray+8]   : byte; // Korrekturwert Lautstärke
  SweepTime[@EditArray+9]    : byte;
  BurstMode[@EditArray+10]   : byte;
  Wave[@EditArray+11]  : byte;

  Level[@EditArray+12]       : Float;

  SaveArray      : Table[0..15] of byte;
  Save1[@SaveArray]    : byte;

  EditString     : String[16];
  EditString1[@EditString+1] : char;  //erstes Zeichen nach Längenbyte
  SaveEditString : String[16];
  LevelStr       : String[7];
  freqStr        : String[8];

  SerInpStr      : String[63];
  SerInpPtr      : byte;
  ParamStr       : String[40];
  CmdStr         : String[7];
  CmdWhich       : tCmdWhich;
  OmniFlag, verbose    : Boolean;
  CurrentCh, SubCh     : byte;
  Param          : Float;
  ParamInt       : Integer;
  Parambyte      : byte;
  nachkomma:byte;

  DisplayTimer   : SysTimer8;
  ActivityTimer  : SysTimer8;
  SweepTimer     : SysTimer8;

  Status         : byte;
  BusyFlag[@Status, 7]       : bit;
  UserSRQFlag[@Status, 6]    : bit;
  OverloadFlag[@Status, 5]   : bit;
  EEUnlocked[@Status, 4]     : bit; // EEPROM-unlocked-Flag
  ErrCount       : Integer;
  ErrFlag        : Boolean;

const
//Anpassen bei Änderungen der Reihenfolge: Letzter - Erster Eintrag
  EditArrayLen               = 16;

{###########################################################################}

{$NOSAVE}
{$I DDSL-HW.pas}

procedure onsystick;
//Burst-Generierung per Interrupt
begin
{
  asm;  //  auf AD-Wandlung warten, falls Systick "überfahren" wurde
    ADC10endLoop1:
    in _ACCB, ADCSRA
    sbrc _ACCB,6 // auf Bit 6 low warten
    rjmp ADC10endLoop1
  endasm;
}
  if BurstMode <> 0 then
    if BurstCount = 1 then
      dss_cmd:= wave_cmd;
      SendDDS;
    endif;
    if BurstCount = 0 then
      dss_cmd:= c_ddsresetcmd;
      SendDDS;
      BurstCount:= BurstMode + 1;
    endif;
    dec(BurstCount);
  endif;
end;

procedure RTCtickSecond; {CallBack procedure}
begin
  MP3SecondChanged:= true;
end;

procedure RTCtimer; {CallBack procedure}
begin
  MP3elapsed:= true;
end;

function GetADC10(myChannel:byte):word;
//Zu-Fuss-Implementation der getadc()-Funktion
var
  CurrentADC10:word;
  CurrentADC10lo[@CurrentADC10]:byte;
  CurrentADC10hi[@CurrentADC10+1]:byte;
begin
  ADMUX:=((myChannel-1) and $07) OR %11000000;  {Internal ADC Reference}
  asm;
    ldi  _ACCB, 15
    ADC10settleLoop2:  // 3 µs warten
    dec _ACCB
    BRNE ADC10settleLoop2
    ldi  _ACCB, $C7    // Teiler 128
    out ADCSRA, _ACCB
    ADC10endLoop2:
    in _ACCB, ADCSRA
    sbrc _ACCB,6 // auf Bit 6 low warten
    rjmp ADC10endLoop2
  endasm;
  CurrentADC10lo:=ADCL;
  CurrentADC10Hi:=ADCH;
  return(CurrentADC10);
end;


{###########################################################################}
//EEPROM-Routinen


procedure PresetSave;
//Sichert aktuelles EditArray bei Wechsel auf Memory
begin
  CopyBlock(@Edit1, @Save1, EditArrayLen);
  SaveEditString:= EditString;
end;

procedure PresetRestore;
//Stellt EditArray nach Wechsel von Memory auf Presets wieder her
begin
  CopyBlock(@Save1, @Edit1, EditArrayLen);
  EditString:= SaveEditString;
end;

procedure PatchCopyFromEE;
//AktuelleFrequenz und Settings aus EEPROM holen
var myPatchAdr: Word;
begin
  myPatchAdr :=  Word(MemoryNum)*EditArrayLen;
  for i := 0 to EditArrayLen - 1 do
    settable(EditArray, i, (EEPatchArray[myPatchAdr + Word(i)]));
  endfor;
  EditString:= EEStringArray[MemoryNum];
end;

procedure PatchCopyToEE;
//AktuelleFrequenz und Settings in EEPROM speichern
var myPatchAdr: Word;
begin
  myPatchAdr := Word(MemoryNum)*EditArrayLen;
  for i := 0 to EditArrayLen - 1 do
    EEPatchArray[myPatchAdr + Word(i)]:= gettable(EditArray, i);
  endfor;
  EEStringArray[MemoryNum]:= EditString;
end;

{###########################################################################}
//Serielle Kommunikation und Parser-Routinen

procedure SerOutFreq(myfreq : Longint);
var
  lastdigit      : char;
  l              : byte;
begin
  freqStr:= longtostr(myfreq); // ohne führenden Nullen
  l:= length(freqStr);
  lastdigit:= freqStr[l];  //letztes Zeichen
  dec(l);
  SetLength(freqStr, l);
  write(serout, freqStr);
  serout('.');
  serout(lastdigit);
end;

procedure SetSweepUpper;
// obere Frequenz-Grenze für Sweep setzen
begin
  SweepUpper:= LongInt(gettable(SweepFak, SweepRange));
  SweepUpper:= SweepUpper*Frequenz;
  SweepStarted:= false;
end;

procedure SweepGen;
//Sweep-Generierung per Software, Ausgabe aktueller Frequenz seriell
begin
  if (SweepRange <> 0) then
//    dec(SweepCount);
    if not SweepStarted then
      setSystimer(SweepTimer, SweepTime);
      SetLevelDDS;
//      SweepCount:=0;
      SweepStarted:= true;
    endif;
    if isSysTimerZero(SweepTimer) then
      setSystimer(SweepTimer, SweepTime);
      AktuelleFrequenz:= (AktuelleFrequenz * 102) div 100;
      SetLevelDDS;
      mdelay(Word(SweepTime));
      if AktuelleFrequenz >= SweepUpper then
        SweepStarted:= false;
        AktuelleFrequenz:= Frequenz;
      endif;
    endif;
  else
    AktuelleFrequenz:= Frequenz;
  endif;
end;

procedure GetInLevel;
//ermittelt aktuellen Input-Level
begin
  Param:=float(getadc10(7));
end;

{###########################################################################}
//allg. Menü-Prozeduren

procedure SerCRLF;
begin
  serout(#$0D);
  serout(#$0A);
end;

procedure WriteChPrefix;
begin
  serout('#');
  serout(char(SlaveCh + 48));
  serout(':');
  write(serout, ByteToStr(SubCh));
  serout('=');
end;

procedure WriteSerInp;
begin
  write(serout, SerInpStr);   // Befehl weiterreichen
  SerCRLF;
end;

procedure SerPrompt(myErr : tError; myStatus : byte);
//Error-Meldung und Status-Request-Antwort,
//Status Bit 7=Busy, 6=UserSRQ, 5=OverLoad, 4=!!!, 3..0=Fehler-Nummer
begin
  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;
  if myERR <> NoErr then
    inc(ErrCount);
    ErrFlag:= true;
  endif;
end;

procedure WriteParam;
var ParamStr: String[15];
begin
  ParamStr:= floatToStr(Param:1:nachkomma);
  write(serout, ParamStr);
  SerCRLF;
end;

//Menü-Prozeduren

procedure LCD1xy;
begin
  LCDxy_M(LCD_m1, x, y);
end;

procedure LCD1x0y0;
begin
  x:= 0;
  y:= 0;
  LCD1xy;
end;

procedure LCD1x0y1;
begin
  x:= 0;
  y:= 1;
  LCD1xy;
end;

procedure LCD1space;
begin
  LCDout_M(#32);   //Leerzeichen
end;

procedure SetCursor(whichItem : TEditWhich);
begin
  i:= ord(whichItem) - 16;  // wg. Description 16 Bytes
  x:= gettable(cursx, i);
  y:= gettable(posy, i);
  LCD1xy;
//Cursor-Zeichen "<" ist #0
  if IncrFine then
    LCDout_M(#3);
  else
    if ShowAnalyzeflag then
      LCDout_M(#4);  // Cursor hohl
    else
      LCDout_M(#0);
    endif;
  endif;
end;

procedure SetCursorUnderline(whichItem : TEditWhich);
begin
  i:= ord(whichItem);
  x:= i;
  y:= 1;
  LCD1xy;
  LCDcursor_m(LCD_m1, true, false);
  mdelay(1); //sonst manchmal Fehlfunktionen
end;

{$I DDSL-buttons.pas}
{$I DDSL-mp3control.pas}


procedure CheckItemLimits;
//EditValue eingrenzen, Carry nur wenn EditWrap true
begin
  if EditValue > ItemUpperLimit then
    if EditWrap then
      EditValue := ItemLowerLimit;  // wieder von vorn
    else
      EditValue := ItemUpperLimit;  // auf Maximalwert bleiben
    endif;
  endif;
  if EditValue < ItemLowerLimit then
    if EditWrap then
      EditValue := ItemUpperLimit;  // wieder von vorn
    else
      EditValue := ItemLowerLimit;  // auf Minimalwert bleiben
    endif;
  endif;
end;


procedure WriteItem(myDigit : byte);
//einzelnen Wert DisplayValue darstellen
begin
//Zahlenausgabe ein- bis zweistellig, ggf. mit führender Null:
  write(LCDout_M, ByteToStr(myDigit : 2 : '0'));
end;

procedure WriteDigit(myDigit : byte);
//einzelne Ziffer DisplayValue darstellen
begin
  LCDout_M(char((myDigit mod 10) + 48));
end;

procedure WriteSweepRange;
var
  aInt           : Integer;
//Wellenform ausschreiben
begin
  i:= SweepRange mod 9;
  aInt:= Integer(gettable(SweepFak, i));
  case SweepRange of
    0     :
            write(LCDout_M, EmptyStr);
          |
    1..8  :
            write(LCDout_M, IntToStr(aInt : 3));
          |
    9..15 :
            write(LCDout_M, IntToStr(aInt : 2));
            LCDout_M(#107);  // "k"
          |
  endcase;
  LCD1space;
end;

procedure WriteWave;
//Wellenform ausschreiben
begin
  case wave of
    c_off   :
              write(LCDout_M, EmptyStr);
            |
    c_sinw  :
              write(LCDout_M, SinStr);
            |
    c_triw  :
              write(LCDout_M, TriStr);
            |
    c_squw  :
              write(LCDout_M, SquStr);
            |
    c_logic :
              write(LCDout_M, LogicStr);
            |
    5..99   :
//Audio-File-Nummer -5
//bei externer Quelle wurde DDS abgeschaltet
              LCDout_M('A'); // A wie Audio
              WriteItem(Wave - 4); // Track-Nummer
            |
  endcase;
  LCD1space;
end;

procedure WriteOutputMode;
//OutputMode ausschreiben: Normal, Symmetrisch, PowerAmp, SymPowerAmp
begin
  case OutputMode of
    c_symo  :
              write(LCDout_M, SymStr);
            |
    c_pwro  :
              write(LCDout_M, PwrStr);
            |
    c_psymo :
              write(LCDout_M, SymPwrStr);
            |
  else
    write(LCDout_M, NrmStr);
  endcase;
  LCD1space;
end;

procedure WriteVoltMode;
//RMS oder Effektiv oder Audio-File
begin
  case wave of
    c_squw, c_triw, c_logic :
                              write(LCDout_M, VssStr);
                              LCD1space;
                            |
    c_sinw                  :
                              write(LCDout_M, EffStr);
                              LCD1space;
                            |
  else
    writewave;
  endcase;
  LCD1space;
end;


procedure ShowFrequSpaced;
//AktuelleFrequenz mit Leerzeichen zwischen einzelnen Ziffern darstellen
begin
//  FreqStr:= longtostr(AktuelleFrequenz : 8:'0'); // mit führenden Nullen
  freqStr:= longtostr(Frequenz : 7 : '0'); // mit führenden Nullen
  LCD1x0y0;
  WriteLCD(@FrequDispStr);
  LCD1x0y1;
  for i:= 1 to 7 do
    LCDout_M(freqStr[i]);
    if i = 6 then
      LCDout_M(#46); //Dezimalpunkt nach 7. Stelle
    else
      LCD1space;
    endif;
  endfor;
  WriteLCD(@HzStr);
  SetCursor(EditWhich);
end;

procedure ShowFrequency;
//AktuelleFrequenz darstellen
begin
  freqStr:= longtostr(Frequenz : 8); // ohne führenden Nullen
  y:= 1;
  x:= 4;
  LCD1xy;
  for i:= 0 to 6 do
    LCDout_M(freqStr[i + 1]);
  endfor;
  LCDout_M(#46); //Dezimalpunkt nach 7. Stelle
//  DisplayValue:= FrequDez;
  LCDout_M(freqStr[8]);
  LCD1space;
  WriteLCD(@HzStr);
end;


procedure ShowLevelmV;
//m_db-Wert in AttnStep in mV umrechnen und darstellen
var myLevel: Float;
  Prefix         : char;
{mV aus m_db ausrechnen}
begin
  y:= 1;
  x:= 7;
  LCD1xy;
  if OutputMode = c_pwro then
    myLevel:= Level*Float(EEPwrGainFac);
  else
    myLevel:= Level;
  endif;
// full Scale Level = 4096 mV RMS bei Sinus, 10,24 Vss bei Rechteck
  case wave of
    c_squw, c_logic :
                      myLevel:= myLevel*2.5;
                    |
    c_triw          :
                      myLevel:= myLevel*2.8284; // RMS-Faktor 2*pi für Vss
                    |
  endcase;
  if myLevel >= 1000.0 then
    myLevel:= myLevel / 1000;
    Prefix:= ' ';
    i:= 3;
  else
    Prefix:= 'm';
    i:= 1;
    if myLevel < 100 then
      i:= 2;
    endif;
  endif;
  write(LCDout_M, floatToStr(myLevel : 6 : i));
  LCD1space;
  LCDout_M(Prefix);
  LCDout_M('V');
end;

function Level2dB(myLevel : float) : float;
begin
  return (20 * log10(myLevel / 774.597));     // 774.59667 mV
end;

function dB2Level(mydb : float) : float;
begin
  return (774.597 * pow(10, (mydb / 20)));
end;

procedure WriteLeveldB;
var mydb: Float;
//m_db-Wert AttnStep ausschreiben, Pegel anhand PwrAmp-Gain und Wave berechnen
begin
  dB:= Level2dB(Level);
  if OutputMode = c_pwro then
    mydb:= dB + Float(EEPwrGaindB);
  else
    mydb:= dB;
  endif;
  case wave of
    c_squw, c_logic :
                      mydb:= mydb + 1.95; // RMS-Faktor Rechteck
                    |
    c_triw          :
                      mydb:= mydb - 1.75; // RMS-Faktor Dreieck
                    |
  endcase;
  write(LCDout_M, floatToStr(mydb : 6 : 1));
  LCD1space;
  WriteLCD(@dBStr);
end;

procedure ShowMP3BarLevel;
//Bargraph-Anzeige für Eingangspegel vom MP3-Player
var aWord: Word;
begin
  LCD1x0y1;
  aWord:= getadc10(8);
  m:= byte(aWord shr 6) and 15;
  for i:= 0 to 15 do
    if i < 12 then
      if m >= i then
        LCDout_M(#1);
      else
        LCDout_M(#32);
      endif;
    else
      if m >= i then
        LCDout_M(#2);
      else
        LCDout_M(#32);
      endif;
    endif;
  endfor;
end;

procedure ShowWaveMP3trackTime;
//Wave sowie Message, Spielzeit seit Repeat-Start ausschreiben,
//sofern MP3-Player läuft
begin
  if MP3isOn then
    y:= 1;
    x:= 0;    // nur für Position
    LCD1xy;
    writewave;
    write(LCDout_M, PlayStr);
    WriteItem(RTCgetMinute);
    LCDout_M(#58);
    WriteItem(RTCgetSecond);
  endif;
end;

procedure ShowWaveOptions;
//Überschrift und alle Optionswerte darstellen
begin
  LCD1x0y0;
  write(LCDout_M, WaveOptionsStr);
//Alle Optionen (Teil des EditArray, vier Werte) darstellen
  LCD1x0y1;
  WriteSweepRange;
  LCD1space;
  WriteItem(SweepTime);
  LCD1space;
  LCD1space;
  WriteItem(BurstMode*10);
  LCD1space;
  writewave;
  SetCursor(EditWhich);
end;

procedure ShowDescription;
//Sequenz-Array (Teil von EditArray) mit Leerzeichen
//zwischen einzelnen Ziffern darstellen
begin
  LCD1x0y0;
  write(LCDout_M, DescDispStr);
  LCD1x0y1;
  write(LCDout_M, EditString);
end;

procedure ShowMemory;
//Sequenz-Array (Teil von EditArray) mit Leerzeichen
//zwischen einzelnen Ziffern darstellen
begin
  LCD1x0y0;
  write(LCDout_M, MemoryStr);
  x:= 5;
  LCD1xy;
  WriteItem(MemoryNum);
  LCD1x0y1;
  write(LCDout_M, EditString);
  SetCursor(EditWhich);
end;


procedure ShowAudioOptions;
var
  aInt           : Integer;
//Überschrift und alle Optionswerte darstellen
begin
  LCD1x0y0;
  write(LCDout_M, AudioOptionsStr);
//Alle Optionen (Teil des EditArray, vier Werte) darstellen
  LCD1x0y1;
  WriteItem(MP3min);
  LCDout_M(#58);   //Doppelpunkt
  WriteItem(MP3sec);
  write(LCDout_M, '   ');
  aInt:= (Integer(MP3dBKorr shl 1)) - 16;
  write(LCDout_M, IntToStr(aInt : 3));
  LCD1space;
  WriteOutputMode;
  SetCursor(EditWhich);
end;


procedure MP3Setup;
//MP3-Spieler initialisieren und Track anfahren, falls erforderlich
begin
  if Wave > 4 then // Audio-File gewünscht
    MP3Track:= Wave - 4;
    dss_cmd:= c_ddsresetcmd; // Generator abschalten
    SendDDS;
    if (not MP3isOn) then
      MP3on;
    endif;
    if MP3isOn then
      if MP3Track <> MP3currenttrack then
        MP3gotoTrack;  //        MP3SetVol;
      endif;
    endif;
  else
    if MP3isOn then   // wieder Wave, MP3 ausschalten
      MP3off;
    endif;
  endif;
end;

procedure MemoryChange;
//Datensatz holen und alles einstellen
begin
  PatchCopyFromEE;   // Datensatz in MemoryNum
  ShowMemory;
  AktuelleFrequenz:= Frequenz;
  MP3currenttrack:= 0; // MP3-Neustart erzwingen
  MP3Setup;
  MP3SetVol;
  SetLevelDDS; // Falls durch Burst abgeschaltet
end;

procedure ShowPreset;
begin
  LCD1x0y0;
  if MP3isOn then
    WriteLCD(@AudioStr);
  else
    case presetNum of
      0      :
               write(LCDout_M, PresetStr);
               WriteItem(PresetNum);
             |
      1..31  :
               m:= (PresetNum - 1) mod 10;
               Frequenz:= LongInt(gettable(TerzArray, m));
               m:= (PresetNum - 1) div 10;
               if m > 0 then
                 for i:= 1 to m do
                   Frequenz:= Frequenz * 10;
                 endfor;
               endif;
               write(LCDout_M, TerzStr);
               WriteItem(PresetNum);
             |
      32..95 :
               write(LCDout_M, NoteStr);
               i:= PresetNum - 32;
               Frequenz:= LongInt(gettable(TonleiterArray, i));
               m:= (i mod 12) shl 1;
               LCDout_M(TonlStr[m + 1]);
               LCDout_M(TonlStr[m + 2]);
               m:= (i div 12);
               LCDout_M(char(m + 50)); // C2 = 65,4 Hz
             |
    endcase;
  endif;
  AktuelleFrequenz:= Frequenz;
  SetLevelDDS;
end;

procedure PatchChange;
//m_patch/Preset-Nummer und Pegel ausschreiben
begin
  ShowPreset;
  WriteLeveldB;
  ShowPreset;
  WriteLeveldB;

//Darstellung der unteren Zeile Hauptmenü: AktuelleFrequenz, Pegel in mV oder Spielzeit
  LCD1x0y1;
  LCDclrEOL_M(LCD_m1);

  if EditWhich = m_db then
    WriteVoltMode;
    ShowLevelmV;
  else
    if MP3isOn then
      ShowWaveMP3trackTime;
    else
      writewave;
      ShowFrequency;
    endif;
  endif;
  SetCursor(EditWhich);
end;

procedure PatchAnalyze;
var
  myADC          : Integer;
  mydb           : Float;
//
begin
//Darstellung der unteren Zeile Hauptmenü: AktuelleFrequenz, Einganspegel in mV
  myADC:= Integer(getadc10(7));
  LCD1x0y1;
  if myADC > 1021 then
    WriteLCD(@OverloadStr);
  else
    mydb:= Level2dB(Float(myADC) + 0.1);     // 774.59667 mV
    write(LCDout_M, IntToStr(myADC : 4));
    LCD1space;
    WriteLCD(@mVStr);
    LCD1space;
    write(LCDout_M, floatToStr(mydb : 5 : 1));
    LCD1space;
    WriteLCD(@dBStr);
  endif;
end;

{$I DDSL-parser.pas}

procedure CheckSer;
var
  myChar         : char;
begin
  while SerInp_TO(myChar, 2) 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
      ParseSubCh;           //Befehl interpretieren, wenn für uns
      SerInpStr:= '';
      MP3Setup;
    endif;
  endwhile;
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
  DDRA:=  DDRAinit;            {PortA dir}
  PortA:= PortAinit;           {PortA}
  DDRB:=  DDRBinit;            {PortB dir}
  PortB:= PortBinit;           {PortB}
  DDRC:=  DDRCinit;            {PortC dir}
  PortC:= PortCinit;           {PortC}
  DDRD:=  DDRDinit;            {PortD dir}
  PortD:= PortDinit;           {PortD}

  i:= EESerBaudReg;
  if not i in [9..239] then
    EESerBaudReg:= 51;
    i:= 51;
  endif;
  ubrr1:= i;
  UCSRA:= UCSRA OR 2; // Set U2X bit
//  serBaud(EESerbaud);        // nur mit 644

  ADMUX:= ADMUX OR %11000000;  {Internal ADC Reference}

  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, $1F, $1F, $1F, $1F, $1F, $1F, $1F, $00); {Bargraph-Block}
    LCDCharSet_M(LCD_m1, #2, $15, $0A, $15, $0A, $15, $0A, $15, $00); {Bargraph-Block schraffiert}
    LCDCharSet_M(LCD_m1, #3, $01, $02, $05, $0A, $05, $02, $01, $00); {"<" Cursor IncrValeinstellung}
    LCDCharSet_M(LCD_m1, #4, $02, $06, $0A, $12, $0A, $06, $02, $00); {"<" Cursor hohl}
  endif;
  LCDclr_M(LCD_m1);

  WriteLCD(@Vers1LCDStr);
  LCD1x0y1;
  WriteLCD(@Vers2LCDStr);
  mdelay(1000);
  LCD1x0y1;
  LCDclrEOL_M(LCD_m1);
  write(LCDout_M, AdrStr);
  LCDout_M(char(SlaveCh + 48));
  mdelay(1000);

  MemoryNum:= 0;
  PatchCopyFromEE;       // Optionen-Defaults
  PresetSave;
  PresetNum:= 0;
  BtnTime:= 40; // Initial timer value
  BtnTime2:= 10; // Anzahl Durchläufe bis fast forward

  MemoryOn:= false;
  LCDclr_M(LCD_m1);
  Modified:= false;
  MP3SecondChanged := false;
  EnableInts;

  MP3off;
  BurstCount:= 1;
  setSystimer(DisplayTimer, 1);
  IncrCount4start;
  ClearIncrAll4;
  IncrFine:= false;
  WriteChPrefix;
  write(serout, Vers1Str);
  if EEinitialised <> $AA55 then
    write(serout, EEnotProgrammedStr);
  endif;
  SerCRLF;
  SetLevelDDS;
  IncrValueRel:= 0; // div 2; // pro Raste zwei Zählerpunkte
end;

{###########################################################################}

begin
  Initall;
  EditWhich:= m_patch;
  PatchChange;

//Hauptschleife, wird bei jedem Druck eines Aux- oder Navi-Buttons durchlaufen
  loop

    LCDcursor_m(LCD_m1, false, false);
    SetSweepUpper;
    SetLevelDDS;
    MP3Setup;
    ItemLowerLimit:= 0;
    ItemUpperLimit:= 9;
    EditWrap:= false;
    ShowAnalyzeflag:= false;
    CheckSer;
    if MemoryOn then
      EditValue:= Integer(MemoryNum);
      EditWhich:= m_patch;
      MemoryChange;
    else
      case EditWhich of
        m_patch           :
                            BtnTime:= 40; //kurz stehenbleiben bei Autorepeat
                            if MP3isOn then
                              ItemLowerLimit:= 5;  // Audio 1
                              ItemUpperLimit:= 99;
                              EditValue:= Integer(Wave);
                            else
                              EditValue:= Integer(PresetNum);
                              ItemUpperLimit:= 95;
                            endif;
                            PatchChange;
                            setSystimer(DisplayTimer, 150);
                          |
        m_dB              :
                            if IncrFine then
                              Level:= dB2Level(dB);
                              ItemLowerLimit:= 1;
                              ItemUpperLimit:= 4095; // eigener Regelbereich in mV
                              EditValue:= Integer(Level);
                            else
                              dB:= Level2dB(Level);
                              ItemLowerLimit:= - 70;
                              ItemUpperLimit:= 10; // eigener Regelbereich in dB
                              EditValue:= Integer(dB);
                            endif;
                            PatchChange;
                            setSystimer(DisplayTimer, 150);  // für Level-Anzeige
                          |
        m_fr1..m_frdec    :
                            EditWrap:= true;
                            ShowFrequSpaced;
                          |
        m_wav             :
                            ItemLowerLimit:= 1;  // Sinus
                            ItemUpperLimit:= 99;
                            EditValue:= Integer(Wave);
                            ShowWaveOptions;
                          |
        m_sweep           :
                            EditValue:= Integer(SweepTime);
                            EditWrap:= true;
                            ItemUpperLimit:= 99;
                            ShowWaveOptions;
                          |
        m_swoct           :
                            EditValue:= Integer(SweepRange);
                            ItemUpperLimit:= 14;
                            ShowWaveOptions;
                          |
        m_brst            :
                            EditValue:= Integer(BurstMode);
                            EditWrap:= true;
                            ItemUpperLimit:= 9;
                            ShowWaveOptions;
                          |
        m_out             :
                            EditValue:= Integer(OutputMode);
                            ItemUpperLimit:= 3;
                            ShowAudioOptions;
                          |
        m_audb            :
                            EditValue:= Integer(MP3dBKorr);
                            ItemUpperLimit:= 16;
                            ShowAudioOptions;
                          |
        m_rtime           :
                            EditValue:= Integer(MP3sec + 60*MP3min);
                            ItemUpperLimit:= 1200;
                            ShowAudioOptions;
                          |
        m_desc0..m_desc15 :
                            ItemLowerLimit:= 32;  // ASCII " " bis "z"
                            ItemUpperLimit:= 122;
                            ShowDescription;
                            SetCursorUnderline(EditWhich);
                            i:= ord(EditWhich);
                            inc(i);
                            EditValue:= Integer(EditString[i]);
                          |
      endcase;
    endif;
    LastMenu:= m_lastmenu;
    SlowRepeatRelease; // auf Loslassen oder Autorepeat warten
    Modified:= false;
    SetIncrVal4(0, 0);
    IncrValueRel:= 0;
//Up-Down-Unterschleife
    repeat
// Überprüfen, ob Parser aufgerufen; dann auf Remote-Anzeige schalten
      repeat
        CheckSer;
        if RemoteFlag and (not RemoteDisplayFlag) then
          LCDclr_M(LCD_m1);
          write(LCDout_M, RemoteStr);
          RemoteDisplayFlag:= true;
        endif;
        if RemoteFlag and isSysTimerZero(ActivityTimer) then
          RemoteFlag:= false;
          RemoteEnded:= true; // zum Neustart der Schleife von oben
        endif;
      until not RemoteFlag;
      RemoteDisplayFlag:= false;

      IncrValueRel:= GetIncrVal4(0);
      if IncrValueRel > 0 then
        dec(IncrValueRel);
      endif;
      if IncrValueRel < 0 then
        inc(IncrValueRel);
      endif;
      IncrVerdreht:= IncrValueRel <> 0;
//Wenn Up/Down-Taste gedrückt, Wert verändern, auf den EditWhich zeigt
      if UpDownbutton OR IncrVerdreht then
//Absolutwert in EditValue, Relativwert in IncrValueRel:
        SetIncrVal4(0, 0);
        EditValue:= EditValue + IncrValueRel;
        if (not b_TasteAuf) and (not b_TasteAb) then
          EditValue:= 0;
          IncrValueRel:= 0;
        else
          if not b_TasteAuf then
            inc(EditValue);
            inc(IncrValueRel);
          endif;
          if not b_TasteAb then
            dec(EditValue);
            dec(IncrValueRel);
          endif;
        endif;
        ShowAnalyzeflag:= false;
        if MemoryOn then
          setSystimer(DisplayTimer, 150);
          CheckItemLimits;
          MemoryNum:= byte(EditValue);
          MemoryChange;
        else
          Modified:= true;
          case EditWhich of
            m_patch           :
                                if MP3isOn then
                                  CheckItemLimits;
                                  Wave:= byte(EditValue);
                                  MP3Setup;
                                else
                                  if IncrFine then
                                    if abs(IncrValueRel) > 3 then   // Beschleunigung
                                      IncrValueRel:= IncrValueRel shla 1;
                                    endif;
                                    FrequFak:= 100 + LongInt(IncrValueRel);
                                    Frequenz:= (Frequenz * FrequFak) div 100;
                                    AktuelleFrequenz:= Frequenz;
                                    SetLevelDDS;
                                    ShowFrequency;
                                  else
                                    CheckItemLimits;
                                    PresetNum:= byte(EditValue);
                                    Frequenz:= 10000; // 1000 Hz default
                                    PatchChange;
                                    AktuelleFrequenz:= Frequenz;
                                    Modified:= false;    //hier wird kein Wert verändert
                                  endif;
                                  setSystimer(DisplayTimer, 60);  // für Level-Anzeige
                                endif;

                              |
            m_db              :
                                CheckItemLimits;
                                if IncrFine then
                                  Level:= Float(EditValue);
                                else
                                  dB:= Float(EditValue);
                                  Level:= dB2Level(dB);
                                endif;
                                PatchChange;
                                setSystimer(DisplayTimer, 60);  // für Level-Anzeige
                              |
            m_fr1..m_frdec    :  // Darstellung in 1/10 Hz-Stufen
                                //Stellen-Multiplikator errechnen
                                FrequFak:= 1;
                                if EditWhich <  m_frdec then
                                  for i := m_fr6 downto EditWhich do
                                    FrequFak:= FrequFak*10;
                                  endfor;
                                endif;
                                FrequFak:= FrequFak * LongInt(IncrValueRel);
                                if (Frequenz >= - FrequFak) and (Frequenz + FrequFak <= 9999999) then
                                  Frequenz:= Frequenz + FrequFak;
                                endif;
                                AktuelleFrequenz:= Frequenz;
                                SetLevelDDS;
                                ShowFrequSpaced;
                              |
            m_wav             :
                                CheckItemLimits;
                                Wave:= byte(EditValue);
                                SetLevelDDS;
                                ShowWaveOptions;

                              |
            m_sweep           :
                                CheckItemLimits;
                                SweepTime:= byte(EditValue);
                                ShowWaveOptions;
                              |
            m_swoct           :
                                CheckItemLimits;
                                SweepRange:= byte(EditValue);
                                ShowWaveOptions;
                              |
            m_brst            :
                                CheckItemLimits;
                                BurstMode:= byte(EditValue);
                                ShowWaveOptions;
                              |
            m_out             :
                                CheckItemLimits;
                                OutputMode:= byte(EditValue);
                                SetLevelDDS;
                                ShowAudioOptions;
                              |
            m_audb            :
                                CheckItemLimits;
                                MP3dBKorr:= byte(EditValue);
                                ShowAudioOptions;
                                MP3SetVol;
                                setSystimer(DisplayTimer, 60);  // für Level-Anzeige
                              |
            m_rtime           :
                                CheckItemLimits;
                                MP3sec:= byte(EditValue mod 60);
                                MP3min:= byte(EditValue div 60);
                                ShowAudioOptions;
                              |
            m_desc0..m_desc15 :
                                CheckItemLimits;
                                i:= ord(EditWhich);
                                EditString[i + 1]:= char(EditValue);
                                ShowDescription;
                                SetCursorUnderline(EditWhich);
                              |
          endcase;
        endif;

        SetSweepUpper;
        MP3Setup;
        IncrValueRel:= 0;   // Wert wurde berücksichtigt
        SetLevelDDS; // Falls durch Burst abgeschaltet
        FastRepeatRelease;
      endif;

      SweepGen;
      if MP3SecondChanged then
        MP3SecondChanged := false;
        MP3checkRepeat;
      endif;

      if EditWhich in [m_patch, m_db] then
        if (not MP3isOn) and isSysTimerZero(DisplayTimer) then // Eingangspegel anzeigen
          ShowAnalyzeflag:= true;
          SetCursor(EditWhich);
          PatchAnalyze;
          setSystimer(DisplayTimer, 20);  // Refresh für Level-Anzeige
        endif;
      endif;

      if isSysTimerZero(DisplayTimer) and MP3isOn then
        setSystimer(DisplayTimer, 10);
        if EditWhich = m_patch then
          ShowWaveMP3trackTime; //kleine Anzeige-Verzögerung
        endif;
        if EditWhich in [m_db, m_audb]  then
          ShowMP3BarLevel; //Pegel-Anzeige
        endif;
      endif;

    until NaviButton OR (not b_IncPB) OR RemoteEnded;
    RemoteEnded:= false;
    AktuelleFrequenz:= Frequenz;
    setSystimer(DisplayTimer, 100);  // für Level-Anzeige
    if not b_IncPB then
      if EditWhich in [m_patch, m_db] then
        IncrFine:= not IncrFine;
      endif;
    repeat until b_IncPB;
      checkdelay(1); //Prellzeit
    endif;
    if not b_TasteLinks then
      IncrFine:= false;
      if MemoryOn then
        PresetNum:= 0;
        PresetSave; // später in EditArray übertragen
        MemoryOn:= false;
        b_LEDaux := high;
      else
        EditWhich:= pred(EditWhich);
      endif;
    endif;
    if not b_TasteRechts then
      IncrFine:= false;
      if MemoryOn then
        PresetNum:= 0;
        Presetrestore; // später in EditArray übertragen
        LCD1x0y0;
        write(LCDout_M, OverwriteStr);
        SlowRepeatRelease; // auf Loslassen oder Autorepeat warten
      repeat until NaviButton;
        if not b_TasteRechts then
          LCD1x0y0;
          write(LCDout_M, CopyOKStr);
          PatchCopyToEE;
          mdelay(700);
        endif;
        SlowRepeatRelease;
        MemoryOn:= false;
        b_LEDaux := high;
      else
        if EditWhich < LastMenu then
          inc(EditWhich);
        endif;
      endif;
    endif;
    if not b_TasteAux then
      IncrFine:= false;
      if MemoryOn then
        MemoryOn:= false;
        b_LEDaux := high;
        Presetrestore;
      else
        setSystimer(DisplayTimer, 150);
        MemoryOn:= true;
        b_LEDaux := low;
        PresetSave;
        MemoryChange;
      endif;
    endif;
  endloop;
end DDS.

