Creating Delays In C

Thread Starter

henry6635

Joined Feb 27, 2018
6
Hi

I am writing a program in C for a 16F690. I am using a poteniometer but I need some way of reading the POT whilst the delay is occuring for example the user could have changed settings but it wont appear until after the 10s delay. There will be a total of 10 modes, (Mode 1 having a delay of 10s and a mode 10 a delay of 1s.)

Any help would be greatly appreciated.

Code:
[/B]
if(mode_chosen > 0 && mode_chosen < 100) // Mode 1 - Between values 0 to 100. 
{ 
PORTC=one; // Number 1 on 7 segment display. 
PORTCbits.RC7 = 1; // Pump is turned on. 
__delay_us(750000); // 0.75s delay.
PORTCbits.RC7 = 0; // Pump is turned off.
__delay_us(10000000); // 10s delay.
}



else if(mode_chosen > 100 && mode_chosen < 200) // Mode 2 - Between values 100 to 200. 
{ 
PORTC= two; // Number 2 on 7 segment display. 
PORTCbits.RC7 = 1; // Pump is turned on. 
__delay_us(750000); // 0.75s delay. 
PORTCbits.RC7 = 0; // Pump is turned off. 
__delay_us(9000000); // 9s delay. 
}
[B]
 

MrChips

Joined Oct 2, 2009
30,824
Simple. Don't use the library functions. Write your own delay function.
There are two types of delay functions you can create, blocking and non-blocking.

In a blocking function, the function will not return unless the operation (delay) is completed.

In a non-blocking function, the function returns a status indicating whether the operation is successful (completed) or not.
In your case, you obviuosly want to create a non-blocking delay function.
 

Thread Starter

henry6635

Joined Feb 27, 2018
6
Simple. Don't use the library functions. Write your own delay function.
There are two types of delay functions you can create, blocking and non-blocking.

In a blocking function, the function will not return unless the operation (delay) is completed.

In a non-blocking function, the function returns a status indicating whether the operation is successful (completed) or not.
In your case, you obviuosly want to create a non-blocking delay function.

Is there an example of this anywhere or could you create one even for the first mode?
 

JohnInTX

Joined Jun 26, 2012
4,787
For non-blocking timers using PICs, here are some basics:
Use TIMER 2 with PR2 to create a periodic interrupt. For your timings maybe start at 100ms interrupt period.
Then create a count-down timer that in the interrupt routine that counts to zero and stays there.
C:
  unsigned char timer_100ms
void interrupt(void)
{
  if(TMR2IF){  // interrupts every 100ms
      TMR2IF = 0; // ack the flag
      // decrement timer(s) until zero
      if(timer_100ms) timer_100ms--;
  }//TMR2 interrupt
}

In main, set the timer to however many of those 100ms ticks you need for the delay:
timer_100ms = 10; // 1 sec delay

Then your main loop looks something like this:
C:
  while(1){
      timer_100ms = 10;  // 1 sec delay

      if(timer_100ms ==0){ // when the timer runs out
          timer_100ms = 10;  // reload it and
          do_something()// perform the action
      }
     // meanwhile while the timer is NOT zero i.e. for the other 9.9sec
         do_everything_else();
}// while
Note now rather than burning up all your CPU time counting, you let the timer /interrupt do it. You have lots of time while the timer is >0 to do other tasks. Since the interrupt will not decrement the timer past 0, you don't have to worry about when you sample it - the timeout indication will be ready when you are. Note also that you can have multiple independent timer registers driven off that single interrupt.

There are some enhancements and caveats to all of this - timer granularity for one and the fact that the timer accuracy is +0/-100ms - but those are the basics. Sometimes for longer times with a finer granularity, you need multibyte timers that add some complexity or alternatively, you can keep everything single bytes and use prescalers. Lots of options but as you are finding out, using simple blocking delays can be problematic requiring a more sophisticated approach.

Good luck!
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
Here's a complete quickie that runs on the 16F690 in MPSIM under MPLABX and XC8. It uses timer 2 to generate 1ms tiks then counts those into 3 derived timers, two 16 bit and one 8 bit. You can compile this in MPLABX with the free XC8 and use the simulator to run it. Set the simulator for 2MHz Fcyc in Project Properties. Set breakpoints on the 'do' routines and use the stopwatch to confirm that the timing is working. Note that in main, the while loop has lots of extra time at the bottom after checking the timers for zero. Nice.

Have fun!
C:
/*
* File:  DerivedTimers.c
* Author: JohnInTX on AAC
*
* Created on February 27, 2018, 11:38 AM
* Shows how to use 8 and 16 bit derived timers using a timer interrupt instead
* of blocking delay().
*/

// CONFIG
#pragma config FOSC = INTRCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF  // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF  // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON  // MCLR Pin Function Select bit (MCLR pin function is MCLR)
#pragma config CP = OFF  // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF  // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON  // Brown-out Reset Selection bits (BOR enabled)
#pragma config IESO = ON  // Internal External Switchover bit (Internal External Switchover mode is enabled)
#pragma config FCMEN = ON  // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)

#include <xc.h>

#define FALSE 0
#define TRUE !FALSE

//-------------------- TIMERS  ----------------------------

unsigned int timer1_1ms;  // 16bit derived timers
unsigned int timer2_1ms;
unsigned char bytetimer_1ms;

bit timeout1,timeout2;  // 1= 16 bit timer has run to 0

//------------------------ INTERRUPT SERVICE  ------------------------
// for 16 bit timers, decrement timer when timeout flag is FALSE.
// When timer runs to 0000, set timeout flag and stop decrementing.
// When loading 16 bit timer, load timer first then clear timeout flag.
// Timeout flag protects multi byte timer from interrupts while loading.
// If reloading a 16 bit timer with nonzero value, set timeout flag to
// protect timer or disable TMR2IE while loading.
// 8 bit timers don't require protection since their loading and checking
// is atomic.
void interrupt isr(void)
{
  if(TMR2IF){
  TMR2IF = 0;  // process 8 bit timer
  if(bytetimer_1ms)bytetimer_1ms--;
 
  if(!timeout1){  // process non-zero 16 bit timer
  timer1_1ms--;  // decrement timer
  if(timer1_1ms == 0) // set timeout/inhibit bit on Z
  timeout1 = TRUE;
  }
 
  if(!timeout2){  // process the other one
  timer2_1ms--; 
  if(timer2_1ms == 0)
  timeout2 = TRUE;
  } 
  }// TMR2IF
}// interrupt service

//------------- INIT TIMER CHAIN  -----------------------------
// Sets TMR2 for 1msec interrupts.  Clears/inits derived timers
// Expects 8MHz Fosc

void init_timers(void){
  TMR2IE = 0;  // disable timer 2 IE )
  bytetimer_1ms = 0;  // clear 8 bit timer
  timer1_1ms = 0;  // clear 16 bit timers
  timer2_1ms = 0;
  timeout1 = TRUE;  // 16 bit timers are 0
  timeout2 = TRUE;
 
  OSCCON = 0b01111000; // internal OSC, FOSC specifies internal 8MHZ
 
  PR2 = 250;  // set timer 2 for 1ms tiks
  T2CON = 0b00001001; // TMR2 OFF, postscaler 2, prescaler 4
  TMR2IF = 0;  // clear stray interrupt flags
  TMR2IE = 1;  // enable TM2 interrupt
  PEIE = 1;  // enable peripheral interrupts
  GIE = 1;  // and global interrupts
  TMR2ON = 1;  // start timer 
}

//--------------- TIMED TASKS  -----------------------
// Called when associated timer is 0.  Reload timer then
// do whatever the task is to do.
void do_1(void)
{
  // reload timer then process the 500ms task
  timer1_1ms = 500;  // 500 msec
  timeout1 = FALSE;
}

void do_2(void)
{
  // reload timer then process the 10 sec task
  timer2_1ms = 10000;  // 10 sec
  timeout2 = FALSE;

}

void do_byteT(void)
{
  bytetimer_1ms = 100; // 100msec
}

//------------------ MAIN  ------------------------
void main(void)
{
  init_timers();
  while(1){  // check timers and process task on each timeout
  if(timeout1)
  do_1();
  if(timeout2)
  do_2();
  if(bytetimer_1ms == 0)
  do_byteT();
  // meanwhile, process other stuff
  }// while 
}
 

Attachments

Ian Rogers

Joined Dec 12, 2012
1,136
ADC modules have interrupts.... Just use the interrupt to store the changes.. I doubt it will have a great affect on the delay time... Especially at 1 second intervals..
 

Thread Starter

henry6635

Joined Feb 27, 2018
6
Code:
if(mode_chosen > 0 && mode_chosen < 100)                // Mode 1 - Between values 0 to 100.
            {      
            PORTC=one;                                             // Number 1 on 7 segment display.
            PORTCbits.RC7 = 1;                                       // Pump is turned on.
              timer2_1ms = 5000;  // 5 sec
              timeout1 = FALSE;
            PORTCbits.RC7 = 0;                                    // Pump is turned off.
              timer2_1ms = 1000;  // 10 sec
              timeout1 = FALSE;
            }
Is this the correct way to add it to an existing array? I have all the relevant material up above
 

JohnInTX

Joined Jun 26, 2012
4,787
Code:
if(mode_chosen > 0 && mode_chosen < 100)                // Mode 1 - Between values 0 to 100.
            {     
            PORTC=one;                                             // Number 1 on 7 segment display.
            PORTCbits.RC7 = 1;                                       // Pump is turned on.
              timer2_1ms = 5000;  // 5 sec
              timeout1 = FALSE;
            PORTCbits.RC7 = 0;                                    // Pump is turned off.
              timer2_1ms = 1000;  // 10 sec
              timeout1 = FALSE;
            }
Is this the correct way to add it to an existing array? I have all the relevant material up above
Not really. Trace through the code and you see that you just set 2 timers to what I set them to in the example.
You'll need to do some re-structuring of your code.
Question: is the basic cycle always the same except for the times? For example, regardless of the mode, the pump turns ON for a time then turns OFF for a time? The ON / OFF is the same but just the times are different?
 

Thread Starter

henry6635

Joined Feb 27, 2018
6
Not really. Trace through the code and you see that you just set 2 timers to what I set them to in the example.
You'll need to do some re-structuring of your code.
Question: is the basic cycle always the same except for the times? For example, regardless of the mode, the pump turns ON for a time then turns OFF for a time? The ON / OFF is the same but just the times are different?
Yea that's exactly it. Always on for 0.75s no matter the mode it's the off time that changes.
Mode 1 -- Off for 10s
Mode 2 -- Off for 9s
Mode 3 -- Off for 8s

Etc
 

JohnInTX

Joined Jun 26, 2012
4,787
Yea that's exactly it. Always on for 0.75s no matter the mode it's the off time that changes.
Mode 1 -- Off for 10s
Mode 2 -- Off for 9s
Mode 3 -- Off for 8s

Etc
OK. And if you are in the middle of timing ON or OFF and you detect that the mode has changed, what should it do? Restart the cycle from ON, continue with the current cycle or???
 

JohnInTX

Joined Jun 26, 2012
4,787
Here is one way to do what you want. There are two tasks that are controlled by interrupt-driven derived timers:
1) A task that polls the ADC once every 100ms and generates the mode number
2) A task that uses a state machine made with switch statement that controls the ON/OFF cycle and remembers where the system is in the cycle. Note that in each state, the timers are only sampled, never waited upon. That allows the mode task to run during those long times. Round and round it goes sampling and looking for timeouts and mode changes. Note that since the ON/OFF cycle is the same for all modes except for the OFF time, there is only one state machine for all modes. It just loads the appropriate OFF time from an array that is indexed by the mode. Nice.

I didn't spend a lot of time slicking this up or avoid some clumsiness in the various constructs - its mostly for illustration - but it does run in MPSIM. You can set breakpoints and watch the various timers and state indicator run around. You'll have to do your own ADC and IO stuff at the indicated points. You might want to clean up what happens when the mode changes in the middle of a cycle. There's also only 4 modes in the array. Add as many as you need and change the N_MODES define.

As I said, this is one way to do it, there are others but this one is easy to understand and modify. If nothing else, it will get you off the dumb delay train. If this is the first time with one of these, it might be difficult to understand at first. Just walk through it and step the code in MPSIM and all will become obvious.

Good luck!

C:
/*
* File:  DerivedTimersAndFSM.c
* Author: JohnInTX
*
* Created on February 27, 2018, 11:38 AM
* Shows how to use 8 and 16 bit derived timers using a timer interrupt instead
* of blocking delay().
* Add state machine to control cycle to illustrate one way to do things during
* delays.
*/

// CONFIG
#pragma config FOSC = INTRCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF  // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF  // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON  // MCLR Pin Function Select bit (MCLR pin function is MCLR)
#pragma config CP = OFF  // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF  // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON  // Brown-out Reset Selection bits (BOR enabled)
#pragma config IESO = ON  // Internal External Switchover bit (Internal External Switchover mode is enabled)
#pragma config FCMEN = ON  // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)

#include <xc.h>

#define FALSE 0
#define TRUE !FALSE

//-------------------- STATES  ----------------------------

enum states {IDLE, TURN_ON, TIMING_ON, TURN_OFF, TIMING_OFF};  // States system can have
enum states SysState;  // declare the state indicator

//------------------- CYCLE TIMES  ------------------------
// Array of cycle OFF times indexed by SysMode
// Mode 1 -> time[0] etc.
// ON time is fixed at 750ms
// OFF times are stored in an array of timer settings indexed by the mode.
// No bounds checking is done here.

#define N_MODES 4  // number of modes for this example
#define ONmsec 750  // .75 sec on for all modes

// array of OFF times:
const unsigned int OFFtimes[N_MODES]= {10000,9000,8000,7000};  // 10-7 sec in milliseconds
unsigned char SysMode;  // what mode its in
unsigned char lastSysMode;  // what it was last time (to detect changes)
bit SysModeChanged;  // 1 = mode changed

//-------------------- TIMERS  ----------------------------
// 16 bit timer for motor
// 8 bit timer for scheduling mode changes
unsigned char ModeTimer_1ms; // 8 bit timer for scheduling mode changes

unsigned int timer1_1ms;  // 16bit derived timer
bit timeout1;  // 1= 16 bit timer has run to 0

#define MODE_POLL_PERIODms 100  // update mode every 100ms

//------------------------ INTERRUPT SERVICE  ------------------------
// for 16 bit timers, decrement timer when timeout flag is FALSE.
// When timer runs to 0000, set timeout flag and stop decrementing.
// When loading 16 bit timer, load timer first then clear timeout flag.
// Timeout flag protects multi byte timer from interrupts while loading.
// If reloading a 16 bit timer with nonzero value, set timeout flag to
// protect timer or disable TMR2IE while loading.

void interrupt isr(void)
{
  if(TMR2IF){
  TMR2IF = 0;  // process non-zero 8 bit timer
  if(ModeTimer_1ms) ModeTimer_1ms--;

  if(!timeout1){  // process non-zero 16 bit timer
  timer1_1ms--;  // decrement timer
  if(timer1_1ms == 0) // set timeout/inhibit bit on Z
  timeout1 = TRUE;
  }
  }// TMR2IF
}// interrupt service

//------------- INIT TIMER CHAIN  -----------------------------
// Sets TMR2 for 1msec interrupts.  Clears/inits derived timers
// Expects 8MHz Fosc

void init_timers(void){
  TMR2IE = 0;  // disable timer 2 IE )

  timer1_1ms = 0;  // clear 16 bit timer
  timeout1 = TRUE;  // 16 bit timer is 0

  ModeTimer_1ms = 0;

  OSCCON = 0b01111000; // internal OSC, FOSC specifies internal 8MHZ

  PR2 = 250;  // set timer 2 for 1ms tiks
  T2CON = 0b00001001; // TMR2 OFF, postscaler 2, prescaler 4
  TMR2IF = 0;  // clear stray interrupt flags
  TMR2IE = 1;  // enable TM2 interrupt
  PEIE = 1;  // enable peripheral interrupts
  GIE = 1;  // and global interrupts
  TMR2ON = 1;  // start timer
}

//--------------- TIMER UTILS  -----------------------
// Called when associated timer is 0.  Reload timer then
// do whatever the task is to do.
void SetTimer1_1ms(unsigned int t)
{
  // reload timer then process the 500ms task
  timeout1 = TRUE;  // set to inhibit interrupt changes while setting
  timer1_1ms = t;  // t msec
  timeout1 = FALSE;  // run timer
}

//------------------  CHECK FOR NEW MODE  --------------------
// Add code to read ADC and generate a mode number. Be sure to
// keep it within the array of OFF times boundaries above.

unsigned char GetCurrentMode(void)
{
  return 1;  // for tests
}

//------------------ MAIN  ------------------------
void main(void)
{
  init_timers();
  SysState = IDLE;
  lastSysMode = 0xFF;  // force an update on startup
  SysModeChanged = FALSE;

  while(1){  // check timers and process task on each timeout
  // ----------------- CHECK AND UPDATE MODE  -----------------------
  // ModeTimer controls how often the ADC is read and mode is updated
  // If mode changes, set SysModeChanged.
  // Stores previous mode to detect changes
  if(ModeTimer_1ms==0){ // time to update mode?
  ModeTimer_1ms = MODE_POLL_PERIODms;  // reset timer then..
  SysMode = GetCurrentMode();  // get current mode
  if(SysMode != lastSysMode){  // look for change and flag if so
  SysModeChanged = TRUE;
  lastSysMode = SysMode;
  }
  }
  //-------------- STATE MACHINE TO PERFORM CYCLE  --------------------
  // Runs the ON/OFF cycle as a state machine.  Breaks out of timed
  // states and goes to IDLE if mode changes so that cycle can be restarted
  // with new parameters
  // Each timing state just quickly checks the timer and mode changes
  // then breaks so that other things can be done i.e. checking ADC and
  // updating mode.

  switch(SysState){
  case IDLE:
  // do what's needed to start or change when mode changes
  // this just drops through to restart the cycle
  SysModeChanged = FALSE; // ack any mode change
  SysState = TURN_ON;  // proceed to turn on]
  break;

  case TURN_ON:
  //port = motor ON  // turn on motor
  SetTimer1_1ms(ONmsec);  // always on the same amount of time
  SysState = TIMING_ON;  // next state is timing the ON time
  break;  // go off to do other things..

  case TIMING_ON:  // wait for timer or mode change
  if(timeout1){  // goto turn off state if timer is zero
  SysState = TURN_OFF;
  break;
  }
  if(SysModeChanged){  // check system mode for changes while timing
  SysState = IDLE;  // and go to IDLE to re-start
  break;
  }
  break;  // no changes, just go off to do other things

  case TURN_OFF:  // turn off motor for time indicated by mode
  // port = motor OFF  // turn motor off
  SetTimer1_1ms(OFFtimes[(unsigned int)SysMode-1]);  // look up OFF time by current mode
  SysState = TIMING_OFF;  // could just drop through also..
  break;

  case TIMING_OFF:  // motor is off, watch timer or mode change
  if(timeout1){
  SysState = IDLE;  // its been off long enough
  break;
  }
  if(SysModeChanged){  // if mode changed, we are done here
  SysState = IDLE;
  break;
  }
  break;

  default : SysState = IDLE;
  }// switch


  }// while
}
Sorry about the indents - it looks better in MPLABX
 

Attachments

Top