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

Post here first, or if you can't find a relevant section!
stevestrong
Posts: 1611
Joined: Mon Oct 19, 2015 12:06 am
Location: Munich, Germany

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

Post by stevestrong » Thu Aug 03, 2017 11:58 am

Guys,
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...

victor_pv
Posts: 1654
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 Aug 03, 2017 5:17 pm

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

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 6:53 pm

@Victor, according to this document : http://www.st.com/content/ccc/resource/ ... 042534.pdf

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.

victor_pv
Posts: 1654
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 Aug 03, 2017 7:14 pm

nicolas_soundforce wrote:
Thu Aug 03, 2017 6:53 pm
@Victor, according to this document : http://www.st.com/content/ccc/resource/ ... 042534.pdf

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.
You are right, I made a mistake calculating when I read the code, it's 6x285, 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.

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 » Fri Aug 04, 2017 9:58 pm

@Victor, thanks for the advice. Glad to know with my logic for the timer period is sound.
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!

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 Aug 05, 2017 1:17 pm

Victor!! It is working! new PCB and double buffer fixed all my issues. Audio is super smooth @96mhz with 2 buffers of 512. Although it is running a bit off sync when it try to sync it with the original audio on the computer. Most likely because the sample freq is actually 96000000/2175 = 44137 hz

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):

Code: Select all

  Timer2.pause();
  
  Timer2.setPrescaleFactor(1);
  Timer2.setOverflow(2175);

  Timer2.setChannel1Mode(TIMER_OUTPUT_COMPARE);
  Timer2.attachCompare1Interrupt(audio_int);

  Timer2.refresh();
  Timer2.resume();
The buffer1 is filled in the setup():

Code: Select all

file1.read(buf1, 512);
The Timer ISR audio_int:

Code: Select all

  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_2.transfer(buf[1]);  // Data bits 15-8
    SPI_2.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;
    }
  }
length_audio1 is parsed from the header in the wave file.

In the loop refill the buffers:

Code: Select all

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;
    }*/
}
Reading the wave file header and getting sample rate, length of data bytes, bitrate...

Code: Select all

sd.chvol();
  //-----------------//
  //---Open FILE1----//
  //-----------------//

  if (!file1.open("mono.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);
  }
  

victor_pv
Posts: 1654
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 » Sat Aug 05, 2017 2:57 pm

@Nicolas to answer some of your questions.
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.

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

I think I found your code with rtos and the ILI9341 screen in the PT8211 thread. I will look into that.

victor_pv
Posts: 1654
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 » Mon Aug 07, 2017 2:17 am

This fork has one of the latest versions, although I do not remember if I have uploaded there the very latest changes, but definitely should include the part where it sets up a DMA channel to upload values to 2 timer channel registers on each timer update event:
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.

victor_pv
Posts: 1654
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 » Mon Aug 07, 2017 2:19 am

nicolas_soundforce wrote:
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 ;)

Post Reply