VS1010 Example Programs

Designing hardware and software for systems that use the VS1010 MP3 Audio DSP Microcontroller.
Post Reply
Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

VS1010 Example Programs

Post by Kalle »

In this thread we will present some examples of what VS1010 programs might look like in practice. These aren't necessarily meant to be perfect solutions to any particular problems, but they should give you some ideas on how to approach your own programs.

VS1010 System Utility Programs are documented here.
Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

readVoltage - Reads and prints a voltage value

Post by Kalle »

The following program can be used to read and print the voltage supplied to from the JP2 connector on the VS1010 Developer board. The program uses the 12-Bit Successive Approximation Register AD converter (SAR). Info about SAR can be found in the VS1010 datasheet chapter 9.18.

Code: Select all

#include <vo_stdio.h>
#include <protocol.h>

// Register values for selecting the correct channels. On the VS1010 
// Developer Board, JP2 is connected to AUX2. RCAP is the reference voltage
// channel.
#define SAR_AUX2 (0x5 << 8) 
#define SAR_RCAP (0x8 << 8)

float GetBatteryVoltage();

ioresult main (char *params) {

	float voltage;
	PERIP(ANA_CF1) |= ANA_CF1_SAR_ENA; // Powers and enables the SAR
	voltage = GetBatteryVoltage();
	printf("Voltage: %2.2f\n",voltage);
	
}

s_int16 GetSarValue(register u_int16 channelShifted) {
	// Configure SAR and start the conversion. 
	PERIP(SAR_CF) = SAR_CF_ENA | (10) | channelShifted;
	// Wait until the conversion is complete.
	while (PERIP(SAR_CF) & SAR_CF_ENA);
	// Return the conversion result (0...4095)
	return PERIP(SAR_DAT);
}

float GetBatteryVoltage() {
	u_int16 sar_aux, sar_rcap;
	float volts;

	// Read the AUX channel level using the SAR.		
	sar_aux = GetSarValue(SAR_AUX2);
	// Read also the reference voltage using SAR. Since the reference voltage is
	// known, this protects against inaccuracies caused by fluctuations in the
	// analog supply voltage.
	sar_rcap = GetSarValue(SAR_RCAP);
	
	// Remove the commenting to print the raw SAR values.
	//printf("aux=%4d,  rcap=%4d  ",sar_aux,sar_rcap);
	
	// Convert SAR_AUX conversion result to millivolts using the resistor
	// divide factor and a known 1.662V reference voltage (RCAP).
	// Resistor divide factor (2K vs 20K) * bandgap voltage (1.662 millivolts)
	sar_aux = ((s_int32)sar_aux * ((20/2) * 1662)) / sar_rcap; 
	// Convert integer millivolts to float volts.
	volts = sar_aux * 0.001;	
	
	return volts;
}
The code itself already has a decent amount of commenting, but we can have short summary here as well.

The two definitions don't have much to do with functionality, but help with readability. The register values for the channel can be found in the VS1010 datasheet.

The main function doesn't have anything special going on either, it simply calls the voltage reading function, and then prints its return value.

GetSarValue uses the actual SAR ADC. It takes the channel as parameter, configures the SAR to the correct settings, waits for the result of the conversion, and then returns it to GetBatteryVoltage.

GetBatteryVoltage (somewhat misleading name due to the code being re-purposed from an older program) gets the two results of the SAR conversion, and then calculates the actual end result. The SAR results on their own do not have much value.

The .zip-file includes the necessary files of the VSIDE solution, so that you can easily try and build the program yourself.
Attachments
readVoltage.zip
VS1010 readVoltage program
(6.75 KiB) Downloaded 329 times
Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

voltAlarm - Reads voltage and plays an alarm if a limit is reached

Post by Kalle »

This program reads the voltage supplied to the JP2 connector on the VS1010 Developer Board, and starts playing an alarm if the voltage reaches a specified limit. The limit can be given as millivolts as a parameter with the function call (e.g. "voltAlarm 2500" for 2.5V), and it can be changed with a simple button interface while the program is running.

Note that some values are given in millivolts, and some in volts. The program works fine in its current state, but if you make modifications it's easy to get decimal errors when not paying attention.

This program uses the same SAR ADC functions as readVoltage did, with the exception that they are in a separate source code file, instead of being in main.c.

Code: Select all

#include <vo_stdio.h>
#include <vs1010bRom.h>
#include <vo_gpio.h>
#include <playerinfo.h>
#include <string.h>
#include <protocol.h>
#include <stdlib.h>
#include <lowlevels.h>

// sar.h is the header for sar.c, which includes the same GetBatteryValue
// and GetSarValue functions as readVoltage.
#include "sar.h"

// For the sake of readability, the names for the pin read functions
// are redefinded as the names of the corresponding buttons.
#define S1 !GpioReadPin(0x1c)
#define S3 !GpioReadPin(0x1d)


ioresult main (char *params) {
	
	// If no paremeter is given, this value is used. In mV.
	const u_int16 defaultLimit = 0; 
	
	// Lowest value reachable with the button interface. In V.
	const float minLimit = 0; 
	
	// Highest value reachable with the button interface. In V.
	const float maxLimit = 5; 
	
	// How much each button press changes the limit. In V.
	const float voltageStep = 0.1; 
	
	// With the function parameter it's possible to give a limit that is 
	// outside the range set for the button interface. However, trying to
	// further decrease the voltageLimit while below the minLimit, or increase
	// while above the maxLimit, will set the value to the min or the max, 
	// respectively. Increasing toward the min, or decreasing toward the max
	// will still go by the voltageStep.
	
	// When alarmOn is a non-zero, the alarm is played on each round of the
	// while-loop.
	u_int16 alarmOn = 0;
	
	// When settings is non-zero, the program stays in the button interface
	// loop. Note that the voltage is not read during this period, and no alarm
	// is sounded.
	u_int16 settings = 0;

	float voltageLimit;

	// Get the integer value from the parameter. Override with default if it's
	// zero.
	u_int16 value;
	value = atoi(params);
	if (value == 0) {
		value = defaultLimit;
	}
	
	voltageLimit = value / 1000.0;
	
	printf("Parameter: %d, limit=%fV\n",value,voltageLimit);
	
	// Set Player Callback to point to a function in VS1010B ROM that does not
	// do anything. This will disable default Pause/Next button functionality 
	// of the MP3 player, which would intervene with the button interface of 
	// the program.
	SetJmpiHookFunction((void*)97, (void*)37114);

	// Same as in readVoltage, powers and enables the SAR
	PERIP(ANA_CF1) |= ANA_CF1_SAR_ENA; 

	// The main loop
	while(1) {
		
		float voltage = GetBatteryVoltage();
		printf("Volts: %2.2f\n",voltage);
		
		// If voltage reaches the limit, alarmOn will be set to 1. Because both
		// the supplied voltage and SAR might have some variation, there is a 
		// 2% error margin on turning the alarm back to off-state.
		// (e.g. with 5V limit alarm will only turn off after the voltage drops
		// to 4.9V or below.
		if(voltage >= voltageLimit) {
			alarmOn = 1;
		} else if(voltage <= (voltageLimit - (voltageLimit * 0.025))) {
			alarmOn = 0;
		}
		
		// This will play the alarm sounds exactly once, from the start until 
		// the end. Because of this, the chosen alarm sound should be fairly 
		// short. For this demonstration, hello.mp3 is being used
		// because it was the shortest file on the supplied SD card.
		// If no alarm is sounded, there is a short delay until the next check.
		if(alarmOn) {
			player.currentFile = 0;
			strncpy(player.fileSpec, "S:SYS/H*.MP3", 126);
			PlayerPlayFile();
		} else {
			DelayL(1000000);
		}
		
		// This structure is responsible for the button interface, which is
		// launched by holding S1 and S3 at the same time. 
		// There are multiple while-loops located inside if-statements, to trap
		// the program until the pressed buttons have been released.
		if(S1 && S3) {
			while(S1 | S3){}
			alarmOn = 0;
			settings = 1;
			printf("\nSet voltage limit\n\n");
			printf("Press S1 to decrease and S3 to increase.\n");
			printf("Hold both simultaneously to return to normal functionality.\n\n");
			printf("Current voltage limit is %2.2f\n", voltageLimit);
			
			// The button interface loop will stay on until both buttons are pressed
			// simultaneously again, which sets the settings-variable to 0.
			while(settings) {
				
				if(S1) {
					while(S1) {
						if(S3) { 
							settings = 0;
						}
					}
					
					// Prevents the voltageLimit from going under the minimum limit.
					// The settings-variable is also included in the condition. 
					// Otherwise exiting the button interface could trigger one 
					// extra change to the voltageLimit. 
					if(voltageLimit <= (minLimit + voltageStep) && settings) {
						voltageLimit = minLimit;
						
						printf("Min voltage limit %2.2f reached \n", voltageLimit);
						
					} else if (settings) {
						voltageLimit = (voltageLimit - voltageStep);
						printf("New voltage limit: %2.2f\n", voltageLimit);
					}		
					
				} else if(S3) {
					while(S3) {
						if(S1) {
							settings = 0;
						}
					}
					if(voltageLimit >= (maxLimit - voltageStep) && settings) {
						voltageLimit = maxLimit;
							printf("Max voltage limit %2.2f reached \n", voltageLimit);
					} else if (settings){
						voltageLimit = (voltageLimit + voltageStep);
						printf("New voltage limit: %2.2f\n", voltageLimit);
					}
					
				}
			}
			while(S1 | S3){}
		}
	}
	return S_OK;
}
The voltAlarm code is a bit long, but it's mostly fairly simple standard C code. We'll go through the code piece by piece, and there should be no difficulties. Since the code view of the forum is quite small, I would recommend downloading the .zip and opening the source code in VSIDE or your program of choice.

There are quite a few includes, but all of them are necessary in this case. The sar.h is a header for sar.c, which was mentioned. The two defines are just to make the code look a bit easier.

Inside the main function, the first thing are four constants. The first one can be useful while testing the program through the shell, as you can drop the parameter from the function call, making it a bit faster to write. The three others affect the functionality of the button interface.

After that are two u_int16 variables, which act as Boolean values. The first one determines if the alarm sound is played during the loop, and the second keeps the button interface running, until the user wants to exit it.

The next significant line is the SetJmpiHookFunction, which disables the normal Pause and Next functions of the buttons. After that is the SAR enable, which was also in readVoltage.

The next while loop is the where most of the functionality happens. The first line is getting the current voltage. After that the voltage is compared to the limit, and the alarmOn variable is changed accordingly. There is a small hysteresis in turning the alarm off, to prevent it from constantly changing between on and off states.

After that, either the defined alarm sound played, or there is a short delay. The alarm sound does play completely, so it should be fairly short. The program has been tested with hello.mp3, but a single beep sound would be the most ideal for this purpose.

The last part of the code is the fairly large if-structure, which is for the button interface. While the program is running, you can enter the interface by holding the S1 and S3 buttons of the Developer Board. Once the program has printed the messages, you can use S1 to decrease and S3 to increase the limit. Again, hold both to exit the interface. This will set the settings-variable to 0, which prevents any further changes from happening, and stops the loop. There are multiple while loops to stop the program for the duration of any button presses, to stop one press registering as multiple. You can probably come up with more elegant ways to avoid that particular problem, but the current solution is enough for the purposes of this program.

The VSIDE solution with the source code is available below.
Attachments
voltAlarm.zip
voltAlarm - VSIDE solution with source code
(11.24 KiB) Downloaded 340 times
Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

uartPlayB - ROM specific UART controlled player

Post by Kalle »

This program can be used to to control the player through UART on the VS1010B. This is a ROM specific program, and cannot be on ran other VS1010 board versions.

The program works similarly to the default ROM player, the major difference being that it's controlled through single character commands on the UART, instead of the buttons. It also uses a separate CountFiles program to get the amount of .mp3 files in the directory.

To make browsing a bit easier, we'll divide the main.c to three parts. UartPlayB has no other source code files.

As usual, VSIDE solutions for both uartPlayB and CountFiles are available for download at the bottom. I would also recommend downloading the VS1010B ROM VSOS Kernel Source Codes, as seeing some of the functions there can help understanding this. If you use VSIDE, use the Find in Files (CTRL + Shift + F) to do searches in all of the source code.

Code: Select all

#include <vo_stdio.h>
#include <volink.h>     // Linker directives like DLLENTRY
#include <vs1010bRom.h>
#include <playerinfo.h>
#include <protocol.h>

SETHANDLER(97, MyPlayerCallback)

u_int16 running = 1;

void MyPlayerCallback(AUDIO_DECODER *auDec) {	

	u_int16 input;

	// The condition of the if-statement checks if new data has been received
	// through the UART. The next line fetches that data.
	// This program only uses one character inputs.
	if (!FIFOYEmpty(&uart0Fifo)) {
		input = FIFOYGet(&uart0Fifo);
		
		if (input == 'n') { // Next track
			player.nextStep = 1;
			goto nextSong; // Jumps the program to inside the last else if-structure
		} else if (input == 'p') { // Previous track
			player.nextStep = -1;
			goto nextSong;
		} else if (input == ' ') { // Pause and un-pause with space 
			player.auDec.pause ^= 1;			
		} else if (input == 'q') { // Quit the program
			running = 0;
			goto nextSong; 
		} else if ((input >= '1') && (input <= '9')) { // Plays the specified track
			player.nextFile = input - '0';
			nextSong:
			player.auDec.pause = 0;	// Un-pause		
			player.auDec.cs.cancel = 1; //Stop playing current file.					
		} else {
			printf("Unrecognized command: %c\n", input);
		}
	}
		
}
The first function of the code is the MyPlayerCallback(), which replaces the DefaultPlayerCallback() and its button controls. For the callback to run, the sethandler() is required. The running-variable is the condition of the main while-loop which comes later. Because MyPlayerCallback() needs be able to modify it, it's global.

MyPlayerCallback() has an if-structure, which has another larger if-else if-structure inside it. The condition of the first if (!FIFOYEmpty(&uart0Fifo)) checks if the UART0 FIFO is not empty. If it isn't, the FIFOYGet(&uart0Fifo) will return the character to the input-variable.

The if-elseif-structure in itself is should be pretty obvious to everyone. If the given input was n, p, space, q or a number 1-9, the appropriate action is performed. Otherwise the error message is printed.

The actions are as follows:
- n = Next track
- p = Previous track
- space = Pause/Unpause
- q = Quit the program
- 1-9 = Plays the track with the given number

Tracks are automatically numbered in alphabetical order. With the existing program it's not possible to read more than one character at a time from the UART, so tracks with numbers larger than 9 are not reachable with the number command. They do still play just fine, and the Next/Previous functions have no limitations in this regard either.

The contents of the statements are more unclear, because they depend on the internals of the PlayerPlayFile() as well as the main() function. Basically the most important part is the three variables: player.currentFile, player.nextStep, and player.nextFile.

Player.currentFile is the track that is being played currently, or what PlayerPlayFile() is going to play once it's called the next time. Most of what the program does revolves around modifying player.currentFile in some way.

Player.nextStep is always either 1 or -1. PlayerPlayFile() uses it to modify player.currentFile:

Code: Select all

player.currentFile += player.nextStep;
This is the last thing called by PlayerPlayFile() once it has stopped playing a track. Most of the time player.nextStep is kept at 1, which makes the player move to the next track after it has finished playing one. However, if a 'p' is sent to UART, player.nextStep will be temporarily set to -1, which will cause the player to decrease player.currentFile, which means that it will play the previous track the next time it's called. In main() the value will be set back to 1 again.

The last variable, player.nextFile, is technically used by PlayerPlayFile() as well, but the existing code for this has some problems and doesn't do what it's supposed to. Because of this, there is a working implementation for it in the main().

Pausing the track is done by setting player.auDec.pause to 1. Since the same button is used for both pause and unpause, the value is simply toggled with a XOR (^= 1).

One very important aspect about the actions is that the player must stop playing the current track for the actions to take effect. The Pause/Unpause is the one exception to this.

To stop the player, player.auDec.cs.cancel is set to 1. Pause is also disabled, so that the next track will start playing immediately. Both of these actions are reached with the goto jump, to avoid repeating the same lines in each else if-statement.

After the callback the next function is init().

Code: Select all

// This is a rom-specific program, check that we are running in VS1010B.
void init(void) {
	if (USEX(0xbfff) != 0x5e27) {
		SysError("NOT VS1010B ROM");
		while(1) {
			// Stop
		}
	}
}
The point of this is just to make sure that the program is running on the correct VS1010 version.

After that comes main().

Code: Select all

ioresult main (char *params) {	
	
	u_int16 totalFiles = 0;
	
	printf("VS1010 UART Controlled player\n");
	
	// With RunLib, it's possible to run other programs.
	// In this case we use CountFiles to get the amount of .mp3
	// files on the SD card.
	totalFiles = RunLib("CountFiles","S:*.MP3");
	//printf("%d .mp3 files found\n",totalFiles);
	
	// This is a quick way to check if CountFiles has been found. If
	// it isn't, totalFiles value will be at -31234. By setting the running- 
	// variable to 0, we can completely skip the while loop and exit the 
	// program. If this is not done, the program will bug once the track 
	// number goes out of range.
	if (totalFiles == -31234) {
		printf("CountFiles not found\n");
		running = 0;
	}

	// player.currentFile specifies which track the player is 
	// going to play.
	player.currentFile = 1;

	while(running) {

		// nextStep is set to -1 when going back to previous track.
		// It needs to be set back to 1, otherwise the player will keep
		// going backwards after a track ends.
		player.nextStep = 1;
		
		printf("Playing track #: %d\n", player.currentFile);
		
		// PlayerPlayFile is the function that actually plays the files. The main
		// loop won't run past this during that time, and only the contents of 
		// MyPlayerCallback are executed.
		PlayerPlayFile();

		// Setting player.nextFile to a non-zero will play the track with that number.
		if (player.nextFile) {
			player.currentFile = player.nextFile;
			player.nextFile = 0;
		}
		
		// If the track number goes outside the range, it will be set 
		// to a correct value. This can happen both with track number input and the
		// next or previous commands.	
		if (player.currentFile <= 0) {
			player.currentFile = totalFiles;
		} else if(player.currentFile > totalFiles) {
			player.currentFile = 1;
		}

	}

	return S_OK;
}

Before the actual program loop is started, the amount of .mp3 files is counted with the separate CountFiles program. This is done with the RunLib() function. The totalFiles variable will either get the amount of files, or if RunLib fails to find the CountFiles program, it will return a value -31234. In this case the running-variable is set 0 and the main while-loop is skipped.

In the main loop is the mentioned player.nextStep reset, a normal print function, and then perhaps the most important part of the program, the PlayerPlayFile() function call.

After that, the player.nextFile value will be checked, and if non-zero, player.currentFile will be overridden with it.

The last if-else if-structure is for checking that player.currentFile's value is still in the range of the track numbers. It will loop around back to the first track once the end has been reached, or loop to the end if the user goes to previous track while in the beginning.



UartPlayB and CountFiles are available for download below.
Attachments
CountFiles.zip
CountFiles - VSIDE solution
(6.27 KiB) Downloaded 375 times
uartPlayB.zip
uartPlayB - VSIDE solution
(13.05 KiB) Downloaded 326 times
Kalle
VLSI Staff
Posts: 17
Joined: Tue 2017-06-06 8:59

uartPlayB_2 - added amplifier and volume controls

Post by Kalle »

This is a slightly improved version of the existing uartPlayB.

The first change is rather small, a simple toggle command for the D class ampliefier on the VS1010 Developer Board.

Code: Select all

} else if (input == 'a') { // Toggle D class amplifier
	if (amp) printf("Amplifier disabled\n");
	else printf("Amplifier enabled\n");			
	amp ^= 1;
	GpioSetPin(0x00,amp);
The amplifier connects to the headers JPL and JPR, and to the GPIO0 pin on the VS1010. Setting the pin to 0 or 1 can therefore be used to disable or enable the amplifier, respectively. The amp-variable is global, so that it's value is preserved when the player moves to the next track. Note that the amplifier only runs if the JP3 jumper is closed on the Developer Board. Once closed, the default state is that the amplifier is enabled. The full volume could be quite loud, it would be recommended to lower the volume before plugging anything in. There is also an automatic disable if headphones are plugged to the HP connector.

The volume control is a bit more complex.

Volume up and volume down are naturally two separate commands. On the UI side, a range of 0-50 is used. Higher value means more attenuation, making the value 0 the highest volume.

Code: Select all

} else if (input == 'u') { // Volume up
	if ( vol > 0 ) { 
		--vol;
		setVolume(vol);
	} else {
		printf("Max volume reached\n");
	}
			
} else if (input == 'd') { // Volume down	
	if ( vol < 50 ) {
		++vol;
		setVolume(vol);
	} else {
		printf("Min volume reached\n");
	}
The else if-statements should be rather clear. The vol-variable is global. Most of the required logic is in a new function setVolume().

Code: Select all

void setVolume(u_int16 vol) {

	// The 0-50 values of vol correspond with 0-150 values of realVol.
	u_int16 realVol = vol * 3;

	// The fine values only go up to 11, since 12 times +0.5dB would be +6dB, 
	// and coarse values are in -6dB steps.
	u_int16 coarse = (realVol / 12);
	u_int16 fine = 11 - (realVol - coarse*12);

	if ((realVol < 0)|(realVol > 191)) {
		printf("Invalid volume\n");
		return;
	}

	// The register has the separate setting for left and right, but we'll use
	// the same values for both.
	PERIP(DAC_VOL) = fine << 12 | coarse << 8 | fine << 4 | coarse;

	printf("Volume set: %d/50\n", (50 - vol));
}
Each step of the realVol-variable equals to 0.5dB. This is a fairly small change, which is why the value of vol is multiplied by three.

The attenuation could of course be pushed beyond 150 realVol (= -69.5dB), but on the tested equipment this was already inaudible to human ear.

On the VS1010, the volume of the audio outputs is determined by the 16bit DAC_VOL register (datasheet chapter 9.7.2). In short, it's a 16bit register with four 4bit values:
- Left fine increase (+0.5dB)
- Left coarse attenuation (-6dB)
- Right fine increase (+0.5dB)
- Right coarse attenuation (-6dB)

Some math is required to get the register values out of realVol. Essentially there is a modulo 12 to see how much fine increase is needed, and the rest is done with coarse attenuation.

A small error check is included for realVol, to make sure that the values stay within the limits of the register (4 bits each).

The register is modified with the PERIP function which has been used in previous examples. The values are shifted to correct positions, and combined with OR.

The print function has an inverted value for vol, because it's more intuitive for the user.

uartPlayB_2 available for download below. Remember that this still a VS1010B specific program, and that the CountFiles program is also needed.
Attachments
uartPlayB_2.zip
uartPlayB_2 - VSIDE solution
(14.19 KiB) Downloaded 377 times
AskariSon
User
Posts: 17
Joined: Fri 2021-09-24 7:36

Re: VS1010 Example Programs

Post by AskariSon »

uartPlayB_2 available for download below. Remember that this still a VS1010B specific program, and that the CountFiles program is also needed.
I need this one.
How do I get it to work with VS1010D.

Best regards
Hannu
VLSI Staff
Posts: 526
Joined: Mon 2016-05-30 11:54
Location: Finland
Contact:

Re: VS1010 Example Programs

Post by Hannu »

I have used many times this strategy:
  1. Create new solution and VS1010D ROM specific project.
  2. Remove the template main.c from project and project directory.
  3. Copy the files from the source project to the VS1010 project
  4. Import files to project with right click source files -> add existing on left side solution browser
  5. Build
  6. Go through the errors, ask how to fix unsolvable problems, Repeat Building until no errors
  7. Test
This should be quite straight forward port as the chips are relatively same.
AskariSon
User
Posts: 17
Joined: Fri 2021-09-24 7:36

Re: VS1010 Example Programs

Post by AskariSon »

Ok. Thanks.
Post Reply