program SoftDDS;
(*
DDS-Funktionsgenerator mit AD98833 und Yampp-I3-MP3-Spieler
als universelle Audio-Quelle
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

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 Inkrementalgeber, jetzt relativ
10.08.2006 Frequenz-Ziffern mit Überlauf auf nächste Stelle

MIDIvice-Platine mit ATmega168 (16KByte Code!) und
AD9833-DDS-Platine an ManualScan-Port, Pegeleinstellung
in 1-dB-Schritten per LM1972, Ausgangspegel asym./sym. mit
entsprechender Korrektur

LCD mit 2x16 Zeichen, 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
*)

{$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 = mega168, VCC = 5;

Define_Fuses
Override_Fuses; // optional, always replaces fuses in ISPE
LockBits0 = [];
FuseBits1 = [BODLEVEL0,BODLEVEL1];
ProgMode = SPI;

Import SysTick, RTClock, TWImaster, LCDmultiPort, ADCport, IncrPort4, SerPort;

From RTclock Import RTCtimer;
From System Import LongInt;

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      = PinC, 1, 16; // pin-reg used, channels, 16 or 32bit integer
  IncrScan4      = Timer1, 4; // timer used, scan rate 1kHz (1..100)

  SerPort        = 9600, Stop2;    {Baud, StopBits|Parity}
  RxBuffer       = 16, iData;
  TxBuffer       = 16, iData;

  SysTick        = 10;              //msec
  StackSize      = $0080, iData;
  FrameSize      = $0100, iData;

  ADCchans = [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_frvari, m_fr1, m_fr2, m_fr3, m_fr4, m_fr5, m_fr6, m_frdec, m_frequ, m_mv);

{--------------------------------------------------------------}
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;

//OutputMode Normal,
  c_nrmo                     = 0;
  c_symo                     = 1;
  c_pwro                     = 2;
  c_psymo                    = 3;

{ Keyboard-Interface, Taster und LEDs PortBits }

  DDRBinit                   = %00111111;            {PortB dir }
  PortBinit                  = %11101111;            {PortB, b_STROBE=0 }

  DDRCinit                   = %00001100;            {PortC dir }
  PortCinit                  = %11111111;            {PortC mit IncrEncoder}

  DDRDinit                   = %00000100;            {PortD dir }
  PortDinit                  = %11111100;            {PortD }

  BtnInPortReg               = @PinD;   {Taster-Eingangsport}
  LEDOutPort                 = @PortD;   {LED-Port}
  DDSOutPort                 = @PortB;   {DDS-Port}

  b_SCLK                     = 0; //Takt für alle
  b_SDATA                    = 1; //Daten für alle
  b_FSYNC                    = 2; //DSS-Frame
  b_LOADPOT                  = 3; //LM1972 LOADS
  b_STROBE                   = 4; //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;

{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);
{Dezimalstelle gerundet, nur die ersten 10 benutzt}
  fdhz           : Table[0..15] of LongInt = (0, 6, 13, 19, 26, 32, 38, 45, 51, 58, 0, 6, 13, 19, 26, 32);

  dBfac          : Table[0..31] of Word = (
{$I dB775mV.txt}
  );

  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, 9,13, 1,3,5,7,9,11,13,15, 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,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 );

//Lineal     .----.----.----.----.----.----.----.----.----
  Vers1Str                   = 'Audio-DDS V#2.10';    //Vers1
  Vers2Str                   = 'C. Meyer 11/05';    //Vers2

  HzStr                      = ' Hz';
  dBStr                      = ' dB';
  SinStr                     = 'Sin';
  SquStr                     = 'Sqr';
  TriStr                     = 'Tri';

  NrmStr                     = 'Nrm';
  SymStr                     = 'Sym';
  PwrStr                     = 'Pwr';
  SymPwrStr                  = 'PSy';

  EffStr                     = 'RMS';
  VssStr                     = 'P-P';

  PlayStr                    = '  Time ';

  CopyOKStr                  = 'Copied to Memory';
  MemoryStr                  = 'Memory      P<>M';
  OverwriteStr               = 'Overwrite?  N<>Y';
  AudioStr                   = 'Audio  ';
  PresetStr                  = 'Patch  ';
  TerzStr                    = 'TerzFr ';
  FrequDispStr               = 'Frequency Hz    ';
  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                    = 'Note  ';
  EmptyStr                   = 'off';
  SCPIFreqStr                = 'FREQ';
  SCPISweepStr               = 'SWEEP';
//  AdjustStr                  = 'Adjust ';     //MemStr

  high                       = true;
  low                        = false;


//Preset: Terzfrequenzen
//20, 25, 31,5, 40, 50, 63, 80, 100, 125, 160, 250, 315, 400, 500,
//630, 800, 1k, 1,25k, 1,6k, 2k, 2,5k, 3,15k, 4k, 5k, 6,3k, 8k, 10k, 12.5k, 16k, 20k
  TerzArray      : Table[0..127] of LongInt =
  (
  {$I presets-tl128.txt}
  );

//Default-EEPROM-Werte:
{$EEPROM}
structconst

  EEPatchArray   : array[0..142] of Byte =
  (
  $10,$27,0,0, 10, 0, 8, 0,  //Frequ 1000 Hz (LongInt), MP3sec, MP3min, dBkorr, Out
  0, 10, 0, 1, 70,     //SweepOct SweepTime Brst Wav dB
  200, 0, 0, 0, 30, 0, 8, 0,     //Frequ 10 Hz Sweep 1000x
  9, 50, 0, 1, 70,
  $10, $27, 0, 0, 10, 0, 8, 0,    //White Noise @0dB Abgleichpunkt
  0, 0, 0, 4, 70,
  $10, $27, 0, 0, 30, 2, 14, 0,    //Groovin
  0, 0, 0, 5, 70,
  $10, $27, 0, 0, 20, 3, 14, 0,
  0, 0, 0, 6, 70,
  $10, $27, 0, 0, 30, 3, 14, 0,
  0, 0, 0, 7, 70,
  $10, $27, 0, 0, 30, 8, 14, 0,
  0, 0, 0, 8, 70,
  $10, $27, 0, 0, 30, 3, 14, 0,
  0, 0, 0, 9, 70,
  $10, $27, 0, 0, 30, 3, 14, 0,
  0, 0, 0, 10, 70,
  $10, $27, 0, 0, 30, 3, 12, 0,
  0, 0, 0, 11, 70,
  $10, $27, 0, 0, 30, 7, 14, 0,
  0, 0, 0, 12, 70
  );


  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       ');

  EEdBadj        : Table[0..63] of byte = (
{$I dBadj.txt}
  );
  EEPwrGain :byte = 20;   // 20 dB PowerAmp-Nachverstärkung
{--------------------------------------------------------------}

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_LEDaux[LEDOutPort, 2]    : bit; {Bit 2 ist Memory-LED, separat}
  b_LEDpwr[@PortC, 2]  : bit; {Bit 2 ist "Power-Amp" LED, separat}
  b_LEDsym[@PortC, 3]  : bit; {Bit 3 ist "Symmetry"-LED, separat}

  

  b_SquareSw[@SwitchState, 0]: bit;
  b_AttnSw[@SwitchState, 1]  : bit;
  b_SymSw[@SwitchState, 2]   : bit;
  b_ExtOnMP3Power[@SwitchState, 3]: bit;
  b_PwrSw[@SwitchState, 4]   : bit;
  IncrValueRel2,            // doppelte Rast-AktuelleFrequenz Inkrementalgeber
  IncrValueRel,             // Vergleichswert für Incremental-Encoder
  EditValue,
  ItemUpperLimit, ItemLowerLimit      : Integer;
  SweepStarted,
  MemoryOn,                 // Abruf der Memory-Werte, Status Mem-Taste
  IncrVerdreht   : Boolean;
  EditWrap:boolean; // Überlauf pro Stelle zulässig?
  DDSfrequ,                       //Frequenzwert für DSS
  AktuelleFrequenz, FrequFak: LongInt;    //Absolutfrequenz real, Skalierungsfaktor
  EditWhich, LastMenu: TEditWhich;
  x, y, {x,y Koordinaten LCD}
  GainOffset,                    //0 oder 20 für NormOut/SymOut oder PowerOut/PowerSym
  BtnTime, BtnTime2,
  SweepCount,
  BurstCount     : Byte;
  BurstPrime, Modified, aBool, MP3isOn: boolean;
  MP3currenttrack: Byte;

  MP3elapsed,MP3SecondChanged : Boolean;      // Repeat-Punkt B gesetzt?
  AttnAdjIndex : byte; // Index für Ajust-Tabellenzugriff, gesetzt durch SetLevelDDS
  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;

  AttnStep[@EditArray+12]     : byte; //0...7= -60 ... +10dB

  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];
  BurstTimer,MP3DispTimer     : SysTimer8;


const
//Anpassen bei Änderungen der Reihenfolge: Letzter - Erster Eintrag
  EditArrayLen               = 1 + @AttnStep - @EditArray;

{###########################################################################}

Procedure RTCtickSecond; {CallBack procedure}
begin
  MP3SecondChanged:=true;
end;

Procedure RTCtimer; {CallBack procedure}
begin
  MP3elapsed:= true;
end;

{$NOSAVE}

{###########################################################################}

{$IFNDEF MenuTest}
{$I MP3source-HW.pas}
{$ELSE}
procedure SetupDSS;  //Freqenz und Pegel einstellen
begin
end;
procedure SetLevelDDS;  //Sende alle Daten
begin
end;
procedure SendDDS;  //Sende ein Wort an den DDS-Chip
begin
end;
procedure SendSR;  //Sende MP3- und SwitchState an den 4094 oder Ausgangsport
begin
end;
procedure miniDDS;
begin
end;
{$ENDIF}

{###########################################################################}
//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
  LCDout_M(#0);
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);
       |
    4..99:
//Audio-File-Nummer -3
//bei externer Quelle wurde DDS abgeschaltet
         LCDout_M('A'); // A wie Audio
         WriteItem(Wave - 3); // 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:
    write(lcdout_m, VssStr);
    LCD1space;
            |
  c_sinw, c_triw:
    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 : 8:'0'); // mit führenden Nullen
  LCD1x0y0;
  WriteLCD(@FrequDispStr);
  LCD1x0y1;
  LCDout_M(#35); // "#"
  LCD1space;
  for i:= 2 to 8 do
     LCDout_M(FreqStr[i]);
    if i = 7 then
      LCDout_M(#46); //Dezimalpunkt nach 7. Stelle
    else
      LCD1space;
    endif;
  endfor;
  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]);
  WriteLCD(@HzStr);
end;

procedure ShowLevelmV;
//m_db-Wert in AttnStep in mV umrechnen (nach mV-Tabelle) und darstellen
var blank: boolean;
  dBtab,komma          : byte;
  Prefix         : char;
{mV aus m_db ausrechnen}
begin
  y:=1;
  x:=4;
  LCD1xy;
  dBtab := AttnStep + GainOffset + 10; // für AD9833-DDS mit LM1972
  case wave of
    c_squw  :
       dBTab:=dBTab+9;
            |
    c_triw  :
       dBTab:=dBTab-2;
            |
  endcase;

  i:= dBtab mod 20;
  komma:= ((dBtab + 10) div 20) + 2;  //Komma-Position ermitteln
  Prefix:= 'm';   // für mV
  LevelStr:= inttostr(gettable(dBfac, i) : 6);

  //hier: 10...110 = -70 ... +30dB
  //eigener Regelbereich 0...80 = -70 ... +10dB
  case dBtab of
    10..29  :      //-70..-51
              komma:= komma + 3;
              insert('.', LevelStr, komma);
              Prefix:= #228;   // für uV
            |
    30..89  :     //-50..+9
              insert('.', LevelStr, komma);
            |
    90..109 :     //+10..+29
              insert('.', LevelStr, 4);
              Prefix:= #32;   //kein Prefix
            |
    110..119:     //+30
              insert('.', LevelStr, 5);
              Prefix:= #32;   //kein Prefix
            |
  endcase;
  LCD1space;
  LCD1space;
  write(lcdout_m, LevelStr);
  LCD1space;
  LCDout_M(Prefix);
  LCDout_M(#86);
end;

procedure WriteLeveldB;
//m_db-Wert AttnStep ausschreiben
var dBval: Integer;
begin
  //0...100 = -70 ... +30dB
  //eigener Regelbereich 0...80 = -70 ... +10dB
  dBval:= integer(AttnStep) - 70 + integer(GainOffset);
  case wave of
    c_squw  :
              dBVal:=dBVal+3;
            |
    c_triw  :
              dBVal:=dBVal-2;
            |
  endcase;
  write(lcdout_m, IntToStr(dBval : 4));
  WriteLCD(@dBStr);
end;

procedure ShowMP3BarLevel;
//Bargraph-Anzeige für Eingangspegel vom MP3-Player
var aWord:Word;
begin
  LCD1x0y1;
  aWord:=getadc;
  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 BurstGen;
//Burst-Generierung per Software, während auf Tastatureingabe gewartet wird
begin
  if BurstMode <> 0 then
    if isSysTimerZero(BurstTimer) then
      setSystimer(BurstTimer, 1);
      dec(BurstCount);
      if BurstCount = 0 then
        dss_cmd:= c_ddsresetcmd;
        SendDDS;
        BurstCount:= BurstMode + 1;
        BurstPrime:= true;
      endif;
    endif;
    if BurstCount = 1 then
      if BurstPrime then
        dss_cmd:= wave_cmd;
        SendDDS;
        BurstPrime:= false;
      endif;
    endif;
  endif;
end;

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 SerOutInputVolt;
begin
  write(serout, SCPIVoltStr);
  write(serout, inttostr(getadc));
  serout(#10);
end;
}

procedure SetSweepUpper;
// obere Frequenz-Grenze für Sweep setzen
begin
  SweepUpper:=longint(gettable(SweepFak,SweepRange));
  SweepUpper:=SweepUpper*Frequenz;
  SweepStarted:=false;
end;

procedure SweepGen;
var
  aword: Word;
//Sweep-Generierung per Software, Ausgabe aktueller Frequenz seriell
begin
  if SweepRange <> 0 then
    dec(SweepCount);
    if not SweepStarted then
      SetLevelDDS(wave);
      SweepCount:=0;
      write(serout, SCPISweepStr);
      serout(#58);
      SerOutFreq(Frequenz);
      serout(#58);
      SerOutFreq(SweepUpper);
      serout(#10);
      SweepStarted:=true;
    endif;
    AktuelleFrequenz:=(AktuelleFrequenz * 101) div 100;
{
    if SweepCount <= 0 then
      SweepCount:=3; // Alle 4 neuen Frequenzen eine Ausgabe
      write(serout, SCPIFreqStr);
      SerOutFreq(AktuelleFrequenz);
      serout(#10);
    endif;
}
    SetLevelDDS(wave);
    mdelay(word(SweepTime));
    if AktuelleFrequenz>=SweepUpper then
      SweepStarted:=false;
      AktuelleFrequenz:=Frequenz;
    endif;
  else
    AktuelleFrequenz:=Frequenz;
  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;
  WriteDigit(BurstMode);
  LCDout_M(#48);   //Null
  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:=7;
  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 > 3 then // Audio-File gewünscht
    MP3Track:= Wave - 3;
    SetLevelDDS(c_off);
    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(wave); // Falls durch Burst abgeschaltet
end;

procedure PatchChange;
//m_patch/Preset-Nummer und Pegel ausschreiben
begin
  LCD1x0y0;
  Frequenz:= gettable(TerzArray, PresetNum);
  AktuelleFrequenz:=Frequenz;
  case presetNum of
  0:
      SetLevelDDS(wave);
      write(lcdout_m, PresetStr);
      WriteItem(PresetNum);
      |
  1..31:
      SetLevelDDS(wave);
      write(lcdout_m, TerzStr);
      WriteItem(PresetNum);
      |
  32..127:
      SetLevelDDS(wave);
      write(lcdout_m, NoteStr);
      m:= ((PresetNum - 32) mod 12) shl 1;
      LCDout_M(TonlStr[m + 1]);
      LCDout_M(TonlStr[m + 2]);
      m:= ((PresetNum - 32) div 12);
      LCDout_M(char(m + 49)); // C1 = 32 Hz
      |
  endcase;
//  LCDout_M(#32);

  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 serprompt(ok:boolean);
begin
  if ok then
   Serout(#$3D);  //"="
  else
    Serout(#$3F);   //"?"
  endif;
  Serout(#$3E);   //">"
  Serout(#$0D);
  Serout(#$0A);
end;

procedure parseCmd;
var InpStr,CmdStr,ParamStr:string[15]; ParamDez:char;
begin
  readln(SerInp, InpStr);
//  InpStr:=strClean(InpStr,true,#0);
  InpStr:=uppercase(InpStr);
  i:=pos(' ',InpStr);
  cmdStr:=copy(InpStr,1,i-1);
//  cmdStr:=copy(cmdStr,0,5); // auf 5 Zeichen verkürzen
  ParamStr:=copy(InpStr,i+1,15);
  i:=pos('.',ParamStr);
  ParamDez:=#48;
  if i > 1 then
    ParamStr:=copy(ParamStr,1,i-1);
    ParamDez:=ParamStr[i+1];  // 1. Nachkommastelle
  endif;
  if cmdStr = SCPIFreqStr then
    AktuelleFrequenz:= StrtoInt(ParamStr) * 10 + longInt(byte(ParamDez)-48);
    setlevelDDS(wave);
    SerOutFreq(AktuelleFrequenz);
    serprompt(true);
  else
    serprompt(false);
  endif;
end;

{ $I adjustLM1972.pas}

{###########################################################################}


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}

  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}
  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

  PwrGain:=EEPwrGain;

  MemoryOn:= false;
  LCDclr_M(LCD_m1);
  Modified:= false;
  MP3secondchanged :=false;
  EnableInts;
  MP3off;
  BurstCount:= 1;
  setSystimer(BurstTimer,1);
  setSystimer(MP3DispTimer,1);
  IncrCount4start;
  ClearIncrAll4;
  SetLevelDDS(wave);
  IncrValueRel:= GetIncrRel4(0);// div 2; // pro Raste zwei Zählerpunkte
end;

{###########################################################################}

begin
  Initall;
  if Navibutton then
//    adjustloop;
  endif;
  EditWhich:= m_patch;
  PatchChange;
//Hauptschleife, wird bei jedem Druck eines Aux- oder Navi-Buttons durchlaufen
  loop
    LCDcursor_m(LCD_m1, false, false);
    SetSweepUpper;
    SetLevelDDS(wave);
    MP3Setup;
    ItemLowerLimit:= 0;
    ItemUpperLimit:= 9;
    EditWrap:=false;
    if MemoryOn then
      EditValue:= integer(MemoryNum);
      EditWhich:= m_patch;
      MemoryChange;
    else
      case EditWhich of
        m_patch        :
                         BtnTime:= 40; //kurz stehenbleiben bei Autorepeat
                         EditValue:= integer(PresetNum);
                         ItemUpperLimit:= 127;
                         PatchChange;
                         setSystimer(MP3DispTimer,0);
                       |
        m_dB           :
                         ItemUpperLimit:= 80;   // 80dB eigener Regelbereich
                         EditValue:= integer(AttnStep);
                         PatchChange;
                         setSystimer(MP3DispTimer,100);  // für Level-Anzeige
                       |
        m_frvari..m_frdec :
                         EditWrap:=true;
                         ShowFrequSpaced;
                       |
        m_wav          :
                         EditValue:= integer(Wave);
                         ItemUpperLimit:= 99;
                         ShowWaveOptions;
                       |
        m_sweep          :
                         EditValue:= integer(SweepTime);
                         EditWrap:=true;
                         ItemUpperLimit:= 99;
                         ShowWaveOptions;
                       |
        m_swoct          :
                         EditValue:= integer(SweepRange);
                         EditWrap:=true;
                         ItemUpperLimit:= 14;
                         ShowWaveOptions;
                       |
        m_brst         :
                         EditValue:= integer(BurstMode);
                         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;
//Up-Down-Unterschleife
    ClearIncrAll4;
    IncrValueRel2:=0;
    IncrValueRel:=0;
    repeat
      IncrValueRel2:=IncrValueRel2+GetIncrRel4(0);   // pro Raste zwei Zählerpunkte!
// Schleife wird pro Raste im Leerlauf zweimal durchlaufen, deshalb dieser Umstand
      if IncrValueRel2 < -1 then
        IncrValueRel:=IncrValueRel2 shra 1;
        IncrValueRel2:=0;
      endif;
      if IncrValueRel2 > 1 then
        IncrValueRel:=IncrValueRel2 shra 1;
        IncrValueRel2:=0;
      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:
        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(MP3DispTimer,200);
          CheckItemLimits;
          MemoryNum:= byte(EditValue);
          MemoryChange;
        else
          Modified:= true;
          case EditWhich of
            m_patch        :
                             CheckItemLimits;
                             PresetNum:= byte(EditValue);
                             PatchChange;
                             Modified:= false;    //hier wird kein Wert verändert
                             AktuelleFrequenz:= Frequenz;
                           |
            m_db           :
                             CheckItemLimits;
                             AttnStep:= byte(EditValue);
                             PatchChange;
                             setSystimer(MP3DispTimer,100);  // für Level-Anzeige
                           |
            m_frvari          :
                             if IncrValueRel > 0 then
                               FrequFak:= longint(IncrValueRel) * 5 + 96;
                               Frequenz:=(Frequenz * FrequFak) div 100 +1;
                             else
                               FrequFak:= abs(longint(IncrValueRel)) * 5 + 96;
                               Frequenz:=(Frequenz * 100) div FrequFak;
                             endif;
                             AktuelleFrequenz:= Frequenz;
                             SetLevelDDS(wave);
                             ShowFrequSpaced;
                             mdelay(10);
                           |

            m_fr1..m_frdec:  // Darstellung in 1/10 Hz-Stufen
                             FrequFak:=1;
                             //Stellen-Multiplikator errechnen
                             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(wave);
                             ShowFrequSpaced;
                           |
            m_wav          :
                             CheckItemLimits;
                             Wave:=byte(EditValue);
                             SetLevelDDS(wave);
                             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(wave);
                             ShowAudioOptions;
                           |
            m_audb:
                             CheckItemLimits;
                             MP3dBKorr:=byte(EditValue);
                             ShowAudioOptions;
                             MP3setvol;
                             setSystimer(MP3DispTimer,100);  // 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(wave); // Falls durch Burst abgeschaltet
        FastRepeatRelease;
        write(serout, SCPIFreqStr);
        SerOutFreq(Frequenz);
        SerOut(#10);
      endif;

      SweepGen;
      if serstat then
        parseCmd;
      endif;
      if MP3secondchanged then
        MP3secondchanged :=false;
        MP3checkRepeat;
        
      endif;
      if isSystimerzero(MP3DispTimer) and MP3isOn then
        setSystimer(MP3DispTimer,10);
        if EditWhich=m_Patch then
          ShowWaveMP3trackTime; //kleine Anzeige-Verzögerung
        endif;
        if (EditWhich=m_db) or (EditWhich=m_audb)  then
          ShowMP3BarLevel; //Pegel-Anzeige
        endif;
      endif;
      BurstGen;
    until NaviButton;
    AktuelleFrequenz:=Frequenz;
    setSystimer(MP3DispTimer,100);  // für Level-Anzeige
    if not b_TasteLinks then
      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
      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
        inc(EditWhich);
        if EditWhich > LastMenu then
          EditWhich:= LastMenu;
        endif;
      endif;
    endif;
    if not b_TasteAux then
      if MemoryOn then
        MemoryOn:= false;
        b_LEDaux := high;
        PresetRestore;
      else
        setSystimer(MP3DispTimer,200);
        MemoryOn:= true;
        b_LEDaux := low;
        PresetSave;
        MemoryChange;
      endif;
    endif;
  endloop;
end SoftDDS.

