Dealing with missing pulses of an external clock for synchronization

Hi. I have an external device connected to my Arduino Due board ADC pin as a reference clock. It provides pulse-like signals at approximately 50Hz. That is, the ADC value is 0 by default, but raises to 1 or higher values every 1/50 seconds (approx, but not exactly 1/50s). I would like to keep running a function following that external clock, as strict as possible, but that dirty clock has some missing pulses. To mitigate the impact of the missing pulse, one naive way I can think of is to keep replaying that function at 50Hz, until the Due board catch up with the next available pulse from the clock.

I draw a draft figure of my naive method:

Is there a way of programming on the Due board, so I can let it keep looping a function while listening to the ADC input, until triggered by a new pulse, and then restart looping that function? Or is there any better method to deal with this problem?

Thanks.

Edit:
The goal is to keep synchronized with the device providing the pulse signal.

why not have the DUE generate the 50Hz timing signal and sync to the incoming signal, when possible. In other words if the external signal does not come in the Due still does its 50Hz thing.

The Due can multitask. I used uMT as an OS for multitasking on the Due. It works very well and is a great intro to using freeRTOS.

here is a copy of my use of uMT on the DUE to multi-task.





#include <uMT.h>
#include <Wire.h>
#include "TFMini.h"
#include <Servo.h>
#include <SimpleDHT.h>


//*************************************************
// scl pin 5, sda pin 4, vcc 3.3
// temperature, pressure, altitude, sealevelpressure
#define BMP085_ADDRESS 0x77  // I2C address of BMP085
#define BMP_Interval 65503
const unsigned char OSS = 0;  // Oversampling Setting
// Calibration values
int ac1;
int ac2;
int ac3;
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1;
int b2;
int mb;
int mc;
int md;
// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5;
//*********************************************
const String sSeperator = " ";
//*********************************************
Servo ServoLIDAR;
#define ServoLIDARPosit 1450
#define  byteServo_LIDAR_Pin 11
String sA = "";
//*********************************************
//X Axis servo
Servo X_Axis;
#define byteXaxisServoPin 9
#define iX_Posit90 1538 //using writeMicroseconds
int iX_HowMuch = iX_Posit90;
//Y Axis servo
Servo Y_Axis;
#define byteYaxisServoPin 10
#define iY_Posit90 1492 //using writeMicroseconds
int iY_HowMuch = iY_Posit90;
//*************************************************
TFMini tfmini;
// unsigned long lPreviousLIDAR_SequenceMillis = 0;
#define lLIDAR_SequenceInterval 50 // milliseconds
// 25mS is 40Hz
// 33mS ~30Hz
// 50mS 20Hz
// bool bBlankLIDAR = false;
//*************************************************
//
#define byteUnoDataReady 22
//*************************************************
// unsigned long lCurrentMillis = 0;
//*************************************************
//microwave motion detector= iMW
#define MWM_PINin 34
#define MWM_PINout 36
//*************************************************
// #define lLEDRateInterval 125 //milliseconds,  second
// unsigned long lPreviousLED_BlinkMillis = 0;
// bool bLedOnOff = false;
//*************************************************
String sMessageToSend;
String sPi;
//*************************************************
//*************************************************
// bool bRcveDone = false;
//*************************************************
// for DHT11,
//      VCC: 5V or 3V
//      GND: GND
//      DATA: 2
#define pinDHT11 2
SimpleDHT11 dht11(pinDHT11);
#define iDHT_Interval 60503
//*************************************************
#define iDoVolts_Interval 45007
//*************************************************
#define MINservo 1000
#define MAXservo 2000
#define SerialBaudRate 9600
#define Serial2BaudRate 115200
#define StringBufferSize 50
//*************************************************
// bool bTask2Started = false;
//*************************************************
#define EVENT_A  0x0001
TaskId_t iEvtID_A;
//
#define EVENT_B  0x0002
TaskId_t iEvtID_B;
//
#define EVENT_C  0x0003
TaskId_t iEvtID_C;
//
#define EVENT_D  0x0004
TaskId_t iEvtID_D;
//
#define EVENT_E  0x0005
TaskId_t iEvtID_E;
//
#define EVENT_F  0x0006
TaskId_t iEvtID_F;
//*************************************************
#define  SEM_ID_01   1   // Semaphore id
#define  SEM_ID_02   2   // Semaphore id
#define  SEM_ID_03   3   // Semaphore id
#define  SEM_ID_04   4   // Semaphore id
// #define TIMEOUT_A
//*************************************************
Stream* stream2Ptr;
//*************************************************


void setup()
{

  pinMode(40, OUTPUT);
  digitalWrite( 40, LOW );
  ////
  pinMode(byteUnoDataReady, OUTPUT);//setup interrupt pin as output
  digitalWrite(byteUnoDataReady, LOW);//set state initial
  ////
  Serial1.begin(TFMINI_BAUDRATE);
  // Initialize the TFMini LIDAR
  tfmini.begin(&Serial1);
  // Initialize single measurement mode with external trigger
  tfmini.setSingleScanMode();
  ////
  Serial2.begin( Serial2BaudRate );
  stream2Ptr = &Serial2;
  ////
  pinMode(MWM_PINin, INPUT);
  pinMode(MWM_PINout, OUTPUT);
  ////
  pinMode(LED_BUILTIN, OUTPUT);//blink LED at pin 13
  ////
  analogReadResolution( 12 );
  ////
  ServoLIDAR.attach( byteServo_LIDAR_Pin, MINservo, MAXservo );  // attaches the servo pin to the servo object
  ServoLIDAR.writeMicroseconds(ServoLIDARPosit);//sets initial posit
  ////
  X_Axis.attach( byteXaxisServoPin, MINservo, MAXservo );
  X_Axis.writeMicroseconds( iX_Posit90 );
  ////
  Y_Axis.attach( byteYaxisServoPin, MINservo, MAXservo );
  Y_Axis.writeMicroseconds( iY_Posit90 );
  ////
  Serial.begin(SerialBaudRate);
  ////
  // allocate a buffer in memory for these strings
  sMessageToSend.reserve( StringBufferSize );
  sPi.reserve( StringBufferSize );
  ////
  Wire.begin();
  bmp085Calibration();
  ////
  ADC->ADC_MR |= 0x80;  //set free running mode on ADC
  REG_ADC_MR = (REG_ADC_MR & 0xFFF0FFFF) | 0x00020000; // adc start up value
  // ADC->ADC_CHER = 0x80; //enable ADC on pin A0
  ////
  Kernel.Kn_Start();

} // setup()



void loop()
{
  // if ( !bTask2Started )
  // {
  Errno_t error;

  TaskId_t fC_P;
  // fCheck_Pressure
  error = Kernel.Tk_CreateTask(fCheck_Pressure, fC_P);
  if ( error != E_SUCCESS )
  {
    fKernalError( "fCheck_Pressure", error );
  }
  ////
  // bTask2Started = !bTask2Started;
  ////
  Kernel.Tk_StartTask(fC_P);
  // fCheck_TH
  TaskId_t fC_TH;
  error = Kernel.Tk_CreateTask(fCheck_TH, fC_TH);
  if ( error != E_SUCCESS )
  {
    fKernalError( "fCheck_TH", error );
  }
  Kernel.Tk_StartTask( fC_TH ) ;
  // fCheckInputVoltage
  TaskId_t fDoVolts;
  error = Kernel.Tk_CreateTask( fCheckInputVoltage, fDoVolts );
  if ( error != E_SUCCESS )
  {
    fKernalError( "fCheckInputVoltage", error );
  }
  Kernel.Tk_StartTask( fDoVolts ) ;
  //  fDoLIDAR
  TaskId_t fLIDAR;
  error = Kernel.Tk_CreateTask( fDoLIDAR, fLIDAR );
  if ( error != E_SUCCESS )
  {
    fKernalError( "fDoLIDAR", error );
  }
  Kernel.Tk_StartTask( fLIDAR ) ;
  //    //
  //    ////////////////////////////
  //    ////////////////////////////
  //    ////////////////////////////
  // fReadSerial
  error = Kernel.Tk_CreateTask( fReadSerial, iEvtID_A );
  Kernel.Tk_StartTask( iEvtID_A );
  // fDO_bRcveDone
  error = Kernel.Tk_CreateTask( fDO_bRcveDone, iEvtID_B );
  Kernel.Tk_StartTask( iEvtID_B );
  // fTweakServoX
  error = Kernel.Tk_CreateTask( fTweakServoX, iEvtID_C );
  Kernel.Tk_StartTask( iEvtID_C );
  // fTweakServoY
  error = Kernel.Tk_CreateTask( fTweakServoY, iEvtID_D );
  Kernel.Tk_StartTask( iEvtID_D );
  // fLIDAR_ServoAspectChange
  error = Kernel.Tk_CreateTask( fLIDAR_ServoAspectChange, iEvtID_E );
  Kernel.Sm_Release( SEM_ID_02 ); // release a single SEMAPHORE token
  Kernel.Sm_Release( SEM_ID_03 );
  Kernel.Tk_StartTask( iEvtID_E );
  // fSendOut_SEMAPHORE
  error = Kernel.Tk_CreateTask( fSendOut_SEMAPHORE, iEvtID_F );
  Kernel.Sm_Release( SEM_ID_01 ); // release a single SEMAPHORE token
  Kernel.Tk_StartTask( iEvtID_F );
  ////////////////////////////////////////
  ////////////////////////////////////////
  ////////////////////////////////////////
  // }
  for ( ;; )
  {
    if ( stream2Ptr->available() >= 1 )
    // if (Serial2.available() >= 1)
    {
      Kernel.Ev_Send(iEvtID_A, EVENT_A);
      Kernel.Sm_Claim(SEM_ID_04, uMT_WAIT); // stop and wait for serial to be received and release of SEMAPHORE token
    }  //  serial available
    Kernel.Tk_Yield();
  }
} // loop
//******************************************
//******************************************
//******************************************
//
void fKernalError(String sTask, unsigned error)
{
  Serial.print( " Tk_CreateTask(): " + sTask + " Failure! - returned " );
  Serial.println( error );
  Serial.flush();
  Kernel.isr_Kn_FatalError();
} // void fKernalError()
//******************************************
//   digitalWriteDirect / digitalReadDirect
//******************************************
// example: digitalWriteDirect(13, HIGH)
inline void digitalWriteDirect(int pin, boolean val)
{
  if (val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
  else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;
}
// example: digitalReadDirect(12)
inline int digitalReadDirect(int pin)
{
  return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin);
}
//******************************************
//
//******************************************
//
//******************************************
//******************************************
///
//******************************************
void fDoLIDAR()
{
  //
  long iLIDAR_Distance = 0;
  long iLIDAR_Strength = 0;
  String sMsgToSend;
  String s_cm = "cm ";
  sMsgToSend.reserve( 16 );
  while (1)
  {
    Kernel.Tm_WakeupAfter( lLIDAR_SequenceInterval );   // Wake up after ... milliseconds
    Kernel.Sm_Claim(SEM_ID_02, uMT_WAIT); // // stop lidar servo
    Kernel.Sm_Claim(SEM_ID_03, uMT_WAIT); // stop x y servo
    //    if ( !bBlankLIDAR )
    //    {
    if ( digitalReadDirect( MWM_PINin ) )
      // if ( digitalRead( MWM_PINin ) == 1 )
    {
      digitalWriteDirect( MWM_PINout, HIGH );
      tfmini.externalTrigger();
      iLIDAR_Distance = tfmini.getDistance();
      iLIDAR_Strength = tfmini.getRecentSignalStrength();
      if (iLIDAR_Distance < 600)
      {
        // Serial.println( "TF Mini distance " + String(iLIDAR_Distance) + s_cm + " strength " + String(iLIDAR_Strength ) );
        // Serial.flush();
        sMsgToSend.concat( s_cm + String(iLIDAR_Distance) + " " + String(iLIDAR_Strength ) );
        Kernel.Sm_Claim(SEM_ID_01, uMT_WAIT); // // claim a single SEMAPHORE token
        sMessageToSend = sMsgToSend;
        Kernel.Ev_Send(iEvtID_F, EVENT_F);  // trigger fSendOut_SEMAPHORE + consume a single SEMAPHORE token
        sMsgToSend = "";
      }
    }
    else
    {
      digitalWriteDirect( MWM_PINout, LOW );
    }
    Kernel.Sm_Release( SEM_ID_02 ); // release a single SEMAPHORE token
    Kernel.Sm_Release( SEM_ID_03 );
  }
}
//******************************************
//      bmp085 functions
//******************************************
// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;

  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6) >> 12) >> 11;
  x2 = (ac2 * b6) >> 11;
  x3 = x1 + x2;
  b3 = (((((long)ac1) * 4 + x3) << OSS) + 2) >> 2;

  // Calculate B4
  x1 = (ac3 * b6) >> 13;
  x2 = (b1 * ((b6 * b6) >> 12)) >> 16;
  x3 = ((x1 + x2) + 2) >> 2;
  b4 = (ac4 * (unsigned long)(x3 + 32768)) >> 15;

  b7 = ((unsigned long)(up - b3) * (50000 >> OSS));
  if (b7 < 0x80000000)
    p = (b7 << 1) / b4;
  else
    p = (b7 / b4) << 1;

  x1 = (p >> 8) * (p >> 8);
  x1 = (x1 * 3038) >> 16;
  x2 = (-7357 * p) >> 16;
  p += (x1 + x2 + 3791) >> 4;

  return p;
}
// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;

  x1 = (((long)ut - (long)ac6) * (long)ac5) >> 15;
  x2 = ((long)mc << 11) / (x1 + md);
  b5 = x1 + x2;

  return ((b5 + 8) >> 4);
}
// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;

  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x4 + (OSS << 6));
  Wire.endTransmission();

  // Wait for conversion, delay time dependent on OSS
  // delay(2 + (3 << OSS));
  Kernel.Tm_WakeupAfter( 2 + (3 << OSS) );
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);

  // Wait for data to become available
  while (Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8 - OSS);

  return up;
}
// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
  // Wait at least 4.5ms
  Kernel.Tm_WakeupAfter( 5 );
  // delay(5);
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}
// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}
// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;

  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(BMP085_ADDRESS, 2);
  while (Wire.available() < 2);
  msb = Wire.read();
  lsb = Wire.read();

  return (int) msb << 8 | lsb;
}
// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;

  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(BMP085_ADDRESS, 1);
  while (!Wire.available())
    ;

  return Wire.read();
}
void fCheck_Pressure()
{
  Errno_t error;
  // TimerId_t TmId;
  String sMsgToSend;
  sMsgToSend.reserve( StringBufferSize );
  short bmp_temperature;
  long bmp_pressure;
  // float bmp_altitude;
  //Serial.println( "do fCheck_Pressure" );
  //Serial.flush();
  while (1)
  {
    error = Kernel.Tm_WakeupAfter( BMP_Interval );   // Wake up after ... seconds
    if (error != E_SUCCESS)
    {
      Serial.print(F(" WakeupAfter(): Tm_WakeupAfter() Failure! - returned "));
      Serial.print((unsigned)error);
      Serial.flush();
      break;
    }
    if ( digitalReadDirect( MWM_PINin ) == 0 )
    {
      sMsgToSend = "Pa ";
      // Serial.println( "do fCheck_Pressure3" );
      // Serial.flush();
      bmp_temperature = bmp085GetTemperature(bmp085ReadUT());
      bmp_pressure = bmp085GetPressure(bmp085ReadUP());
      // sMsgToSend.concat( String(bmp_temperature) + sSeperator );
      sMsgToSend.concat( String(bmp_pressure) + sSeperator );
      // sMsgToSend.concat( String(bmp_altitude) + sSeperator );
      // Serial.println( sMsgToSend );
      // Serial.flush();
      Kernel.Sm_Claim(SEM_ID_01, uMT_WAIT); // // claim a single SEMAPHORE token
      sMessageToSend = sMsgToSend;
      Kernel.Ev_Send(iEvtID_F, EVENT_F);  // trigger fSendOut_SEMAPHORE + consume a single SEMAPHORE token
      sMsgToSend = "";
    }
  }
  //  // bTask2Started = false;
}
//******************************************
void fCheck_TH()
{
  // TimerId_t TmId1;
  String sMsgToSend;
  byte temperature = 0;
  byte humidity = 0;
  String sTH = "TH ";
  sMsgToSend.reserve( 16 );
  while (1)
  {
    Kernel.Tm_WakeupAfter( iDHT_Interval );   // Wake up after ... seconds
    if ( digitalReadDirect( MWM_PINin ) == 0 )
    {
      dht11.read(&temperature, &humidity, NULL);
      sMsgToSend = sTH;
      sMsgToSend.concat( String(temperature) + sSeperator + String(humidity) );
      // Serial.println( sMsgToSend );
      // Serial.flush();
      Kernel.Sm_Claim(SEM_ID_01, uMT_WAIT); // // claim a single SEMAPHORE token
      sMessageToSend = sMsgToSend;
      Kernel.Ev_Send(iEvtID_F, EVENT_F);  // trigger fSendOut_SEMAPHORE + consume a single SEMAPHORE token
      sMsgToSend = "";
    }
  } // while (1)
}
//******************************************
//
//******************************************
void fDO_bRcveDone()
{
  String sAspectCng = "AspectCng";
  String s_gX = "gX";
  String s_gY = "gY";
  Event_t  eventoutB;
  while (1)
  {
    Kernel.Ev_Receive(EVENT_B, uMT_ANY, &eventoutB);
    digitalWriteDirect( 40, HIGH );
    sPi.replace( "/", " " );
    // sPi.trim();
    // Serial.println(sPi + " bRcveDone" );
    // Serial.flush();
    ////
    if (sPi.substring(0, 9) == sAspectCng)
    {
      // Serial.println(sPi + " rcvd" );
      sA = sPi.substring(9);
      Kernel.Ev_Send(iEvtID_E, EVENT_E);
      // Serial.println("AspectString " + sA );
      // Serial.flush();
    }
    ////
    // Serial.println( sPi.substring(0, 2) );
    if ( sPi.substring(0, 2) == s_gX )
    {
      // Serial.println(sPi);
      // Serial.flush();
      // stop lidar if lidar has token do not wait for freed token skip torque attempt
      if ( Kernel.Sm_Claim(SEM_ID_03, uMT_NOWAIT) != E_WOULD_BLOCK )
      {
        iX_HowMuch = sPi.substring(3).toInt();
        Kernel.Ev_Send(iEvtID_C, EVENT_C);
      }
    }
    ////
    if ( sPi.substring(0, 2) == s_gY )
    {
      // Serial.println(sPi);
      // Serial.flush();
      if ( Kernel.Sm_Claim(SEM_ID_03, uMT_NOWAIT) != E_WOULD_BLOCK )
      {
        iY_HowMuch = sPi.substring(3).toInt();
        Kernel.Ev_Send(iEvtID_D, EVENT_D);
      }
    }
    sPi = "";
    digitalWriteDirect( 40, LOW );
  }
}
//******************************************
//
//******************************************
void fReadSerial()
{
  //
  Event_t  eventoutA;
  char OneChar;
  while (1)
  {
    // Event_t  eventoutA;
    Kernel.Ev_Receive(EVENT_A, uMT_ANY, &eventoutA);
    ////
    while ( stream2Ptr->available() )
    {
      OneChar = stream2Ptr->read();
      if ( (OneChar != '\n') )
      {
        //Serial.println(sPi);
        sPi.concat( OneChar );
        // Serial.println(sPi + " char" );
        // Serial.flush();
        digitalWrite( 40, LOW );
      } // if ( (OneChar != '\n') )
      else
      {
        Kernel.Ev_Send(iEvtID_B, EVENT_B);  // trigger fDO_bRcveDone
        // bRcveDone = true;
        // Serial.println( "bRcveDone set true" );
        Serial.flush();
        break;
      } // if ( (chrOne != '\n') ) else
    } // while ( Serial2.available() )
    Kernel.Sm_Release( SEM_ID_04); // release serial avaible trigger in loop
  } //  while (1)
}
//******************************************
//
/*
  1000 u sec to 2000 u sec limit = 90 degrees of travel +/-45 deg
  1K = 45 to 2K = 135

*/
//******************************************
void fTweakServoX()
{
  Event_t  eventoutC;
  while (1)
  {
    Kernel.Ev_Receive(EVENT_C, uMT_ANY, &eventoutC);
    X_Axis.writeMicroseconds( iX_HowMuch );
    Kernel.Sm_Release( SEM_ID_03 );
  }
}
void fTweakServoY()
{
  Event_t  eventoutD;
  while (1)
  {
    Kernel.Ev_Receive(EVENT_D, uMT_ANY, &eventoutD);
    // Serial.println( "iY_HowMuch = " + String(iY_HowMuch) );
    Y_Axis.writeMicroseconds( iY_HowMuch );
    Kernel.Sm_Release( SEM_ID_03 );
  }
}
//******************************************
//  send data to RPi
//******************************************
void fSendOut_SEMAPHORE()
{
  Event_t  eventoutF;
  while (1)
  {
    Kernel.Ev_Receive(EVENT_F, uMT_ANY, &eventoutF);
    // Serial.println("fSendOut_SEMAPHORE 0 " + sMessageToSend );
    // Serial1.flush();
    if ( sMessageToSend.length() >= 2 )
    {
      // Serial.println("fSendOut_SEMAPHORE 1 " + sMessageToSend );
      // Serial.flush();
      stream2Ptr->println( sMessageToSend );
      digitalWrite(byteUnoDataReady, HIGH);//TRIGGER RPi3 to read data
      // delay( .05 );
      Kernel.Tm_WakeupAfter( 50 );
      // Serial.println( sMessageToSend.length() );
      // Serial.println("fSendOut_SEMAPHORE " + sMessageToSend );
      // Serial.flush();
      digitalWrite(byteUnoDataReady, LOW);//reset
      sMessageToSend = "";
      Kernel.Sm_Release( SEM_ID_01 ); // release a single SEMAPHORE token
    }
  }
}
//****************************************************
//
//****************************************************
void fLIDAR_ServoAspectChange()
{

  char fltBuff[32];
  String sAspectComplete = "AspectComplete";
  Event_t  eventoutE;
  String sMsgToSend;
  sMsgToSend.reserve( 16 );
  //
  while (1)
  {
    Kernel.Ev_Receive(EVENT_E, uMT_ANY, &eventoutE);
    Kernel.Sm_Claim(SEM_ID_02, uMT_WAIT); // // claim a single SEMAPHORE token.
    if ( sA.length() < 8 )
    {
      // bBlankLIDAR = true;
      sA.toCharArray(fltBuff, sizeof(fltBuff));
      float fltAspect = atof(fltBuff);
      //
      if (fltAspect > 900.00f)
      {
        // Serial.println("AspectString " + String(sA));
        // Serial.flush();
        ServoLIDAR.writeMicroseconds(fltAspect);
      }
      //delay(20);
      // Serial.println( "AspectComplete " + sA );
      // Serial.flush();
    }
    else
    {
      Serial.println("AspectString reject " + String(sA));
    }
    // Serial.println( "fLIDAR_ServoAspectChange " + sMessageToSend );
    // Serial.flush();
    Kernel.Sm_Release( SEM_ID_02 ); // release a single SEMAPHORE token
    Kernel.Sm_Claim(SEM_ID_01, uMT_WAIT); // // claim a single SEMAPHORE token
    sMsgToSend = sAspectComplete;
    sMessageToSend = sMsgToSend;
    Kernel.Ev_Send(iEvtID_F, EVENT_F);  // trigger fSendOut_SEMAPHORE + consume a single SEMAPHORE token
    // bBlankLIDAR = false;
    //
  }
}
//******************************************
//
//******************************************
void fCheckInputVoltage()
{
  String sMsgToSend;
  String s_Volts = "VOLT ";
  sMsgToSend.reserve( 16 );
  while (1)
  {
    Kernel.Tm_WakeupAfter( iDoVolts_Interval );   // Wake up after ... seconds
    // Serial.println( "dovolt awake1" );
    // Serial.flush();
    if ( digitalReadDirect( MWM_PINin ) == 0 )
    {
      sMsgToSend = s_Volts;
      sMsgToSend.concat(String(analogRead(A0)));
      // Serial.println( "fCheckInputVoltage" + sMsgToSend );
      // Serial.flush();
      Kernel.Sm_Claim(SEM_ID_01, uMT_WAIT); // // claim a single SEMAPHORE token
      sMessageToSend = sMsgToSend;
      Kernel.Ev_Send(iEvtID_F, EVENT_F);  // trigger fSendOut_SEMAPHORE + consume a single SEMAPHORE token
      sMsgToSend = "";
      // Kernel.Sm_Release(SEM_ID_02);
    }
  }
}
//******************************************
//
//*************************************************

Hope it gives you ideas.

uMT has a users manual.

I would ignore the pulse for triggering the function and use it only to adjust the time when the next function run would occur.

#define period 20

uint32 last_run = 0;

void loop() {
  if (millis() - last_run > period) {
    last_run = millis();
    function();
  }
  if (detect_pulse() && millis() - last_run < period >> 1) {
    last_run = millis();
  }
}

You might have to adapt that if your function runs for some longer time but above code should give you the idea.

like this approach, but not sure about the conditions. if it occur before the period expires, doesn't it make sense to reset last_run AND execute the function? if it occurs soon(??) after the period expires and just after the function is executed, just reset last_run?

That adjusts the timing if the pulse occurs shortly after the function was triggered. If more than half the period went by already it won't adapt. Of course you can modify the code to react in that direction too.

i think the goal is to remain synchronized with a pulse when it occurs

Hi idahowalker,

Sorry for my unclear problem statement. Actually the goal is to keep synced with that external device, so using the internal clock is not an option I guess.

I will look into the uMT and see if it helps. thanks for the info!

Yes! I should have made it clear. The goal is to keep synchronized with the device which provides the pulse signals.

Sure. You are describing a PLL. Implement the three main parts of a PLL in software and then assemble them.

  1. Voltage controlled oscillator
  2. Phase detector
  3. Low pass filter

In this case, voltage doesn't exist, it's just a control variable from the phase detector.

Doing it in this modular way, will keep it clean and allow you to easily implement smart features (e.g. dealing with occasional signal loss etc.).

You are measuring only a few ADC counts? That doesn't sound very reliable. Amplify the signal before conversion if you can. For very small signals, operating the ADC at its lower limit may not be wise. You should probably add some DC offset.

consider
generates events based on the timer. uses the pulse to update the period

const byte InpPin = A1;
byte inpState;

unsigned long period;;
unsigned long msecLst;
unsigned long msec;

enum { Pulse, TimeOut };

char s [80];

// -----------------------------------------------------------------------------
void
action (
    int type)
{
    sprintf (s, " %6lu %s", period, Pulse == type ? "pulse" : "timeout");
    Serial.println (s);

    if (msecLst)
        period  = msec - msecLst;
    msecLst = msec;
}

// -----------------------------------------------------------------------------
void
loop (void)
{
    msec = millis ();

    byte inp = digitalRead (InpPin);
    if (inpState != inp)  {
        inpState = inp;
        delay (10);         // debounce

        if (LOW == inp)  {
            if (! msecLst)
                msecLst = msec;
            else if (! period)
                period = msec - msecLst;
            else if ((msec - msecLst) > (period / 2))
                period = (period + (msec - msecLst)) / 2;
            else
                period = (period + (msec - msecLst + period)) / 2;
        }
    }

    if (period && (msec - msecLst) >= period)  {
        action (TimeOut);
    }
}

void
setup (void)
{
    Serial.begin (9600);

    pinMode (InpPin, INPUT_PULLUP);
    inpState = digitalRead (InpPin);
}

I have had to do that (oddly enough, also at 50Hz) and I took a similar approach. Monitor the incoming signal using pulseIn() and replicate that pulse width on an output pin. If the input signal stops, then continue to replicate the last pulse received until the input restarts.

My situation was a bit more complicated than this and it sounds like your input might not be a logic-level signal, but our basic approaches were the same.

Thanks for the info, and I think a PLL is what I want! But I found programming a PLL quite complicated, though you have suggested implementing the 3 parts and assembling them... Do you have any good material/reference about these 3 parts to start with? Thank you

a conventional PLL uses is a combination of digital logic and analog circuits to drive an voltage controller oscillator. while i'm sure it can be modeled as an electronic circuit in software, i don't believe it is good model for a software implementation (are you going to implement a VCO)?

I did a quick check on PLL and found that it's helpful to deal with my dirty external clock, as I will have both missing pulses (described in this post) and also noisy pulses (which occur unexpectedly). I am curious about it and still looking into the software implementation (and find it not that easy...) Do u have any experience with its software implementation?

I will also try this suggestion to see if I can have an easier solution. Thanks!

not like you think. i have experience synchronizing a local VCO to an external clock using a combination of hardware and software.

as i posted, the timer takes the place of the VCO and needs to be adjusted based on errors between it and the external clock. handling the missing pulses is a challenge especially when starting.

software allows non-linear conditional operations that often can't be done in a non-software implementation

a hardware PLL typically exclusive ORs the 2 clocks, generating a pulse which is low-pass filtered as the voltage input to a VCO to match frequency.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.