program SinusGen;
(*
Sinus-Generator mit ATmega48
(c) by c't magazin und Carsten Meyer, cm@ctmagazin.de

CKOUT-Fuse programmieren -- Schaltung benötigt 12,288-MHz-Takt an PB0!
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


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      = 12288000;        {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                   = %01110000;            {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 = 7;  //  7 = 96 kHz, 15 = 48 kHz an PB2/OC0A mit 12,288 MHz
  SampleTimer48 = 15; // 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
  TerzArray : table[0..31] of word =
  (
  10, //0
  12,
  16, // 31,5
  20,
  25,
  32, // 62,5
  40,
  48,
  64, // 125
  80, //9
  100, // 200
  128, // 250
  160,
  192,
  256,  // 500
  320,  // 625 !!
  384,  // 750 !!

  512,  // 1000

  640,  // 1250
  768,  // 1500 !!
  1024, // 2000
  1280, // 2500
  1536, // 3000 !!
  2048, // 4000
  2560, // 5000
  3200, // 6250 !!
  4096, // 8000
  5120, // 10000
  6400, // 12000 !!
  8192, // 16000
  10240, //0
  0);

//SinusWaveHi[@$600]  : table[0..255] of byte
//SinusWaveLow[@$700]  : 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;

  JP1[@PinC, 0]             : bit; //Sense-Pin Karten-Merkmal vorhanden
  JP2[@PinC, 1]             : bit; //Sense-Pin Karten-Merkmal vorhanden
  JP3[@PinC, 2]             : bit; //Sense-Pin Karten-Merkmal vorhanden
  JP4[@PinC, 3]             : bit; //Sense-Pin Karten-Merkmal vorhanden
  SPDIFreset[@PortD, 5]     : bit;
  rxcmd:char;
  frequ, I2Ccmd:Word;
  Stereo, Rate96, MuteRight, MuteLeft:Boolean;
  Phasenversatz, TabellenSeite: Byte;

{###########################################################################}

{$I SinusDDSmega.pas}

procedure I2Cout(Register,Data: byte);
begin
  I2Ccmd := word(Register);
  I2Ccmd := (I2Ccmd shl 8) + word(Data);
  TWIout(16, I2Ccmd);
end;

procedure initSPDIF;
begin
  SysTickEnable;  // benötigt für TWI-Timeout
  I2Cout($04, %00000000); // Clock Source Control STOP
  I2Cout($01, %00000001);  // TCBLD ist Ausgang
  I2Cout($05, %00100101);  // Audio Input format
  I2Cout($12, %00000000);  // Channel Status Buffer Control, One Byte Mode
  if Rate96 then
    I2Cout($04, %00110000);  // Clock Source Control
    I2Cout($04, %01110000);  // Clock Source Control RUN
  else
    I2Cout($04, %01000000);  // Clock Source Control RUN
  endif;
  // Channel-Status-Übersicht siehe Cirrus/Crystal AN22 unter "Consumer Mode"
  //   C-Bit   01234567, Buffer-Start bei $20
  I2Cout($20, %00100000); // Consumer, Copy Permit
  I2Cout($21, %00010001); // Category Solid State, Original
  I2Cout($22, %00000000); // Source/Channel: Don't Care
  I2Cout($23, %01001000); // 48kHz Sampling Rate, Clock Acc.
  I2Cout($24, %11000110); // Auxiliary Bits
  I2Cout($25, %10110110);
  I2Cout($26, %11110100);
  I2Cout($27, %11000110);
  I2Cout($28, %11100100);
  I2Cout($29, %00101110);

  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}
  Rate96:=not JP1;
  Stereo:=not JP2;
  MuteRight:=not JP3;
  MuteLeft:=not JP4;
  frequ:=gettable(TerzArray,17);
  SPDIFreset:=high;
  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;
  LEDactivity:= high;
  Phasenversatz:=0;    // von links nach rechts, 0=0°, $30=90°
  TabellenSeite:=$0A;  // Adresse der Sinus-Tabelle MSBs, LSBs eine weiter
end;


begin
  Initall;
  TCCR0B:= %00000010;  // Prescaler /8
  loop
    DDSH2:=hi(frequ);       // Rechter Kanal MSB
    DDSL2:=lo(frequ);       // Rechter Kanal LSB
    Rate96:=not JP1;
    Stereo:=not JP2;
    MuteRight:=not JP3;
    MuteLeft:=not JP4;

    if Rate96 then
      OCR0A:=SampleTimer96;
      SPSR:=   %00000001;   // SPI x2 Highspeed-Bit
      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;
    rxcmd:=char(UDR1);
    case rxcmd of
      'L','l':
        Rate96:=false;
      |
      'H','h':
        Rate96:=true;
      |
      'M','m':
        Stereo:=false;
      |
      'S','s':
        Stereo:=true;
      |
      else
        i:=byte(rxcmd);
        i:=i and 31;
        frequ:=gettable(TerzArray,i);
        if not Rate96 then
          frequ:=frequ shl 1;   // Frequenz verdoppeln, da halbe Sample-Frequenz
        endif;
    endcase;
  endloop;
end SinusGen.

