Pages: [1]   Go Down
Author Topic: Pulsed Output Timing  (Read 1134 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm making a variable pulse injector tester using an attiny85 and two pots as inputs. One pot controls the period of the pulse and the other controls the pulse width.

The period should be between 8 and 60ms and the duty cycle between 15% and 85%, neither needs to be completely accurate.

I've come up with the following code which almost works. The actual output period is between 33 and 85ms so i'm loosing alot of time somewhere in my code. Also, the duty cycle works at the upper end of the period length but if i leave it set to 85% and decrease the period it goes down linearly until about 45% at the lowest period.

Any pointers appreciated!

Code:
int periodPin = 3;
int dutycyclePin = 2;
int transistorPin = 2;
int triggerPin = 1;
int constPin = 0;
int triggerState = 0;
int constState = 0;
float period = 0;
float dutycycle=0;
int hightime = 0;
int lowtime = 0;

void setup() {

pinMode(transistorPin, OUTPUT); 
pinMode(triggerPin, INPUT); 
pinMode(constPin, INPUT); 

}

void loop() {
  triggerState = digitalRead(triggerPin);
  constState = digitalRead(constPin);
  period = analogRead(periodPin)*0.05+8; //calculate period between 8 and 60ms
  dutycycle = (analogRead(dutycyclePin)*0.07)+15; //calculate dutycycle between 15 and 85%
  hightime = (period/100)*dutycycle; //calculate time output is high
  lowtime = period-hightime; //calculate time output is low
 
  if(constState == HIGH) //if constant output button is on
  {
  digitalWrite(transistorPin, HIGH);
  }
 
  else if(triggerState == HIGH) //if pulsed output button is on
  {
 
    digitalWrite(transistorPin, HIGH);
    delay(hightime);
 
    digitalWrite(transistorPin, LOW);
    delay(lowtime);

  }
 
}
Logged

0
Offline Offline
Faraday Member
**
Karma: 16
Posts: 2855
ruggedcircuits.com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Without studying your code in too much detail, my guess is your use of floating-point math (i.e., real numbers). These are very "expensive" to work with on an AVR. With some careful design they can be eliminated.

For example, you can replace:

Code:
period = analogRead(periodPin)*0.05+8; //calculate period between 8 and 60ms

with:

Code:
period = (analogRead(periodPin)*52 + 512)/1024 + 8;

--
The Gadget Shield: accelerometer, RGB LED, IR transmit/receive, speaker, microphone, light sensor, potentiometer, pushbuttons
Logged

Global Moderator
Netherlands
Online Online
Shannon Member
*****
Karma: 224
Posts: 13917
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

better use the map function for this!
Code:
period = map(analogRead(periodPin), 0, 1023, 8, 60); //calculate period between 8 and 60ms

using the map() can removes all floats from your code ...
Note "hightime  = period * dutycycle /100;"     I swapped the * and / to get higher precision as this is one of the pitfalls of integer math.
+ added one line at the end of loop() which I assume was missing.

Code:
int periodPin = 3;
int dutycyclePin = 2;
int transistorPin = 2;
int triggerPin = 1;
int constPin = 0;
int triggerState = 0;
int constState = 0;

unsigned int period = 0;
unsigned int dutycycle=0;
unsigned int hightime = 0;
unsigned int lowtime = 0;

void setup()
{
  pinMode(transistorPin, OUTPUT); 
  pinMode(triggerPin, INPUT); 
  pinMode(constPin, INPUT); 
}

void loop()
{
  triggerState = digitalRead(triggerPin);
  constState = digitalRead(constPin);
  period = map(analogRead(periodPin),0,1023,8,60);            //calculate period between 8 and 60ms
  dutycycle = map(analogRead(dutycyclePin),0,1023, 15, 85);   //calculate dutycycle between 15 and 85%
  hightime = period * dutycycle /100;                        //calculate time output is high
  lowtime = period-hightime; //calculate time output is low
 
  if(constState == HIGH) //if constant output button is on
  {
    digitalWrite(transistorPin, HIGH);
  }
  else if(triggerState == HIGH) //if pulsed output button is on
  {
    digitalWrite(transistorPin, HIGH);
    delay(hightime);
    digitalWrite(transistorPin, LOW);
    delay(lowtime);
  }
  else digitalWrite(transistorPin, LOW);   // ADDED THIS LINE
}
« Last Edit: May 16, 2011, 11:55:24 am by robtillaart » Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

0
Offline Offline
Faraday Member
**
Karma: 16
Posts: 2855
ruggedcircuits.com
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

While better than floating-point math, the map function uses 'long' arithmetic, including division, thus will be quite a bit slower than the 'short' arithmetic solution proposed.

--
The Rugged Motor Driver: two H-bridges, more power than an L298, fully protected
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thank you both, i will do some experimenting and see which code is faster and if i can pinpoint where the bottleneck is
Logged

Left Coast, USA
Offline Offline
Sr. Member
****
Karma: 7
Posts: 499
Sometimes I just can't help myself.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

How about something like this:

Declare some (global or static) variables to save the raw readings from the pots each time through the loop.  Then, each time through the loop do a new reading of each and don't do any calculations unless there has been a change of more than a couple of bits in either reading.

Now the range of period and duty cycle will be limited mainly by the time taken for the analogRead function calls and the time taken for the digitalWrite function  calls. (And there are faster ways of writing to digital outputs than the stock digitalWrite function if it becomes desirable.)


Of course for finer granularity with no jitter you can use a timer to create the output directly on an OC (timer Output Compare) pin so that no program intervention is required (after the timer has been set up) to create the output.  The cost is the "learning curve" for use of timers, and the limitation is that only certain pins can be used for the output, depending on which timer you use.



Regards,

Dave
« Last Edit: May 17, 2011, 12:11:49 pm by davekw7x » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Dave,

Saving the temp variables sounds like a great idea. Earlier i had thought of only checking the the pins once before entering the output loop (so once the trigger button is held the pots have no affect) but your way is better smiley-wink

I have been looking into direct port manipulation but i'm not sure if i need to learn how to use it just yet!

Thanks
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well, i done so experimenting and once i put any sort of delay in i get a minimum delay of 14ms.

Could this be because i'm using the attiny? I haven't tried on a 328
Logged

Left Coast, USA
Offline Offline
Sr. Member
****
Karma: 7
Posts: 499
Sometimes I just can't help myself.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well, i done so experimenting and once i put any sort of delay in i get a minimum delay of 14ms.
...
Go back to your original code.  Put some print statements to show what the calculated times are.  (Your calculation for high time was incorrect, since it was done with integer arithmetic.)

For example, I measured times for various operations using micros().  I faked the values for the pot readings to try to get an 8 ms period with 50% duty cydle.

Code:
const int pot1Pin = 0;
const int pot2Pin = 1;
const int outPin = 13;
const int constPin = 8;
const int triggerPin = 9;
unsigned long tbegin, tend;

void setup()
{
    Serial.begin(9600);
    pinMode(outPin, OUTPUT);
    // Enable pullups on digital input pins
    digitalWrite(constPin, HIGH);
    digitalWrite(triggerPin, HIGH);
    tbegin = micros();
    analogRead(pot1Pin);
    analogRead(pot2Pin);
    tend = micros();
    Serial.print("Time for two analogReads = ");
    Serial.println(tend-tbegin);

    tbegin = micros();
    digitalRead(triggerPin);
    digitalRead(constPin);
    digitalWrite(outPin, HIGH);
    digitalWrite(outPin, LOW);
    tend = micros();
    Serial.print("Time for two digitalReads + two digitaWrites = ");
    Serial.println(tend-tbegin);

}

int old1 = 32767;
int old2 = 32767;
int new1, new2;
int period, dutycycle, hightime, lowtime;
volatile int triggerState, constState;
const int tol = 5;

void loop()
{
    new1 = analogRead(pot1Pin);
    new2 = analogRead(pot2Pin);
    // I will ignore the values that I just read and give it what I want.
    new1 = 0;
    new2 = 512;
    triggerState = digitalRead(triggerPin);
    constState = digitalRead(constPin);

    if ((abs(new1-old1) > tol) || (abs(new2-old2) > tol)) {
        
        tbegin = micros();
        period = new1*0.05+8; //calculate period between 8 and 60ms
        dutycycle = new2*0.07+15; //calculate dutycycle between 15 and 85%
        Serial.print("Period = ");
        Serial.print(period);
        Serial.print(", Duty cycle = ");
        Serial.println(dutycycle);
        hightime = (period/100.0)*dutycycle; //<=== Force it to use floating point here!
        lowtime = period-hightime;
        old1 = new1;
        old2 = new2;
        tend = micros();
        
        Serial.print("Calculaton time = ");      
        Serial.println(tend-tbegin);
        Serial.print("hightime = ");
        Serial.print(hightime);
        Serial.print(", lowtime = ");
        Serial.println(lowtime);
    }
    
    // For now, ignore the buttons; make it toggle
    constState = LOW;
    triggerState = HIGH;
    
    if(constState == HIGH) {
        digitalWrite(outPin, HIGH);
    }

    else if(triggerState == HIGH) {//if pulsed output button is on
        digitalWrite(outPin, HIGH);
        delay(hightime);

        digitalWrite(outPin, LOW);
        delay(lowtime);
    }
}

Output:

Time for two analogReads = 320
Time for two digitalReads + two digitaWrites = 24
Period = 8, Duty cycle = 50
Calculaton time = 29976
hightime = 4, lowtime = 4


period and hightime and lowtime are the times  (in milliseconds) for the waveform that we are trying to generate, using delay(); other times reported above are from micros(), which gives microsecond timing with a granularity of 4 microseconds.


Output on a Duemilanove was an approximate square wave.  Period was about 8.26 milliseconds and duty cycle was about 50%.  Slight jitter (roughly 10 usec) is unavoidable due to Timer 0 interrupt used for millis(), delay() and other Arduino timing chores.  In other words all measurements are consistent with the calculations.


Regards,

Dave


Footnote:

With the expression for hightime shown in the Original Post, integer arithmetic makes for an incorrect answer.  I showed floating point arithmetic in my example above.  (If I were really picky, I would have used rounding to get the integer approximation of the floating point value).  Anyhow, the formula could be restated to keep with integer arithmetic:
Code:
       hightime = (period*dutycycle)/100; //Re-arrange formula to avoid integer arithmetic problem

I found that results are about the same, and execution time is about the same, but the code size is smaller with integer arithmetic.

There may (or may not) be advantages to using the Arduino map() function or other equivalent integer arithmetic for calculating period and duty cycle from pot readings.  The point is, if you are concerned about such things, you can instrument the code with print statements to tell you what differences there may be.

The important fact is that these calculations are only performed when there are changes to be made, so they don't add delay to the loop that would impose limits on the range of periods that can be generated.
« Last Edit: May 19, 2011, 08:28:29 am by davekw7x » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Dave,

Thanks for that.

As i'm using the attiny85 i can't print to the serial monitor so i've been taking all measurements on the oscilloscope. I ran individual bits of the code (and variations as suggested above)

Interestingly checking if the analogue values have changed takes as long as, or longer than, the calculations themselves. The fastest calculation time was using the integer math from the first reply at 2.7ms. The way i was doing it originally was 2.8ms and using the map function brought it to just over 3.0ms.

The problem, as i said above, is that the minimum delay i get when using delay() is 14ms. i.e. if i use the following the pulse is 14ms.

Code:
    digitalWrite(transistorPin, HIGH);
    delay(1);

For now i'm using this as a workaround and it seems to be working

Code:
  begintime = micros();
  while(micros()<=begintime+hightime)

I'm still limited by the max lowtime of 2.7ms, unless i go the route of only doing the calculations at the start of a loop
Logged

Left Coast, USA
Offline Offline
Sr. Member
****
Karma: 7
Posts: 499
Sometimes I just can't help myself.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

...using the attiny85 ...
What is the clock frequency?   Internal 8 MHz or what?

 Can you post your complete code?  I am very interested in porting some applications to the smaller processors, and I would like to check it out.

I mean, it is my understanding that the ATtiny and ATmega processors have essentially the same core CPU instruction set timing, but I will study some more before making a commitment to a new project.  My plan was to do preliminary testing on an ATmega (so that I have the debugging capabilities of the serial port) and then just take the basic functionality to the smaller device.


Regards,

Dave
« Last Edit: May 20, 2011, 12:16:57 pm by davekw7x » Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 210
Posts: 13039
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I mean, it is my understanding that the ATtiny and ATmega processors have essentially the same core CPU instruction set timing

Yup.

The biggest difference I've found are the timers.  The ATtiny84 and ATtiny85 processors have two timers.  One that is very similar to the ATmega timers and one that is radically different (and different from each other).

Quote
(so that I have the debugging capabilities of the serial port)

Not a problem...
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1285218245/25#25
Logged

Left Coast, USA
Offline Offline
Sr. Member
****
Karma: 7
Posts: 499
Sometimes I just can't help myself.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset



Thanks for that!


Regards,

Dave
Logged

Pages: [1]   Go Up
Jump to: