2
\$\begingroup\$

I am designing an application where a microcontroller should output audio from an external (flash) memory, and at the same time, write some data to a display. More precisely, the audio is from a .wav file sampled at 44.1 kHz (downsampled to 8 or 12 bit depth) but only the raw PCM data is stored in the external memory (most likely communicating via a SPI). The display is using a SSD1309 chip, and either I2C or SPI will be used for this communication path.

I have narrowed down the microcontroller selection to either an ATmega328, a STM32G071GB (both of which I already have available), or a RPi 2040 (as it is still in stock at my preferred supplier). My question now is not which microcontroller I should use, but more general, how would/could I decide without testing all three options in real hardware, based on the requirements above.

I am guessing the ATmega will not be a good choice due to not having DMA, which could be a problem when reading the memory and writing to the display at the same time. My current favorite is the STM32, as it has DMA, a dedicated DAC (otherwise I would use a PWM output), and seems like a more low power solution than the RPi 2040 (the device will be battery powered).

But as these are all guesses, is there a way/a process to somehow make an informed decision before committing to one chip and finding out it was a poor choice?

\$\endgroup\$
4
  • \$\begingroup\$ in general, estimate where the bottlenecks are, estimate how fast the bottlenecks are, and see if they're all fast enough. Audio has a real-time requirement so your bottlenecks there will be fetching the data from the external memory and converting it to analog - but not at terribly high speeds - but the ATMega is slow - but still something to check. Will you need to buffer anything? If using an SD card with a filesystem, you might need some buffers which might exceed the ATMega's internal RAM. \$\endgroup\$ Commented Jul 11, 2022 at 12:19
  • \$\begingroup\$ Same for the display. Assuming the display doesn't have to be terribly fast your biggest constraint there could be memory: do you need to store any large pictures? If so, again, the ATMega's small RAM might rule it out. \$\endgroup\$ Commented Jul 11, 2022 at 12:20
  • \$\begingroup\$ Is the display one of those I2C/SPI ones where you send it commands, or does it need to be fed with a constant stream of pixel data like a computer monitor? If it's the second type, you have substantial requirements for generating the pixel stream on time. \$\endgroup\$ Commented Jul 11, 2022 at 12:21
  • \$\begingroup\$ Choose the chip with the best documentation. \$\endgroup\$
    – Bryan
    Commented Jul 11, 2022 at 15:59

2 Answers 2

2
\$\begingroup\$

Start by lining up some rough requirements:

  • Execution speed: This is a pretty standard MCU application, it doesn't sound like you'll have tough real-time requirements nor a need for a lot of processing power.

  • RAM: Having a bit of RAM to store all the data in between is nice, and whenever dealing with displays you'll typically want to store a copy of what's shown on the display in RAM too. Some 10-20kb will likely be more than enough.

  • Flash: doesn't sound critical unless you wish to store a lot of graphical images for the display in flash.

  • Serial com: As you have noted, a part with DMA would be nice, especially if it has 2 separate SPI peripherals. Muxing between display and memory on the same SPI bus will be unnecessarily complex and should be avoided.

  • DAC: a DAC peripheral might be nice but I wouldn't limit myself only to parts with DAC, since it is simple enough to use PWM instead.

  • Logic levels: Not mentioned so I'll assume 3.3V or 3.3/5V tolerant. Ideally Pick something that doesn't require buffer circuits in between.

  • Low power: in case this is a concern, there's various degrees of what one would considered "low". Utilizing sleep modes and picking a low clock frequency will get you quite far.

    Also low power tends to rule out all the old 8-bitters, because while their overall current consumption might look very nice, their current consumption per MIPS is horrible and that is what matters. The longer the part has to stay awake doing calculations, the more current it consumes. So if some slow poke AVR takes 100 cycles longer than an ARM to do some 32 bit calculation, it doesn't matter if the ARM draws twice the current while awake, it is still way less current consuming.

  • Resolution: you don't specify it other than sample rate, so I can't tell. But ADC/DAC etc resolution might matter (as will the accuracy of resistors etc).

  • Tool chain support. Worth looking into, especially if you are used at one particular tool chain or have purchased one before. These days everyone ought to have an ARM tool chain that they like, for example, so that it may be reused in multiple projects.

Based on this, the most "exotic" requirement is a part with 2 SPI peripherals and DMA. Not particularly exotic at all, but let that be the first thing to look for. STM32G071 seems to fulfil all of them. So given the choice between STM32G071 and the other completely unsuitable hobbyist stuff you mention, it's a no-brainer, go with STM32. If the choice was between STM32 and some other Cortex M, then it would be a harder choice.

The current component shortage crisis should be your next concern though - ST has handled it very poorly compared to some other vendors. Their reputation has taken a serious blow and I would think twice before designing them in now - you should make serious comparison against equivalent parts from the competition, so that the next time the component market takes a sneeze, you won't be stuck with MCU designs that can't be produced in the real world.

However, at the hour when I'm writing this at least, STM32G071 is widely available from a lot of vendors. Maybe consider it's cousins STM32L071 etc if current consumption is a priority. Those I've worked with personally and they are nice, apart from the fact that ST has been giving me the middle finger in reply when I've tried to purchase them during the past 2 years.

The ST tool chain and libs are horrible in my personal opinion - but that's of no concern since I use neither, but commercial tools and my own libs. Also, ST is far from the only silicon vendor with horrible free tools and libraries, you do get what you pay for.

\$\endgroup\$
1
\$\begingroup\$

If in doubt, better to air on the high side. You can always buy more CPU cycles -- they're incredibly cheap these days -- but you can't squeeze a single extra cycle out of a too-small one, no matter how deeply you optimize.

And, once you've written most of the thing for one platform, then find you need to port it to something else -- that's wasted time developing for both.

As for estimation, that requires a lot of experience. There's no simple answer here, because you have to know precisely what it is you're doing, and how the platform will handle it. AVR for example, you can count cycles and know precisely how long a function will take to execute -- but this is only after you have the machine code output from the compiler (or hand written assembler!). This is true for most lower-tier MCUs.

Whereas, if it has instruction/data caches ("flash accelerator" etc.), all bets are off -- but some average figures may prove accurate/reliable enough to work with. So, most chips under 60MHz or so are easy enough in this way (counting cycles), and things just get worse as you go to higher performance chips, with caches, pipelines and so on.

For example, say your audio update loop just pulls values from ROM and dumps it to a -- what are you using for DAC anyway? Which, keep that in mind as well: hardware can offload many challenges you'll otherwise have to solve with yet more software; for instance, having an internal buffered DAC would be ideal, unbuffered okay, external SPI poor (will you have to interleave writes with memory reads? can you use two SPI ports independently?). Anyway, if you're doing that and nothing else, I would guess that will be very easy on mega328. Probably wouldn't run with optimization disabled, but GCC should do well enough with it on. Maybe Arduino library stuff is fast enough as well (well... maybe).

But say you wanted to add something seemingly simple, like a volume control -- now you need to receive the value from memory, and multiply by a parameter, and this needs to be done in fixed or floating point -- which if you don't know how to do fixed point, you might opt for the "simpler" floating point method, but this will take hundreds of cycles to complete using built-in (library) function calls -- AVR has no hardware floating point support. And with a maximum 32MHz clock rate*, taking hundreds of cycles leaves hardly any time to get that data in and out. (It might actually manage, if just the one float op; the libraries perform well for what they are. But just a couple ops and you're easily over the limit.)

*Mostly for XMEGA series AVRs, I think. Or if you try overclocking others, which, eh, it's been tried, it can work; if maybe not reliably.

Another point of note: programming languages make no consideration of execution time. C language for example guarantees that valid code will do what it's supposed to**, not how long it'll take to do it on any particular platform. Or that it'll even fit on a platform at all -- you can easily write an interrupt service routine (ISR) that takes all the time in the world, freezing up the system; it's not up to the language to tell you you're doing something "wrong" like that. Or if you write 20k of program code and try to stuff it on a 8k platform; the compiler can do it just fine but good luck loading it.

**For some values of "suppose". You'll probably not be using fully compliant, no-undefined-behavior code on an embedded platform. Undefined behavior like overflow and bit shifting tricks are great tools, but are hard to use in a universal manner. So, code tends to be idiomatic with these sorts of tricks, which -- in addition to differing hardware interfaces -- also makes porting difficult.

So, in summary:

  • Know exactly what you want to do. To the finest grained level possible: not just statements, but individual arithmetic operations even.
  • Know how to do it. Understand how the compiler will handle those statements, and how long they will take to execute on a given platform.
  • Confirm execution time by writing in checks (e.g. set/clear an IO pin at the start/end of the function; mind this won't include pre/postamble code i.e. PUSH and POP, etc.) or inspecting the machine code output (assumes you know the instruction set for the platform!).
\$\endgroup\$
2
  • \$\begingroup\$ Cortex M doesn't have cache until M7 I believe (which sounds like complete overkill for this application). Sure, 8 bitter assembler like AVR is way easier to read than ARM assembler (at least to me) but that doesn't mean that you won't be able to get deterministic cycle count out of it. Rasp Pi is a completely different story of course, being a PC computer. \$\endgroup\$
    – Lundin
    Commented Jul 11, 2022 at 13:51
  • \$\begingroup\$ " You'll probably not be using fully compliant, no-undefined-behavior code on an embedded platform." Uh... strictly conforming programs, no implementation-defined behavior, indeed won't happen. No undefined behavior though... that's absolutely mandatory in a professional context. The presence of UB can always be explained by programmer inexperience. When in doubt, use MISRA C. Anyway, none of that affects the choice of MCU. \$\endgroup\$
    – Lundin
    Commented Jul 11, 2022 at 13:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.