Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

Delay until next second in PIC 16F

Status
Not open for further replies.

engr_joni_ee

Advanced Member level 3
Advanced Member level 3
Joined
Nov 3, 2018
Messages
820
Helped
2
Reputation
4
Reaction score
7
Trophy points
18
Activity points
6,848
Hello,

I am having a program in MPLAB in which I have a while loop within the main function. There are two tasks in the while loop, each task takes between 400 - 450 m sec which I don't know how to calculate exactly but I need to put a delay at the end to wait until the next second and then the next iteration of the while loop should start. For example if both tasks need 900 m sec then it should wait for 100 m sec and then the next iteration of the loop should begin. The question is which function in MPLAB puts such delay which is until next second ?


Code:
main ()
while (1)
{
task 1 takes some m sec 
.
.
task 2 takes some m sec
.
.
delay until next second
}
 

Use a timer interrupt of say 1 ms and use a counter variable in the timer isr. Clear counter before each task beginning and save counter to another variable after each task completion. After last task is completed you get the final delay used by all tasks.

counter (variable incremented by 1 in 1ms timer isr).

clear counter variable before starting a task

tasks_delay (variable to save delay of each task)
tasks_delay = tasks_delay + counter

delay_required = 1000 - tasks_delay (assuming tasks_delay never exceed 1000 ms)

Use delay_required variable to get the needed delay after all tasks and before starting new tasks cycle.
 

Hi,

I´d not go for that fast "1ms", I´d rather go for 1s if possible.
(because this needs the ISR to be entered every 1 ms with some processing power overhead)

"If possible" here means: It depends on
* Clock frequency
* timer perifierial to be used (8bit / 16 bit, prescaler ...)
* expected accuracy

Example:
* fclock = 8MHz
* 16 bit timer/counter
* prescaler = 128

--> 8000000 / 128 = 62500
--> set the timer overflow value to 62500-1 = 62499 to get an exact 1s tick.
--> if possible let the timer/counter hardware do the reset/preset of values

For such a solution you don´t need an ISR.
in main loop:
* (in "delay for next second") just wait for the interrupt flag to be set, then clear it and go to the next step

***
If you really don´t need the microcontroller to do anything during that "wait", then you also may use any sleep mode to reduce electrical power.
But if you want the microcontroller still to do some tasks (displaying values, check key press...) you could do this in a "delay for next second" loop.


Klaus
 

I don't have MPLAB IDE installed and so I wrote a sample code in mikro C PRO PIC. See if this works for you.

Code:
char flagRegister = 0;
unsigned long tasks_delay = 0;

sbit endOldtaskCycleFlag at flagRegister.B0;

//Timer1
//Prescaler 1:1; Actual Interrupt Time : 5 ms
//Fosc = 4 MHz
void InitTimer1() {
    T1CON = 0x00;
    TMR1IF_bit = 0;
    TMR1H = 0xFC;
    TMR1L = 0x18;
    TMR1IE_bit = 1;
    INTCON |= 0xC0;
}

void interrupt() {
    if((TMR1IE_bit) && (TMR1IF_bit)) {
        TMR1IF_bit = 0;
        TMR1H = 0xFC;
        TMR1L = 0x18;
        //Enter your code here
        if(endOldtaskCycleFlag == 0)
            tasks_delay++;
        if(endOldtaskCycleFlag == 1)
            tasks_delay--;
    }
}

void main() {
    
    InitTimer1();

    while(1) {
          
          while(tasks_delay != 0);   //Wait here till old task completes
          endOldtaskCycleFlag = 0;   //Old taks completed
          
          //New task cycle
          TMR1ON_bit = 1;            //Enable Timer1
          Delay_ms(400);             //Task1
          Delay_ms(450);             //Task2  (Final task)

          TMR1ON_bit = 0;            //Stop Timer1
          
          if(tasks_delay <= 1000) {  //Tasks will never take more than 1000 ms
             tasks_delay -= 1000;
             endOldtaskCycleFlag = 1;
             TMR1ON_bit = 1;
          }
    }
}

- - - Updated - - -

There was a bug in previous code. Discard it. Use below code.

Code:
char flagRegister = 0;
unsigned long tasks_delay = 0;

sbit endOldtaskCycleFlag at flagRegister.B0;

//Timer1
//Prescaler 1:1; TMR1 Preload = 64536; Actual Interrupt Time : 1 ms
//Fosc = 4 MHz
void InitTimer1() {
    T1CON = 0x00;
    TMR1IF_bit = 0;
    TMR1H = 0xFC;
    TMR1L = 0x18;
    TMR1IE_bit = 1;
    INTCON = 0xC0;
}

void interrupt() {
    if((TMR1IE_bit) && (TMR1IF_bit)) {
        TMR1IF_bit = 0;
        TMR1H = 0xFC;
        TMR1L = 0x18;
        //Enter your code here
        if(endOldtaskCycleFlag == 0)
            tasks_delay++;
        if(endOldtaskCycleFlag == 1)
            tasks_delay--;
    }
}

void main() {
    
    TRISD = 0;
    PORTD = 0;
    LATD = 0;

    InitTimer1();

    while(1) {
          
          while(tasks_delay != 0);   //Wait here till old task completes
          endOldtaskCycleFlag = 0;   //Old taks completed
          
          //New task cycle
          TMR1ON_bit = 1;            //Enable Timer1
          Delay_ms(400);             //Task1
          Delay_ms(450);             //Task2  (Final task)

          TMR1ON_bit = 0;            //Stop Timer1
          
          if(tasks_delay <= 1000) {  //Tasks will never take more than 1000 ms
             tasks_delay = 1000 - tasks_delay;
             LATD = tasks_delay;
             endOldtaskCycleFlag = 1;
             TMR1ON_bit = 1;
          }
    }
}

- - - Updated - - -

This code seems to work fine when your tasks take 400 to 450 ms. See attached video. I can't upload video to youtube as the video resolution is getting decreased when uploaded.

Code:
char flagRegister = 0;
unsigned long tasks_delay = 0;

sbit endOldtaskCycleFlag at flagRegister.B0;

//Timer1
//Prescaler 1:1; TMR1 Preload = 64536; Actual Interrupt Time : 1 ms
//Fosc = 4 MHz
void InitTimer1() {
    T1CON = 0x00;
    TMR1IF_bit = 0;
    TMR1H = 0xEC;
    TMR1L = 0x78;
    TMR1IE_bit = 1;
    INTCON = 0xC0;
}

void interrupt() {
    if((TMR1IE_bit) && (TMR1IF_bit)) {
        TMR1IF_bit = 0;
        TMR1H = 0xEC;
        TMR1L = 0x78;
        //Enter your code here
        if(endOldtaskCycleFlag == 0)
            tasks_delay = tasks_delay + 5;
        if(endOldtaskCycleFlag == 1)
            tasks_delay = tasks_delay - 5;
    }
}

void main() {
    
    TRISD = 0;
    PORTD = 0;
    LATD = 0;

    InitTimer1();

    while(1) {
          
          while(tasks_delay != 0);   //Wait here till old task completes
          endOldtaskCycleFlag = 0;   //Old tasks completed
          //New task cycle
          TMR1ON_bit = 1;            //Enable Timer1
          tasks_delay = 0;
          Delay_ms(400);             //Task1
          Delay_ms(430);             //Task2  (Final task)

          if(tasks_delay <= 1000) {  //Tasks will never take more than 1000 ms
             tasks_delay = 1000 - tasks_delay;
             LATD = tasks_delay;
             endOldtaskCycleFlag = 1;
          }
    }
}

- - - Updated - - -

Here you go. Tested and working project. Consider "Delay Until Next Second.rar" project. It works fine. I have tested it for many values.
 

Attachments

  • Delay.rar
    4.8 MB · Views: 122
  • Delay Until Next Second.rar
    33.1 KB · Views: 103
Last edited:

Hi baileychic, Thanks for the code. I actually need 1 ms timer interrupt which I can use to put wait until next second delay.
 

In "Delay Until Next Second.rar" I have used 1 ms timer1 interrupt only but Fosc is 4 MHz. What is your Fosc (crystal freuency) ?
 

Hi,

I am using internal oscillator in PIC16F88. I set the frequency to 4 MHz through OSCCON register.

Code:
OSCCON = 0b01100010; // Internal oscillator @ 4 MHz, Internal RC is used for system clock.

How do you calculate the values for TMR1H and TMR1L registers, and also pre-scalar ?

Is it possible to test the simulation in proteus ? Is it free to install ?
 
Last edited:


Hi,

Any reason to add 10 in the task delay ? Instead of LATD can I also use Delay_ms(1000 - tasks_delay) ?

Code:
          if(tasks_delay <= 1000) {  //Tasks will never take more than 1000 ms
             tasks_delay = 1000 - tasks_delay + 10;
             LATD = tasks_delay;
             endOldtaskCycleFlag = 1;
          }

Also I guess it is ok to use any binary indicator instead of endOldtaskCycleFlag.

Code:
char flagRegister = 0;
sbit endOldtaskCycleFlag at flagRegister.B0;
 

I was getting 10 ms difference for tasks delay between 400 to 450 ms and so I just added 10 to the result.

I am using mikroC PRO PIC Compiler and its Delay_ms() function takes constant value for argument. You cannot pass a variable to it.

LATD is to output the remaining delay value on Leds.

For flag use a char type variable if you are using XC8 or Hi-Tech Compilers.

Wait till wednesday. I have ordered PIC16F88 chip. I have written c code for PIC16F88 and it is working fine in Proteus but I have doubt regarding using CONFIG words. mikroC PRO PIC shows 2 Config words but Proteus has only one field for Config word. The delays generated by Delay_ms() function in PIC16F88 code is working fine and generating exact delays. I just want to test the code on actual hardware before giving the code to you.
 
Last edited:

I would do this a different way - the choice is your of course but by restructuring the code you can make it non-blocking very easily. By non-blocking I mean the processor can do other things while it is waiting, by using fixed or calculated delays you tie it up in a delay loop.

The ISR stays basically the same but do a simple count of interrupts inside it, for example if your timer interrupt occurs every 1mS, count down from 1000 to zero then stop counting. If the interrupts were every 10mS you would count down from 100 instead. The idea is that your count reaches zero after 1 second. Then in your main code, at the start of the period you reset the count to 1000/100 or whatever you chose and the value at any time will be the number of remaining periods before the second has finished. Instead of calculating a delay to waste time until the second ends, you just check the count value for zero. If it isn't zero you can safely do something else and re-check it again later. The other big advantage of doing it that way is you can easily change the delay by loading a different start value in the counter. For example load 1500 and you get 1.5 seconds, 1234 would give 1.234 seconds, 2100 would give 2.1 seconds and so on (assuming 1mS interrupts).

Brian.
 

Hi,

I just tested this program written in MPLAB. It works. I use the timer register values from baileychic code. The delay function in MPLAB accepts variables. The LEDs connected to PORT B toggle after 1 second.

Code:
// CONFIG
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTIO1 oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)
// End of configuration

#include <stdlib.h>  //standard include files
#include <pic.h>
#include <htc.h>        
#include <xc.h>
#include <stdio.h>
#include <string.h>

#define _XTAL_FREQ 4000000
int count = 0;

//Timer1
//Prescaler 1:1; TMR1 Preload = 64536; Actual Interrupt Time : 1 ms
 
//Place/Copy this part in declaration section
void InitTimer1(){
  T1CON	 = 0x01;
  PIR1bits.TMR1IF = 0;
  TMR1H	 = 0xFC;
  TMR1L	 = 0x18;
  PIE1bits.TMR1IE = 1;  
  INTCON = 0xC0;
}
 
void interrupt ISR(){
if (PIR1bits.TMR1IF){ 
    PIR1bits.TMR1IF = 0;
    TMR1H	 = 0xFC;
    TMR1L	 = 0x18;
    //Enter your code here
    count++; 
  }
}

void main(void) {
    __delay_ms (1);
    __delay_us (100);
     
    OSCCON  = 0b01100010;  // Internal oscillator @ 4 MHz, Internal RC is used for system clock.
    TRISB   = 0;           // Port B as output  
    InitTimer1();          // Initialize Timer 1
    PORTB = 0x00;          // Initialize Port B 
    ei();                  // Enable Interrupt
    
     while (1) 
     { 
         __delay_ms (400);   // task 1  
         __delay_ms (400);   // task 2  
         
        // Insert remaining delay until next second 
         __delay_ms (1000-count);
         {
            count = 0;
            PORTBbits.RB0 = ~PORTBbits.RB0; 
            PORTBbits.RB1 = ~PORTBbits.RB1; 
            PORTBbits.RB2 = ~PORTBbits.RB2; 
            PORTBbits.RB3 = ~PORTBbits.RB3; 
            PORTBbits.RB4 = ~PORTBbits.RB4; 
            PORTBbits.RB5 = ~PORTBbits.RB5;        
        }         
      }    
     
    return;
}
 

Assuming that you want the interrupt exactly(!) every second, then when calculating the value for the timer from within the ISR (as opposed to during the initialisation) you need to remember that the counter (TMR1x) will continue to count after the rollover from 0xffff to 0x0000 and until the actual code of the ISR starts to execute. (Remember that the compiler will generate prolog code that saves the registers (if any) that are used within the ISR before your code starts to execute.)
Therefore you should subtract the TMR1x value from the count that you have calculated and then put that back into TMR1x. While the difference might be small, depending on the clock being used by the timers, it can help stop the drift that is often seen.
(Note that the timers that have the PRx registers don't have this issue but the older style timers that only have the TIMnx registers do.)
Susan
 

Yes there will be a drift but the drift will be less then 1 ms per second, right ? I don't understand completely which value to subtract from count and reload TMR1L and TMR1H registers. Can you please indicate this subtract in the code ?
 

The drift will be small but you may be able to compensate by changing the value you re-load into TMR1L (and TMR1H if the value overflows). You need to understand how the timers work:
You configure the pre-scaler and division ratio to make a suitable counting rate for the timer based on the clock frequency,
The timer counts up at that rate, everything is in hardware,
When the timer reaches 0xFFFF, the next count makes it overflow to 0x0000 and at the same time triggers the timer interrupt.

So if you left the timer alone, it would count up to 0xFFFF (65535 in decimal) and the process would repeat.
However, that many counts would probably not produce the exact timing you want so you need to give the counter a head start by loading it with a different value so fewer counts are needed before it rolls over again. The bigger the number you load, the fewer counts it takes to roll over. When you load "TMR1H = 0xFC; TMR1L = x18;" you start the count at 0xFC18 so it only needs 0x0000-0xFC18 = 0x3E7 (1000 in decimal) counts before the next interrupt.

The catch is: the instruction to load the timer takes some time to execute so the re-loading is slightly delayed and the timer will run slightly slower than expected. Unfortunately, with code written in 'C' it isn't easy to tell exactly how many machine instructions will be generated so the exact delay is difficult to predict. If the timing has to be exact, you can make the value loaded into TMR1L a little bigger to compensate for that extra delay. Because we don't know exactly how long re-loading will take you will have to experiment with the value or run an accurate simulation to find the best new one.

Brian.
 

If the machine cycles needed to execute delay function are not known exactly then can this be compensated by inserting little less delay in #12 code, for example delay of value 10 ms less ? This delay has to be there so that the next while loop iteration start from the beginning of the next second.

Code:
__delay_ms (1000-count - 10);
 
Last edited:

Hi,

I´d always rely on hardware over software.
The Microcontroller has the periferal to get perfect timing down to one clock cycle. It just may cause some small jitter. But a delay in one cycle is compensated by the next cycle.
You may build a clock with the hardware functions and it will add no extra error (besides the clock source error).

__delay_ms (1000-count - 10);

This may work for you if you don´t want high precision.
There are two "precision" problems:
* timing of reading / writing values, precision of these values.
* added (unpredictable) delays caused by any interrrupt during this delay.

Klaus
 

It will work but getting the mS delays accurate is better than fiddling around with the results.
I think you are grossly overestimating the error as well, at 4MHz clock the error is only likely to caused by a few instructions so it will only be a few uS. Within 1 second that may be insignificant but the delays will accumulate if you use longer timings.

Brian.
 

Hi,
I think you are grossly overestimating the error as well

With my designs I´m used to keep errors very low. I usually try to keep it as low as possible.
Here - with the correct use of the timer/conter periferal - it is no problem to keep the additional error to 0 ppm.
Thus if you use a 20ppm quartz you may build a clock that is accurate to 50s per month (quarz frequency error)

With the given solution:
* you have a time granularity of 1ms ... within 1s this is an error of 1000ppm (there is a good change that it cancels out a lot over time)
* plus every interrupt will cause an increased delay. Let´s say an ISR during the last delay (to wait for the rest of 1s) will cause the same ammount of error.
This means: if the ISR takes 10ms --> this will cause an error of 10 ms. And this error doesn´t cancel out, it will accumulate instead.

Let´s say the 10ms error happens once every 10s this means an error of 100ppm, or 43 minutes per month...

**
We don´t know the requirements of the OP.
Thus the given solution may work for him or not.
If he is satisfied with the solution he should use it.

I just want to say: With less software effort you may get perfect accuracy.
Thus I see no benefit in the given solution. This is just my personoal opinion.

Klaus
 

The algorithm is basically
timer = (0x0000 - wanted count) + timer
As Betwixt has explained, the counter (the TMRx registers) does not stop after the rollover from 0xffff to 0x0000. Therefore if you read the TMRx registers within the ISR you will get a number - say 5 which means that the timer has received 5 clock pulses between the rollover (which will have triggered the ISR in the first place) and when you read the registers.
You want to set the TMRx register to 0xfc18 but now you know that this is (approx) 5 too low (remember that thew timers count UP) so you need to add the 5 to the 0xfc128 to make 0xfc1d which is the value you need to load back.
Of course I've just used the 5 as an illustration. You get the actual figure by reading the TMRx registers.
The next problem you face is that you are using a very old chip that requires you read the TMRH and TMRL registers separately. (Slightly newer chips save the TMRH value in a temporary bit or hardware when you read the TMRL register - you chip does not do that unfortunately.) However, unless you are trying to do a lot of things in the ISR, you should have a value that is less than 256 (i.e. the TMRH register should always be 0) so just using the TMRL register should be OK - *should* but must be tested!
So code such as (you will need to check the exact register names etc.):
Code:
TMRH = 0xfc;
TMRL = 0x18 + TMRL;
may well work for you. (Strictly speaking you need to account for any overflow from the 0x18+TMRL addition and add that to TMRH but that may also be unnecessary.)
Finally, if you are still getting a bit of drift, then you can adjust the '0x18' value but at least you will know that you have taken care of the major variability.
Susan
 

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top