I’ve been using the bluepill almost exclusively the last months with this core. It is has been amazing, thank you so much to all the members involved to improve this project.
My question is, is it possible to make the blue pill interrupts with a timer of 22.67us ? I am building a wave file player and I want to feed a DAC with samples every 22.67us to reach the sample rate of 44.1khz. At the moment it works at 22 and 23 us, but it’s not playing back at the original speed.
I am using one of the 3 timers.
Thanks in advance,
Nicolas.
https://libstock.mikroe.com/projects/vi … calculator
So the only way to get something better than 22-23us is to play with CPU clock settings such the integer division gives you a better result.
rather than thinking about it from a time perspective,t hink about it from a frequency perspective: what frequency do you need the mcu to run at for you to hit that 44.1khz number with an integer dividor?
then it becomes much easier.
https://en.wikipedia.org/wiki/Crystal_o … requencies
So you may replace the crystal and you get a perfect audio
the calculator is very interesting. Unfortunately the code is for the ST library (I guess) and I doesn’t compile with the STM32duino core. But it’s great to get the prescaler and autoreload registers values. With a autoreload of 1632 I can get as close as 44,117647058823529 kHz, so a period of 22.666666 us. It’s probably enough for my application.
But changing the CPU clock is indeed probably a much better idea. I could easily run a 48mhz and using 48khz instead (another audio sampling freq standard). Simply with a reload of 999.
I hope my program runs smoothly at 48Mhz too. I could also try to overlock the CPU to 96Mhz.
EDIT:silly me, for 48khz I can run at 72mhz and using a reload of 1500.
For those interested the math behind it :
The update event period is calculated as follows:
Update_event = TIM_CLK/((PSC + 1)*(ARR + 1)*(RCR + 1))
TIM_CLK = 72 MHz
Prescaler = 0
Auto reload = 1499
No repetition counter RCR = 0
Update_event = 72*(10^6)/((0 + 1)*(1499 + 1)*(1))
Update_event = 48000 Hz
to set the prescaler:
Timer2.setPrescaleFactor(0);
to set the autoreload value:
Timer2.setOverflow(1499);
A overflow of 1633 would give me a freq of 44090,630740967544397 Hz which is an error of 0,021%. I have to test but I think it’s probably fine for my application.
Thanks for the pointers!
Or use an external DAC, or try with PWM and a low-pass..
[Pito – Thu Jul 13, 2017 11:03 am] –
I am building a wave file player and I want to feed a DAC with samples Btw, there are none internal DACs in the BluePill.. With F103 you have to go with larger packages..![]()
Or use an external DAC, or try with PWM and a low-pass..
Indeed I am building a board with a PT8211 and I am testing at the moment the timings with another board with a MCP4822.
E.g I tried this I2S amplifier from adafruit but I couldn’t get it running.
It needs a quite accurate timing of the I2S signal and the STM32F103 bluepill has not I2S peripheral.
Here I made some experiments with a sigma delta DAC wav player.
It plays only one second of the wav sampled with 8kHz and than crashes …
As I sad … an experiment …
line 28: filename
line 39: DACSAMPLINGRATE_HZ 8000
I’ve posted code for it in the last post of this thread: http://www.stm32duino.com/viewtopic.php?t=1048
It allows you to reach sampling rates of up to 60Khz using that DAC (but does so by overclocking the I2C port, at the official max speed of the i2C port you’re limited to about 20 Khz).
But i’ve had good success using it like that, and it’s a very cheap solution to get good sound from a bluepill
And it’s also very fast code, no overhead at all
It’s posted in a separate thread in the libraries section of the forum.
I tested it with a pt8211 i2s dac, they are available for cents, and don’t need a separate clock other than the data one.
I started using that upon recommendation from Madias, when he started writing some i2s code and I started helping him. Later one I added to that code to turn it into a library.
The i2s driver uses DMA so cpu load is minimal. Currently only for transferring data from the application buffer to the driver buffer, but you can modify the driver so instead it uses the application buffer directly for the DMA, and at that point cpu load would be 0 from the driver, and only from your application loading the buffer with new data.
The i2s driver requires an MCU with i2s (RCT or higher), so would not work on a maple mini.
But technically it’s possible to just use an SPI port for some i2s, if you set a timer to trigger the DMA events for the SPI port, and then set a time channel to toggle on each transfer.
So a timer event can cause a pin to toggle on each overrun event to indicate left and right, and at the same time the timer can trigger a new DMA transfer to the SPI port, and the port sends out the data.
I haven’t tried this, but I think the theory is sound and should work.
[nicolas_soundforce – Thu Jul 13, 2017 7:42 am] – I am building a wave file player and I want to feed a DAC with samples every 22.67us to reach the sample rate of 44.1khz. At the moment it works at 22 and 23 us, but it’s not playing back at the original speed.
Set up interrupt to 23us, after two interrupts shorten the cycle to 22us, then return back to 23us. Interrupt timing should look like 23-23-22-23-23-22-23-23-22-23-23-22 etc… you got the idea. The average interrupt rate will be 22,667us, but this setup will also introduce jitter (phase noise). It’s on your decision on how much this will be a problem, my gut feeling is it’s going to be just fine.
You may use other fractions than 2/3 to achieve more precise tuning.
[victor_pv – Thu Jul 13, 2017 5:05 pm] –
Nicolas I wrote an i2s driver compatible with the Teensy/Arduino ones.
It’s posted in a separate thread in the libraries section of the forum.
I tested it with a pt8211 i2s dac, they are available for cents, and don’t need a separate clock other than the data one.
I started using that upon recommendation from Madias, when he started writing some i2s code and I started helping him. Later one I added to that code to turn it into a library.
The i2s driver uses DMA so cpu load is minimal. Currently only for transferring data from the application buffer to the driver buffer, but you can modify the driver so instead it uses the application buffer directly for the DMA, and at that point cpu load would be 0 from the driver, and only from your application loading the buffer with new data.The i2s driver requires an MCU with i2s (RCT or higher), so would not work on a maple mini.
But technically it’s possible to just use an SPI port for some i2s, if you set a timer to trigger the DMA events for the SPI port, and then set a time channel to toggle on each transfer.
So a timer event can cause a pin to toggle on each overrun event to indicate left and right, and at the same time the timer can trigger a new DMA transfer to the SPI port, and the port sends out the data.
I haven’t tried this, but I think the theory is sound and should work.
Hi Viktor, I read your posts as well in the I2S pt8211. At the moment I am sticking with the blue pill but I will check out your library if I upgrade to another chip. Your idea sounds good I will try to make it work.
[nicolas_soundforce – Thu Jul 13, 2017 10:41 am] –
for those interested, I still have to test this (dont have the hardware with me right now), but according to HardwareTimer.cpp it would should be rightto set the prescaler:
Timer2.setPrescaleFactor(0);to set the autoreload value:
Timer2.setOverflow(1499);A overflow of 1633 would give me a freq of 44090,630740967544397 Hz which is an error of 0,021%. I have to test but I think it’s probably fine for my application.
Thanks for the pointers!
Ok, after testing:
Timer2.setPrescaleFactor(0); is wrong
The library confirms that:
void HardwareTimer::setPrescaleFactor(uint32 factor) {
timer_set_prescaler(this->dev, (uint16)(factor – 1));
}
A parameter of 0 would result in a -1 prescaler. Timer2.setPrescaleFactor(1); give good results as expected.
But a reload of 1633 results in slowed down audio playback.I am 100% sure that the file is 44.1khz and I get a confirmation in byte #24, 25, 26 & 27. At the moment I am clueless why a 44.1khz audio file is not playing sync when every sample is pushed out every 22.68us (1/(72000000/1633)). And it’s not a bit off, it’s way off. I have to set the auto reload to 1500 to make it sound right.
I measured the micros between the timer interruption once in a while, and it is correct around 22us. So why is the audio playing slowed down…
[nicolas_soundforce – Thu Jul 13, 2017 10:19 pm] –[nicolas_soundforce – Thu Jul 13, 2017 10:41 am] –
for those interested, I still have to test this (dont have the hardware with me right now), but according to HardwareTimer.cpp it would should be rightto set the prescaler:
Timer2.setPrescaleFactor(0);to set the autoreload value:
Timer2.setOverflow(1499);A overflow of 1633 would give me a freq of 44090,630740967544397 Hz which is an error of 0,021%. I have to test but I think it’s probably fine for my application.
Thanks for the pointers!
Ok, after testing:
Timer2.setPrescaleFactor(0); is wrongThe library confirms that:
void HardwareTimer::setPrescaleFactor(uint32 factor) {
timer_set_prescaler(this->dev, (uint16)(factor – 1));
}A parameter of 0 would result in a -1 prescaler. Timer2.setPrescaleFactor(1); give good results as expected.
But a reload of 1633 results in slowed down audio playback.I am 100% sure that the file is 44.1khz and I get a confirmation in byte #24, 25, 26 & 27. At the moment I am clueless why a 44.1khz audio file is not playing sync when every sample is pushed out every 22.68us (1/(72000000/1633)). And it’s not a bit off, it’s way off. I have to set the auto reload to 1500 to make it sound right.
I measured the micros between the timer interruption once in a while, and it is correct around 22us. So why is the audio playing slowed down…
Have you confirmed if the file is mono/stereo and 8/16 bits? perhaps you are outputting samples for 2 channels which would make it play at half speed. Doesn’t seem to be the case, but worth confirming.
I worte a loose version of the tmrpcm library and did not have problems outputting at what seemed like the right rate, perhaps by 1 or 2 off, but not as much as you expereience.
BTW, the timer counts from 0 to the reload value, so you need to load ARR with 1 less than what you calculate. So for 44100 you want (rounding to closest integer):
(72000000/44100) -1 ~= 1632
But that will not make a difference like what you describe.
But when i fill an array with as many bytes the blue pill can take and play that back, it is sync/in tune with a reload of 1633.
I already milled an new prototype PCB for the bluepill, pt8211, filter and op amp as well as a winbond flash chip.
At start up I will copy all the data bytes from the sd card to the flash chip, hopefully that will play nice and fast and allow me to stream 2 channels at the same time.
Thanks everybody for thinking with me.
[/u][victor_pv – Fri Jul 14, 2017 4:30 am] –[nicolas_soundforce – Thu Jul 13, 2017 10:19 pm] –[nicolas_soundforce – Thu Jul 13, 2017 10:41 am] –
for those interested, I still have to test this (dont have the hardware with me right now), but according to HardwareTimer.cpp it would should be rightto set the prescaler:
Timer2.setPrescaleFactor(0);to set the autoreload value:
Timer2.setOverflow(1499);A overflow of 1633 would give me a freq of 44090,630740967544397 Hz which is an error of 0,021%. I have to test but I think it’s probably fine for my application.
Thanks for the pointers!
Ok, after testing:
Timer2.setPrescaleFactor(0); is wrongThe library confirms that:
void HardwareTimer::setPrescaleFactor(uint32 factor) {
timer_set_prescaler(this->dev, (uint16)(factor – 1));
}A parameter of 0 would result in a -1 prescaler. Timer2.setPrescaleFactor(1); give good results as expected.
But a reload of 1633 results in slowed down audio playback.I am 100% sure that the file is 44.1khz and I get a confirmation in byte #24, 25, 26 & 27. At the moment I am clueless why a 44.1khz audio file is not playing sync when every sample is pushed out every 22.68us (1/(72000000/1633)). And it’s not a bit off, it’s way off. I have to set the auto reload to 1500 to make it sound right.
I measured the micros between the timer interruption once in a while, and it is correct around 22us. So why is the audio playing slowed down…
Have you confirmed if the file is mono/stereo and 8/16 bits? perhaps you are outputting samples for 2 channels which would make it play at half speed. Doesn’t seem to be the case, but worth confirming.
I worte a loose version of the tmrpcm library and did not have problems outputting at what seemed like the right rate, perhaps by 1 or 2 off, but not as much as you expereience.BTW, the timer counts from 0 to the reload value, so you need to load ARR with 1 less than what you calculate. So for 44100 you want (rounding to closest integer):
(72000000/44100) -1 ~= 1632
But that will not make a difference like what you describe.
Do focus on a few words to quote instead, if required (rarely is)..
[nicolas_soundforce – Sat Jul 15, 2017 1:02 pm] –
Ok. I discovered my problem was that the sd card is not fast enough, or the sd fat library is interfering with the timer.
But when i fill an array with as many bytes the blue pill can take and play that back, it is sync/in tune with a reload of 1633.I already milled an new prototype PCB for the bluepill, pt8211, filter and op amp as well as a winbond flash chip.
At start up I will copy all the data bytes from the sd card to the flash chip, hopefully that will play nice and fast and allow me to stream 2 channels at the same time.Thanks everybody for thinking with me.
I have a sketch I often use to test library and core changes, and does the following without issues, so the sdfat library should be fast enough as far as you use a reasonable sized buffer:
-Read 8bit/16bit mono/stereo WAV files from sdcard with sdfat (using DMA)
-Plays it back with PWM on 1/2 timer channels with either an interrupt at the rate of the WAV sample frequency, or with DMA transfers to the timer.
-Displays some information in an SPI LCD, ILI9163
-Displays a rotating cube in the screen, I forgot at what frame rate.
-Displays total CPU time % used by all the above. That ranges depending on the wav (8/16bit and mono/stereo), and whether I use the timer interrupts, or DMA.
I have run the above in class 4 cards, and i believe a class 2 also.
No matter if I use the timer interrupts to load 1 sample at a time, or use DMA to load the samples from RAM to the timer channels, the CPU has plenty of time to do all the above and have cycles left idle.
The sdfat library works best with at least 512bytes buffer, since that is the size of a block. Reading less than that means that will have to read 1 full block anyway.
Using DMA as much as possible saves a good amount of CPU time.
Also I run the above in RTOS, but use a single buffer so reloading from sdfat to the buffer has to complete in the time between 2 samples, and I have not noticed any clicking or freezes in the sound or the rotating cube display.
If you use double buffering to decouple the sdfat reading from playing the samples you should be able to avoid any clicking, since in that case sdfat would have all the time during playing 1 buffer to reload the other buffer. But honestly I think there must be something else causing the clicking since I can reload 512bytes in my buffer at 44Khz rates.
[victor_pv – Tue Jul 25, 2017 11:28 pm] –
I have a sketch I often use to test library and core changes, and does the following without issues, so the sdfat library should be fast enough as far as you use a reasonable sized buffer:
-Read 8bit/16bit mono/stereo WAV files from sdcard with sdfat (using DMA)
-Plays it back with PWM on 1/2 timer channels with either an interrupt at the rate of the WAV sample frequency, or with DMA transfers to the timer.
-Displays some information in an SPI LCD, ILI9163
-Displays a rotating cube in the screen, I forgot at what frame rate.
-Displays total CPU time % used by all the above. That ranges depending on the wav (8/16bit and mono/stereo), and whether I use the timer interrupts, or DMA.I have run the above in class 4 cards, and i believe a class 2 also.
No matter if I use the timer interrupts to load 1 sample at a time, or use DMA to load the samples from RAM to the timer channels, the CPU has plenty of time to do all the above and have cycles left idle.
The sdfat library works best with at least 512bytes buffer, since that is the size of a block. Reading less than that means that will have to read 1 full block anyway.
Using DMA as much as possible saves a good amount of CPU time.
Also I run the above in RTOS, but use a single buffer so reloading from sdfat to the buffer has to complete in the time between 2 samples, and I have not noticed any clicking or freezes in the sound or the rotating cube display.If you use double buffering to decouple the sdfat reading from playing the samples you should be able to avoid any clicking, since in that case sdfat would have all the time during playing 1 buffer to reload the other buffer. But honestly I think there must be something else causing the clicking since I can reload 512bytes in my buffer at 44Khz rates.
Hi Victor,
you are right. I tried different cards and no luck it didn’t really get any better. I can run the SDcard at 36mhz using the sd fat library, I can read 2 bytes in 4/5 us. I send the 2 bytes to one channel of a PT8211 with another 4us. In total the process takes 9us, it should plenty of time to fit into a 22us timer value. I don’t really have clicks, it just plays back too slow. And I am really positive the audio is at the right sampling rate.
I even tried to overclock at 96mhz and 128mhz with the right prescaler values but it didn’t help.
About the SD fat library, I noticed that #define USE_STM32F1_DMAC 1 is now included in SdSpiSTM32F1.cpp. It takes me 3948us to read 512 bytes!!! And you can do it between 2 samples… I must do something wrong.
I attached my program as a .zip and you ever have the time to take a quick look that you would be fantastic…
I am pulling my hairs out for about 2 weeks now…
please stop quoting the whole previous post
because it is getting very unreadable, it just wastes a lot of space, and I cannot see anymore what exactly was the original problem…
[nicolas_soundforce – Thu Aug 03, 2017 10:04 am] –
Hi Victor,you are right. I tried different cards and no luck it didn’t really get any better. I can run the SDcard at 36mhz using the sd fat library, I can read 2 bytes in 4/5 us. I send the 2 bytes to one channel of a PT8211 with another 4us. In total the process takes 9us, it should plenty of time to fit into a 22us timer value. I don’t really have clicks, it just plays back too slow. And I am really positive the audio is at the right sampling rate.
I even tried to overclock at 96mhz and 128mhz with the right prescaler values but it didn’t help.
About the SD fat library, I noticed that #define USE_STM32F1_DMAC 1 is now included in SdSpiSTM32F1.cpp. It takes me 3948us to read 512 bytes!!! And you can do it between 2 samples… I must do something wrong.
I attached my program as a .zip and you ever have the time to take a quick look that you would be fantastic…
I am pulling my hairs out for about 2 weeks now…
The major problem is that you are reading 2 bytes at a time. That is extremely inefficient.
An sdcard block is 512bytes. Anything lower than that is already inefficient, since you would make more reads than needed to reach 512 bytes. But reading 2 bytes at a time is really extreme. I am sure you still have some RAM left to use at least a 128byte buffer, if possible even larger.
Read more bytes to a large buffer and then play them 2 at time if you want.
Regarding the timer reload, I can’t verify it without seeing the actual files being played.
I did a lousy conversion of the TMRPCM library, and looking back at my files (https://github.com/victorpv/Arduino_STM … TMRpcm.cpp) I was using 285*5 for 44.100Khz, stereo 8bits,
That would be a reload value of 1425.
As you mention something about 44Khz above, I would think you could test with that value.
Or could you explain how you reach the 1633 value you use for the ARR?
Feel free to look at my code and borrow any part you like.
I have since made multiple copies in my local files and use DMA, so only CPU usage is in reloading the buffer. My library plays with PWM pulses, but the periods for the timer should be the same.
I based the 1633 on the calculation : 72 000 000 / 1633 = 44090,63
I want to bounce the question back
what is your though process to get 1425 ?
I will modify the program to read buffer of 512 and see how it goes.
[nicolas_soundforce – Thu Aug 03, 2017 6:53 pm] –
@Victor, according to this document : http://www.st.com/content/ccc/resource/ … 042534.pdfI based the 1633 on the calculation : 72 000 000 / 1633 = 44090,63
I want to bounce the question backwhat is your though process to get 1425 ?
I will modify the program to read buffer of 512 and see how it goes.
You are right, I made a mistake calculating when I read the code, it’s 6×285, that was 1710.
I think those values where adjusted slightly because the playback didn’t sound right with the calculated values.
This line and forward calculated it for any frequency I did not precalculate it, and was the same way:
https://github.com/victorpv/Arduino_STM … m.cpp#L340
The only difference is that I further divided that in multiple cycles to generate a PWM with a higher frequency than the top sample frequency to reduce noise, using a feature of the timers, but has nothing to do with the way you use it, so 72000000/sample_frequency is the same I did.
Reading to a buffer will definitely improve things. Also use double buffering, so while the ISR is playing 1 buffer you can be reloading the other without any constrain to refill it in the period between 2 samples.
Even a couple of small 2 x 128bytes buffers will make a big difference.
One more thing. Take this out of the ISR:
file1.read(buf1, 2);
In the ISR you should dump the buffer data to the port, and use a flag to indicate when a buffer is empty and can be refilled, then somewhere in your loop() provide for checking that flag and refilling the buffer that’s empty.
If you keep the reads in the ISR you will still have problems every time that call takes longer than the period between 2 samples, I guess 600uS at 44Khz.
The dual buffer idea is perfect. I can start the program by loading buffer #1 in the setup() and avoid any start delay. If I load buffer #2 in the loop, it’s not interfering in any way with timer interrupts ? Is it fine for the sd fat library to be “interrupted” all the time by the timer ISR ?
Also just checking, DMA is built-in in the last version of SD Fat nowadays right ?
Thanks for your help!
For anybody trying to do this:
If you are not overlock and running at 72mhz, just use 1633 as prescaler.
I am using the PT8211 cheap 16bits DAC, but a 12bits MCP4822 would be also do fine.
Timer init (for 44.1k):
Timer2.pause();
Timer2.setPrescaleFactor(1);
Timer2.setOverflow(2175);
Timer2.setChannel1Mode(TIMER_OUTPUT_COMPARE);
Timer2.attachCompare1Interrupt(audio_int);
Timer2.refresh();
Timer2.resume();
Latest SdFat has DMA enabled for the libmaple core.
About interrupting it, I think there is some critical timing pieces during card initialization. But after that, since we use DMA, it takes little CPU load to read the blocks and I have never had a problem with interrupts, as long as interrupts are short.
Also the USB port and the Systick timer interrupt it periodially and don’t cause any problem.
That’s one of the reasons to keep the ISR as short as you can, to not affect other code that’s time sensitive, even other interrupts that may need servicing. That ISR now may take just 20 or 30 CPU cycles.
I’m gla you got the PT8211 working fine with the timer. There is even more you could do with the timer and DMA.
You could set the timer to trigger a DMA request in the channel you use (channel1), and the DMA does the loading from the buffer to the SPI port.
I use it for PWM, but for SPI is exactly the same. Then instead of using the a timer interrupt for every sample, you use a DMA interrupt at the end of the DMA transfer, that would trigger only once every time a buffer is emptied.
So even less interrupts and CPU load, and the CPU can be doing whatever else in between.
I dont know what else your sketch does, but if it does some processing you can get more CPU time for your code doing that.
I think I found your code with rtos and the ILI9341 screen in the PT8211 thread. I will look into that.
https://github.com/victorpv/Arduino_STM … ies/TMRpcm
Since you are playing stereo with SPI, you will need to set it up slightly different, but not much. Something like this should work (for example for 44100hz):
Set a timer channel is output compare mode, and to flipflip. You use that as the WS pin for the pt8211.
Set the timer ARR to a value that produces twice the frequency of the samples (88200hz) (so it will produces 2 pulses per interval, one for the left and one for the right channel).
Set the timer channel compare value to the value as the Timer counter AAR. So the channel output pin will flipflop right as the timer counter reload. So the channel output will flip between 0 and 1 at half the frequency as the timer counter reloads, so getting you 44100hz.
Next set the DMA channel connected to the Timer Update event to do transfers between the buffer and the SPI CR register.
Finally enable the timer update DMA generation, and enable the timer to count.
Much of that is similar to how my tmrpcm code works with the timer, only I make the timer overload at 44100Hz, because the timer has a special feature to generate 2 DMA requests at once, so I can load 2 timer CCR with a single request. But that doesn’t work for the SPI port, because the SPI peripheral would output both bytes one right after another instead of in sync with the timer compare output signal, which you need to synchronize the channels to the PT8211.
[nicolas_soundforce – Sun Aug 06, 2017 5:33 pm] –
Interesting! My code does nothing else than triggering the playback of the audio files on an GPIO interruption.
If you dont need to do anything else with the CPU time than play the wav files, then you really dont need to complicate your life with any more DMA stuff, unless you want to do for fun ![]()
[nicolas_soundforce – Sat Aug 05, 2017 1:17 pm] –
For anybody trying to do this:If you are not overlock and running at 72mhz, just use 1633 as prescaler.
I am using the PT8211 cheap 16bits DAC, but a 12bits MCP4822 would be also do fine.…..
sir,
Im trying to use the example you posted above but having a hard time compiling. are there certain #include statements I need to get your code running?
I have SD card on SPI 1, STMduino bootloader on a “blue pill”, and speaker on TIM1_CH1 and TIM1_CH2.
1. Put the timer in output compare mode. Interrupt enabled.
2. Work out the math. For example, 22.67us is a combination of 1x 22us and 2x 23us.
3. In your isr, advance the output compare register by 22us, 23us, and 23us. And then back to 22us, ….
4. Done.
#include <SdFat.h>
#include <SPI.h>
#define WS PA8
File file1;
SdFat sd;
uint8_t buf[2];
byte buf1[13];
byte buf2[13];
uint8_t buffer_refill;
uint8_t flip_flop;
uint8_t play1;
int buffer_counter;
int counter1;
int length_audio1; //long?
int reading, reading_2, reading_3, reading_4;
int number_of_channels;
int sample_rate;
byte sample_rate_lsb, sample_rate_msb;
int bit_depth;
byte start_byte_1;
void setup() {
Timer2.pause();
Timer2.setPrescaleFactor(1);
Timer2.setOverflow(2175);
Timer2.setChannel1Mode(TIMER_OUTPUT_COMPARE);
Timer2.attachCompare1Interrupt(audio_int);
Timer2.refresh();
Timer2.resume();
while (!sd.begin(PA4, SPI_CLOCK_DIV2)) { // see if the card is present and can be initialized:
Serial.println("SD fail. Holding on loop.");
delay(100);
}
file1.read(buf1, 512);
}
void loop() {
/*
if (millis() - what_time > 100) {
what_time = millis();
Serial.println(after - before);
}
*/
if (buffer_refill) {
if (flip_flop) {
file1.read(buf2, 512);
}
else {
file1.read(buf1, 512);
}
buffer_refill = 0;
}
/*
if (rewind_1) {
file1.seekSet(start_byte_1);
rewind_1 = 0;
}
if (rewind_2) {
file2.seekSet(start_byte_2);
rewind_2 = 0;
}*/
}
void audio_int(){
if (play1) {
if (flip_flop) {
buf[0] = buf1[buffer_counter];
buf[1] = buf1[buffer_counter + 1];
}
else {
buf[0] = buf2[buffer_counter];
buf[1] = buf2[buffer_counter + 1];
}
digitalWrite(WS, HIGH); //Select RIGHT Audio channel
SPI.transfer(buf[1]); // Data bits 15-8
SPI.transfer(buf[0]); // Data bits 7-0
digitalWrite(WS, LOW); //Select RIGHT Audio channel
buffer_counter = buffer_counter + 2;
if (buffer_counter == 512) {
flip_flop = !flip_flop;
buffer_counter = 0;
buffer_refill = 1;
}
counter1++;
if (counter1 > length_audio1) {
play1 = 0;
}
}
}
void audio_info(){
sd.chvol();
//-----------------//
//---Open FILE1----//
//-----------------//
if (!file1.open("song0002.wav")) {
}
file1.printName(&Serial);
Serial.println();
for (int i = 0; i < 100; i++) {
reading = file1.read();
if (reading == 0x66) {
reading_2 = file1.read();
reading_3 = file1.read();
reading_4 = file1.read();
//search for Subchunk1ID, Contains the letters "fmt " (0x666d7420 big-endian form).
if ((reading_2 == 0x6d) && (reading_3 == 0x74) && (reading_4 == 0x20)) {
break;
}
else {
file1.seekSet(i - 3);
}
}
}
Serial.print("Subchunk1ID start at : ");
Serial.println(file1.curPosition() - 4, DEC);
//after the Subchunk1ID, we know for sure that the next bytes define important information
//skip Subchunk1Size
for (int i = 0; i < 4; i++) {
file1.read();
}
//skip AudioFormat
for (int i = 0; i < 2; i++) {
file1.read();
}
//get number of channels
number_of_channels = file1.read();
Serial.print("number of channels : ");
Serial.println(number_of_channels, DEC);
//byte 2 is empty
file1.read();
//get sample rate
sample_rate_lsb = file1.read();
sample_rate_msb = file1.read();
sample_rate = sample_rate_lsb;
sample_rate |= sample_rate_msb << 8;
Serial.print("sample_rate : ");
Serial.println(sample_rate, DEC);
//byte 3 & 4 are empty
file1.read();
file1.read();
//skip bytes until bitdepth
for (int i = 0; i < 6; i++) {
file1.read();
}
//bit depth
bit_depth = file1.read();
Serial.print("bitdepth : ");
Serial.println(bit_depth, DEC);
file1.read();
//scroll through file until reaching the Subchunk2ID
for (int i = 0; i < 1000; i++) {
reading = file1.read();
//Serial.print(i);
// Serial.print(" - ");
// Serial.println(reading, HEX);
if (reading == 0x64) {
reading_2 = file1.read();
reading_3 = file1.read();
reading_4 = file1.read();
//search for Subchunk2ID, Contains the letters "data" (0x64617461 big-endian form).
if ((reading_2 == 0x61) && (reading_3 == 0x74) && (reading_4 == 0x61)) {
break;
}
else {
file1.seekSet(i - 3);
}
}
}
Serial.print("Subchunk2ID start at : ");
Serial.println(file1.curPosition() - 4, DEC);
//we then reached the Subchunk2Size, and can read the length of the file
reading = file1.read();
length_audio1 = reading;
reading = file1.read();
length_audio1 |= reading << 8;
reading = file1.read();
length_audio1 |= reading << 16;
reading = file1.read();
length_audio1 |= reading << 32;
//length of audio in samples, it's the half of the amount of bytes in mono 16bits
length_audio1 = length_audio1 / 2;
Serial.println(length_audio1);
start_byte_1 = file1.curPosition();
Serial.print("audio starting start at : ");
Serial.println(start_byte_1, DEC);
// }
}
//}
Go herehttps://github.com/dannyf00/Use-Output- … er/STM32F1 and take the appropriate files, say tmr2oc.h, if you are using TIM2.
the code would be something like this:
//TIM2_IRQHandler() needs to be changed by commenting out "TIMx->CCR1 += _tim_oc1;"
//it looks like this:
/*
void TIM2_IRQHandler(void) {
//oc1 portion
if (TIMx->SR & TIM_SR_CC1IF) { //output compare 1 flag is set
TIMx->SR &=~TIM_SR_CC1IF; //clear the flag
//TIMx->CCR1 += _tim_oc1; //update the output compare register - needs to be commented out
_tim_oc1isrptr(); //execute user handler
}
*/
//my isr
void myisr(void) {
static pr_index=0; //period index
switch (pr_index) {
case 0: TIM2_CCR1 += PR0; pr_index=1; break;
case 1: TIM2_CCR1 += PR1; pr_index=2; break;
case 2: TIM2_CCR1 += PR2; pr_index=3; break;
case 3: TIM2_CCR1 += PR3; pr_index=0; break;
//add additional periods here...
}
do_your_thing_here();
}
tmr2_init(1); //set up tmr2 at 1:1 prescaler
tmr2_setpr1(PR0); //use compare channel 1, set period to PR0
tmr2_act1(myisr); //install my isr
I was thinking that the example nicolas posted was related to the TMRpcm library that Victor has been working on porting. I believe now I understand that the example is using an External DAC. so basically the main loop() is responsible for refilling each 512 byte dual buffers, while the interrupt send out 2 8-bit PCM data (using SPI_2.transfer) to the PT8211 DAC. This is done at a rate that is proportional to the sample rate of the WAV file from the file header info.
would it be possible to use a DAC that supports I2S to have a DMA ‘link’ between the SDcard and the DAC to load the PCM data directly? I believe victor said the new SdFat library supports this and has also posted about the PT8211.
(viewtopic.php?f=3&t=2328&start=20#p32520)
(http://www.stm32duino.com/viewtopic.php … 211#p33300)
The information is spread all out in the forum. But it seams like Victor has written a DMA function in the most recent TMRpcm lib update. This function could be extracted from the TMRpcm.cpp and used in the example nicolas posted?
I trying to the understand the data signal flow from Wav file to the DAC with and without DMA. Does both the PT8211 and the SD card use DMA? or somehow the PT8211 uses i2s and the sd card uses DMA? or just do DMA on SD card and SPI_2.(transfer); to the DAC?
the code below is from the example posted on page 3 of this thread.
digitalWrite(WS, HIGH); //Select RIGHT Audio channel
SPI_2.transfer(buf[1]); // Data bits 15-8
SPI_2.transfer(buf[0]); // Data bits 7-0
digitalWrite(WS, LOW); //Select RIGHT Audio channel
My program is not relying on TMRpcm. It is simply reading a wave file from the sd card and sending the bytes to the pt8211.
The program is made for 16bits 44.1khz files. You would need to modify it to make play back 8 bits 8khz files.
Maybe at first forget about DMA, you really don’t need it, especially for 8khz files.
Good luck!



