Pulse period measurement with Trinket & ATtiny85

Adafruit's tiny microcontroller platform. Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
robert_david
 
Posts: 16
Joined: Thu Jan 14, 2021 6:44 am

Pulse period measurement with Trinket & ATtiny85

Post by robert_david »

Code: Select all

/* This code incorporates the code in <Servo_LD3015_Sweep_pot_control> together with the period
 *  measurement by interrupt from <Tachometer_PWM_Arduino_codeV2c>
 *  Pulse width linearly related to ign frequency with negative slope
 *  Calibration with poteniometer input to give a 0 to 200 usec addition to the pulse width
 *  Count1 & 2 used to determine whether there has been an ign pulse within the 100ms loop time
 *  No pulse in that time means engine speed less than 500, ie stopped
 *  This code makes no provision for count overflow, which since counts 1 & 2 are defined as long 
 *  would take 23,860 hrs
 *  Neither does it allow for Time1,2,3 & 4 overflow, which will occur after 35.7 mins (2,147,483,648/(1,000,000*60)
 *  Tests showed "jitter" in the output pulse from V1 of the ISR code. This version attempts to correct that
 *  by incorporating the pulse output within the ISR, it worked!
 *  See Excelspreadsheet <PWM & Servo test results> Classic Cars/Ethel Refurb/Tachometer
 *  Moving average of period over 3 values
 *  THIS CODE ASUMES THAT THE DISPLAY ANGLE BETWEEN 0 AND 6000 RPM IS 254.5 DEGREES. THE CONSTANTS C1, 2 & 3 
 *  TAKE THIS INTO ACCOUNT. THEY WILL NEED TO BE CHANGED IF THE ANGLE ASSUMPTION IS WRONG
 *  IT ALSO ASSUMES THAT THE SIGNAL FROM THE ECU IS 3 X THE SHAFT FREQUENCY
 */

int InterruptPin = 2;        // Input signal ign frequency from engine ECU
int AdjustPin = 4;           // Analog A2 (= PB4 or pin 2 on ATiny85) for software fine tuning/calibration
int OutputPin = 0;           // Output pin for control pulse pin 5 on ATtiny85
long Time1 = 0;              // Time1 & Time2 used to measure period in usecs
long Time2 = 0;              // Will overflow after 35.76 mins 
long Time3 = 0;              // Time3 & 4 used within ISR to fix ouput pulse interval
long Time4 = 0;                   
long Period = 0;             // period calculated as <Time2 - Time1> in usecs
long count1 = 0;             // Counter incremented in ISR checked in mainloop for no increase, ie no pulse
long count2 = 0;
int Pulseinterval;           // Pulseinterval used to fix pulse output times
long C1 = 2464;              // Define constants to calc demand from period
long C2 = 6996364;
long C3 = 666;
long Adj = 512;              // The input from the adjustment poteniometer. Set to mid point to start
long AdjDash = 25;           // Adj' = Adj * 50 / 1023, again set to mid point to start
int startup =0;              // Flag to go through initial sweep just once
int dt;                      // time for pulse width in initial sweep
int demand;                  // Pulse time for normal operation
int Averagep1 =0;            // Average values used in the moving window averaging
int Averagep2 =0;
int Averagep3 =0;
int Averagedemand = 0;

void setup() 
{
   pinMode(InterruptPin, INPUT);     // ECU trigger signal connected to interrupt, pin 2
   pinMode(AdjustPin, INPUT);        // Adjustment signal connected to Analog pin A0
   pinMode(OutputPin, OUTPUT);
   attachInterrupt(digitalPinToInterrupt(InterruptPin), Pmeasure, RISING);  // internal pull up R keeps pin 2 HIGH, button press takes it low on release 
                                                                            // it goes high, this rising edge used to trigger interrupt call                                                                          // Pmeasurep is the name of the ISR, (Interrupt Service Routine) which is defined                                                                           // as the last statement of this sketch
}

void loop()                          // The loop runs startup routine and then reads calibration pot and checks for no interrupts i.e. engine stopped
{
 if(startup == 0)                     // This section up to <startup = 1> sweeps the pointer through full range and back yo zero
  { 
   delay(1500);  
    for (dt = 2500; dt >= 500; dt -= 15)  // goes from 0 degrees to 270 degrees in steps of 1.35 degree
      {                                   // 270 degrees correspond to pulse width from  2500 to 500 usec
       digitalWrite(OutputPin, HIGH);
       delayMicroseconds(dt);
       digitalWrite(OutputPin, LOW);
       delay(13);
      }
     delay(100); 
   for (dt = 500; dt <= 2500; dt += 15)    // goes from 270 degrees to 0 degrees
      { 
      digitalWrite(OutputPin, HIGH);
      delayMicroseconds(dt);
      digitalWrite(OutputPin, LOW);
       delay(13);
      }
   startup = 1;                           //End of initial pointer sweep to max and return to zero 
  }
    else                                  // Initial sweep just run once at startup. Following section controls pointer
    {
      Adj = analogRead(AdjustPin);        // read the output from the adjustment potentiometer
      AdjDash = Adj * C3;
      AdjDash = AdjDash/Period;           // Scale the value of the adjustment input to a 0 - 100 range to suit pulse time        
            
   if (count1 == count2)                  // Count2 has not changed (in ISR) thus no interrupt received. 
      {                                   // hence the period between pulses is greater than at leasr 60ms i.e. engine stopped              
        digitalWrite(OutputPin, HIGH);    // so set tacho to zero
        delayMicroseconds(2500);
        digitalWrite(OutputPin, LOW);
      }
   else
      {
      count1 = count2;
      } 
     delay(60);                           // Slowest engine speed is 500 rpm, i.e. a pulse frequency of 25Hz, or period of 40 msec    
   }                                      // so ensure loop time exceeds this so that there is normally at least one interrupt
}                                         // within the loop

  void Pmeasure()                         // ISR, include moving window averaging of pulse width and pulse output
{     
      Time2 = micros();                   // Time2 (& Time1) in usec
      Time3 = Time2/1000;                 // Time3 & 4 in msec
      Period = Time2 - Time1;             // Period is the time between successive interrupts usecs
      Time1 = Time2;                      // Reset Time1 in order to measure the next period     
      count2 = count2 + 1;                // Incremented count2 is tested in <loop>, no increment means no interrupt
       Pulseinterval = (Time3 - Time4);
        if(Pulseinterval > 19)            // ensure that ouput pulses are at least 19msec apart
           {
            demand = C1-C2/Period;        // Pulse width = 2500-6.42*frequency; frequency = 1000000/period (usec)
            Averagep3 = Averagep2;
            Averagep2 = Averagep1;
            Averagep1 = demand;
            Averagedemand = (Averagep1 + Averagep2 + Averagep3)/3;
            digitalWrite(OutputPin, HIGH);
            delayMicroseconds(Averagedemand+AdjDash);
            digitalWrite(OutputPin, LOW);
            Time4 = Time3;                // Reset Time4 to time the next ouput pulse
           }
}

User avatar
robert_david
 
Posts: 16
Joined: Thu Jan 14, 2021 6:44 am

Re: Pulse period measurement with Trinket & ATtiny85

Post by robert_david »

Sorry hit <return> by accident and posted before I had explained my problem!
The project is to drive a 270 Degree servo to indicate the frequency of a pulse train, 2ms pulse width at frequencies between 25 & 300 Hz.
The code above does this well when loaded into a Trinket, but fails when I try to programme an ATtiny85 (using an Arduino UNOR3) with the error message <digitalPinToInterrupt was not declared in this scope>

In my ignorance I had assumed that since the Trinket is based on the ATtiny85 the code would work in that chip - I had successfully programmed the ATtiny85 using the UNOR3 with codes that do not involve external interrupts

Does this mean that I cannot use the function <attachInterrupt(digitalPinToInterrupt(Pin),ISR,RISING)>? If so how do I write the sketch to use external interrupt?

User avatar
adafruit_support_mike
 
Posts: 67391
Joined: Thu Feb 11, 2010 2:51 pm

Re: Pulse period measurement with Trinket & ATtiny85

Post by adafruit_support_mike »

The ATtiny85 only has one pin that can recognize rising or falling edge interrupts (GPIO 2), and the corresponding interrupt is INT0.

Just feed attachInterrupt() a zero as the first parameter and see how that goes.

User avatar
robert_david
 
Posts: 16
Joined: Thu Jan 14, 2021 6:44 am

Re: Pulse period measurement with Trinket & ATtiny85

Post by robert_david »

Many thanks. I have followed your suggestion and it appears to have worked - or at least no error messages. I will now test the programmed Tiny85 works as hoped in the project.

User avatar
robert_david
 
Posts: 16
Joined: Thu Jan 14, 2021 6:44 am

Re: Pulse period measurement with Trinket & ATtiny85

Post by robert_david »

I can now report that the Project almost works, but not quite. The measurement of pulse train frequency and the driving of the servo to give an "analogue" reading of the frequency works, but my calibration adjustment does not. The latter is achieved (on a UNO R3 & Trinket) using analogRead of a pin to which is connected a variable voltage 0 - 5V. The output of the ADC is then used to modify the drive to the servo by a small amount to achieve calibration.
However when the code is uploaded to the tiny85 the calibration potentiometer has no effect. I have looked at several articles about ADC on Tiny85's all of which use code such as:-
void adc_setup()
{
DDRB|=(1<<PB1); //PB1 as output to activate LED
ADCSRA|=(1<<ADEN); //Enable ADC module
ADMUX=0x01; // configuring PB2 to take input
ADCSRB=0x00; //Configuring free running mode
ADCSRA|=(1<<ADSC)|(1<<ADATE); //Start ADC conversion and enabling Auto trigger
}
rather than analogRead(). Can I use this command or do I have to write apropriate values to the various registers?
Help would be appreciated!

User avatar
adafruit_support_mike
 
Posts: 67391
Joined: Thu Feb 11, 2010 2:51 pm

Re: Pulse period measurement with Trinket & ATtiny85

Post by adafruit_support_mike »

PB2 is the same pin that's reading the interrupt. Try using PB3 or PB4 instead.

In general, analogRead() should work without any problems. Just make sure each pin has one job.

Locked
Please be positive and constructive with your questions and comments.

Return to “Trinket ATTiny, Trinket M0”