Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 22 of 22

Thread: Teensy 4.1 timing accuracy help.

  1. #1

    Teensy 4.1 timing accuracy help.

    Hi Guys,

    I'm looking at running code at set periods over time and would like the timing to be as stable as possible.

    I have looked at IntervalTimer and elapsedMicros and on the 4.1 I am using here I get around 3ms drift in a minute.

    I looked at the output of AnalogWrite and get the same error in frequency.

    So I'm guessing this is just the crystal used being inaccurate? 1 part in 20,000 seems pretty bad though?

    Is this the expected accuracy?

    Any help would be greatly appreciated.

    Cheers

    Andy

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,660
    No, timing has been seen as much better than that.

    Interrupts are being disabled or something.

    Can a simple sketch showing the issue for review and test be posted?

    Is the current TeensyDuino 1.53 in use? What other devices are attached?

  3. #3
    Hi,

    Thanks for the interest, here is an example using IntervalTimer, 1ms, every 1000 send midi message:

    Code:
    #include <Arduino.h>
    
    IntervalTimer myTimer;
    elapsedMicros since = 0;
    int val = 0;
    bool trig = false;
    unsigned int count = 0;
      
    void trigger()
    {
      digitalWrite(PIN_A9, val);
      val = !val;
      count++;
    }
    
    void setup() {
      pinMode(PIN_A9, OUTPUT);
      myTimer.begin(trigger, 1000);
    }
    
    void loop() 
    {
      unsigned int delay = 1000;
      if(count == 1000)
      {
        usbMIDI.sendNoteOn(60, 99, 1);
        count = 0;
      }
    }
    Here are the midi messages arriving with timestamps:

    Code:
    8895.509	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8896.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8897.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8898.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8899.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8900.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8901.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8902.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8903.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8904.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8905.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8906.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8907.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8908.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8909.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8910.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8911.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8912.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    8913.511	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99

  4. #4
    Actually I think it might be the midi usb stuff that is playing up?

    I just tested on a 4.0 as well:

    If I use the timer to generate a 1khz wave and use a hardware freq counter I am seeing: 999.987 on my 4.1 and 999.996 on a 4.0.

    Both suffer from the drifting midi messages though, nearly identical.

    Even with my bad maths the error in the frequency doesn't add up to the drift on the midi messages.

    p.s. The error on the frequency counter is meant to be 2ppm

  5. #5
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,660
    Try this code and see what happens - took out the MIDI and added Teensy Self reporting of the timing.
    This just shows the Teensy is running as expected here. Would take a true reference to 'time' to know what it looks like.
    Code:
    // https://forum.pjrc.com/threads/62768-Teensy-4-1-timing-accuracy-help?p=251159&viewfull=1#post251159
    IntervalTimer myTimer;
    elapsedMicros since = 0;
    volatile int val = 0;
    volatile unsigned int count = 0;
      
    void setup() {
      while (!Serial && millis() < 4000 );
      Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
      pinMode(PIN_A9, OUTPUT);
      myTimer.begin(trigger, 1000);
    }
    
    void trigger()
    {
      digitalWrite(PIN_A9, val);
      val = !val;
      count++;
    }
    
    
    void loop() 
    {
      if(count == 1000)
      {
      	uint32_t ss = since;
      	float cc=ARM_DWT_CYCCNT;
        since =0;
        // usbMIDI.sendNoteOn(60, 99, 1);
        Serial.printf( "val=%d at us==%d Since==%d Cycles==%f\n", val, micros(), ss, cc/F_CPU_ACTUAL );
        count = 0;
      }
    }
    Output should appear like this even when the usbMIDI_sendNoteOn() is restored?
    T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 00:45:05
    val=0 at us==1422001 Since==1122001 Cycles==1.421550
    val=0 at us==2422001 Since==1000000 Cycles==2.421550
    val=0 at us==3422001 Since==1000000 Cycles==3.421550
    val=0 at us==4422001 Since==1000000 Cycles==4.421550
    val=0 at us==5422001 Since==1000000 Cycles==5.421550
    val=0 at us==6422001 Since==1000000 Cycles==6.421550
    val=0 at us==7422001 Since==1000000 Cycles==0.263271
    val=0 at us==8422001 Since==1000000 Cycles==1.263271
    val=0 at us==9422001 Since==1000000 Cycles==2.263271
    val=0 at us==10422001 Since==1000000 Cycles==3.263271
    val=0 at us==11422001 Since==1000000 Cycles==4.263271
    val=0 at us==12422001 Since==1000000 Cycles==5.263271
    val=0 at us==13422001 Since==1000000 Cycles==6.263271
    val=0 at us==14422001 Since==1000000 Cycles==0.104993
    val=0 at us==15422001 Since==1000000 Cycles==1.104993
    val=0 at us==16422001 Since==1000000 Cycles==2.104992
    val=0 at us==17422001 Since==1000000 Cycles==3.104993
    val=0 at us==18422001 Since==1000000 Cycles==4.104992
    val=0 at us==19422001 Since==1000000 Cycles==5.104992
    val=0 at us==20422001 Since==1000000 Cycles==6.104992
    val=0 at us==21422001 Since==1000000 Cycles==7.104992
    val=0 at us==22422001 Since==1000000 Cycles==0.946714
    val=0 at us==23422001 Since==1000000 Cycles==1.946714
    val=0 at us==24422001 Since==1000000 Cycles==2.946714
    val=0 at us==25422001 Since==1000000 Cycles==3.946714
    Just added the last cycles - 600MHz wraps every few seconds - but generally it is keeping pace with the crystal.

  6. #6
    Thanks for that.

    The timing output on teensy serial looks right, on midi monitor I see the message received in lock with the serial display but I see the drift in timing.

    I'm thinking of attaching a function generator at 1khz and running off a pin interrupt to see if the same thing is happening on the midi side...

  7. #7
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,660
    Perhaps the timing of the messages is just USB transmission variability?
    It doesn't creep away it just bounces up and down from the initial value.
    with SerialMidi - using SerMon with timestamp:
    Code:
    01:06:50.536 -> T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 01:04:53
    01:06:50.536 -> val=0 at us==5000002 Since==4700001 Cycles==4.999563
    01:06:50.536 -> val=0 at us==6000002 Since==1000000 Cycles==5.999563
    01:06:50.536 -> val=0 at us==7000002 Since==1000000 Cycles==6.999563
    01:06:50.536 -> val=0 at us==9120002 Since==1120000 Cycles==1.961284
    01:06:51.533 -> val=0 at us==10120002 Since==1000000 Cycles==2.961284
    01:06:52.564 -> val=0 at us==11120002 Since==1000000 Cycles==3.961284
    01:06:53.548 -> val=0 at us==12120002 Since==1000000 Cycles==4.961284
    01:06:54.533 -> val=0 at us==13120002 Since==1000000 Cycles==5.961284
    01:06:55.565 -> val=0 at us==14120002 Since==1000000 Cycles==6.961284
    01:06:56.549 -> val=0 at us==15120002 Since==1000000 Cycles==0.803005
    01:06:57.531 -> val=0 at us==16120002 Since==1000000 Cycles==1.803005
    01:06:58.550 -> val=0 at us==17120002 Since==1000000 Cycles==2.803005
    01:06:59.533 -> val=0 at us==18120002 Since==1000000 Cycles==3.803005
    01:07:00.566 -> val=0 at us==19120002 Since==1000000 Cycles==4.803005
    01:07:01.551 -> val=0 at us==20120002 Since==1000000 Cycles==5.803005
    01:07:02.534 -> val=0 at us==21120002 Since==1000000 Cycles==6.803005
    01:07:03.531 -> val=0 at us==22120002 Since==1000000 Cycles==0.644727
    01:07:04.533 -> val=0 at us==23120002 Since==1000000 Cycles==1.644727
    01:07:05.565 -> val=0 at us==24120002 Since==1000000 Cycles==2.644727
    Here again with MIDI remove and Serial only as posted:
    01:09:33.639 -> T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 01:09:01
    01:09:33.639 -> val=0 at us==5000001 Since==4700001 Cycles==4.999539
    01:09:33.639 -> val=0 at us==6000001 Since==1000000 Cycles==5.999539
    01:09:33.639 -> val=0 at us==7000001 Since==1000000 Cycles==6.999539
    01:09:33.639 -> val=0 at us==16120001 Since==1000000 Cycles==1.802981
    01:09:34.670 -> val=0 at us==17120001 Since==1000000 Cycles==2.802982
    01:09:35.642 -> val=0 at us==18120001 Since==1000000 Cycles==3.802982
    01:09:36.674 -> val=0 at us==19120001 Since==1000000 Cycles==4.802981
    ...
    01:09:59.657 -> val=0 at us==42120001 Since==1000000 Cycles==6.328145
    01:10:00.673 -> val=0 at us==43120001 Since==1000000 Cycles==0.169866
    01:10:01.675 -> val=0 at us==44120001 Since==1000000 Cycles==1.169866
    01:10:03.439 -> val=0 at us==45120001 Since==1000000 Cycles==2.169866 // this one got really delayed ????
    01:10:03.642 -> val=0 at us==46120001 Since==1000000 Cycles==3.169866
    ...
    01:12:11.658 -> val=0 at us==174120001 Since==1000000 Cycles==2.320847
    01:12:12.672 -> val=0 at us==175120001 Since==1000000 Cycles==3.320847
    01:12:13.642 -> val=0 at us==176120001 Since==1000000 Cycles==4.320848
    ...
    01:12:30.673 -> val=0 at us==193120001 Since==1000000 Cycles==7.004290
    01:12:31.658 -> val=0 at us==194120001 Since==1000000 Cycles==0.846011
    01:12:32.642 -> val=0 at us==195120001 Since==1000000 Cycles==1.846011
    01:12:33.673 -> val=0 at us==196120001 Since==1000000 Cycles==2.846011
    01:12:34.659 -> val=0 at us==197120001 Since==1000000 Cycles==3.846011
    01:12:35.644 -> val=0 at us==198120001 Since==1000000 Cycles==4.846011
    01:12:36.673 -> val=0 at us==199120001 Since==1000000 Cycles==5.846011

  8. #8
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,660
    Added :: Serial.flush();
    After the printf(); and the times may have less variability - and might actually predate the first print at time - not sure if it helps MIDI?
    01:21:40.642 -> T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 01:21:36
    01:21:41.670 -> val=0 at us==3948001 Since==3648001 Cycles==3.947557
    01:21:42.658 -> val=0 at us==4948001 Since==1000000 Cycles==4.947557
    01:21:43.642 -> val=0 at us==5948001 Since==1000000 Cycles==5.947557
    01:21:44.673 -> val=0 at us==6948001 Since==1000000 Cycles==6.947557
    ...
    01:23:56.634 -> val=0 at us==138948001 Since==1000000 Cycles==2.940260
    01:23:57.669 -> val=0 at us==139948001 Since==1000000 Cycles==3.940260
    01:23:58.662 -> val=0 at us==140948001 Since==1000000 Cycles==4.940260
    01:23:59.650 -> val=0 at us==141948001 Since==1000000 Cycles==5.940260
    01:24:00.673 -> val=0 at us==142948001 Since==1000000 Cycles==6.940260
    01:24:01.658 -> val=0 at us==143948001 Since==1000000 Cycles==0.781981
    01:24:02.678 -> val=0 at us==144948001 Since==1000000 Cycles==1.781981
    01:24:03.665 -> val=0 at us==145948001 Since==1000000 Cycles==2.781981

    ... /// and a few minutes later ...
    01:28:13.674 -> val=0 at us==395948001 Since==1000000 Cycles==2.242222
    01:28:14.658 -> val=0 at us==396948001 Since==1000000 Cycles==3.242222
    01:28:15.641 -> val=0 at us==397948001 Since==1000000 Cycles==4.242222
    01:28:16.673 -> val=0 at us==398948001 Since==1000000 Cycles==5.242222
    01:28:17.659 -> val=0 at us==399948001 Since==1000000 Cycles==6.242222
    01:28:18.642 -> val=0 at us==400948001 Since==1000000 Cycles==0.083943

  9. #9
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    12,660
    Quote Originally Posted by AndyCap View Post
    Thanks for that.

    The timing output on teensy serial looks right, on midi monitor I see the message received in lock with the serial display but I see the drift in timing.

    I'm thinking of attaching a function generator at 1khz and running off a pin interrupt to see if the same thing is happening on the midi side...
    No problem - was feeling a bit guilty/anxious not having found anything to program for 2-3 weeks

  10. #10
    Hi,

    Thanks for all the info.

    I think it seems to be a constant drift on the midid usb, I guess I need to keep it running for a while to check.

    I attach a 1khz signal to a pin and run the trigger() from the interrupt, now on the frequency counter I see 1.00001Khz.

    The serial output now looks like this:

    Code:
    val=1 at us==309391992 Since==999981 Cycles==1.585544
    val=1 at us==310391973 Since==999981 Cycles==2.585525
    val=1 at us==311391955 Since==999981 Cycles==3.585507
    val=1 at us==312391936 Since==999981 Cycles==4.585488
    val=1 at us==313391917 Since==999981 Cycles==5.585469
    val=1 at us==314391899 Since==999981 Cycles==6.585450
    val=1 at us==315391880 Since==999982 Cycles==0.427153
    val=1 at us==316391861 Since==999981 Cycles==1.427134
    val=1 at us==317391843 Since==999981 Cycles==2.427115
    val=1 at us==318391824 Since==999982 Cycles==3.427097
    val=1 at us==319391805 Since==999981 Cycles==4.427078
    val=1 at us==320391787 Since==999981 Cycles==5.427059
    val=1 at us==321391768 Since==999982 Cycles==6.427041
    val=1 at us==322391749 Since==999981 Cycles==0.268743
    val=1 at us==323391731 Since==999981 Cycles==1.268724
    val=1 at us==324391712 Since==999982 Cycles==2.268706

    Nice, I thought, this will fix the issue, but...

    Code:
    12922.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12923.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12924.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12925.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12926.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12927.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12928.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12929.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12930.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12931.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12932.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12933.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12934.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12935.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12936.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12937.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12938.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12939.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12940.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12941.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12942.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12943.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12944.922	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    12945.922	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
    Nearly identical.

    I will take your idea and leave it running for a bit to see if it bounces back at some point...

  11. #11
    I found
    Code:
    usbMIDI.send_now();
    which I guess is the equivalent of the flush, didn't help though.

  12. #12
    Senior Member
    Join Date
    Jul 2020
    Posts
    463
    Quote Originally Posted by AndyCap View Post
    I have looked at IntervalTimer and elapsedMicros and on the 4.1 I am using here I get around 3ms drift in a minute.

    I looked at the output of AnalogWrite and get the same error in frequency.

    So I'm guessing this is just the crystal used being inaccurate? 1 part in 20,000 seems pretty bad though?
    That's 50ppm, which is believable for a low tolerance crystal, typical ones are spec'd at +/-100, +/-50,
    +/-30, +/-20ppm or so. For better accuracy a TCXO is needed (temperature compensated crystal oscillator).

    See if you get noticably different results with the pcb actively cooled perhaps?

  13. #13
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    22,996
    I ran the code of msg #3 on a Teensy 4.1. Here's what my frequency counter with GPS disciplined 10 MHz reference sees on pin 23.

    Click image for larger version. 

Name:	freq.jpg 
Views:	13 
Size:	58.5 KB 
ID:	21535

    Error of 0.007169 out of 500 is 14.3 ppm. That's well within the crystal's 30 ppm spec.

    I also tried with the usbMIDI.sendNoteOn(60, 99, 1) commented out. Exactly the same frequency measured both ways.

  14. #14
    Hi Paul and Mark,

    Thanks for the info and for looking into this.

    The timings I see on the generated waveform are:

    Teensy 4.0: 999.996

    Teensy 4.1: 999.987

    Teensy 4.1 Clocking from from 1Khz function generator by interupt: 1000.00001

    All these are actually pretty good.

    The issue seems to be on drift from the USB midi, for each of these different clocks the drift stays around the same at 1ms per 20 seconds.

    I have looked at drift from a couple of midi sequencers here to check it isn't anything to do with the Mac that is logging the metrics I am basing this on.

    One gives 1ms drift per 4 seconds and the other 1 ms per 60 seconds.

    I have a Cirklon sequencer that is meant to be the bees knees when it comes to midi timing unfortunately this is elsewhere, I'm going to go get it tomorrow and do some tests with that as well to see what that gives.

    From the existing two sequencers I have tried though I'm guessing we cannot totally blame the Mac as one of the sequencers is running with 3 times less drift.

    I also have another 4.1 here, tomorrow I will solder the headers and see what the timing is like on that, to be honest though I'm not sure this is totally down to the timing as I am seeing similar drift on the USB midi irrespective of the small differences in the frequency...

  15. #15
    Senior Member
    Join Date
    Aug 2013
    Location
    Gothenburg, Sweden
    Posts
    329
    Increasing the count in the Intervall timer callback, and setting the count to 0 in the main loop looks like dangerous practice.

    count should at least be declared volatile

  16. #16
    It isn't code I will be using, just some code to show the issue

  17. #17
    Senior Member
    Join Date
    Aug 2013
    Location
    Gothenburg, Sweden
    Posts
    329
    Can you guarantee that there are no conditions, yield function activity, handling of USB interrupts, timers or other background activities, so that the main loop handling of (count==1000) condition is finished before the next timer interrupt arrives ?? Otherwise you have a race condition and will lose at seemingly random times 1ms by setting count to 0 after the timer interrupt has ticked on.

    You could try move setting count = 0 up before sending the midi meassage

  18. #18
    Here you go, different code same problem:

    Code:
    #include <Arduino.h>
    #include <Audio.h>
    
    AudioSynthSimpleDrum     drum1;
    AudioOutputUSB           usb1;
    AudioConnection          patchCord2(drum1, 0, usb1, 0);
    AudioOutputI2S           out1;
    
    void togglePinA9()
    {
      static int val = 0;
      digitalWrite(PIN_A9, val);
      val = !val;
    }
    
    void setup() {
      while (!Serial && millis() < 4000 );
      Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
      pinMode(PIN_A9, OUTPUT);
      pinMode(PIN_A8, INPUT_PULLDOWN);
      
      AudioMemory(12);
      drum1.frequency(1200);
      drum1.length(150);
      drum1.secondMix(0.0);
      drum1.pitchMod(0.0);
    }
    
    void loop() 
    {
      static bool           needNoteOff  = false;
      static elapsedMicros  since        = 0;
      const int             duration     = 1000000;
      const int             durationDiv2 = duration/2;
    
      if(since >= durationDiv2/2 && needNoteOff)
      {
        usbMIDI.sendNoteOff(60, 99, 1);
        needNoteOff = false;
      }
    
      if(since >= duration)
      {
      	uint32_t ss = since;
      	float cc=ARM_DWT_CYCCNT;
        since -= duration;
    
        usbMIDI.sendNoteOn(60, 99, 1);
        usbMIDI.send_now();
        needNoteOff = true;
        drum1.noteOn();
        
        Serial.printf( "us==%d Since==%d Cycles==%f\n", micros(), ss, cc/F_CPU_ACTUAL );
      }
    }
    p.s. Reading back here my tone was not good, sorry about that and thanks for the help.
    Last edited by AndyCap; 08-31-2020 at 12:31 PM.

  19. #19
    So with that code above and recording the audio and midi, the midi drifts but the audio doesn't:

    At the start of test:

    Click image for larger version. 

Name:	screenshot_664.jpg 
Views:	10 
Size:	31.9 KB 
ID:	21549


    A few minutes in:

    Click image for larger version. 

Name:	screenshot_665.jpg 
Views:	10 
Size:	37.0 KB 
ID:	21550

  20. #20
    Actually it is even worse, the audio is drifting backwards in time while the midi is drifting forward in time:

    Click image for larger version. 

Name:	screenshot_666.jpg 
Views:	13 
Size:	56.4 KB 
ID:	21551

  21. #21
    Senior Member
    Join Date
    Aug 2013
    Location
    Gothenburg, Sweden
    Posts
    329
    Shot in the dark, discard any eventual incoming MIDI messages:

    Code:
      // MIDI Controllers should discard incoming MIDI messages.
      while (usbMIDI.read()) {
      }

  22. #22
    Thanks, I will stick that in and see if there is any difference.

    The midi looks like it may be behaving better than the audio though!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •