Programming The Sound Blaster 16
Example 1


	    Mission : Obtain Environmental Settings
		      Reset the DSP
		      Read the DSP Version Numbers 
		      Display Everything!

		       Download the Expansion Pack!
                  




Intro


This is the beginning of every interaction with the Sound Blaster 16. In order to communicate with it we must read the environmental variable to find out where it is located. We will then reset it, read its version, and then print out all that has been read. Sounds pretty easy huh? That's because it IS! We'll follow the order above because in order to communicate with the DSP we have to know its port number, and in order to get that we have to read the BLASTER environmental variable, so here we go!

The Code



SB16::SB16()
{ DSPVersionNum=0;
  GetBlasterID();
  if(DSP_Reset())
   { cout<<"\n\nSound Blaster Initialization Successful!\n";
   }
  else
   { cout<<"\n\nSound Initialization FAILED!\n";
   }
  DisplayEnv();
}
SB16::~SB16()
{
}

Here is our main Constructor and Destructor. As it stands they don't do a heck of a lot, but this will really change as we develop our SB16 class! We will be needing DSPVersionNum to hold the DSP version so lets make sure that it's initialized to be complete. From there we call the GetBlasterID() function which, as its name implies gets the Sound Blaster information and sets some variables so the rest of the code will be able to communicate with it. The thing we always have to do 1st whenever we decide to start communicating with the DSP is RESET IT! We don't have to reset it every time we talk to it, but to start off we have to put it into a known state. Chances are that it is just fine, but what if it isn't? Then this wouldn't work properly! The return value from DSP_Reset tells us if we were successful.

void SB16::GetBlasterID()
{ char *blaster;

 if((blaster = getenv("BLASTER")) != 0)
    { while(*blaster)
      { switch(*blaster)
	 { // Sound Blaster IO Address
	   case 'A':{blaster++;BLASTER.IOAddr=Parse(blaster,16);break;}
	   // Sound Blaster Interrupt
	   case 'I':{blaster++;BLASTER.SBIntr=Parse(blaster,10);break;}
	   // DMA Channel
	   case 'D':{blaster++;BLASTER.DMA=Parse(blaster,10);break;}
	   // Card Type
	   case 'T':{blaster++;BLASTER.CardID=Parse(blaster,10);break;}
	   // Mixer Port
	   case 'M':{blaster++;BLASTER.Mixer=Parse(blaster,10);break;}
	   // MIDI Port
	   case 'P':{blaster++;BLASTER.MIDI=Parse(blaster,16);break;}
	   // High DMA Channel
	   case 'H':{blaster++;BLASTER.HDMA=Parse(blaster,10);break;}
         }
      if(*blaster){blaster++;} //if because it could be last item!
   }
 }
 else
  {cout<<"BLASTER environmental variable NOT set!\n";
  }

  // Check to see if stuff is valid
  if(BLASTER.DMA <0 ||BLASTER.DMA>=4)
	{cout<<"Low DMA Channel "<<BLASTER.DMA<<" is Invalid!\n";
	}	       
  if(BLASTER.HDMA<0 ||BLASTER.HDMA>=8)
	{cout<<"High DMA Channel "<<BLASTER.HDMA<<" is Invalid!\n";
	}	    
  SetIO();
}

GetBlasterID actually uses two other functions to complete its task. The majority of the function is within the confines of the while and switch statements. Here's where the trick lies. Both depend on the fact that we are NOT to the end of the string, being the environmental string passed to us by getenv. If the first character can is one inside the switch, basically do the same for each. We first advance the pointer to go past the letter that triggered it, then set the appropriate item in our structure to whatever is being returned by the Parse function. We simply pass the string to the Parse function along with a radix which will automatically turn the hex numbers into the correct form for us. If we wanted all numbers to be displayed in decimal form, we could just pass Parse the string and eliminate that parameter. If the first character of the string does not match one of the cases, then we can assume one of two things. It could either be a space between parameters or we have reached the end of the string. To make sure that we don't screw things up, we place it within an if that will be true only if it is a space, and NOT the end of the string. After each has been read in, it calls SetIO which sets the location of all other used ports using the base port that was read from the BLASTER variable. After calling this all port addresses will be assigned correctly, and the BLASTER structure will be filled in for us to take a look at.

short SB16::Parse(char *string,int radix)
{ short count=0;
  char temp[8],*notused;

 while((*string != ' ') && (*string != '\0'))
  { temp[count++]=*(string++);
  }
  temp[count]='\0';
 return (short)strtol(temp,¬used,radix);
}

void SB16::SetIO(void)
{DSPReset = (BLASTER.IOAddr + 0x0006);
 DSPRead  = (BLASTER.IOAddr + 0x000A);
 DSPWrite = (BLASTER.IOAddr + 0x000C);
 DSPStatus= (BLASTER.IOAddr + 0x000E);
 MixerAddr= (BLASTER.IOAddr + 0x0004);
 MixerData= (BLASTER.IOAddr + 0x0005);
 DSPIntAck= (BLASTER.IOAddr + 0x000F);
}

The Parse function gets passed our string in question, and a radix. As I mentioned before, if we didn't care about hex numbers being stored in decimal form, we could eliminate that parameter and just use 10 for the radix in the strtol function. We first need a temp string to hold the cut contents of the string, notused is being declared just to be passed to the strtol function, and is not used. In the while loop, we make sure that the current character is neither a space nor and EOL. If isn't either one, we can set the current place in our temp string (count) to the current character. Notice that our current character will always be *string since we are advancing the string every loop. Also notice that both count and string are incremented AFTER they are evaluated, making this a pretty compact function. Before calling the strtol function, we manually add and EOL onto our temp string. The reason for this is since this function is being called in succession, there is a pretty good chance that the pointer for temp will be the same spot every time this is called. If that is so, then the prior contents will still be there. This is ok if the new string is longer than the previous, but if not, the old one will show through. So, if one value is 104 and the next one is 6, it will really read 604, NOT 6!! We can either set temp to 0 every call, or just slap on an EOL like we did here. Before printing out the info, lets reset the DSP since we know which port its at (DSPReset).

BOOL SB16::DSP_Reset()
{ outp(DSPReset,0x01); //write 1 to the port
  delay(1);            //wait a milisecond. 
  outp(DSPReset,0x00); //write 0 to the port
  DSPDataAvail();
  for(int p=0;p<1000;p++)
     { if((inp(DSPRead))==DSP_READY)
	   {GetDSPVersion();
	    return 1; //return to caller (SUCCESS)
         }	
     } 
 return 0;//this will happen should 1000 tries fail.
}

void SB16::GetDSPVersion(void)
{ unsigned char VersionMaj,VersionMin;
  char string[7],temp[4],*notused;
  DSPWait();
  outp(DSPWrite,DSPVersion); 	//send instruction
  DSPDataAvail(); 			//wait for dsp to flag u for instruction
  ReadDSP(&VersionMaj);
  ReadDSP(&VersionMin);
  DSPVersionNum=VersionMaj;
  itoa(VersionMaj,string,10);
  strcat(string,".");
  strcat(string,itoa(VersionMin,temp,10));
  DSPVersionNum=(float)strtod(string,¬used);
}

void SB16::WriteDSP(unsigned char value)
{ DSPWait();    
  outportb(DSPWrite,(unsigned char)value);
}

void SB16::ReadDSP(unsigned char *value)
{DSPDataAvail();
 *value=inportb(DSPRead);
}

void SB16::DSPDataAvail()
{while((inp(DSPStatus) & 0x80)==0);
}

void SB16::DSPWait(void)
{while((inp(DSPWrite) & 0x80)!=0);
}

WriteDSP and ReadDSP are the two functions that we have to go through if we want anything to reach the Sound Blaster DSP. When communicating with a port, we have to set up some kind of check system so that we don't have information flying all over the place, and having some of it get forgotten or not received.

To ensure that doesn't happen, the DSPWait function waits until the DSP is ready to receive information before continuing. Likewise the DSPDataAvail function tells us when something is waiting to be read from the DSP. Using these together we know when the DSP is ready to be written to and read from. The only trick left is to actually do the resetting. All we have to do is write a 1 to the reset port, wait a couple of milliseconds (i wait 1) and then write a 0 to the reset port. After that we poll the Read port of the DSP and see if it gives us the ok (DSP_READY)! Now that its been reset we can read its version number and GetDSPVersion gets the job done. The only time we can read the version numbers, and have them be accurate is immediately AFTER we reset the DSP. We send the command to the DSP and then read the DSPRead port two times. The first number is the Major version number, and the second is the Minor. We convert our two numbers into strings and combine them with each other, with a period separating. Then we convert this string into a float to get the correct version!! Now the only thing left is to print out everything!

void SB16::DisplayEnv()
{if(BLASTER.CardID < 7 && BLASTER.CardID >=0)
	{  char CardType[7][34]={
		  "-Creative Sound Blaster Series -",
		  "Sound Blaster",
		  "Sound Blaster Pro",
		  "Sound Blaster 2.0",
		  "Sound Blaster Pro 2.0",
		  "Sound Blaster Pro MCV",
		  "Sound Blaster 16"};

 cout<<"\n\nThe "<<CardType[BLASTER.CardID]<<" is detected\n";
 cout<<"Blaster I/O Port : "<<hex<<BLASTER.IOAddr<<endl;
 cout<<"DSP Version : "<<DSPVersionNum<<endl;
 cout<<"IRQ : "<<BLASTER.SBIntr<<endl;
 cout<<"DMA : "<<BLASTER.DMA<<endl;

 if(BLASTER.HDMA)
   { cout<<"HDMA : "<<BLASTER.HDMA<<endl;
   }
 if(BLASTER.MIDI)
   {cout<<"MIDI : "<<hex<<BLASTER.MIDI<<endl;
   }
 if(BLASTER.Mixer)
   { cout<<"Mixer Addr : "<<hex<<BLASTER.Mixer<<endl;
   }
 }
	
else
 {cout<<"Unknown Card!\n";
 }
}




That's all there is to it! This is the first of several tutorials designed to get you moving in the right direction. I know that you want to rush into the good stuff, but we HAVE to learn the basics before trying to tackle anything more complicated. If you have any comments, questions, rude remarks, need to pass gas whatever...give me some feedback!!