Go Down

Topic: precise delay of 1us (Read 478 times) previous topic - next topic

houbix

Dec 07, 2017, 12:13 am Last Edit: Dec 07, 2017, 02:46 am by houbix
Hello,

I have a problem quite embarassing.

For my application I would like to output 10 pulse of pwm with the duty cycle varying from 10% for the first pulse and 100% for the last pulse .

I have  read a lot of ressources online about arduino timer an delay functions.
I have tried a lot of diffrents things and it dont seems to make what i want.
The closest i have succeed is in my code below.

For example at 30KhZ, we can see on the photo i have an output on my logic analyser with a period of 55us wich is the equivalent of 19000 Hz , we are quite far from the 30 khz .

in lower frequency i dont have exactly what i want  but it seems to respond better . At 5khZ on the software , i have a period of 240us which equal to 4166.66 Hz.


So my question is ,i want to start at 10khz and send 10 pulse with the duty cycle variyng from 10 to 100% and after the 10 pulse i want to increase the frequency with an increment of 5khZ and resend the 10 pulse with the duty cycle variyng .I want to go till 50 khz if possible .

Is it possible to do it on arduino and have a precise pulse duration ?

Thanks a lot guys for reading.

Here is my code
Code: [Select]
/*
Name: Essaipatern.ino
Created: 06/12/2017 14:24:05
Author: Houbix
*/

#include <eRCaGuy_Timer2_Counter.h>
#include <digitalWriteFast.h>


unsigned long timeelapsed;
unsigned long tempsON, tempsOFF;
int period = 0;

// the setup function runs once when you press reset or power the board
void setup() {
pinMode(10, OUTPUT);
pinMode(13, OUTPUT);
digitalWrite(10, HIGH);// set outPin pin as output
Serial.begin(115200);
timer2.setup();
period = 1000000 / 30000; //30 khz frequency


}

// the loop function runs over and over again until power down or reset
void loop() {



for (int i = 1; i <= 10; i++)
{

tempsON = i*0.1*period;
tempsOFF = period - tempsON;    







unsigned long previousT = timer2.get_count();
unsigned long currenT = previousT;
 timeelapsed = 0;

 digitalWriteFast(10, HIGH);
while ((timeelapsed)<(tempsON))
{

currenT = timer2.get_count();
timeelapsed = (currenT-previousT)/2;

}
/* Serial.print("temps écoulé ");
Serial.println(timeelapsed);
Serial.print("TON ");
Serial.println(tempsON);*/




unsigned long previousT2 = timer2.get_count();
unsigned long currenT2 = previousT;
timeelapsed = 0;
digitalWriteFast(10, LOW);
while (timeelapsed<tempsOFF)
{
currenT2= timer2.get_count();
timeelapsed = (currenT2 - previousT2)/2;
}
/*

Serial.print("temps écoulé ");
Serial.println(timeelapsed);
Serial.print("TOOFF ");
Serial.println(tempsOFF);



*/


}
delay(10);
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));


/*currentMillis = millis();
previousMillis = currentMillis;
while (currentMillis - previousMillis < interval)
{
currentMillis = millis();



}
previousMillis = currentMillis;*/

}



aarg

Welcome to the Forum. Please read these two posts:

How to use this forum - please read.
and
Read this before posting a programming question ...
You may also find useful information that would answer your question here:
Useful links - check here for reference posts / tutorials

You have posted code without using code tags. The code tags make the code look
Code: [Select]
like this
when posting source code files. It makes it easier to read, and can be copied with a single mouse click. Also, if you don't do it, some of the character sequences in the code can be misinterpred by the forum code as italics or funny emoticons.  The "Code: [Select]" feature allows someone to select the entire sketch so it can be easily copied and pasted into the IDE for testing.
If you have already posted without using code tags, open your message and select "modify" from the pull down menu labelled, "More", at the lower left corner of the message. Highlight your code by selecting it (it turns blue), and then click on the "</>" icon at the upper left hand corner. Click on the "Save" button. Code tags can also be inserted manually in the forum text using the code and /code metatags.

Unless the sketch is too large, it's better if you post your code, rather than attach it. When it's attached, we have to download it, create a folder then open your code in our IDE. And afterwards, the folder remains unless we navigate to the "Temp" folder and manually remove it. It's much easier to just view the code in your post.

Many questions can be answered by reading the documentation which is provided with the IDE, available under the help tab, or online here.

There are many other things that programmers do to make their code understandable. Please do them. Use a standard indentation to clearly show the code blocks. Never put more than one statement per line. Place any brackets by themselves on a separate line. Use blank lines sparingly, no more than one at a time. Before posting the code, use Ctrl-T in the IDE to reformat the code in a standard format, which makes it easier for us to read.
  ... with a transistor and a large sum of money to spend ...
Please don't PM me with technical questions. Post them in the forum.

TonyWilk

If you want predictable precise timing you really need to read the datasheet for the processor on the particular Arduino you are using.

The general PWM software routines do a good job of setting up the hardware to roughly what you are after, for whatever chip is on the board, but it cannot be 'precise'.

The Timer and PWM circuitry simply divides a source clock by some integer amount,  for example, on a 16MHz AtMega328 on an Arduino Pro Mini if you wanted *exactly* 15Khz that CPU clock would have to be divided by 1066.666666 - something that integer dividers don't do.

In your code you have:

Code: [Select]


int period = 0;

...

period = 1000000 / 30000; //30 khz frequency


The actual result for the calculation would be 33.33333333... But period is set to the integer 33 which is 1% out for a start.

Another example; I have a project using a 'Pro mini' with a 16MHz AtMega328 and I really wanted 32000Hz rate for an interrupt to handle PWM - but the closest the hardware can manage is 31250KHz. You just have to live with it :)

By looking at the actual CPU datasheet and setting the registers yourself you may be able to get closer to what you are after.

However, this is far from an easy thing to do.

Yours,
  TonyWilk

houbix

Thank you for your response ! :)

Yes its true that i can't have a perfectly precise timer .
But the result that i'm getting are very far from optimal .

I have tried to use various delay library. and the delay isn't very accurate to toggle the pin .

I dont know how to use the timer to make the precise delay of 1us.

I just want to delay my program of n amount of uS accurately.

I have a arduino mega.

MasterT

Change the concept, instead of waiting /delay you can arrange an arduino to run some code in background with regular time interval, interrupt based. Not easy for beginners, try to research DDS. Here is an example:
http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-dds-sinewave-generator/

This project generates a sine, but it is basically arbitrary any waveform generator, all you have to do is create appropriate look up table.

westfw

This calculation:
Code: [Select]
tempsON = i*0.1*period;

Is likely to take much longer than 1us (the benchmarks say ~138 cycles for a floating point multiply), so the time in between your pulses is going to be much longer than you expect (which is sort-of what your picture shows.) You can probably pre-calculate all of the expected transition times and get much closer; for 30kHz you have a transition at
about 0, 3us, 30us, 6, 27, 9, 24, 12, 21, ....



houbix

#6
Dec 07, 2017, 02:17 pm Last Edit: Dec 07, 2017, 03:11 pm by houbix
Thank you West for your response ,

Yes it seems that the calculation of the multiplication take too many cycle .
I will try to precalculate and put in an array the time transition that i want.
I hope that i'll be much closer.

Here is my result in the attachment :

For 40 Khz i'm supposed to have a period of 25us , on my logic analyser i get 34 us much closer that i was presviously . But can i improve my  pulse period to get much closer to 25us ?

Do you see something on my code that i can change to get that ? I have already take away the floating calculation that took too much cycle .

I dont know if the unsigned long slow my process .

Any thought  guys ?


Code: [Select]

/*
Name: Essaipatern.ino
Created: 06/12/2017 14:24:05
Author: Houbix
*/

#include <eRCaGuy_Timer2_Counter.h>
#include <digitalWriteFast.h>


unsigned long timeelapsed;

int period = 0;

volatile int tempsON[11];
volatile int tempsOFF[11];
// the setup function runs once when you press reset or power the board
void setup() {
pinMode(10, OUTPUT);
pinMode(13, OUTPUT);
digitalWrite(10, HIGH);// set outPin pin as output
Serial.begin(115200);
timer2.setup();
period = 1000000 /40000; //40 khz frequency

for (int i = 1; i <= 10; i++)
{
tempsON[i] = 0.1*i*period;
tempsOFF[i] = period - 0.1*i*period;

}

}

// the loop function runs over and over again until power down or reset
void loop() {

for (int i = 1; i <= 10; i++)
{    


unsigned long previousT = timer2.get_count();
digitalWriteFast(10, HIGH);
unsigned long currenT = previousT;

  timeelapsed = 0;


while ((timeelapsed)<(tempsON[i]))
{

currenT = timer2.get_count();
timeelapsed = (int)(currenT-previousT)/2;

}
/* Serial.print("temps écoulé ");
Serial.println(timeelapsed);
Serial.print("TON ");
Serial.println(tempsON[i]);*/


unsigned long previousT2 = currenT;
digitalWriteFast(10, LOW);
unsigned long currenT2 = previousT2;
timeelapsed = 0;

while (timeelapsed<tempsOFF[i])
{
currenT2= timer2.get_count();
timeelapsed = (int)(currenT2 - previousT2)/2;
}

/*
Serial.print("temps écoulé ");
Serial.println(timeelapsed);
Serial.print("TOOFF ");
Serial.println(tempsOFF[i]);
*/







}
//delay(10);
//digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));




}


aarg

You still haven't fixed your crazy indenting and gratuitous line spacing.

Consider the value here if i == 1 and period == 25.

Code: [Select]
tempsON[i] = 0.1*i*period;


0.1*1 == 0.1, 0.1 * 25 == 2.5 ... that will be truncated to 2 as tempsON is an int!

Of course your timing will be off.
  ... with a transistor and a large sum of money to spend ...
Please don't PM me with technical questions. Post them in the forum.

houbix

Thank you aarg !

So waht you are saying is that i should make the array tempsOn a float ?

I have tried that and it doesn't seems to change the result :/

I really dont know what to do anymore :(

westfw

Which timer2 and fast digital libraries are you using?
fundamentally, you're doing:

Code: [Select]
  setup high-time
  wait for high-time end
  setup low-time
  wait for low-time end

With the "setup" phases not being counted by the timing loops, and adding "some" time to the overall period.
I'm not sure I believe that that should account for 9 microseconds, but it will add some.
It's also more than you need to do.  Instead of saying "now is 1, I need to turn off at 9.  Then it's 12 and I need to turn off at 102", you can calculate "now is  1, so I need to turn off at 9 and on again at 99" and cut down on some of that time.

cattledog

Quote
For my application I would like to output 10 pulse of pwm with the duty cycle varying from 10% for the first pulse and 100% for the last pulse .

i want to start at 10khz and send 10 pulse with the duty cycle variyng from 10 to 100% and after the 10 pulse i want to increase the frequency with an increment of 5khZ and resend the 10 pulse with the duty cycle variyng .I want to go till 50 khz if possible .

I think you will have better control if you learn to manipulate the timers directly instead of using a library.

The frequency will be set with the prescaler and top value of the timer. For simplicity I would use a fast PWM mode which turns the output pulse on at the bottom and turns it off with a compare match. Then use a timer overflow interrupt which increments a counter and adjusts the compare match value with each cycle.

When the ten duty cycles are completed, change the top value for a new frequency.


Jimmus

I agree with CattleDog, except I would disable interrupts completely and just poll the overflow bit.  Pre-calculate all the compare values and use a prescaler of 1.  This could give you insane accuracy with relatively simple programming.

TonyWilk

For my application I would like to output 10 pulse of pwm with the duty cycle varying from 10% for the first pulse and 100% for the last pulse .
Just had a thought...

You say you only want 10 pulses of PWM
The first at 10%, the second at 20% .... the last one at 100%

Max frequency you said at 50KHz, which is a PWM cycle of 20uS and you want increments of 2uS

So... you could generate that pretty close (if not exactly) just in software...

Code: [Select]
//
// TimingTest
//
// To output specific PWM wit s/w loop timings
// on Arduino Pro Mini which has an AtMega328 processor running at 16MHz
//
// TonyWilk
//


#define LED_ON()  (PORTB |= 0x20)     // only used for timing debug
#define LED_OFF() (PORTB &= 0xdf)

#define PWM_PORT  PORTD  // Port of the pin the pixels are connected to
#define PWM_DDR   DDRD   // Port of the pin the pixels are connected to
#define PWM_BIT   4      // Bit of the pin the pixels are connected to

inline void tinyDelay( uint8_t val )  __attribute__((always_inline));
inline void asm2uS()  __attribute__((always_inline));




inline void asm2uS()
{
      asm volatile (
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        ::
      );
}

// delay 2uS / val
//
volatile uint8_t t;
inline void tinyDelay( volatile uint8_t t )
{
  asm volatile (
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    "nop \n\t"
    ::
  );
  while( --t ){
      asm volatile (
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        "nop \n\t"
        ::
      );
  }
}


void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  bitSet( PWM_DDR , PWM_BIT );    // our PWM output

  LED_ON();
  delay( 1 );
  LED_OFF();
}



void loop() {
  // put your main code here, to run repeatedly:

  cli();      // Disable interrupts (by default Timer0 interrupt is in use

  LED_ON();
  asm2uS();            // should be 2uS on
  LED_OFF();
  tinyDelay( 9 );      // off for  18uS
 
  LED_ON();
  asm2uS();            // should be 4uS on
  asm2uS();
  LED_OFF();
  tinyDelay( 8 );      // off for  16uS

  LED_ON();
  tinyDelay( 3 );      // 6
  LED_OFF();
  tinyDelay( 7 );      // 14

  LED_ON();
  tinyDelay( 4 );      // 8
  LED_OFF();
  tinyDelay( 6 );      // 12

  LED_ON();
  tinyDelay( 5 );      // 50% and so on...
  LED_OFF();
  tinyDelay( 5 ); 

  LED_ON();
  tinyDelay( 6 );      // 6
  LED_OFF();
  tinyDelay( 4 );      // 14

  LED_ON();
  tinyDelay( 7 );
  LED_OFF();
  tinyDelay( 3 );

  LED_ON();
  tinyDelay( 8 );
  LED_OFF();
  tinyDelay( 2 );

  LED_ON();
  tinyDelay( 9 );
  LED_OFF();
  asm2uS();            // should be 2uS off 

  LED_ON();
  tinyDelay( 10 );
  LED_OFF();
 
  sei();      // Enable interrupts

  delay( 10 );
}


Trying hard not to get too deep into assembler stuff, this outputs 10 pulses at (as close as I can measure it)  50302Hz - I'm sure it can be closer to 50KHz by tweaking the number of 'nop's or, for extra bonus points, do the whole delay routines in assembler.

NOTE: I was running this on an Arduino Pro Mini which has an AtMega328 processor running at 16MHz

Yours,
  TonyWilk

P.S. Ahh, this takes me back to generating video in s/w on a good ol' Z80. (you had to be there :) )

houbix

Thank you to all of you guys ,TonyWilk,Jimmus and cattledog .

I understand now how i can impletent the solution .
I will try this tomorrow.

Thanks again for everything it was very helpful .

Go Up