Cheap AVR based audio player help

Go To Last Post
21 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello everyone. This is my first post so hopefully I put this in the right spot.

 

I am trying to design an AVR audio player that plays around 4-8 short recordings. about 4 seconds max.

 

I just need to know if I need to upgrade from what I have or if I'm doing something wrong in my code. I haven't been able to find much in regards to the Attiny412 and sample projects so I've been kinda winging it.

 

Design requirements:

1. The quality doesn't have to be great, just enough to be able to understand speech. (8000Hz 8-bit audio) Sample rate can be lower if necessary

2. the data has to be stored either internally or on SPI flash. I guess i could go the sd card route but this thing has to be mass produced so I kinda wanna keep it cheap without a sd card slot and all.

3. The AVR has to be fast enough for either PCM audio playing or DAC output.

4. The entire thing has to be around 5 dollars max, maybe 8 if i cant get anything that cheap.

 

If anyone has any experience with anything like this please let me know an alternative I can go with or help me with what I have already.

 

 

I was initially trying to use an attiny412 and a 4Mbit SPI flash chip to play sounds but for some reason the audio that I get out of it is all garbled. I programmed the flash chip with my RPi and every page in memory has values that correspond to a sine wave. The most ive ever gotten was the shape of a sine wave on my oscilloscope when playing it from the AVR but the signal has these spikes where the signal is higher than it should be in that spot (its also quite a long time, not just a really quick blip.) If anyone has ever used a really old storage oscilloscope with a bad ADC it looks similar. Like some of the bits are stuck. Only I know that's not the case because I've tried multiple chips. I have uploaded the source code for this project that I have for the 412. Please ignore any weird things in the code because I have been troubleshooting this for weeks and haven't found a solution and cleaned it up.

 

Basically, It runs a counter that triggers an interrupt every time a sample should be taken (~8000hz normally but i slowed it down for my scope). When the interrupt occurs, the SPI transfer is initiated and it sends a read opcode followed by 3 address bytes and then I write to the data buffer again to receive data from the chip. I know that you're supposed to disable the DAC before writing data to it but I have tried that as well and no luck.

 

Thanks for your time.

Attachment(s): 

This topic has a solution.

AVRs, Electronics, ESK8 boards, CNC, 3D Printing, LASERs, and Tesla Coils.

Last Edited: Tue. Apr 9, 2019 - 01:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bears0 wrote:
I haven't been able to find much in regards to the Attiny412 and sample projects so I've been kinda winging it.
tiny817 and an 8Mb SPI flash (1 minute)

AVR Parrot

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why go through all teh nonsense and headache?

 

Use one of these:

https://www.ebay.com/itm/MP3-pla...

 

You save MP3's to an SD card, pop that in and you are good to go.

 

If you do a search of MP3 player boards you will find a lot of options on Ebay, Aliexpress etc.  Its a far simpler solution than the path you are going down.

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have the same recommendation but a different MP3 IC.  This https://www.ebay.com/itm/TF-Card... YT5300 module board allows you to use Arduino's Software Serial library and the DF_Player library to select one (of thousands) of the MP3 files on the SD card to play.  Plus it outputs a stereo signal at headphone-driving levels without needing an external amp.   It's my primary stereo now, with a music collection of several thousand songs/pieces on a 32Gigabyte SD card.   If used without a microprocessor, it will increment/decrement song/volume according to the long or short presses on a pair of buttons (not unlike the Chinese documentation indicates on the device referred to in the above post).

Last Edited: Wed. Apr 3, 2019 - 10:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have not heard of a TF card in years.  Surprised some still call them out.

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


ISR(TCA0_OVF_vect){ //TCA0_OVF_vect

	slaveSelect();
	SPI0_Write_Opcode(0x03); // read opcode
	SPI0_Write_Opcode(0x00); // MSB address
	SPI0_Write_Opcode(0x00);
	SPI0_Write_Opcode(0x00); // LSB address
	uint8_t data = SPI0_exchangeData(0x00);
	slaveDeselect();
	DAC_Write(data);
	//if (address < 0xffff){
		//address++;
	//}
	//else {
		//address = 0x0000;
	//}
	TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;

        reti();
}

That is very bad news indeed! I'm totally astonished that this software actually does anything when you have a reti() in an ISR() ?!?

 

I'm guessing you didn't read the user manual?:

Returns from an interrupt routine, enabling global interrupts. This should be the last command executed before leaving an ISR defined with the ISR_NAKED attribute.

perhaps that does not make the point strongly enough but what it means is "ONLY with ISR_NAKED - it you use it elsewhere it will be totally fatal".

 

The whole point is that when you write:

ISR(vect) {
    // stuff
}

then the '}' already implies "RETI" but it will only do that RETI once all the stack is rebalanced. If you use:

ISR(vect) {
    // stuff
    reti();
}

then on entry the prologue will push various things to the stack but before this code gets the chance to reach the epilogue where those things on the stack are then removed to rebalance things the reti() means "return early whatever might still be on the stack". So each time the ISR is called it will leave some stuff on the stack. After a few interrupts the stack will work its way back through memory until it crashes into the end of .bss or .data and then things will go seriously wrong!

 

What gave you the idea that ISR()s should have reti() in them??

 

PS forgot to say that reti() is, of course, OK in an ISR_NAKED because the "naked" means "no prologue, no epilogue". However, because ISR_NAKED does not save the registers used or protect SREG then if you use ISR_NAKED and reti() then YOU have to be sure to save SREG (if touched) and save any registers that must be preserved - that can be very difficult to achieve as you can't easily know which opcodes or registers the compiler may use so you don't know if it might touch SREG or which registers. The manual does say this:

 

 

But in this example they have cleverly picked a C statement that is known to use "SBI" which is an opcode that does not affect SREG. However even this example fails if that code is built with -O0 optimization!

 

(your TCA_OVF interrupt is so complex it must touch SREG and it almost certainly corrupts a number of registers that must be preserved too)

 

Last Edited: Thu. Apr 4, 2019 - 10:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for all of the feedback! A lot of the things mentioned here I have seen before but i decided against them because I need something that will play a random section (or in sequence) when a button is held. There is absolutely no human intervention to the device. It's more of a gag gift. hold a button, play a track, wait for release, repeat.

 

about the reti();

I did read that in the manual but that whole section was really confusing. This is my first time ever using interrupts so I was learning from basically nothing. Without the reti(); it appeared to do nothing, but there could have been something else timing wise that was the problem. For my application, is there a benefit to using either the ISR or the ISR_naked? I would like to make my code as efficient as possible. Preferably I would read the data sequentially and not have to write the start address every time but im afraid it might leave little pops from when the addresses have to be resent. The time period in that space is greater than 125us or 8000Hz like im looking for. May just have to settle with ever so slightly less. The chip does run at 20Mhz but not on 3.3 volts and I need to interface the 3.3v flash. Maybe a dumb question but will the avr still output 3.3v if its running on 5?

AVRs, Electronics, ESK8 boards, CNC, 3D Printing, LASERs, and Tesla Coils.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Maybe a dumb question but will the avr still output 3.3v if its running on 5?

The output, high, state of the I/O pins will be about the Vcc level.

 

If you run your chip on Vcc= 5V, then the I/O pins will output 5 V.

 

If you have nice audio data then you shouldn't have clicks and pops in the output.

 

You should run the output through a Low Pass Filter, then to your audio amplifier and speaker.

 

Typically, you might run a Timer/Counter in CTC mode, so that it generates an interrupt at the desired sample playback rate.

 

Within the ISR you simply load the new (next) value into your DAC hardware.

 

With a clean audio input the signal level won't change that much from sample to sample, and hence no clicks and pops.

 

Then, either within the ISR before returning you can get the next, upcoming, DAC data value, and have it ready, or you can set a flag and within your Main Loop you get the next data value ready, and clear the flag.

 

If the micro isn't doing much else then doing the lookup within the ISR isn't a bad approach, (although the majority of Freaks will tell you keep the ISR as short as possible).

In this case, if you aren't running a User Interface, a display, and a bunch of sensors or incoming data, it doesn't matter if your micro spends 5 % of its time within the ISR and 95 % in the Main Loop, or 15 % in the ISR, and 85 % in the Main Loop. 

 

Some of those eBay modules are incredibly cheap!, but I get it, if you want a commercial product you need a reliable supply chain over which you have control.

Building your own is then a reasonable decision.

 

Do your high level design before you get tied up in ISR's and code.

 

Which micro?

What Vcc?

Internal RC Osc or external resonator, (or external Xtal), at what clock frequency?

External memory options?

At what operating voltages?

Amount of memory needed?

DAC?  (On chip, PWM on I/O Pin, External DAC, etc.)?

DAC LPF, (Simple RC, Active filter and Amp rolled into one?...)?

Audio output device, (Speaker, earphone, piezo, etc., and hence its driver options)?

Audio Amp, single transistor vs Class D chip?

Power Supply, Battery types, battery connectors, etc?

Reverse polarity protection, Y/N?

How is the audio data initially to be provided to the device?

 

So many questions, and none of them have to do with code!

 

Sort out the high level design, then tackle the code!

 

JC

 

Edit: Typo

 

Last Edited: Thu. Apr 4, 2019 - 10:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As another example, Search for Atmel Butterfly.

 

The Butterfly was a credit card sized Mega169 demo board, (The M169 has a glass GLCD driver module, NOT what you need).

 

The point of the research, however, is that it was a small PCB with a coin cell that played any of several songs on a small speaker, (albeit low quality audio).

 

There are also many other projects for micros playing audio which you can study to see how their designers tackled various aspects of the project.

 

JC

 

Edit:

 

BTW, Welcome to the Forum.

Last Edited: Thu. Apr 4, 2019 - 10:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bears0 wrote:
For my application, is there a benefit to using either the ISR or the ISR_naked?
After about 10 years of experience and when you really have an extraordinarily strong reason to do it then one day it's just vaguely possible you might need to use ISR_NAKED. Until that day forget you ever saw it (and reti()).

 

The tutorial forum here is full of examples showing how the ISR()s in avr-gcc can be used (ADC, UART, timers etc). Suggest you read a few of those - the none use of ISR_NAKED will be fairly obvious by its total absence!

Bears0 wrote:
I would like to make my code as efficient as possible.
Well one thing that hit me immediately was that 

	SPI0_Write_Opcode(0x03); // read opcode
	SPI0_Write_Opcode(0x00); // MSB address
	SPI0_Write_Opcode(0x00);
	SPI0_Write_Opcode(0x00); // LSB address

happening at the top of the ISR. Normally you would set up a memory chip in such a way that it was in some kind of auto-increment/continuous read mode so the ISR would do nothing more than one SPI operation to "read next byte". You wouldn't be sending addressing stuff in every interrupt. Also it does not make much sense to me anyway. It seems that in every interrupt you are using that addressing stuff to go back to location 0x0000 so isn't the ISR going to send the exact same byte every time??  If it does that no wonder you don't hear anything! Sound works by variations in the sound pressure - constant sound pressure is effectively "silent".

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Clawson, the reason its all zeros there is because that was an older version of the code where I was testing to see if i could at least get a value to the DAC. I didn't upload the new code because it was more of a test and not aimed toward the final goal. I did at one time have a 16bit variable that I incremented every time but I wasn't sure if my bit manipulation was doing what it should. (Im sending the most significant bit first, but I didn't know whether the compiler would look at the first 8 bits or the last 8).  Then I had two variables. One incremented every ISR() and the other incremented when the other reached max and was set to zero.

That's when I saw I was getting a waveform but it looked a little like this:

Distorted Sine wave

I was using the first address byte as "partitions" if you will to select which part of the chip I wanted to play audio from. As I mentioned above, I have changed the code so much because, to be honest, I have very little experience.

 

I know that the SPI chip has a sequential read function (the 03h opcode).

The reason I'm worried about pops is because the sequential read only goes to the end of the page and loops back around. only 256 bytes. The problem is that it takes more than one standard 8000Hz sample rate time period to send all of the address data. Basically It would be a 8khz sample rate until it had to switch to the next page, Which would take about 1.4 times the period. (it's set to be really slow in my code for now but I plan to speed it up).

 

I do have the basic functional design finished but it may have to change based on my coding ability here.

 

2 questions.

If I increment a byte to 0xff and increment it one more time, what happens? does it go back to zero?

and is it necessary to disable the DAC before writing data. I would like to skip this time wasting step if it's not necessary.

AVRs, Electronics, ESK8 boards, CNC, 3D Printing, LASERs, and Tesla Coils.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bears0 wrote:

If I increment a byte to 0xff and increment it one more time, what happens? does it go back to zero?

Yes.

 

You can clamp the result to the maximum using a

function such as this:

 

uint8_t add_clamped (uint8_t x, uint8_t y)
{
    uint8_t sum = x + y;

    if (sum >= x) {
        return sum;
    }

    // the addition overflowed so
    // return the maximum value

    return 0xFF;
}

 

--Mike

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

8ksps 8 bits audio, is pretty easy.  It means your software needs to be able to deliver a byte to the DAC every 125 microseconds, that is sure plenty of time for an AVR core running at 16MHz, bare numbers could show that the AVR could run around 2 thousand instructions in between each byte delivered to the DAC... that is even ridiculous idle time.

 

The more easy and cheap way for the DAC is a simple 8 bits port of the AVR with 16 resistors connected to the port, in the R-2R fashion poor man DAC, it works pretty well for audio, since the small errors caused by the resistors values become irrelevant, you can not really ear such tinny differences.  If you don't know what R-2R is, Google for it, there are plenty of information at the Internet.  Of course, anyway, you will need a small audio amplifier chip to produce an audible sound in some kind of speaker.

 

So, everything your code must do is just read from the Flash and OUT to that DAC port, nothing else.

Of course, you can this "timing" to generate the 8ksps audio by interrupts or by time delay, doesn't matter, what is easier for you.

 

Reading your texts, it becomes clear that you have not so much experience with programming, no problem at all (we all were born knowing profusely about AVR programming... right?) so I would recommend leaving the interrupt for later.  First make your flash reading works nicely, do a time delay of 120 microseconds (more or less, doesn't really matter), OUT the byte to the port and be happy.

 

Even running the AVR at 8MHz (internal clock) is pretty neat, plenty of time.

 

For example, the following (assembly) generates a delay of 120us when running at 8MHz

 

Delay120us:   ldi  r18, 2
              ldi  r19, 60
A1:           dec  r19
              brne A1
              dec  r18
              brne A1
              ret

 

So, just read a byte from flash, OUT to the DAC port and call the (Delay120us) above routine to waste 120us... repeat.

Note that R18 and R19 will be used in such delay, if you need them somewhere else, push and pop them into the delay routine.

 

Delay120us:   push r18
              push r19
              ldi  r18, 2
              ldi  r19, 60
A1:           dec  r19
              brne A1
              dec  r18
              brne A1
              pop  r19
              pop  r18
              ret

 

Later, after everything is working nice, then worry about the interruption every 125us.

 

This kind of programming is quite simple, you just need to learn about it, step by step.

Don't be ashamed to ask for help. You will be surprise how many people want to help.

As a last recommendation, go for assembly language, it is much quick and simpler to do what you need.

 

And yes, flash page reading is quite simple, you can do sequential, just read one 256 bytes page, send the address to select the next page, sequential reading, read it all, next page... easy.

 

Many years ago, before AVR, when 8051 microcontroller was king, I packed more than 40 seconds of very good spoken words and numbers into the 64kBytes (27C512) Eprom, including more than 9k of assembly code.  It was a very delicate and fine tuning software.  The sound to be produced by the "S" of "seven" for example, was the same of the "six", the "f" of four the same of five, the "ee" for three was also used in "six", and so on.  Lots of work to find all this little pieces of audio, addresses, quantity of bytes, etc.  I even used variable output sample rate for high frequency sounds and very low for low frequency, saving bytes and increasing quality wherever it was possible. The original audio recording was my wife's. The result was very good indeed, and that thing was even generating DTMF frequencies for phone call...  old times. 

Wagner Lipnharski
Orlando Florida USA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I now have audio playing through the DAC! Thanks everyone for all of your help. I ran into another snag regarding the sleep function and limited pins but I think it would be better to post another question since its not related to this one. (unless its been posted before)

 

To get it running I just got rid of the interrupt all together and put a small delay in the code. (Thanks for the "no reti(); tip! that was really helpful) It works well enough for me and the sample rate is 16khz. The sound quality is well above what I was expecting.

 

Just a guess, but I think there was also another bit of code that was setting the ouptut of my DAC pin high and since I used the reti(); it was randomly restarting my code and that would explain the weird blips in the output.

 

Thanks again everyone.

AVRs, Electronics, ESK8 boards, CNC, 3D Printing, LASERs, and Tesla Coils.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bears0, keep posting your results, it is very interesting and rewarding to see the learning progress in action.

Wagner Lipnharski
Orlando Florida USA

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It may be pertinent to post the old mantra, or a variation of it:

 

Cheap, good, fast ... you get two out of three if you are lucky.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry it's been a while. I came up with a solution to get around the lack of pins, however, I ran into other problems where the flash chip didn't always initialize. I think it has to do with the fact that I am just now learning about bypass capacitors. If you don't know what they are, Dave Jones has some good videos on his EEVBlog. I have abandoned the AVR design because the sound quality is pretty bad with only 8 bits. I kindof expected it but it was way worse than my expectations. Some parts were almost completely incomprehensible.

 

To get around the lack of pins I just used a really small capacitor and a really high value bleed resistor. A click of a button pulled the pin low for a split second but the resistor and the cap were the perfect size so they didn't affect operation. (The problems mentioned earlier were there before I added the cap)

 

I did find a really cheap MP3 player chip though. The WT588D. Super cheap and able to drive a speaker from PWM, or DAC with external amp.

 

Thanks everyone for your help.

 

AVRs, Electronics, ESK8 boards, CNC, 3D Printing, LASERs, and Tesla Coils.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Some parts were almost completely incomprehensible.

Glad to hear you have a solution in mind.

 

For the record, however, if your audio is incomprehensible then it is a firmware issue, and not an AVR issue.

There are many AVR audio projects around that work well.

 

Take care,

 

JC

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bears0 wrote:
I kindof expected it but it was way worse than my expectations. Some parts were almost completely incomprehensible.
I can't help thinking you were doing something wrong then. I have played 8 bit 8kHz audio using AVR and the sound quality was somewhere between "telephone" and "FM radio". Every note and syllable was perfectly audible.

 

(caveat: I am an old man with an old man's hearing range - I am not a bat)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would be more inclined to believe it was a hardware design issue. I didn't have bypass caps on my micro-controller. Just the amplifier because it was specified in the datasheet. I don't know if the flash chip needed them either but I would have put them just to be safe if I knew about them before designing the board. I have very little experience doing this, I just have the ability to pick things up fairly quickly but sometimes I miss stuff. I saw several people online that were describing the same problems I was having with the amplifier I was using. It could be due to some component mismatch or something but I looked everywhere and couldn't find anything that helped with the problem. I was also wondering if the weird noise on certain parts was because of how I was driving the DAC. I tried to read up on them and get as much info as I could but nothing really explained the samples per second and how all of that worked. I understand that is the maximum rate at which the DAC can output accurate voltages but it didn't really explain whether the peripheral clock for the DAC had to be below that or just the rate at which the binary value changed. Maybe it was because I was using the voltage pump? Or maybe because I wasn't? I can't really remember which one I used off the top of my head. I'm pretty sure i tried it both ways though. I also changed the value of the DAC reference. It helped a little when I lowered it, but not much. I could try using PWM in the future if I decided to revisit my old design.

AVRs, Electronics, ESK8 boards, CNC, 3D Printing, LASERs, and Tesla Coils.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Essentially all digital electronics needs by-pass caps.

It is just a "given", and assumed that the designer knows that.

 

If you buy a new tire for your car the paper that comes with it doesn't tell you that you need an air pump and lug nuts, its a "given", and assumed that you know that.

 

One can gain much of that knowledge from on-line web site, on-line articles and projects, text books, (e.g. The Art of Electronics, etc.), and hobbyist electronics magazines, (which are rapidly becoming a thing of the past). 

You can also learn for sites like this!

 

If you have never had a project that failed then you really haven't pushed yourself very much.

I'd guess that most of the posters here could recount tales of failures, (or learning opportunities...).

 

If you wish to continue this this particular project then you will have to post a complete and accurate schematic of your project.

 

A $15 (USD) "toy" O'scope to "see" the signal would also be incredibly useful, both for this project, and future ones, at next to no cost

(Free shipping as well!)

 

Finally, switching between the DAC and PWM won't solve anything.

Typically the DAC is the easier approach, anyway, (IMHO).

Both require a Low Pass Filter on the output.

 

JC