Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Sparkfun MP3 Player Shield


  • Please log in to reply
3 replies to this topic

#1 NicholasSTG

NicholasSTG

    Member

  • Members
  • PipPip
  • 16 posts

Posted 17 December 2011 - 01:41 AM

I have a regular Netduino and a Sparkfun MP3 Player Shield with build in SD card reader (http://www.sparkfun.com/products/10628). It works great as an SD card reader, but I cannot manage to get it to play any mp3s. All of the examples I have found for it online point to the previous version of the board without the SD card reader so my gut feeling is that the addition of the SD card is causing my issues. Has anyone had any luck with this shield?

#2 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 17 December 2011 - 05:11 AM

Hello Nicholas. What's exactly the problem? Could you post some code that you tried to get it work? Upon what you're saying the player does not play any song? Cheers
Biggest fault of Netduino? It runs by electricity.

#3 NicholasSTG

NicholasSTG

    Member

  • Members
  • PipPip
  • 16 posts

Posted 17 December 2011 - 08:43 PM

Hello Nicholas.
What's exactly the problem? Could you post some code that you tried to get it work?
Upon what you're saying the player does not play any song?
Cheers


I'm terribly sorry, I was on my way out the door and wasn't thinking.

The code is below, but I have two files on the MP3 card and when it tries to play the second it simply stops all program execution. My first instinct was that it was trying to play the song and simply could not (and was not really locked, but just taking a long time), but the track is only 4 seconds and program execution takes easily 2 minutes before I stop it.

Main Program

using System;
using Microsoft.SPOT;
using SecretLabs.NETMF.Hardware.Netduino;
using SecretLabs.NETMF.IO;
using System.IO;

namespace NetduinoMp3
{
    public class Program
    {
        private static readonly int FileBufferSize = Vs1053.BufferSize * 10;

        public static void Main()
        {
            Debug.Print(DateTime.Now.ToString());
            String sdroot = "MP3SD";
            StorageDevice.MountSD(sdroot, SPI_Devices.SPI1, Pins.GPIO_PIN_D9);
                        
            String[] files = Directory.GetFiles(@"\" + sdroot);
            Debug.Print("file count: " + files.Length);

            Vs1053.Initialize();
            Vs1053.SetVolume(0xFEFE);

            for (var i = 0; i < files.Length; i++)
            {
                Debug.Print("filename: " + files[i]);
                Vs1053.SetVolume(0x60,0x60);
                var fileStream = new FileStream(files[i], FileMode.Open, FileAccess.Read, FileShare.None, FileBufferSize);

                Vs1053.SendData(fileStream);

                fileStream.Close();
            }
        }
    }
}

Shield Class


using System;
using System.IO;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.Threading;

namespace NetduinoMp3
{
    public static class Vs1053
    {
        // GPIO ports:
        static private OutputPort _reset;
        static private InterruptPort _dreq;

        const Cpu.Pin PinBsync = Pins.GPIO_PIN_D7;  // was 2
        const Cpu.Pin PinDreq = Pins.GPIO_PIN_D2;   // was 3
        const Cpu.Pin PinReset = Pins.GPIO_PIN_D8;  // was 6  // NOTE: This doesn't map to an actual pin
        const Cpu.Pin PinCs = Pins.GPIO_PIN_D6;     // was 9

        // Define SPI Configuration for VS1053 MP3 decoder:
        static private readonly SPI.Configuration DataConfig = new SPI.Configuration(PinBsync, false, 0, 0, false, true, 1000, SPI.SPI_module.SPI1);    // changed speed from 3000 to 1000
        static private readonly SPI.Configuration CmdConfig = new SPI.Configuration(PinCs, false, 0, 0, false, true, 1000, SPI.SPI_module.SPI1);        // changed speed from 3000 to 1000
        static private SPI _spi;

        // Registers:
        const int RegisterSciMode = 0x00;
        const int RegisterSciVol = 0x0B;
        const int RegisterSciClockf = 0x03;

        public static readonly int BufferSize = 32; // was 96
        private const ushort SciMode = 0x880;  // SM_SDINEW (default) + SM_EARSPEAKER_HI

        static private bool _isInitialized;
        static private readonly byte[] ReadBuffer = new byte[BufferSize];
        static private readonly byte[] CmdBuffer = new byte[4];

        static private readonly AutoResetEvent AutoResetEvent = new AutoResetEvent(false);

        public static void Initialize() {
            if (_isInitialized)
                Shutdown();
 
            _spi = new SPI(CmdConfig);
            _reset = new OutputPort(PinReset, true);
            _dreq = new InterruptPort(PinDreq, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeBoth);
            _dreq.OnInterrupt += _dreq_OnInterrupt;
 
            _isInitialized = true;
 
            Reset();
 
            CommandWrite(RegisterSciMode, SciMode | (1 << 2));
            CommandWrite(RegisterSciClockf, 7 << 13);
            CommandWrite(RegisterSciVol, 0x2424);
 
            _spi.Config = DataConfig;
        }

        private static void _dreq_OnInterrupt(uint port, uint state, DateTime time)
        {
            if (state == 0)
            {
                AutoResetEvent.Set();
            }
            else
            {
                AutoResetEvent.WaitOne();
            }
            _dreq.ClearInterrupt();
        }

        private static void Reset()
        {
            _reset.Write(false);
            Thread.Sleep(1);
            _reset.Write(true);
            Thread.Sleep(1);
        }

        static private void CommandWrite(byte address, ushort data)
        {
            CmdBuffer[0] = 0x02;
            CmdBuffer[1] = address;
            CmdBuffer[2] = (byte)(data >> 8);
            CmdBuffer[3] = (byte)data;

            _spi.Write(CmdBuffer);
        }

        static private ushort CommandRead(byte address) {
            CmdBuffer[0] = 0x03;
            CmdBuffer[1] = address;
            CmdBuffer[2] = 0;
            CmdBuffer[3] = 0;
 
            _spi.WriteRead(CmdBuffer, CmdBuffer, 2);
 
            ushort command = CmdBuffer[0];
            if (command <  100)
                throw new ArgumentOutOfRangeException("volume");
 
            // SetVolumePercent(volume, volume);
            return command; // this was not here, but since CommandRead is not referenced anywhere I'm not sure it affects anything ...
        }

        public static void SetVolumePercent(int leftChannel, int rightChannel)
        {
            if (leftChannel < 0 || leftChannel > 100)
                throw new ArgumentOutOfRangeException("leftChannel");
            if (rightChannel < 0 || rightChannel > 100)
                throw new ArgumentOutOfRangeException("rightChannel");

            // TODO: Invert decibel value, divide by percent, call SetVolume(ushort leftChannel, ushort rightChannel)
        }

        public static void SetVolume(ushort bothChannels)
        {
            CommandWrite(RegisterSciVol, bothChannels);  // TODO: This doesn't work outside the Initialize() method
        }

        public static void SetVolume(ushort leftChannel, ushort rightChannel)
        {
            SetVolume((ushort)(leftChannel * 256 + rightChannel)); // TODO: Verify no loss of fidelity
        }

        public static void SendData(FileStream fileStream)
        {
            var size = fileStream.Length - fileStream.Length % BufferSize;
            for (var i = 0; i < size; i += BufferSize)
            {
                fileStream.Read(ReadBuffer, 0, BufferSize);

                _spi.Write(ReadBuffer);
            }
        }

        public static void Shutdown()
        {
            if (!_isInitialized) return;

            Reset();

            _spi.Dispose();
            _reset.Dispose();
            _dreq.Dispose();

            _isInitialized = false;
        }
    }
}


I found the original shield class on http://www.jaypm.com...-the-netduino/. The code did not work and I have made changes (noted in the comments).

The output of the program looks like this

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
01/01/2009 22:52:07
file count: 2
filename: \MP3SD\TRACK002.MP3
filename: \MP3SD\TRACK001.MP3
The program '[9] Micro Framework application: Managed' has exited with code 0 (0x0).


Thanks so much for your help.

#4 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 18 December 2011 - 05:14 AM

Well, Nicholas...at first glance there are a series of thing I would change. Not sure how much they're involving the playing issues, though.
  • Why making the Vs1053 class static? Consider the driver class as a normal dynamic class, as long the whole content.
  • Implement the IDisposable pattern instead of "Shutdown". Also take care to follow the MS's guidelines on that,
  • The "Initialization" could be moved in the class constructor. However, since there are several task to perform, it's better to leave it as a static method, which return the class instance:

public sealed class Vs1053
{
  private Vs1053() //can't be instantiated directly, nor derived (note the "sealed" on the class)
  {
  }

  public static Vs1053 Create()
  {
    // init...
  }
}

then:

Vs1053 player = Vs1053.Create();

  • There's an issue about the SPI configuration, which I'd avoid to take advantage. BTW, the preparation of the two SPI.Configuration structures is a good point.
Use this pattern instead:

using (SPI spi = new SPI(config))
{
  // perform the transfer
}

It takes no additional time over other usages, but ensures a correct usage of the SPI port, and the related pins.

  • The DReq pin and the AutoResetEvent seem having no real importance: I'd cut them off the code. Consider that the Netduino has not the speed of an Arduino, thus the DReq pulse (very short) isn't actually registered by the Framework, I guess.
The natural latency of the MF execution should be enough.

  • Finally, a strange thing on the CommandRead function (not sure, though). On the above func (CommandWrite), you set the data as a ushort, having the high byte first. Well...in the CommandRead, you're pulling out the data from the (supposed) high-order byte. Why don't you compose the result as a normal ushort?

Hope it helps.
Cheers

EDIT: I forget a couple of things...
* The Thread.Sleep(1) is superfluous;
* The SPI speed could reach about 5MHz, thus there's no reason for getting it so slow.

Edited by Mario Vernari, 18 December 2011 - 05:17 AM.

Biggest fault of Netduino? It runs by electricity.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.