"Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post here first, or if you can't find a relevant section!
racemaniac
Posts: 520
Joined: Sat Nov 07, 2015 9:09 am

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by racemaniac » Thu Jul 13, 2017 2:22 pm

Concerning DAC's, if you don't mind 12bit accuracy, the MCP4725 (which you can find very cheap online) is great in combination with the bluepill.
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 :).

victor_pv
Posts: 1650
Joined: Mon Apr 27, 2015 12:12 pm

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by 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.

jaromir
Posts: 31
Joined: Sat Apr 30, 2016 4:50 pm

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by jaromir » Thu Jul 13, 2017 6:09 pm

nicolas_soundforce wrote:
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.

nicolas_soundforce
Posts: 22
Joined: Sat Jun 17, 2017 10:18 am

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by nicolas_soundforce » Thu Jul 13, 2017 10:11 pm

victor_pv wrote:
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
Posts: 22
Joined: Sat Jun 17, 2017 10:18 am

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by nicolas_soundforce » Thu Jul 13, 2017 10:19 pm

nicolas_soundforce wrote:
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 right

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!
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...

victor_pv
Posts: 1650
Joined: Mon Apr 27, 2015 12:12 pm

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by victor_pv » Fri Jul 14, 2017 4:30 am

nicolas_soundforce wrote:
Thu Jul 13, 2017 10:19 pm
nicolas_soundforce wrote:
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 right

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!
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...
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.

nicolas_soundforce
Posts: 22
Joined: Sat Jun 17, 2017 10:18 am

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by 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.

[/u]
victor_pv wrote:
Fri Jul 14, 2017 4:30 am
nicolas_soundforce wrote:
Thu Jul 13, 2017 10:19 pm
nicolas_soundforce wrote:
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 right

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!
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...
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.

User avatar
Pito
Posts: 1528
Joined: Sat Mar 26, 2016 3:26 pm
Location: Rapa Nui

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by Pito » Sat Jul 15, 2017 1:36 pm

Guys, do not quote all previous posts as the threads will be 10000km long then..
Do focus on a few words to quote instead, if required (rarely is)..
Pukao Hats Cleaning Services Ltd.

victor_pv
Posts: 1650
Joined: Mon Apr 27, 2015 12:12 pm

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by victor_pv » Tue Jul 25, 2017 11:28 pm

nicolas_soundforce wrote:
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.

nicolas_soundforce
Posts: 22
Joined: Sat Jun 17, 2017 10:18 am

Re: "Irregular" non-integer timer interrupt values (e.g. 22.67us/44.1khz)

Post by nicolas_soundforce » Thu Aug 03, 2017 10:04 am

victor_pv wrote:
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...
Attachments
Sample_trigger_PT8211_SD_for_forum.zip
(5.95 KiB) Downloaded 8 times

Post Reply