manzara@cpsc.ucalgary.ca (Leonard Manzara) (11/25/89)
There have been a number of requests for my source code which
allows you to synthesize a sine wave on the NeXT's DAC.
Enclosed is a copy of the source. Make sure you separate the code into three
different files: sine.c, sine.asm, and ioequ.asm.
Compile sine.c with: cc -ansi -lm -lsys_s -O.
Assemble sine.asm with: asm56000 -a -b -l; ioequ.asm is included
by sine.asm.
Leonard Manzara (manzara@cpsc.UCalgary.CA)
/*********************************************************************************
*
* PROGRAM: sine.c
*
* AUTHOR: Leonard Manzara
*
* THIS PROGRAM GENERATES A SINE WAVE ON THE DSP AND PLAYS IT THROUGH
* THE NeXT DAC. BASED HEAVILY ON dsp_example_3.
* COMPILE THIS WITH: cc sine.c -ansi -lm -lsys_s -O
* THE DSP ASSEMBLY PROGRAM IS: sine.asm
*
*********************************************************************************/
#import <sound/sound.h>
#import <sound/sounddriver.h>
#import <mach.h>
#import <stdlib.h>
#import <stdio.h>
static int read_count;
main()
{
int s_err;
kern_return_t k_err;
port_t dev_port, owner_port, cmd_port, temp_port, stream_port, read_port, reply_port;
SNDSoundStruct *dspStruct;
int reply, read_buf_size, read_width, low_water, high_water, protocol;
/* INITIALIZE DMA STREAMING VARIABLES */
low_water = 48*1024;
high_water = 64*1024;
read_width = 2;
read_buf_size = 512;
/* GET THE DEVICE PORT FOR THE SOUND/DSP DRIVER ON THE LOCAL MACHINE */
k_err = netname_look_up(name_server_port,"","sound",&dev_port);
if (k_err != KERN_SUCCESS) {
mach_error("netname lookup failure ",k_err);
exit(1);
}
/* GET THE OWNER PORT */
k_err = port_allocate(task_self(),&owner_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot allocate owner port ",k_err);
exit(1);
}
/* BECOME OWNER OF DSP RESOURCES */
temp_port = owner_port;
k_err = snddriver_set_dsp_owner_port(dev_port,owner_port,&temp_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot become owner of dsp resources ",k_err);
exit(1);
}
/* GET THE DSP COMMAND PORT */
k_err = snddriver_get_dsp_cmd_port(dev_port,owner_port,&cmd_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot acquire command port ",k_err);
exit(1);
}
/* ACQUIRE OWNERSHIP OF THE SOUND OUTPUT DEVICE */
k_err = snddriver_set_sndout_owner_port(dev_port,owner_port,&temp_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot become owner of sound out device ",k_err);
exit(1);
}
/* SET UP TO STREAM DATA FROM THE DSP TO THE SOUND DEVICE */
protocol = 0;
k_err = snddriver_stream_setup(dev_port,owner_port,SNDDRIVER_STREAM_DSP_TO_SNDOUT_44,
read_buf_size,read_width,low_water,high_water,
&protocol,&read_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot set up stream from DSP ",k_err);
exit(1);
}
/* SET THE DSP PROTOCOL */
k_err = snddriver_dsp_protocol(dev_port,owner_port,protocol);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot set up DSP protocol ",k_err);
exit(1);
}
/* ALLOCATE A PORT FOR REPLIES */
k_err = port_allocate(task_self(),&reply_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot allocate reply port ",k_err);
exit(1);
}
/* PARSE THE .LOD ASSEMBLY FILE AND BOOT THE DSP WITH IT */
s_err = SNDReadDSPfile("sine.lod",&dspStruct,NULL);
if (s_err != SND_ERR_NONE) {
fprintf(stderr,"Cannot parse DSP load image: %s\n",SNDSoundError(s_err));
exit(1);
}
s_err = SNDBootDSP(dev_port,owner_port,dspStruct);
if (s_err != SND_ERR_NONE) {
fprintf(stderr,"Cannot boot dsp: %s\n",SNDSoundError(s_err));
exit(1);
}
/* START STREAM FROM THE DSP */
k_err = snddriver_stream_start_reading(read_port,NULL,
read_count,0,
0,0,0,0,0,0,reply_port);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot enqueue read request ",k_err);
exit(1);
}
/* SEND INTIAL TABLE VALUE TO DSP */
k_err = snddriver_dspcmd_req_condition(cmd_port,SNDDRIVER_ISR_HF3,SNDDRIVER_ISR_HF3,
SNDDRIVER_HIGH_PRIORITY,PORT_NULL);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot request condition ",k_err);
exit(1);
}
printf("Enter table value: ");
scanf("%d",&reply);
getchar();
k_err = snddriver_dsp_write(cmd_port,&reply,1,4,SNDDRIVER_MED_PRIORITY);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot write to DSP ",k_err);
exit(1);
}
k_err = snddriver_dspcmd_req_condition(cmd_port,SNDDRIVER_ISR_HF3,0,
SNDDRIVER_HIGH_PRIORITY,PORT_NULL);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot request condition ",k_err);
exit(1);
}
/* SEND HOST COMMAND TO THE DSP CHIP; VECTOR = P:$0026
CAUSES PROGRAM CONTROL TO JMP TO MAIN, WHERE SOUND SAMPLES ARE SENT OUT.
NOTE THAT THE HOST COMMAND IS THE VECTOR ADDRESS DIVIDED BY 2 */
printf("Push return to start sine wave:");
reply = getchar();
k_err = snddriver_dsp_host_cmd(cmd_port,(u_int)0x13,SNDDRIVER_HIGH_PRIORITY);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot send host command ",k_err);
exit(1);
}
/* PLAY THE SINE TONE UNTIL RETURN PUSHED AGAIN. VECTOR = p:$0028
CAUSES PROGRAM CONTROL TO JMP TO RESET, EFFECTIVELY STOPPING SAMPLE OUT */
printf("Push return to stop sine wave:");
reply = getchar();
k_err = snddriver_dsp_host_cmd(cmd_port,(u_int)0x14,SNDDRIVER_HIGH_PRIORITY);
if (k_err != KERN_SUCCESS) {
mach_error("Cannot send host command ",k_err);
exit(1);
}
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; PROGRAM: sine.asm
;;
;; AUTHOR: Leonard Manzara
;;
;; THIS PROGRAM, WHEN USED IN CONJUNCTION WITH sine.c, PRODUCES A SINE TONE THROUGH
;; THE NeXT'S D/A CONVERTER. ASSEMBLE THIS WITH: asm56000 -a -b -l sine.asm
;; BASED HEAVILY ON dsp_example_3.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
include 'ioequ.asm'
;; MEMORY STORAGE
DMA_DONE equ 0
x_DMA_flags equ $00ff
x_OutputValue equ $00fe
y_DMA_size equ $00ff
y_Buffer_count equ $00fe
XRAMLO equ 8192
YTABLERAM equ 8192
TABLE_LENGTH equ 1
DMA_R_REQ equ $050001 ; message to host to request DMA transfer
;; INTERRUPT VECTORS
org p:$0
jmp <reset ; reset interrupt
org p:$0024 ; set flag when dma transfer done
bset #DMA_DONE,x:x_DMA_flags
org p:$0026 ; host command to start sine wave send
jmp <main
org p:$0028 ; host command to stop sine wave send
jmp <reset
;; RESET SUBROUTINE
org p:$40
reset
movec #6,omr ; chip set to mode 2; ROM enabled
bset #0,x:m_pbc ; configure port B so it acts as host interface
bset #3,x:m_pcddr ; program pin 3 (pc3) of port C to be output
bclr #3,x:m_pcd ; zero to enable the external ram
movep #>$000000,x:m_bcr ; set 0 wait states for all external RAM
movep #>$00b400,x:m_ipr ; set interrupt priority register to SSI=0, SCI=1, HOST=0
move #>$0200,a
move a,y:y_DMA_size ; set buffersize to 512
move #>XRAMLO,r7 ; set pointer to beginning of DMA buffer
clr a
move a,x:x_DMA_flags ; clear DMA flags
move a,y:y_Buffer_count ; set Buffer count to 0
jsr read_table ; read initial table value(s)
move y:YTABLERAM,n2 ; intialize n2, for pitch
nop ; waste a cycle to let n2 set
bset #m_hcie,x:m_hcr ; enable host command interrupts
move #0,sr ; unmask interrupts
_loop jmp _loop ; loop until interrupt sends us to main
;; MAIN ROUTINE
main
move #>$0100,r2 ; load in origin of sine table into r2
move #>$0100-1,m2 ; make it mod 256
nop ; waste one cycle to allow r2 and m2 to be initialized
_toploop
move y:(r2)+n2,a ; read value from sine table
rep #8
asr a ; shift the value right to avoid clipping
jsr putHost ; since stereo put it into
jsr putHost ; DMA buffer twice
jmp _toploop ; loop forever
;; PUTHOST SUBROUTINE
putHost
move a,x:x_OutputValue ; store a, so we can restore it at end of routine
move a,x:(r7)+ ; put output value into DMA register, increment pointer
move y:y_Buffer_count,b
move #>1,a
add a,b
move b,y:y_Buffer_count ; increment buffer count by 1
move y:y_DMA_size,a
cmp b,a
jgt _exit ; if Buffer count < DMA size, exit
; (only send DMA buffer when it is full)
_transfer
jclr #m_htde,x:m_hsr,_transfer ; loop until htde bit HSR is set
movep #DMA_R_REQ,x:m_htx ; send DMA initiate read message to host
_ackBegin
jclr #m_hf1,x:m_hsr,_ackBegin ; loop until host acknowledges (HF1=1)
move #>XRAMLO,r7 ; point to beginning of DMA buffer
move y:y_DMA_size,b ; initialize loop size
do b,_send_loop ; top of DMA buffer send loop
_send jclr #m_htde,x:m_hsr,_send ; loop until htde bit of HSR is set
movep x:(r7)+,x:m_htx ; send buffer element to host
_send_loop
btst #DMA_DONE,x:x_DMA_flags ; if interrupt has set flags, then go to end
jcs _endDMA
jclr #m_htde,x:m_hsr,_send_loop ; else keep sending zeroes until interrupt sets flags
movep #0,x:m_htx
jmp _send_loop
_endDMA
bclr #DMA_DONE,x:x_DMA_flags ; reset flag to 0, we know we are done--be ready to
; set flag on next interrupt
_ackEnd
jset #m_hf1,x:m_hsr,_ackEnd ; loop until host acknowledge has ended (HF1=0)
move #>XRAMLO,r7 ; reset DMA buffer pointer to beginning
clr a ; reset buffer count
move a,y:y_Buffer_count
_exit move x:x_OutputValue,a ; put output value into a, in case it's needed by the
rts ; calling routine
read_table
bset #m_hf3,x:m_hcr ; set HF3 (tells host that DSP ready to receive data)
move #>YTABLERAM,r3 ; reset pointer to beginning of table
do #TABLE_LENGTH,_tableloop ; loop until table filled
_loop jclr #m_hrdf,x:m_hsr,_loop ; loop until hrdf bit of HSR becomes set
movep x:m_hrx,y:(r3)+ ; move data into y memory; increment pointer
_tableloop
bclr #m_hf3,x:m_hcr ; clear HF3 (tells host that DSP finished reading data)
rts;
;************************************************************************
;
; ioequ.asm
;
; This program originally available on the Motorola DSP bulletin board.
; It is provided under a DISCLAMER OF WARRANTY available from
; Motorola DSP Operation, 6501 Wm. Cannon Drive W., Austin, Tx., 78735.
;
; Motorola Standard I/O Equates (lower case).
;
; Last Update 25 Aug 87 Version 1.1 (fixed m_of)
;
;************************************************************************
;
; EQUATES for DSP56000 I/O registers and ports
;
;************************************************************************
ioequlc ident 1,0
;------------------------------------------------------------------------
;
; EQUATES for I/O Port Programming
;
;------------------------------------------------------------------------
; Register Addresses
m_bcr EQU $FFFE ; Port A Bus Control Register
m_pbc EQU $FFE0 ; Port B Control Register
m_pbddr EQU $FFE2 ; Port B Data Direction Register
m_pbd EQU $FFE4 ; Port B Data Register
m_pcc EQU $FFE1 ; Port C Control Register
m_pcddr EQU $FFE3 ; Port C Data Direction Register
m_pcd EQU $FFE5 ; Port C Data Register
;------------------------------------------------------------------------
;
; EQUATES for Host Interface
;
;------------------------------------------------------------------------
; Register Addresses
m_hcr EQU $FFE8 ; Host Control Register
m_hsr EQU $FFE9 ; Host Status Register
m_hrx EQU $FFEB ; Host Receive Data Register
m_htx EQU $FFEB ; Host Transmit Data Register
; Host Control Register Bit Flags
m_hrie EQU 0 ; Host Receive Interrupt Enable
m_htie EQU 1 ; Host Transmit Interrupt Enable
m_hcie EQU 2 ; Host Command Interrupt Enable
m_hf2 EQU 3 ; Host Flag 2
m_hf3 EQU 4 ; Host Flag 3
; Host Status Register Bit Flags
m_hrdf EQU 0 ; Host Receive Data Full
m_htde EQU 1 ; Host Transmit Data Empty
m_hcp EQU 2 ; Host Command Pending
m_hf EQU $18 ; Host Flag Mask
m_hf0 EQU 3 ; Host Flag 0
m_hf1 EQU 4 ; Host Flag 1
m_dma EQU 7 ; DMA Status
;------------------------------------------------------------------------
;
; EQUATES for Serial Communications Interface (SCI)
;
;------------------------------------------------------------------------
; Register Addresses
m_srxl EQU $FFF4 ; SCI Receive Data Register (low)
m_srxm EQU $FFF5 ; SCI Receive Data Register (middle)
m_srxh EQU $FFF6 ; SCI Receive Data Register (high)
m_stxl EQU $FFF4 ; SCI Transmit Data Register (low)
m_stxm EQU $FFF5 ; SCI Transmit Data Register (middle)
m_stxh EQU $FFF6 ; SCI Transmit Data Register (high)
m_stxa EQU $FFF3 ; SCI Transmit Data Address Register
m_scr EQU $FFF0 ; SCI Control Register
m_ssr EQU $FFF1 ; SCI Status Register
m_sccr EQU $FFF2 ; SCI Clock Control Register
; SCI Control Register Bit Flags
m_wds EQU $3 ; Word Select Mask
m_wds0 EQU 0 ; Word Select 0
m_wds1 EQU 1 ; Word Select 1
m_wds2 EQU 2 ; Word Select 2
m_sbk EQU 4 ; Send Break
m_wake EQU 5 ; Wake-up Mode Select
m_rwi EQU 6 ; Receiver Wake-up Enable
m_woms EQU 7 ; Wired-OR Mode Select
m_re EQU 8 ; Receiver Enable
m_te EQU 9 ; Transmitter Enable
m_ilie EQU 10 ; Idle Line Interrupt Enable
m_rie EQU 11 ; Receive Interrupt Enable
m_tie EQU 12 ; Transmit Interrupt Enable
m_tmie EQU 13 ; Timer Interrupt Enable
; SCI Status Register Bit Flags
m_trne EQU 0 ; Transmitter Empty
m_tdre EQU 1 ; Transmit Data Register Empty
m_rdrf EQU 2 ; Receive Data Register Full
m_idle EQU 3 ; Idle Line
m_or EQU 4 ; Overrun Error
m_pe EQU 5 ; Parity Error
m_fe EQU 6 ; Framing Error
m_r8 EQU 7 ; Received Bit 8
; SCI Clock Control Register Bit Flags
m_cd EQU $FFF ; Clock Divider Mask
m_cod EQU 12 ; Clock Out Divider
m_scp EQU 13 ; Clock Prescaler
m_rcm EQU 14 ; Receive Clock Source
m_tcm EQU 15 ; Transmit Clock Source
;------------------------------------------------------------------------
;
; EQUATES for Synchronous Serial Interface (SSI)
;
;------------------------------------------------------------------------
; Register Addresses
m_rx EQU $FFEF ; Serial Receive Data Register
m_tx EQU $FFEF ; Serial Transmit Data Register
m_cra EQU $FFEC ; SSI Control Register A
m_crb EQU $FFED ; SSI Control Register B
m_sr EQU $FFEE ; SSI Status Register
m_tsr EQU $FFEE ; SSI Time Slot Register
; SSI Control Register A Bit Flags
m_pm EQU $FF ; Prescale Modulus Select Mask
m_dc EQU $1F00 ; Frame Rate Divider Control Mask
m_wl EQU $6000 ; Word Length Control Mask
m_wl0 EQU 13 ; Word Length Control 0
m_wl1 EQU 14 ; Word Length Control 1
m_psr EQU 15 ; Prescaler Range
; SSI Control Register B Bit Flags
m_of EQU $3 ; Serial Output Flag Mask
m_of0 EQU 0 ; Serial Output Flag 0
m_of1 EQU 1 ; Serial Output Flag 1
m_scd EQU $1C ; Serial Control Direction Mask
m_scd0 EQU 2 ; Serial Control 0 Direction
m_scd1 EQU 3 ; Serial Control 1 Direction
m_scd2 EQU 4 ; Serial Control 2 Direction
m_sckd EQU 5 ; Clock Source Direction
m_fsl EQU 8 ; Frame Sync Length
m_syn EQU 9 ; Sync/Async Control
m_gck EQU 10 ; Gated Clock Control
m_mod EQU 11 ; Mode Select
m_ste EQU 12 ; SSI Transmit Enable
m_sre EQU 13 ; SSI Receive Enable
m_stie EQU 14 ; SSI Transmit Interrupt Enable
m_srie EQU 15 ; SSI Receive Interrupt Enable
; SSI Status Register Bit Flags
m_if EQU $2 ; Serial Input Flag Mask
m_if0 EQU 0 ; Serial Input Flag 0
m_if1 EQU 1 ; Serial Input Flag 1
m_tfs EQU 2 ; Transmit Frame Sync
m_rfs EQU 3 ; Receive Frame Sync
m_tue EQU 4 ; Transmitter Underrun Error
m_roe EQU 5 ; Receiver Overrun Error
m_tde EQU 6 ; Transmit Data Register Empty
m_rdf EQU 7 ; Receive Data Register Full
;------------------------------------------------------------------------
;
; EQUATES for Exception Processing
;
;------------------------------------------------------------------------
; Register Addresses
m_ipr EQU $FFFF ; Interrupt Priority Register
; Interrupt Priority Register Bit Flags
m_ial EQU $7 ; IRQA Mode Mask
m_ial0 EQU 0 ; IRQA Mode Interrupt Priority Level (low)
m_ial1 EQU 1 ; IRQA Mode Interrupt Priority Level (high)
m_ial2 EQU 2 ; IRQA Mode Trigger Mode
m_ibl EQU $38 ; IRQB Mode Mask
m_ibl0 EQU 3 ; IRQB Mode Interrupt Priority Level (low)
m_ibl1 EQU 4 ; IRQB Mode Interrupt Priority Level (high)
m_ibl2 EQU 5 ; IRQB Mode Trigger Mode
m_hpl EQU $C00 ; Host Interrupt Priority Level Mask
m_hpl0 EQU 10 ; Host Interrupt Priority Level Mask (low)
m_hpl1 EQU 11 ; Host Interrupt Priority Level Mask (high)
m_ssl EQU $3000 ; SSI Interrupt Priority Level Mask
m_ssl0 EQU 12 ; SSI Interrupt Priority Level Mask (low)
m_ssl1 EQU 13 ; SSI Interrupt Priority Level Mask (high)
m_scl EQU $C000 ; SCI Interrupt Priority Level Mask
m_scl0 EQU 14 ; SCI Interrupt Priority Level Mask (low)
m_scl1 EQU 15 ; SCI Interrupt Priority Level Mask (high)