;---------------------------------------------------------------
; DSKSG.ASM
; Keith Larson
; TMS320 DSP Applications
; (C) Copyright 1995,1996
; Texas Instruments Incorporated
;
; This is unsupported freeware with no implied warranties or
; liabilities.  See the disclaimer document for details
;----------------------------------------------------------------
; DSKSG.ASM is a precision signal generator application which uses
; a wave table lookup to create multiple precision signals.  The
; table lookup scheme uses both interpolation in time and level to
; achieve precision in frequency and in level
;
; NOTE: Please see DSKDTMF.ASM for generating ultra-precise 'pure'
;       sine waves.  This application will even produce both the
;       sine and cosine (complex conjugate) at the same time.
;
; Sine Generation
; ---------------
; DSKSG.ASM generates a sine wave when the SINE_SG.ASM file has been
; assembled and loaded into the signal lookup table.
;
; The output waveform is produced by assuming that the table length
; of N=128 samples is 2*PI radians.  The desired frequency (radian
; accumulation rate) is then used to determine both the whole step
; step values and also a residual step which is used in a first order
; interpolation (slope and intercept).
;
; NOTE: This technique produces 32 bit floating point frequency accuracy
;       with excellent amplitude precision for signals that fit the
;       interpolation and sampling criteria.  For a sine wave, linear
;       interpolation will result in approximately 2x the bits of precision
;       used in the lookup, or about 14 bits in a 7 bit (128 element) table.
;
; The output amplitude of each signal wave is then controlled by a scale
; factor.
;---------------------------------------------------------------
TA       .set   11     ; AIC Default startup matches host side app
TB       .set   18     ;
RA       .set   11     ;
RB       .set   18     ;
         .include "C3XMMRS.ASM"
;==================================================
; Create a bit-reversed (circular addressable) sinewave lookup table
;==================================================
         .start  "TABLE",0X809800
         .sect   "TABLE"
N        .set    128
;==================================================
          .start  "CODE",0x809C00  ; Start of RAM1
          .sect   "CODE"           ;
;==================================================
_STOP     .set   1
_START    .set   2
;
MSG_BOX   .word  0                 ; 0x809C00
A_REG     .word  (TA<<9)+(RA<<2)+0 ; 0x809C01
B_REG     .word  (TB<<9)+(RB<<2)+2 ; 0x809C02
C_REG     .word  00000011b         ; 0x809C03  +/- 1.5 V
LOAD      .word  1                 ; 0x809C04 set to 1 to reload AIC

          .word  0                 ; 0x809C05  NOISE ON/OFF
WNOISELVL .float 1.0               ; 0x809C06
WNOISESCL .word  -18<<24           ; 0x809C07 <- NOT MODIFIED BY HOST
OFF_LVL   .float 0.0               ; 0x809C08

MAXTBL    .set   16                ; Maximum entries in table
PHASEx
          .loop  MAXTBL
          .float 0                 ; 0x809C09  Phase accumulation rate
          .float 0                 ; 0x809C0A  Current offset into array
          .float 0                 ; 0x809C0B  Amplitude of term
          .endloop
          .float 0.0               ; Terminate table
;--------------------------------
FTBL      .word  PHASEx

First_Add .word  TABLE          ;
Last_Add  .word  TABLE+N        ;
FLAGS     .word  0              ;
NF        .float N              ;
;
S0_gctrl_val  .word  0x0E970300 ; Runtime value XINT/RINT enabled
S0_xctrl_val  .word  0x00000111 ;
S0_rctrl_val  .word  0x00000111 ;
;
PHASE .set  0
OFFS  .set  1
A_LVL .set  2
;-------------------------------
main      ldi   0x34,IE         ;
          idle                  ; Wait for next ADC sample
          ;- - - - - - - - - - -
          ldi   @LOAD,R2        ; If signalled by host, reinitialize AIC
          bz    main            ;
          call  AIC_INIT        ; Restart with new AIC setup

          andn  GIE,ST
          ldi   MAXTBL-1,RC
          ldi   @FTBL,AR0
          ldi   0,R2
          rptb  INITBL
          sti   R2,*+AR0(OFFS)
INITBL    nop   *++AR0(3)

          ldi   0,R2            ; reset the LOAD flag
          sti   R2,@LOAD        ;
          b     main            ; close loop
;=============================================================================
MinS  .float -16000.0
MaxS  .float  16000.0

RACC  .set    0
IACC  .set    1
RSIG  .set    2
ISIG  .set    3
MAGN  .set    4
pi    .set    3.14159265
Rpi   .set    2.0*pi/360.0

SFFTdata .word $+1
      .float  cos(Rpi*40.0)     ; phase accumulation rate
      .float  sin(Rpi*40.0)     ; 10' per step
      .float  1.0               ; vector length is unity at all times
      .float  0.0               ; and rotates at phase accumulation rate
      .float  0.5               ;

      .float  cos(Rpi*41.01)    ;
      .float  sin(Rpi*41.01)    ;
      .float  1.0               ;
      .float  0.0               ;
      .float  0.5               ;

      .float  0.0

FX        .float 0
ADC       push  ST              ; On interrupt, set a software flag to
          push  R0              ; let the CPU know that the RINT has been
          ldi   @S0_rdata,R0    ; received
          ldi   @FLAGS,R0       ;
          or    0x20,R0         ;
          sti   R0,@FLAGS       ;
          pop   R0              ;
          pop   ST              ;
          reti                  ;
          ;- - - - - - - - - - -
DAC       push  ST              ;
          push  R0              ;
          pushf R0              ;
          push  R1              ;
          pushf R1              ;
          push  R2              ;
          pushf R2              ;
          push  R3              ;
          pushf R3              ;
          push  R4              ;
          pushf R4              ;

          push  AR1
          push  IR1             ;
;=======================================================================
          ldf   0,R1            ;
          ldi   @FTBL,AR1       ; Load start of F,A pairs
          stf   R1,@FX          ;
         ;======================; Calculate F,A pairs
NextPair  ldf   *+AR1(OFFS),R1  ; Accumulate phase
          addf  *+AR1(PHASE),R1 ; 2*PI radians of phase offset == N samples
          cmpf  @NF,R1          ; Check for phase offset rollover
          blt   Bypass          ;
          subf  @NF,R1          ;
Bypass    stf   R1,*+AR1(OFFS)  ; Save the new phase offset
          ldf   R1,R2           ; R2>= R1
          fix   R1,R1           ; R1 = offset is now 0-N integer
          float R1,R1           ;
          subf  R1,R2           ; R2 = 0.000->0.999 fraction of step
          fix   R1,R1           ;
          ldi   @First_Add,AR0  ;
          addi  R1,AR0          ;
          ldf   *AR0++,R1       ; Get 1 st data point, point to next
          andn  N,AR0           ; Circular roll for 2^N tables on 2^N boundary
          ;- - - - - - - - - - -
          ldf   R1,R0           ; Linear Interpolation doubles mantissa
          subrf *AR0,R0         ; bit accuracy (7 bits to 14 bits)
          mpyf  R0,R2           ;
          addf  R2,R1           ; accuracy is now improved
          mpyf  *+AR1(A_LVL),R1 ; Magnitude scaling
          ;=====================
          addf  @FX,R1          ; Sum results
          stf   R1,@FX          ; Save sum
          addi  3,AR1           ; point to next pair
          ldf   *+AR1(PHASE),R1 ; is this the end (phase accum=0)?
          bnz   NextPair        ;
;=======================================================================
          ldf   @FX,R1          ; Reload sum of pairs
          addf  @OFF_LVL ,R1    ; Offset addition
          ;---------------------
          ldf   @WNOISELVL,R0   ; If white noise level != 0.0
ADD_NOISE callnz  RANDX         ; Get next 32 bit random
          float R0,R0           ; convert to float
          mpyf  @WNOISESCL,R0   ; scale to +/-1.0
          mpyf  @WNOISELVL,R0   ;
          addf  R0,R1           ;
          ;---------------------
NO_NOISE  mpyf  @SATURATE,R1    ; Using the ALU saturation mode saves 2
          mpyf  @UNSATUR8,R1    ; cycles compared to magnitude comparison
          fix   R1,R1           ; Convert to fixed point
          andn  3,R1            ; Clear MSBs to prevent AIC reprogramming
          sti   R1,@S0_xdata    ; output the DAC value
          ;- - - - - - - - - - -
          pop   IR1             ; Restore register context before return
          pop   AR1
          popf  R4              ;
          pop   R4              ;
          popf  R3              ;
          pop   R3              ;
          popf  R2              ;
          pop   R2              ;
          popf  R1              ;
          pop   R1              ;
          popf  R0              ;
          pop   R0              ;
          pop   ST              ;
          reti                  ; return from interrupt
;----------------------------------------
; Fast 32 bit random number generator
;----------------------------------------
RANDX:  ldi     @SEED,R0        ; Call here for last SEED
RAND:   mpyi    @A,R0           ; Calculate RAND(R0)
        addi    @C,R0           ;
        sti     R0,@SEED        ; Result is returned in R0
        rets                    ;
        ;----------------------
A       .word   0107465h        ; Constants needed for RAND
C       .word   0234567h        ;
SEED    .word   0               ;
;- - - - - - - - - - - - - - - -
                                ; By using the saturation feature of the
                                ; TMS320C3x ALU during multiplies, several
                                ; cycles can be shaved off of a compare/load
                                ; clipping operation
                                ;
SATURATE  .word   113<<24       ; Since max AIC magnitude is 2^15, multiply
UNSATUR8  .word   -113<<24      ; by 2^113 which results in 2^128.  The
                                ; multiplier constant is created by directly
                                ; placing the value of 113 into the exponent
                                ; exponent field of a 32 bit hex number.
;--------------------------------
GIE      .set 0x2000

myidle   ; idle
         ; rets

          andn  GIE,ST          ; Use XINT polling during setup to
          tstb  0x10,IF         ; avoid contaminating the DXR during
          bz    $-1             ; programming
          andn  0x10,IF         ;
          ;idle                 ;
          rets                  ;

prog_AIC  push  R1              ;
          push  IE              ;
          ldi   0x10,IE         ;
          andn  0x30,IF         ;
          ldi   @S0_xdata,R1    ; Use original DXR data during 2 ndy
          sti   R1,@S0_xdata    ;
        call myidle
          ldi   @S0_xdata,R1    ; Use original DXR data during 2 ndy
          or    3,R1            ; Request 2 ndy XMIT
          sti   R1,@S0_xdata    ;
        call myidle                  ;
          sti   R0,@S0_xdata    ; Send register value
        call myidle                  ;
     ;    andn  3,R1            ;
          ldi   0,R1
          sti   R1,@S0_xdata    ; Leave with original safe value in DXR
          pop   IE              ;
          pop   R1              ;
          rets                  ;
;======================================================;
; This section of code is called by the initialization ;
; code as well as by the main program loop.  It is     ;
; therfor assembled into the regular program RAM       ;
;======================================================;
AIC_INIT  push  R0              ;
          LDI   0x10,IE         ; Enable XINT interrupt
          andn  0x34,IF         ;
AIC_reset
          LDI   2,IOF           ; XF0=0 resets AIC
          ldi   0,R0            ;
          rpts  0x40            ;
          sti   R0,@S0_xdata    ;

          LDI   6,IOF           ; XF0=1 runs AIC
          ldi   0,R0            ;
          rpts  0x40            ;
          sti   R0,@S0_xdata    ;
          ldi   @S0_rdata,R0    ;
          ;-----------------------------
          ldi   @C_REG,R0       ; Setup control register
          call  prog_AIC        ;
          ldi   0xfffc  ,R0     ; Program the AIC to be real slow
          call  prog_AIC        ;
          ldi   0xfffc|2,R0     ;
          call  prog_AIC        ;
          ldi   @B_REG,R0       ; Bump up the Fs to final rate
          call  prog_AIC        ; (smallest divisor should be last)
          ldi   @A_REG,R0       ;
          call  prog_AIC        ;
          pop   R0              ;
          ldi   0,R0            ; Put a safe 0 in DXR
          sti   R0,@S0_xdata    ;
          ldi   @S0_rdata,R0    ; Clear receive underrun
          rets                  ;
;*****************************************************;
; Startup stub...                                     ;
;                                                     ;
; The following section of code is used only once for ;
; initialization and can be safely overwritten by     ;
; assembling it into the stack or volatile data       ;
; storage.                                            ;
;*****************************************************;
          .entry   ST_STUB

ST_STUB   ldp   T0_ctrl         ; Use kernel data page and stack
          ldi   @stack,SP
          ldi   0,R0            ; Halt TIM0 & TIM1
          sti   R0,@T0_ctrl     ;
          sti   R0,@T1_ctrl     ;
          sti   R0,@T0_count    ; Set counts to 0
          sti   R0,@T1_count    ;
          ldi   1,R0            ; Set periods to 1
          sti   R0,@T0_prd      ;
          sti   R0,@T1_prd      ;
          ldi   0x2C1,R0        ; Restart both timers
          sti   R0,@T0_ctrl     ;
          sti   R0,@T1_ctrl     ;
          ;---------------------
          ldi   @S0_xctrl_val,R0;
          sti   R0,@S0_xctrl    ; transmit control
          ldi   @S0_rctrl_val,R0;
          sti   R0,@S0_rctrl    ; receive control
          ldi   0,R0            ;
          sti   R0,@S0_xdata    ; DXR data value
          ldi   @S0_gctrl_val,R0; Setup serial port
          sti   R0,@S0_gctrl    ; global control
          ;---------------------
          andn  0x30,IF
          ldi   0x30,IE         ; Service both RINT/XINT

   ;      call  AIC_INIT        ; Initialize the AIC

          ldi   @S0_rdata,R0    ;
          b     main            ;
stack    .word  $+1
;****************************************************;
; Install the XINT/RINT ISR handler directly into    ;
; the vector RAM location it will be used in         ;
;****************************************************;
          .start   "SP0VECTS",0x809FC5
          .sect    "SP0VECTS"
          B     DAC           ; XINT0
          B     ADC           ; RINT0
