program SinusGen;
(*
24-Bit-Sinus-Generator mit ATmega48 für Audio-D/A und S/PDIF
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

CKOUT-Fuse programmieren -- Schaltung benötigt 24,576-MHz-Takt an PB0!
ATmega48 wird um 20% übertaktet

Benutzte Pins:
19 PB5/SCK für SPI/I2S-Takt
17 PB3/MISO für SPI/I2S-Daten
14 PB0/CLKO für Master-Clock 12,288 MHz CS8406-S/PDIF und CS4334-Wandler
12 PD6/OC0A für LRCLK bzw. FSYNC
13 PD7 für Latch Enable PCM56

Config-Byte seriell empfangen mit 19200 Bd:
Bit 7: 96kHz statt 48kHz
Bit 6: Stereo, rechter Kanal konstant 1 kHz
Bit 5: MuteRight, rechten Kanal abschalten
Bit 4: MuteLeft, linken Kanal abschalten
Bit 3..0: Frequenztabellen-Eintrag, 0 = 1 kHz

23.10.2007 #0.01 aus ADA importiert
25.10.2007 #0.10 erste funktionsfähige Version
26.10.2007 #0.20 96kHz-Modus, Stereo-Betrieb mit zwei unabhängigen DDS-Registern
27.10.2007 #0.15 Terz-Frequenzen einstellbar
28.10.2007 #0.30 Befehlsempfänger über RS-232 USART
29.10.2007 #0.31 Stereo-Modus auch bei 96kHz!

*)

{$W- Warnings}            {Warnings off}
{$TYPEDCONST OFF}

//Defines aktivieren durch Entfernen des 1. Leerzeichens!

Device = mega48, VCC = 5;
Import Systick, TWImaster, serport;
//in FuseBits0 [CKOUT] setzen für Clock-Ausgang auf PD2 Pin6!

Define
  ProcClock      = 24576000;        {Hertz}
  TWIpresc       = TWI_BR400;
  StackSize      = $0010, iData;
  FrameSize      = $0010, iData;
//  RxBuffer       = 7, iData;
  SerPort        = 19200, Stop1;    {Baud, StopBits|Parity}
  SysTick        = 10, Timer2;              //msec


Implementation

{--------------------------------------------------------------}
const

{ PortBits }

  DDRBinit                   = %11111111;            {PortB dir, PB2=SS}
  PortBinit                  = %11000111;            {PortB SCK und MOSI auf 0}

  DDRCinit                   = %00000000;            {PortC dir }
  PortCinit                  = %00111111;            {PortC}

  DDRDinit                   = %11110000;            {PortD dir }
  PortDinit                  = %11111111;            {PortD }

  b_SCLK                     = 0; //Port B, Takt für alle
  b_SDATAOUT                 = 1; //Daten für alle
  b_STRDAC                   = 3; //Strobe-Load für alle DA-Wandler

  //ocr0a=@$56;

  high                       = true;
  low                        = false;

  SampleTimer96 = 15;  //  7 = 96 kHz, 15 = 48 kHz an PB2/OC0A mit 12,288 MHz
  SampleTimer48 = 31; // 11 = 96 kHz, 23 = 48 kHz an PB2/OC0A mit 18,432 MHz

//  DDSL1: 1 => 1,953125 Hz, f = DDSL1*1,953125, DDSL1 = f*256/1000
//  DDSH1: 1 => 500 Hz
  FreqTabelle : table[0..15] of word =
  (
  16,   // 31,25
  32,   // 62,5
  64,   // 125
  96,   // 200
  128,  // 250
  256,  // 500
  384,  // 750 !!
  512,  // 1000
  768,  // 1500 !!
  1024, // 2000
  1536, // 3000 !!
  2048, // 4000
  3072, // 5000
  3840, // 7500
  5120, // 10000
  7680  // 15000
  );
  
//SinusWaveHi[@$A00]  : table[0..255] of byte
{$I SinusWaveBytes.pas}

{--------------------------------------------------------------}

var
{$EEPROM}

{$DATA} {Schnelle Register-Variablen}
  DDSL1, DDSH1, DDSL2, DDSH2  : Byte;
  i  : Byte;

{$IDATA}  {Langsamere SRAM-Variablen}
  n : byte;
  LEDactivity[@PortD, 4]    : bit;
  config: byte;
  OldJumper: Byte;
  MuteLeft[@config, 4]      : bit; // JP1
  MuteRight[@config, 5]     : bit; // JP2
  Stereo[@config, 6]        : bit; // JP3
  Rate96[@config, 7]        : bit; // JP4, Defaults, Konfigurationsregister

  SPDIFreset[@PortD, 5]     : bit;
  rxcmd:byte;
  frequ, I2Ccmd:Word;
  Phasenversatz, TabellenSeite: Byte;

{###########################################################################}

{$I SinusDDSmega.pas}

procedure I2CoutAdr10(Register,Data: byte);
// für CS8406
begin
  I2Ccmd := word(Register);
  I2Ccmd := (I2Ccmd shl 8) + word(Data);
  TWIout($10, I2Ccmd);
end;

procedure I2CoutAdr4C(Register,Data: byte);
// für CS4398
begin
  I2Ccmd := word(Register);
  I2Ccmd := (I2Ccmd shl 8) + word(Data);
  TWIout($4C, I2Ccmd);
end;

procedure initSPDIF;
begin
  SysTickEnable;  // benötigt für TWI-Timeout
  I2CoutAdr10($04, %00000000); // Clock Source Control STOP
  I2CoutAdr10($01, %00000001);  // TCBLD ist Ausgang
  I2CoutAdr10($05, %00100101);  // Audio Input format
  I2CoutAdr10($12, %00000000);  // Channel Status Buffer Control, One Byte Mode
  if Rate96 then
//    I2CoutAdr10($04, %00000000);  // Clock Source Control
    I2CoutAdr10($04, %01000000);  // Clock Source Control RUN
  else
    I2CoutAdr10($04, %01100000);  // Clock Source Control RUN
  endif;
  // Channel-Status-Übersicht siehe Cirrus/Crystal AN22 unter "Consumer Mode"
  //   C-Bit   01234567, Buffer-Start bei $20
  I2CoutAdr10($20, %00100000); // Consumer, Copy Permit
  I2CoutAdr10($21, %00010001); // Category Solid State, Original
  I2CoutAdr10($22, %00000000); // Source/Channel: Don't Care
  I2CoutAdr10($23, %01001000); // 48kHz Sampling Rate, Clock Acc.
  I2CoutAdr10($24, %11000110); // Auxiliary Bits
  I2CoutAdr10($25, %10110110);
  I2CoutAdr10($26, %11110100);
  I2CoutAdr10($27, %11000110);
  I2CoutAdr10($28, %11100100);
  I2CoutAdr10($29, %00101110);

  SysTickDisable;
end;

procedure initDA;
begin
  SysTickEnable;  // benötigt für TWI-Timeout
  I2CoutAdr4C($08,%01000000); // CPEN Control Port Enable
  I2CoutAdr4C($02,%00010000); // I2S Format
  SysTickDisable;
end;

{###########################################################################}


procedure initall;
//nach Reset aufgerufen
begin
  DDRB:=  DDRBinit;            {PortB dir}
  PortB:= PortBinit;           {PortB}
  DDRC:=  DDRCinit;            {PortC dir}
  PortC:= PortCinit;           {PortC}
  DDRD:=  DDRDinit;            {PortD dir}
  PortD:= PortDinit;           {PortD}
  frequ:=gettable(FreqTabelle,7);
  Phasenversatz:=0;    // von links nach rechts, 0=0°, $30=90°
  TabellenSeite:=$0A;  // Adresse der Sinus-Tabelle MSBs, MIDs, LSBs je eine weiter
  i:=PinC shl 4;
  config:=(not i) and $F0;
  if rate96 then
    n:=17; // 9 mal blinken
  else
    n:=7;  // 4 mal blinken
    frequ:=frequ shl 1;   // Frequenz verdoppeln, da halbe Sample-Frequenz
  endif;
  for i:=0 to n do
    LEDactivity:= not LEDactivity;
    mdelay(120);
  endfor;
  SPDIFreset:=high;
  LEDactivity:= low;
  InitDA;
end;


begin
  Initall;
  TCCR0B:= %00000010;  // Prescaler /8
  loop
    DDSH2:=hi(frequ);       // Rechter Kanal MSB
    DDSL2:=lo(frequ);       // Rechter Kanal LSB

    if Rate96 then
      OCR0A:=SampleTimer96;
      SPSR:=   %00000001;   // SPI x2 Highspeed-Bit0
      DDSH1:=2;             // 1 kHz rechts konstant
      DDSL1:=0;
    else
      OCR0A:=SampleTimer48;
      SPSR:=   %00000000;   // kein SPI x2 Highspeed-Bit
      DDSH1:=4;             // 1 kHz rechts konstant
      DDSL1:=0;
    endif;
    initSPDIF;
    if not Stereo then
      DDSH1:=DDSH2;
      DDSL1:=DDSL2;
    endif;
    if MuteRight then
      DDSH1:=0;
      DDSL1:=0;
    endif;
    if MuteLeft then
      DDSH2:=0;
      DDSL2:=0;
    endif;
    DDSstereo;  // wird bei empfangenem Zeichen beendet
    config:=UDR1;
    i:=config and $0F;
    frequ:=gettable(FreqTabelle,i);
    if not Rate96 then
      frequ:=frequ shl 1;   // Frequenz verdoppeln, da halbe Sample-Frequenz
    endif;
  endloop;
end SinusGen.

