How to make a microsecond-precise timer on the STM32L-Discovery ARM board?

9.5k Views Asked by At

I'm trying to implement the Dallas OneWire protocol, but I'm having trouble generating a microsecond delay on the STM32l-Discovery.

How do I implement a timer accurate enough to delay the program for x microseconds?

1

There are 1 best solutions below

1
On BEST ANSWER

For start I must tell you that there is no way to accomplish a precise usec delay using software. Even if you use an interrupt based system you will have latencies. Off course you can achieve a better accuracy with a larger CPU frequencies.

In order to connect with a 1-Wire device you can use:

  • A external interface like DS2482-100
  • A software 1-wire implementation using pin polling.

For the second solution you have to call a software based delay. You can make a flag polling delay or an interrupt based flag polling delay. In both cases you will be sure that a certain amount of time has passed but you can not be sure how match more time has passed. This is because of the CPU latency, the CPU clock etc...

For example consider the following implementation. We program a HW TIMER to continuously count up and we check TIMER's value. We name "jiffy" the time between each TIMER's ticks and jiffies the TIMERS max value:

Low level driver part (ex: driver.h)

// ...
#define  JF_TIM_VALUE         (TIM7->CNT)
int JF_setfreq (uint32_t jf_freq, uint32_t jiffies);
// ...

Low level driver part (ex: driver.c)

// ...
#include <stm32l1xx.h>
#include <misc.h>
#include <stm32l1xx_rcc.h>
#include <stm32l1xx_tim.h>
/*
 * Time base configuration using the TIM7
 * \param jf_freq  The TIMER's frequency
 * \param jiffies  The TIMER's max count value
 */
int JF_setfreq (uint32_t jf_freq, uint32_t jiffies) {
   uint32_t psc=0;

   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
   SystemCoreClockUpdate ();

   if (jf_freq)
      psc = (SystemCoreClock / jf_freq) - 1;

   if (psc < 0xFFFF)       TIM7->PSC = psc;
   else                    return 1;

   if (jiffies < 0xFFFF)   TIM7->ARR = jiffies;
   else                    return 1;
   TIM7->CR1 |= TIM_CR1_CEN;
   return 0;
}
// ...

Middleware jiffy system with some delay implementations jiffy.h:

#include "string.h"

typedef int32_t   jiffy_t;    // Jiffy type 4 byte integer
typedef int (*jf_setfreq_pt) (uint32_t, uint32_t);   //Pointer to setfreq function

typedef volatile struct {
   jf_setfreq_pt  setfreq;       // Pointer to driver's timer set freq function
   jiffy_t        *value;        // Pointer to timers current value
   uint32_t       freq;          // timer's  frequency
   uint32_t       jiffies;       // jiffies max value (timer's max value)
   jiffy_t        jpus;          // Variable for the delay function
}jf_t;


/*
 *  ============= PUBLIC jiffy API =============
 */

/*
 * Link functions
 */
void jf_link_setfreq (jf_setfreq_pt pfun);
void jf_link_value (jiffy_t* v);

/*
 * User Functions
 */
void jf_deinit (void);
int jf_init (uint32_t jf_freq, uint32_t jiffies);

jiffy_t jf_per_usec (void);
void jf_delay_us (int32_t usec);
int jf_check_usec (int32_t usec);

jiffy.c:

#include "jiffy.h"

static jf_t _jf;
#define JF_MAX_TIM_VALUE      (0xFFFF)    // 16bit counters

//Connect the Driver's Set frequency function 
void jf_link_setfreq (jf_setfreq_pt pfun) {
   _jf.setfreq = pfun;
}

// Connect the timer's value to jiffy struct
void jf_link_value (jiffy_t* v) {
   _jf.value = v;
}

// De-Initialize the jf data and un-connect the functions
// from the driver
void jf_deinit (void) {
   memset ((void*)&_jf, 0, sizeof (jf_t));
}

// Initialise the jf to a desired jiffy frequency f
int jf_init (uint32_t jf_freq, uint32_t jiffies) {
   if (_jf.setfreq) {
      if ( _jf.setfreq (jf_freq, jiffies) )
         return 1;
      _jf.jiffies = jiffies;
      _jf.freq = jf_freq;
      _jf.jpus = jf_per_usec ();
      return 0;
   }
   return 1;
}

// Return the systems best approximation for jiffies per usec
jiffy_t jf_per_usec (void) {
   jiffy_t jf = _jf.freq / 1000000;

   if (jf <= _jf.jiffies)
      return jf;
   else
      // We can not count beyond timer's reload
      return 0;
}

/*!
 * \brief
 *    A code based delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 1 usec.
 * \param
 *    usec     Time in usec for delay
 */
void jf_delay_us (int32_t usec) {
   jiffy_t m, m2, m1 = *_jf.value;

   usec *= _jf.jpus;
   if (*_jf.value - m1 > usec) // Very small delays will return here.
      return;

   // Delay loop: Eat the time difference from usec value.
   while (usec>0) {
      m2 = *_jf.value;
      m = m2 - m1;
      usec -= (m>0) ? m : _jf.jiffies + m;
      m1 = m2;
   }
}

/*!
 * \brief
 *    A code based polling version delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 1 usec.
 * \param
 *    usec     Time in usec for delay
 */
 int jf_check_usec (int32_t usec) {
   static jiffy_t m1=-1, cnt;
   jiffy_t m, m2;

   if (m1 == -1) {
      m1 = *_jf.value;
      cnt = _jf.jpus * usec;
   }

   if (cnt>0) {
      m2 = *_jf.value;
      m = m2-m1;
      cnt-= (m>0) ? m : _jf.jiffies + m;
      m1 = m2;
      return 1;   // wait
   }
   else {
      m1 = -1;
      return 0;   // do not wait any more
   }
}

Hmmm you made it till here. Nice

So now you can use it in your application like this: main.c:

#include "driver.h"
#include "jiffy.h"

void do_some_job1 (void) {
   // job 1
}
void do_some_job2 (void) {
   // job 2
}
int main (void) {
   jf_link_setfreq ((jf_setfreq_pt)JF_setfreq);  // link with driver
   jf_link_value ((jiffy_t*)&JF_TIM_VALUE);
   jf_init (1000000, 1000);  // 1MHz timer, 1000 counts, 1 usec per count

   // use delay version
   do_some_job1 ();
   jf_delay_us (300);  // wait for at least 300 usec
   do_some_job1 ();

   // use polling version
   do_some_job1 ();
   while (jf_check_usec (300)) {
      do_some_job2 ();  // keep calling for at least 300 usec
   }
}