NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.
Everything Else » SD card for PCM sound
November 26, 2009 by pbfy0 |
I wrote a program for reading a SD card and playing the sound on it. It can also take sound from the computer over the UART and load it into the SD card. You send the nerdkit 'l' and then PCM data in binary. use libsndfile (compile & install) and pcm2nlsv to get PCM audio with values separated by newlines. Here's a pinout of a SD card and the connections are this:
I can't find the source right now, but I'll post it when I find it. |
---|---|
November 27, 2009 by pbfy0 |
I found the source, and I changed it so it takes a number in ASCII instead of bin. here it is. I'll make a perl script to send a file with PCM values separated by newlines, and post it when I'm done. |
November 27, 2009 by pbfy0 |
Libsndfile should be here. |
November 27, 2009 by pbfy0 |
I have the perl script, here, and I have the complete process for getting a wav file onto the chip.
<infile> for sndfile-convert is any music file <outfile.raw> for sndfile-convert is <infile.raw> for pcm2nlsv <outfile> for pcm2nlsv is <infile> for sound.pl <port> for sound.pl is the serial port address. Only do steps 2 & 3 if you're repeating the sequence. Remember, the C program is UNTESTED. |
November 29, 2009 by pbfy0 |
I noticed an error in the code, here's the fixed one. |
March 09, 2010 by pbfy0 |
the SS pin on the connections table should be connected to SD pin 1 where it just says SD pin |
March 20, 2010 by brian |
I thought SD cards ran off 3.3 volts, doesn't the NerdKit use 5V? |
March 22, 2010 by pbfy0 |
the LD1086V33 is a 3.3 volt regulator, and the resistors regulate the data levels. |
October 12, 2010 by brockmjp |
I'm trying to reproduce this project, but I'm having some problems with the C code. I installed and ran libsndfile and pcm2nlsv to process a small wav file. No problem there. I compiled the C code on the chip with no error. When I use the Perl script to send the processed file, it takes a while to transfer, but eventually it tells me its finished. However, no sound plays. I know that my speaker works, because I did the musicbox tutorial and got "Happy Birthday" to play. The wiring, as described, is quite simple and I've checked all my connections. For the SD card, I'm using a 2GB microSD with an SD adaptor. I have pin headers soldered to the adaptor contacts so I can plug it into my breadboard. I'm troubleshooting the C code and I've found that my first problem (not likely to be the last) seems to be initiating the SD card. For troubleshooting, I've got six LEDs plugged into PORTC (pins PC0 to PC5). I've stripped down the C code (see below) to isolate the problem. The only modifications I've made to the code other than deleting all code that comes after MMC initiation, is to add instructions to turn on the LEDs at various points. When I run this code on the chip, I get the first three LEDs to light up. The first one indicates that I've entered the main loop. The second indicates that SPIinit() executed successfully. The third and fourth are in the for loop of the MMC_Init subroutine. The third LED lights up in the first round of the loop (when i==0) after SPI(0xFF) is executed. The fourth LED should be turned on in the subsequent round of the loop (when i==1), but it doesn't, indicating that SPI(0xFF) fails after the first time. Any ideas for next troublshooting steps? Do I need to format the microSD card somehow? I've been using this card with a camera, so I know it works (and it still works, so I know I haven't killed it). Thanks for your time, -Joel
|
October 13, 2010 by brian |
To be totally honest, I can't speak for the code you're using - but MicroSD cards don't always implement SPI. Is there a chance you can try with another (full size) SD card? |
October 15, 2010 by pbfy0 |
well, I didn't write this code myself. It partly came from Arduino. It looks like the sd card isn't responding correctly, though. |
October 17, 2010 by brockmjp |
I'm using a SanDisk 2GB microSD with a SanDisk microSD to SD adaptor. I've read in at least two other forums that SPI is implemented on the SanDisk microSD. Its clear from the comments in the C code that it was intended for an Arduino ATmega168 running at 16MHz. It looks like the speaker is run off of PB3. I've had a little more success with an alternative circuit described here. This circuit replaces the 5v regulator with the 3.3v regulator, so you run the whole thing on 3.3v and get rid of the voltage dividers. I am now getting past the MMC_init() step, but it seems to be failing somewhere in the startPlayback(). What I need now is some simple code to test that I can write to and read from the card. I don't want to depend on the uart for this test so I want hard coded values for the data to write and what sector to write to. Something like:
When I do this, the first LED comes on right away and then the second LED comes on after a short, but noticable delay (less than 1sec). The other LEDs remain off. |
October 17, 2010 by pbfy0 |
I think the problem is that set and get512block take arrays, not single values. |
October 17, 2010 by brockmjp |
OK, I've replaced the LEDs with more informative print statements to a putty session using the libnerdkits uart library. There are some error handling print statements in the write512block() and get512block() functions. I'm still just trying to get a simple write and read to happen so I want to hard code the data to write for now. I don't want to use the Perl script to send the data, because its not necessary and I wouldn't be able to read the print statements coming from the chip unless I figure out how to receive data with the Perl module (I'll go down that rabbit hole another day). Let's take a look at the main in the C code and step through what happens when we read one or two lines from the infile (that would be) sent by the Perl script:
First the SPI and MMC are initiated and then startPlayback() will play any sound data that is stored on the SD card. At this point the code sits waiting until it receives data from the Perl script: while (uart_getc() != 'l'); Once the Perl script sends an 'l', the code continues. So after that, when fscanf_P(&uart_stream, PSTR("%un"), &tmpb); executes, does tmpb == 'l' or does it equal the first line of the infile (which is '125' in my case? Regardless, I have no idea what the next 11 lines are doing. Can someone explain it to me? All I can tell is that values are assigned to the first four elements of the buffer array. In the while loop that follows, it looks like each line of the infile sent by the Perl script is assigned to an incremented element of the buffer array starting with the fifth element (buffer[4]). When i reaches 512 then i %= 512 changes i to 0, which causes if(i!) to be true so write512block(buffer, j); executes. So I should be able to write a 512 block with the following code (assuming that tmpb should equal the first line of the infile):
I'm able to flash this onto the chip without error. I switch on the chip, start a putty session and send a 'T', and the output I get is: Initialized The 'MMC: write error 2' comes from the write512block() function:
|
November 19, 2010 by brockmjp |
I finally have this project working, however there is one problem with it that seems intractable. There is a rapid clicking noise in the playback, which I belive is caused by a delay when reading from the SD card. The mcu reads a 512 byte block of samples from the SD card and plays them at a rate of 8kHz. After the 512th sample is played, the mcu reads another 512 byte block from the SD card. For smooth playback, it must do this in the time between playing the 512th sample of the last block and playing the 1st sample of the next block (0.125ms). However, the maximum data rate setting for master mode in the SPI registers is clk/2 which is 7,372,800Hz with the nerdkit crystal. That means it takes 0.556ms to read 512 bytes. So we get a delay (click) of 0.431ms every 64ms. The 8kHz sample rate already seems low. I can't see reducing that 5-fold, and we can't run the mcu 5-fold faster, so how can we get smooth playback? Would be great to have a solution. Thanks to pbfy0 for the original post, and Mike and Humberto for the email support, you guys are awesome. Here is the working C code and here is my version of the Perl script to send the PCM data. -Joel |
November 20, 2010 by pbfy0 |
I don't really know, but loading the next block on the 508th sample might make it work. |
November 20, 2010 by Rick_S |
I don't know if this will help any or not, but HERE is a link to someone who has devleoped a wav player on a USB Based AVR. It appears he double buffers so while one buffer is playing, the other loads. I haven't messed around with any of this so what info this provides, may or may not be of help... Rick |
November 21, 2010 by brockmjp |
Thanks for the ideas guys. I tried triggering the get512block at the 508th sample (and earlier). It did seem to reduce the click, but its still there. I'll look into the double buffering strategy. -Joel |
November 25, 2010 by brockmjp |
Double buffering did not remove the click either. It sounds exactly the same. Apparently the timer interrupt is unable to interrupt the SPI data transfer from the SD? Here is how I implemented the double buffer in the timer interrupt. I had to switch to a ATmega328 to have enough RAM to accomodate both 512 byte buffers.
|
November 27, 2010 by bretm |
Are you sure the remaining click is caused by the reason you think it is? After a section is finished you reset j to 0, but then you immediately increment it to 1. That means j only spans 1 to 511 and you're not playing all 512 samples. This line also seems wrong:
The comment suggests that sounddata_length is 1-based. If it ranges from 1 to 512 and you do %512 on it, the values will go from 1 to 511 and then 512 will become 0. If you subtract 1 from that, the values will go from 0 to 510 and then -1. That means the last sample is being read from beyond the buffer bounds for certain values of sounddata_length, but that should only affect the tail end of the playback. |
November 28, 2010 by brockmjp |
Thanks bretm. Good catches. I moved the j++ to an else at the end of the j==511 conditionals. As for the lastSample line,I believe [(sounddata_length-1) % 511] should be the way to do it, but as you said, that would only affect the end of play and wouldn't help with the clicks during play. In fact, I tried just going to stopPlayback() when sample >= sounddata_length, as shown below, but the playback still sounds exactly the same:
I also added a low pass RC filter (100nF cap, 5k trimmer). That improves the quality a little by removing a slight ringing overtone, but does nothing with respect to the clicking noise. -Joel |
November 28, 2010 by bretm |
Inside the timer ISR, interrupts are disabled. That means samples won't be processed during get512block. Try calling sei() before get512block, or else do the block management outside the ISR (preferred). |
November 29, 2010 by brockmjp |
bretm, I tried calling sei() before the get512block calls and that didn't help. I tried moving the block management outside of the ISR (see code below) and no sound played at all. I did notice an error (fixed in the code below) in the sound upload part of the code similar to the increment error in the ISR you pointed out earlier. That fix by itself seemed to help the sound quality a little, but the click is definitely still there. Please let me know if you see any error in the way I coded the block management in the main loop. Seems like it should work. I took a look at the SanDisk SD card product manual and it appears that its possible to read partial blocks. If I can get just 32 or 64 bytes with each read, that should be fast enough to complete between timer interrupts.
-Joel |
November 30, 2010 by bretm |
That's not going to work. You don't call get512block until j reaches 512. Once that happens, j remains at 512 and doesn't get reset to 0 until get512block is done, so best case is that there will be a click. You need to update section and reset j to 0 before you call get512block so that the timer can immediately continue with the next buffer. That would also explain why sei() didn't help before. I didn't catch that. That means you probably need to switch things around so that you're calling get512block with the opposite buffers and with "section - 1" instead of section since you will have already incremented the section variable. Since section and j are now shared between the ISR and main, they need to be declared as "volatile" otherwise they may be optimized into constants in the ISR. That could explain why you hear no sound in this version. |
November 30, 2010 by brockmjp |
More good ideas. I tried all your suggestions, except instead of section - 1, I just made section = 2 coming out of startPlayback, instead of 3 as it was before. I tried this with block management inside the ISR and alternatively in the main, calling sei() before the get512blocks either way. For clarity, I will include the startPlayback loop in the code below (version with block management in main). The clicking seems to be unaffected by these changes, but at least the sound plays when block management is done in the main loop. So the "volatile" declaration helped in that regard. As a sanity check I tried again my program that plays the same sound samples, but from program memory instead of the SD card. That still sounds perfect, no clicking. Is it really possible that the SPI data transfer cannot be interrupted? Or maybe only data reception on the mcu is being interrupted while the SD continues to send data resulting in some dropped bytes with every block read.
-Joel |
December 01, 2010 by bretm |
The buffer management seems right now. I don't see anything in get512block that would be preventing the timer interrupt from happening on schedule. There's a race condition that might be causing problems, but it's hard to say. Once j reaches 512, you set it to 0 and increment section in the main block. But an interrupt could happen between those statements, which would cause the ISR to re-play the first sample from the same buffer. You should cli() before doing those two statements and sei() after. But this should only happen once in a while, not every block. Technically, since j is more than one byte, you should theoretically protect reading from it the same way, but I don't think that's necessary in this case because by waiting for 512 you're really only waiting for the high byte to change. If the number wasn't a multiple of 256 it would be another race condition. So if the clicking still happens after fixing that race condition, I don't know why it's still clicking. If it is SPI causing the problem, your program is about to get more complicated. You'll have to stop using the timer interrupt and instead check for timer overflow during your SD card reading to see if it's time to play another sample. It should be doable, but ugly and seemingly unnecessary. |
Please log in to post a reply.
Did you know that NerdKits believes in the importance of a mixture of meaningful topics, clear instruction, and engaging projects? Learn more...
|