Converting to Cortex M0 ASM - Page 1
Forum section: electronics / general

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 18, 2022, 12:37:35 pm
content: Hi,I want to do this Saturation math in Cortex M0 ASM language to get maximum speed ( and I have very little experience in ASM for ARM Cortex M), it's from this topic,https://www.eevblog.com/forum/microcontrollers/mp3-decoding-on-stm32g070rbt6/The code is fine for Cortex M3 upwards, But I need a help to reimplement it for M0.Code:static __inline__ __attribute__((always_inline)) int32_t minimp3_clip_int16_arm(int32_t a){int32_t x = 0;__asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a));return x;}

author: Silenos
author_rank: Regular Contributor
timestamp: June 18, 2022, 12:54:22 pm
content: No saturation instructions in M0, write it yourself in C.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 18, 2022, 01:22:39 pm
content: I know that, So I wanted a clever fast way of doing it in M0

author: voltsandjolts
author_rank: Unknown
timestamp: June 18, 2022, 02:03:05 pm
content: If you write it in C, what does the assembly listing look like for that?

author: eugene
author_rank: Frequent Contributor
timestamp: June 18, 2022, 02:17:49 pm
content: I'm not even close to expert in assembly, and know almost nothing about Cortex assembly. Having said that, most of what I do know about assembly I learned by studying the output of C compilers. In the rare cases that I felt a need to use asm over C for performance reasons, I started with C and optimized the compiler output.

author: brucehoult
author_rank: Super Contributor
timestamp: June 18, 2022, 02:24:09 pm
content: Quote from: ali_asadzadeh on June 18, 2022, 01:22:39 pmM0 doesn't do clever.Code:int ssat(int n, int sat){int max = 1<<sat;if (n < -max)n = -max;else if (n >= max)n = max-1;return n;}You probably can't do better on M0.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 18, 2022, 02:54:59 pm
content: The straightforward C implementation,Code:#include <stdint.h>int_fast16_t int16_clip(int_fast32_t v){return (v < -32768) ? -32768 :(v > 32767) ? 32767 : v;}usinggcc -Os -march=armv6-m -mtune=cortex-m0compiles toCode:int16_clip:movs r3, #128lsls r3, r3, #8cmp r0, r3blt .L2ldr r0, .L4.L2:ldr r3, .L4+4cmp r0, r3bge .L3movs r0, r3.L3:bx lr.L4:.word 32767.word -32768which seems to be acceptable to me on Cortex-M0. I am not aware of any tricks related to saturating signed integer math; there are some for saturatingunsignedinteger math, though, but experimenting with Compiler Explorer indicates that it won't help here – the complexity is greater than above if one first branches depending on the sign, and then does unsigned integer saturation in each branch.

author: brucehoult
author_rank: Super Contributor
timestamp: June 18, 2022, 03:08:34 pm
content: Quote from: Nominal Animal on June 18, 2022, 02:54:59 pmIt's pretty horrid that its loading the result values from memory instead of reusing the values it already has.I think mine compiles to better code...https://godbolt.org/z/voqEsGbqeThere's only ever one taken branch, and no memory operations.

author: Jeroen3
author_rank: Super Contributor
timestamp: June 18, 2022, 04:07:37 pm
content: What does the arm cmsis dsp library put in for cortex m0?https://github.com/ARM-software/CMSIS/blob/f2cad4345783c948ed4a7f5cdb02cdc0856366f1/CMSIS/Include/arm_math.h#L663

author: hans
author_rank: Super Contributor
timestamp: June 18, 2022, 07:06:46 pm
content: That library code looks absolutely horrible. Why the for-loop with multiply-by-2 when you could use an arithmetic shift?Anyhow, I don't think you can do better than the code from bruce. The m0 is quite a simple CPU, and there isn't anything simpler than an if-else bounds check.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 18, 2022, 08:23:06 pm
content: Quote from: brucehoult on June 18, 2022, 03:08:34 pmI agree, although the difference is lost whenever one supplies a compile-time constantsat:https://godbolt.org/z/rvY7bdrcn.I looked atthe Cortex-M0 Instruction Set, and remembered that while it does not have any conditional assignment operators, it does have 16-to-32-bit sign extension instruction (sxth). This means that((int32_t)n != (int32_t)(int16_t)n)gets compiled to a pair of instructions, one to sign-extend the parameter, and the other to compare the two; and this comparison is only true if the value does not fit in a signed 16-bit integer. In other words,https://godbolt.org/z/cYWdPf3zb:Code:#include <stdint.h>int_fast16_t saturate16(int_fast32_t v){if ((int32_t)v == (int32_t)(int16_t)v)return v;const int_fast16_t m = 32768; // constexpr int_fast16_t m = 32768; for C++if (v > 0)return m-1;elsereturn -m;}which compiles (gcc -Os -mcpu=cortex-m0 -mfloat-abi=soft) toCode:saturate16(int):sxth r3, r0cmp r3, r0beq .L5asrs r3, r0, #31subs r3, r3, r0asrs r3, r3, #31uxth r0, r3ldr r3, .L7adds r0, r0, r3.L5:bx lr.L7:.word -32768Whether this is faster than brucehoults, I dunno; but I do expect it to be in practice.Edited: It looks likeCode:#include <stdint.h>static inline int_fast16_t u32767(void){return 32767;}int_fast16_t saturate16(int_fast32_t v){if ((int32_t)v == (int32_t)(int16_t)v)return v;elsereturn (int16_t)((v >= 0) - 1) ^ u32767();}is even faster, as it compiles to (assuming zero wait states, according toARM Cortex-M0 Technical Reference)Code:saturate16:sxth r3, r0 ; 1 cyclecmp r3, r0 ; 1 cyclebeq .L2 ; 2 cycles if taken, 1 cycle if not takenldr r3, .L3 ; 2 cyclesasrs r0, r0, #31 ; 1 cycleeors r0, r3 ; 1 cycle.L2:bx lr.L3:.word 32767taking 4 cycles if the value is withinint16_trange, and 7 cycles if clamped; but it does get into the obfuscated C code territory...I did verify it works, of course. (The expression(int16_t)((v >= 0) - 1)evaluates to0xFFFFifvis negative, and to0x0000otherwise. Exclusive-OR that with 32767, and you'll get0x8000 = -32768ifvis negative, and0x7FFF = 32767otherwise.)If you want to avoid the memory load, you can replace the constant with inline assembly, e.g.Code:static inline int_fast16_t u32767(void){int_fast16_t value;asm volatile ( "mov\t%0, #128\n\t""lsl\t%0, #8\n\t""sub\t%0, #1\n\t": "=r" (value));return value;}in which case you replace the 2-cycle memory load (slower, if you have wait states when running code from e.g. Flash) with three single-cycle instructions, and end up withCode:saturate16(int):sxth r3, r0 ; 1 cyclecmp r3, r0 ; 1 cyclebeq .L2 ; 2 cycles if taken, 1 if not takenmov r3, #128 ; 1 cyclelsl r3, #8 ; 1 cyclesub r3, #1 ; 1 cycleasrs r0, r0, #31 ; 1 cycleeors r0, r3 ; 1 cycle.L2:bx lrwhich has no memory accesses and only dirties one register, takes 4 cycles for valid values and 8 cycles for clamped values, when inlined (i.e., ignoring function call overhead).

author: abyrvalg
author_rank: Frequent Contributor
timestamp: June 18, 2022, 09:34:39 pm
content: Code:saturate16:ASRS R1, R0, #16 ; test if high 16 bits are 0, keeping the sign bitBEQ exit ; are 0 -> return the valueBMI ret_min ; are not 0 and value is negative -> return -32768; are not 0 and value is positive -> return 32767MOVS R0, #0MVNS R0, R0 ; FFFFFFFFLSRS R0, #17 ; 7FFFBX LRret_min:MOVS R0, #1LSLS R0, #15exit:BX LRPerhaps the 7FFF value generation could be shortened with some better trick

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 19, 2022, 02:23:16 am
content: Quote from: abyrvalg on June 18, 2022, 09:34:39 pmDid you check it? GivenR0 = 0xFFFF8888 = -30548, afterASRS R1, R0, #16we'll haveR1=0xFFFFFFFF,Z=0, andN=1, so theBEQbranch is not taken, but theBMIis (becauseN=1), and your function will return 0x00008000 = -32768 asint16_t; while it should return either 0x8888 (int16_ton Cortex-M0) or 0xFFFF8888 (int_fast16_tandinton Cortex-M0). Unless I'm interpreting the code wrong, of course.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: June 19, 2022, 08:32:39 am
content: You are right, my bad

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 19, 2022, 09:42:46 am
content: Thanks guys for the feedback!

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 19, 2022, 01:21:49 pm
content: I have used it on a simulation project in Keil, decoding a 320K sin wave MP3 file, it's working, and I have used keil states counter in simulation to count the clock cycles for decoding one frame, the results shown that it would impact performance around 1% for M3 and around 5.5% on M4, But surprisingly the decoder would use more clock's for a M7 part, if Keil state counter is working properly!Check several frame decode times!

author: Jeroen3
author_rank: Super Contributor
timestamp: June 19, 2022, 02:44:23 pm
content: You really have to be careful with these kinds of functions. Especially copied randomly from the internets. The off-by-ones are everywhere.For example, bruces example doesn't do saturations of negatives given n=31. Instead, it will always output 2147483647.It's really best to stay with what tested code from libraries in my opinion. It also maintains portability accross the platform.The old "I can do better" is the best waste of time in the life of the programmer.Quote from: hans on June 18, 2022, 07:06:46 pmGood question...

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 19, 2022, 06:59:13 pm
content: Bruce's suggested function yields pretty much an "optimal" solution for RISC-V, incidentally. And as always (and as you can see from the link below), keep in mind modern optimizing compilers will pretty much compile this kind of functions as though they were macros as long as they are in the same compilation unit, inlining them and simplifying them if you pass constants. (And no, the 'inline' qualifier is not required here, it won't make a difference.)https://godbolt.org/z/a3W3EMx7EThe only "weird" thing from the output of GCC here is the last 'li a0,-32768'. Why not just 'mv a0, a5' ? Since the immediate is wider than 12 bits, the 'li' will yield two instructions instead of just one for the 'mv' solution. If Bruce has any idea why GCC emits that...

author: brucehoult
author_rank: Super Contributor
timestamp: June 19, 2022, 09:21:22 pm
content: Quote from: Jeroen3 on June 19, 2022, 02:44:23 pmFor n>=31 the 1<<n overflows, obviously. To negative for n==31 and to 0 for n>31. It's intended for smaller n, and if I was being paid to put this in a library or program it would be documented as such. And have an assert on n. And if the spec said it has to work for n or 31 and 32 then it would have bigger and slower code.The OP wanted code for n==16.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 19, 2022, 09:25:43 pm
content: Quote from: SiliconWizard on June 19, 2022, 06:59:13 pmMine compiles toCode:saturate16(int):slli a5, a0, 16srai a5, a5, 16beq a5, a0, .L2li a5, 32768srai a0, a0, 31addi a5, a5, -1xor a0, a0, a5.L2:retwhich is shorter in the common case, input within the allowed range (two instructions and a taken jump, as opposed to two instructions and two taken jumps in Bruce's version), but longer in the clamping case (albeit with just one load of a 16-bit immediate instead of three).However, I would not like to maintain code that had something like my function (or worse, the equivalent conditional expression,((int32_t)v==(int32_t)(int16_t)v)?v:(int16_t)((v>=0)-1)^32767) without a good long comment and an alternate version in an#ifndef .. #else .. #endiffor verification and testing. Now, I *have* verified the expression yields the correct result for all 32-bit integersv, so technically it should not need maintenance... but if the clamping limits ever change, mine has to be completely rewritten from scratch. I'm not sure I like it, but if one is counting cycles already, perhaps it is warranted, perhaps not.Quote from: SiliconWizard on June 19, 2022, 06:59:13 pmThe code generated isCode:ssat_16(int):li a5, 32768blt a0, a5, .L7addi a0, a5, -1.L7:li a5, -32768bge a0, a5, .L8li a0, -32768.L8:retbut it could be better: the firstli a5, -32768could be simplyneg a5, a5, and the secondmv a0, a5.This looks to me like a failure to effectively reuse existing register state, something that is kinda-sorta a weak point in GCC; it is known for producing superfluous register moves (especially in earlier versions on register-starved architectures like x86) in similar snippets of code on other architectures, too.

author: brucehoult
author_rank: Super Contributor
timestamp: June 19, 2022, 09:36:37 pm
content: Quote from: SiliconWizard on June 19, 2022, 06:59:13 pmNo one knows the ways of gcc. And especially not on such a small scale of ±1 instruction, given that optimisation decisions are made using heuristics, not by trying every possible way.It's entirely possible gcc doesn't know that "li" can expand to more than 1 instruction and just leaves it to the assembler. It doesn't know the C extension exists. Still, you'd think "reuse rather than rematerialise" would be a good heuristic even when the "reload" is a constant.Clang makes a similar decision here, but the value it generates more than once is +32768, which is a single "lui" (which it shows as such)

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 19, 2022, 09:46:57 pm
content: Quote from: brucehoult on June 19, 2022, 09:36:37 pmOK.

author: brucehoult
author_rank: Super Contributor
timestamp: June 19, 2022, 09:55:03 pm
content: Quote from: Nominal Animal on June 19, 2022, 09:25:43 pmYeah, it's a good idea to see if sign-extending keeps the same value.The slli/srai will work for any number of bits, not only 16 like the ARM sxth instruction (or 8 for sxtb), but it's not a portable thing in C, unlike casting to short and back to int.Or is casting an out of range int to short undefined too?If you really care about an instruction plus or minus then you should probably study your CPU and write it in assembly language. Except that puts you between the rock of calling the assembly language function (more overhead than just running 1 or 2 instructions more than optimal in an inline C function) and the hard place of writing a 10 instruction sequence using the inline assembler which is (I think) more than you should do that way.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 19, 2022, 10:14:42 pm
content: If you're going the assembly way, you better write bigger chunks of code than just the saturation function anyway, to get the full benefit of assembly. IMHO.

author: brucehoult
author_rank: Super Contributor
timestamp: June 19, 2022, 11:24:04 pm
content: Quote from: SiliconWizard on June 19, 2022, 10:14:42 pmYes and no. If it was super performance-critical then you'd just use a CM3 instead of CM0 anyway -- or you're selling bazillions of them and should be making your own chip to minimise BoM count.

author: DavidAlfa
author_rank: Super Contributor
timestamp: June 20, 2022, 12:03:58 pm
content: Some already did the MP3 asm for the M0+:https://forum.arduino.cc/t/fast-maths-for-cortex-m0-cpu-for-mp3-decoder/567795/3

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 20, 2022, 12:33:22 pm
content: Thanks david, But there is no saturation math on this link

author: DavidAlfa
author_rank: Super Contributor
timestamp: June 20, 2022, 01:12:51 pm
content: Weren't you aiming at decoding MP3 on your M0+ STM32G070?As explained inlief's minimp3(Which seems what you're using), it uses floating point, contrary asKeij's minimp3.Forget mp3 decoding in FP on a M0+! You absolutely need a fixed point decoder.Helix Mp3 decoder also uses fixed point, uses about 32KB of RAM after adjusting the MP3/audio buffers.Only needs theseasm routines, which are very similar to the arduino post.Although the guy posted half the code, missing the required tables, etc... what a faggot! Better to do nothing then!Code:#elif defined(ARM_TEST)static __inline__ int MULSHIFT32(int x, int y){int zlow;__asm__ volatile ("smull %0,%1,%2,%3" : "=&r" (zlow), "=r" (y) : "r" (x), "1" (y) : "cc");return y;}static __inline int CLZ(int x){int numZeros;__asm__ ("clz %0, %1" : "=r" (numZeros) : "r" (x) : "cc");return numZeros;}static __inline Word64 MADD64(Word64 sum64, int x, int y){U64 u;u.w64 = sum64;__asm__ volatile ("smlal %0,%1,%2,%3" : "+&r" (u.r.lo32), "+&r" (u.r.hi32) : "r" (x), "r" (y) : "cc");return u.w64;}

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 20, 2022, 01:55:53 pm
content: Quoteminimp3 does it with Fixed point too! It's just way simpler than helix, a single header file

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 20, 2022, 03:16:26 pm
content: DavidAlfa you where right, stupid me just was looking to PCM buffer and the int (missing ASM function in minimp3 ), so please help to port the Helix to M0, maybe we had a chance to use it on this 1$ cortex M0 part (STM32G070RBT6)

author: DavidAlfa
author_rank: Super Contributor
timestamp: June 20, 2022, 04:03:07 pm
content: Check theCMSIS library, usually has some math tricks.Code:/*** @brief Clips Q31 to Q15 values.*/static __INLINE q15_t clip_q31_to_q15(q31_t x){return ((q31_t) (x >> 16) != ((q31_t) x >> 15)) ?((0x7FFF ^ ((q15_t) (x >> 31)))) : (q15_t) x;}Test:Code:int16_t sat16(int32_t x){return ((x>>16) != (x>>15)) ?(0x7FFF^(x >> 31)) : x;}11 instructions:Code:8000480:1402asrsr2, r0, #168000482:13c3asrsr3, r0, #158000484:429acmpr2, r38000486:d003beq.n8000490 <saturate+0x10>8000488:4b02ldrr3, [pc, #8]; (8000494 <saturate+0x14>)800048a:17c0asrsr0, r0, #31800048c:4058eorsr0, r3800048e:4770bxlr8000490:b200sxthr0, r08000492:e7fcb.n800048e <saturate+0xe>8000494:00007fff.word0x00007fffOutput seems correct:Code:IN OUT1 132767 3276732768 3276785000 32767-1 -1-32768 -32768-32769 -32768-85000 -32768Still slower than Nominal Animal's code

author: DavidAlfa
author_rank: Super Contributor
timestamp: June 20, 2022, 04:18:31 pm
content: BTW, what's your real application? If it works at all, the decoder will likely eat 80-90% of the stm32 resources...

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 21, 2022, 06:12:41 am
content: QuoteIt's for a Robot Toy that can talk!These are the ASM functions in Helix,Code:MULSHIFT32(x, y) signed multiply of two 32-bit integers (x and y), returns top 32 bits of 64-bit resultFASTABS(x) branchless absolute value of signed integer xCLZ(x) count leading zeros in xMADD64(sum, x, y) (Windows only) sum [64-bit] += x [32-bit] * y [32-bit]SHL64(sum, x, y) (Windows only) 64-bit left shift using __int64SAR64(sum, x, y) (Windows only) 64-bit right shift using __int64So Beginning with the first one, MULSHIFT32, I think it's the most critical one!So What if I just took the top 16 bits of the inputs & did 16 x 16 ==> 32-bit result ?

author: westfw
author_rank: Super Contributor
timestamp: June 21, 2022, 07:07:02 am
content: QuoteThere's a guy over on the ARM forums that seems pretty obsessed with the idea of getting MP3 decoding working on a CM0, and there have been some interesting optimization discussions on the 32x32->64 bit multiply and CLZ, IIRC.For example:https://community.arm.com/support-forums/f/architectures-and-processors-forum/11313/cortex-m0-m0-m1-32-bit-x-32-bit-----64-bit-signed-multiply/168510#168510

author: DavidAlfa
author_rank: Super Contributor
timestamp: June 21, 2022, 09:44:19 am
content: I forgot. I already said the decoder already uses 31 out of 36KB RAM... But you must also take the data from somewhere. SDcard, SPI flash, USB...? They will also take some ram for buffering.Why not use a cheaper/simpler IC likeAs for a simple test, you can skip asm optimization for now:Code:#elif defined(ARM_TEST)static __inline__ int MULSHIFT32(int x, int y){return (x*y);}static __inline int FASTABS(int x){int sign;sign = x >> (sizeof(int) * 8 - 1);x ^= sign;x -= sign;return x;}static __inline int CLZ(int x){int numZeros;if (!x)return (sizeof(int) * 8);numZeros = 0;while (!(x & 0x80000000)){numZeros++;x <<= 1;}return numZeros;}typedef union _U64 {Word64 w64;struct {/* ARM ADS = little endian */unsigned int lo32;signed int hi32;} r;} U64;static __inline Word64 MADD64(Word64 sum64, int x, int y){return sum64 + (int64_t)x*y;}static __inline Word64 SAR64(Word64 x, int n){return x >> n;}use a bit of googling, searching these functions for cortex-M0/M0+ might drop some results:https://community.arm.com/support-forums/f/architectures-and-processors-forum/11313/cortex-m0-m0-m1-32-bit-x-32-bit-----64-bit-signed-multiplyhttps://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/a-fairly-quick-count-leading-zeroes-for-cortex-m0Consider a cheap external solution like:- DFplayer (IC YX5200-24SS)- GPD2856C (IC GPD2846A)- BT 201 (IC KT1025A)These boards are pretty inexpensive and usually include a low-impedance ~1W amplifier, so you might get two things done.They work with USB, SD-card, also eeprom, spi flash (But FAT16/32 filesystem)... some have a small programable internal memory to store sounds in low quality.If it's just for a toy, you might get the job done with a ESP-32 module, disabling Wifi. Might be even cheaper than the STM32.Pretty sure you'll find everything you required for mp3 decoding in Arduino.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: June 21, 2022, 01:36:54 pm
content: Thanks, david for the Tips, Though I need some help regarding using the Lib,Suppose that I have an array containing a minimal MP3 file, (so I can use internal flash for storage) something like thisCode:const unsigned char _mp3Data ={ 0xFF, 0xFB, 0xE2, 0x04, 0x00, 0x01, 0x12, 0xE3 ,etc...};MP3FrameInfo mp3FrameInfo;static HMP3Decoder hMP3Decoder;short pcm[1152*2];int bytesLeft = 1940;//1940 is the Maximum MP3 frame sizeunsigned char* ptr = &_mp3Data;hMP3Decoder = MP3InitDecoder();MP3Decode(hMP3Decoder, &ptr, &bytesLeft, pcm, 0);ptr+=(1940 - bytesLeft);bytesLeft = 1940;MP3Decode(hMP3Decoder, &ptr, &bytesLeft, pcm, 0);The decoder do it right for the first frame, But I think the Decoder does not do it right for the second frame, what’s am I missing!?

author: DavidAlfa
author_rank: Super Contributor
timestamp: June 21, 2022, 11:34:51 pm
content: I have no idea how HelixMP3 works. But a basic reading gives some hints.First,the decoder will update pointer and bytes left.QuoteYou're completely breaking it here:QuoteTry this:Code:const unsigned char mp3Data ={ 0xFF, 0xFB, 0xE2, 0x04, 0x00, 0x01, 0x12, 0xE3 ,etc...};unsigned char * mp3Ptr = &mp3Data;int bytesLeft = sizeof(mp3Data); // All your mp3 buffer sizeMP3FrameInfo mp3FrameInfo;static HMP3Decoder hMP3Decoder = MP3InitDecoder();short pcm[1152*2]; // Each MP3 frame outputs 1152 pcm samples per channelint result;while(bytesLeft)// Decode one frameresult = MP3Decode(hMP3Decoder, &mp3Ptr, &bytesLeft, pcm, 0); // Decode one frame. mp3Ptr and bytesLeft are updated by the decoder.//Check resultif (result != ERR_MP3_NONE ){mp3_error(result);}// Get last decoded frame info. Output samples will be in mp3FrameInfo.outputSamps.// Should be 1152*2 ? Perhabs this could be omitted if output is constant by standard, but better check for now...)MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);// Now we have new samples in pcm buffer organized as LRLRLRLR...// Normally you would start a DMA to start playing the pcm buffer, decode the next frame while playing, filling a second pcm buffer, and keep switching them...}void mp3_error(int res){ // If error occurs, check which...switch(result){case ERR_MP3_INDATA_UNDERFLOW://break;case ERR_MP3_MAINDATA_UNDERFLOW ://break;case ERR_MP3_FREE_BITRATE_SYNC ://break;case ERR_MP3_OUT_OF_MEMORY ://break;case ERR_MP3_NULL_POINTER ://break;case ERR_MP3_INVALID_FRAMEHEADER ://break;case ERR_MP3_INVALID_SIDEINFO ://break;case ERR_MP3_INVALID_SCALEFACT ://break;case ERR_MP3_INVALID_HUFFCODES ://break;case ERR_MP3_INVALID_DEQUANTIZE ://break;case ERR_MP3_INVALID_IMDCT ://break;case ERR_MP3_INVALID_SUBBAND ://break;case ERR_UNKNOWN ://break;}}