program DDS;
(*
DDS-Funktionsgenerator mit AD98833 und Yampp-I3-MP3-Spieler
als universelle Audio-Quelle
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

01.10.2006 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 Billigst-MP3-Spieler mit
Myson Century MTM809/CS8822 Chipsatz von EFB/ETT, Braunschweig
oder 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!

//sonst AD9833-DDS Anpassungen und mega168 auf MIDIvice-Platine


{ $DEFINE minEEPROM}         //TEST: minimale EEPROM-Presets während Entwicklung
{ $DEFINE MenuTest}          //TEST: Keine HW-IO-Routinen, zum Testen der Menüs
{$DEFINE yi3}         //MP3-Player Yampp Industrial an serieller Schnittstelle
{ $DEFINE bournsincr}  //Bourns-Incrementalgeber mit doppelter AktuelleFrequenz, 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, ADCport, IncrPort4, SerPort;

From RTclock Import RTCtimer;
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        = 1;              //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       = 16;

  RTclock = iData,Time;
  RTCsource = SysTick;

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, m_analyze);

    tCmdWhich = (str,idn,trg,
                 val,frq,lvl,lvp,dbv,wav,bst,pwr,inl,ofs,
                 dsp,all,scl,wen,erc, sbd,nop, err);
  tError = (NoErr, UserReq, BusyErr, OvlErr, SyntaxErr, ParamErr, LockedErr,ChecksumErr);

{--------------------------------------------------------------}
const
  m_lastmenu                 = m_analyze;

//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 Terz-16.pas}
  {$I 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                   = 'Audio-DDS V#2.32';    //Vers1
  Vers2Str                   = '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';
  ErrStr1                    = 'ERR SYNTAX';
  ErrStr2                    = 'ERR PARAMETER';
  ErrStr3                    = 'ERR RANGE';
  OKStr                      = 'OK';

  CopyOKStr                  = 'Copied to Memory';
  MemoryStr                  = 'Mem         P<>M';
  OverwriteStr               = 'Overwrite?  N<>Y';
  PresetStr                  = 'Set  ';
  TerzStr                    = 'Terz ';
  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                 = 'InLevel';

  high                       = true;
  low                        = false;

  ErrSubCh                   = 255;

  ErrStrArr      : array[0..7] of String[9] = (
    '[OK]','[SRQUSR]','[BUSY]','[OVERLD]','[CMDERR]','[PARERR]','[LOCKED]','[CHKSUM]');

  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
  'ALL', // 99
  '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, 99, 200, 250, 251, 252, 0);

//Default-EEPROM-Werte:
{$EEPROM}
structconst

  EEPatchArray   : array[0..159] of Byte =
  (
  $10,$27,0,0, 10, 0, 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, 50, 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

  SlaveCh:byte=1;    //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}
  LEDactivity[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,
  aBool, MP3isOn: boolean;
  MP3currenttrack: Byte;

  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;

  BurstTimer      : SysTimer8;
  DisplayTimer     : SysTimer;
  ActivityTimer:Systimer;

  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 DDS-HW.pas}

procedure onsystick;
//Burst-Generierung per Interrupt
begin
  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;



{###########################################################################}
//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;

{###########################################################################}
//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
    LCDout_M(#0);
  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 buttons.pas}
{$I 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:=getadc(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);
  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;
  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;
  AktuelleFrequenz:=Frequenz;
  SetLevelDDS;
end;

procedure PatchChange;
//m_patch/Preset-Nummer und Pegel ausschreiben
begin
  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
  y:=0;
  x:=9;
  LCD1xy;
  WriteLCD(@InLevelStr);
  myADC:=integer(getadc(7));
  mydB:= Level2dB(float(myADC)+0.1);     // 774.59667 mV
  LCD1x0y1;
  write(lcdout_m, IntToStr(myADC:4));
  LCD1space;
  WriteLCD(@mVStr);
  LCD1space;
  write(lcdout_m, FloatToStr(mydB:5:1));
  LCD1space;
  WriteLCD(@dBStr);
end;

{$I parser.pas}

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
      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}

  ADMUX:= ADMUX OR %11000000;  {Internal ADC Reference}


  if LCDsetup_M(LCD_m1) then
    LCDcursor_M(LCD_m1, false, false);
    LCDCharSet_M(LCD_m1, #0, $02, $06, $0E, $1E, $0E, $06, $02, $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, $02, $06, $0A, $12, $0A, $06, $02, $00); {"<" Cursor hohl}
  endif;
  LCDclr_M(LCD_m1);

  WriteLCD(@Vers1Str);
  LCD1x0y1;
  WriteLCD(@Vers2Str);

  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(BurstTimer,1);
  setSystimer(DisplayTimer,1);
  IncrCount4start;
  ClearIncrAll4;
  IncrFine:=false;
  SetLevelDDS;
  IncrValueRel:= 0;// div 2; // pro Raste zwei Zählerpunkte

  while serstat do
    i:=SerInp;
  endwhile;
  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;
    if MemoryOn then
      EditValue:= integer(MemoryNum);
      EditWhich:= m_patch;
      MemoryChange;
    else
      case EditWhich of
        m_patch, m_analyze:
                         BtnTime:= 40; //kurz stehenbleiben bei Autorepeat
                         EditValue:= integer(PresetNum);
                         ItemUpperLimit:= 95;
                         PatchChange;
                         setSystimer(DisplayTimer,1000);
                       |
        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,1000);  // 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:= 99;
                         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
      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;
        if MemoryOn then
          setSystimer(DisplayTimer,2000);
          CheckItemLimits;
          MemoryNum:= byte(EditValue);
          MemoryChange;
        else
          Modified:= true;
          case EditWhich of
            m_patch, m_analyze:
                             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,1000);  // für Level-Anzeige

                           |
            m_db           :
                             CheckItemLimits;
                             if IncrFine then
                               Level:=float(EditValue);
                             else
                               db:=float(EditValue);
                               level:=dB2Level(dB);
                             endif;
                             PatchChange;
                             setSystimer(DisplayTimer,500);  // 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,1000);  // 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;

      CheckSer;

      if Editwhich=m_analyze then
        if (not MP3isOn) and isSystimerzero(DisplayTimer) then // Eingangspegel anzeigen
          PatchAnalyze;
          setSystimer(DisplayTimer,200);  // Refresh für Level-Anzeige
        endif;
      endif;

      SweepGen;

      if MP3secondchanged then
        MP3secondchanged :=false;
        MP3checkRepeat;
      endif;
      if isSystimerzero(DisplayTimer) and MP3isOn then
        setSystimer(DisplayTimer,100);
        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);
    AktuelleFrequenz:=Frequenz;
    setSystimer(DisplayTimer,1000);  // für Level-Anzeige
    if not b_IncPB then
      if EditWhich in [m_patch, m_db, m_analyze] then
        IncrFine:= not IncrFine;
      endif;
      repeat until b_IncPB;
      mdelay(10); //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,2000);
        MemoryOn:= true;
        b_LEDaux := low;
        PresetSave;
        MemoryChange;
      endif;
    endif;
    if isSystimerZero(ActivityTimer) then
//      b_LEDaux:=high; // LED aus
    endif;
  endloop;
end DDS.

