In this post, we will explore how to create an interrupt-based delay timer using the microcontroller and the compiler. This approach replaces the traditional __delay_ms() macro with a more flexible and efficient timer using Timer0 and interrupts.

Why Replace __delay_ms()?

The __delay_ms() macro, while convenient for simple tasks, has some significant limitations:

  • It relies on a series of NOP (No Operation) instructions, which can consume a lot of code space.
  • It is not flexible enough to handle variable delays, such as those based on analog input values.

By using Timer0 and interrupts, we can overcome these limitations and create a more versatile delay timer.

The Circuit

10F322 blink led circuit

Setting Up Timer0 and Interrupts

The following code demonstrates how to set up Timer0 and interrupts to create a delay timer that can blink an LED on PORTA.0.

 * File:   isr_timer.c
 * Author: Jamie
 * Created on 9/13/2021, 8:56 PM


#pragma config FOSC = INTOSC  // Oscillator Selection 
#pragma config BOREN = ON    // Brown-out Reset
#pragma config WDTE = OFF    // Watchdog Timer
#pragma config PWRTE = ON    // Power-up Timer
#pragma config MCLRE = OFF   // MCLR Pin Function Select bit->MCLR pin function is digital input, MCLR internally tied to VDD
#pragma config CP = OFF      // Code Protection 
#pragma config LVP = OFF     // Low-Voltage Programming 
#pragma config LPBOR = ON    // Brown-out Reset Selection bits
#pragma config BORV = LO    // Brown-out Reset Voltage Selection
#pragma config WRT = OFF    // Flash Memory Self-Write Protection

//Used to calculate the delay time - Change depending on processor Speed
#define _XTAL_FREQ 8000000  //8 MHz (default after Reset)

//Function prototypes
void setup (void);
unsigned long millis(void);
void delay(uint32_t ms);

volatile unsigned long timer0_millis = 0;

//interrupt handler 
void __interrupt () isr_routine (void) {

//TMR0 = 8;
timer0_millis +=1;  //increase the counter by 1  
INTCONbits.TMR0IF = 0; //clear tmr0 irq flag

void main(void)
       LATAbits.LATA0 = 1; //turn on port A.0
       LATAbits.LATA0 = 0; //turn off port A.0

void setup (void)
    //Set Port A0 as output
    TRISAbits.TRISA0 = 0;
    //Clear any analog and port settings
    ANSELA = 0x00;
    LATA = 0x00;
    /*Set TMR0 to use FOSC/4 At 8Mhz the timer with increment at 2Mhz
    That is 500us       
    TMR0 will overflow ever 128us. Which is faster than we want - I am aiming for 1ms.
    So lets use a prescaler to get it close
    Assign prescaler to TMR0 With prescaler of 8 we get  500ns * 256 * 8 = 1.024ms
    Then set the prescaler */
    OPTION_REGbits.PSA = 0;
    OPTION_REGbits.PS = 0b010; //prescaler of 8
    //To Start the timer we need to set the clock source to fosc/4 but first clear the timer just to make sure    
    TMR0 = 0;    
    OPTION_REGbits.T0CS = 0;
    //Assign interrupt to timer 
    INTCONbits.TMR0IF = 0; //clear tmr0 irq flag
    INTCONbits.TMR0IE = 1; //enable tmr0 irq enable
    INTCONbits.GIE = 1; //enable global interrupts      

//Reads the MS variable
unsigned long millis(void)
	unsigned long m;
	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	INTCONbits.GIE = 0;
	m = timer0_millis;
    INTCONbits.GIE = 1;
	return m;

//Delay based on the number of MS
void delay(uint32_t ms)
    //Get the timer value as we go into the delay
	uint32_t start = millis();
    //We wait here until while current timer value, minus the start value, is less than the value we want
	while (millis() - start < ms){}

How the Code Works

Device Configuration:

  • The configuration bits set various hardware options, such as oscillator selection, brown-out reset, and power-up timer settings.

Setup Function:

  • Disables the analog function on PORTA.0 and sets it as an output.
  • Configures Timer0 with an internal clock source, assigns the prescaler, and enables the Timer0 interrupt and global interrupts.

Interrupt Service Routine (ISR):

  • Checks if Timer0 caused the interrupt.
  • Clears the Timer0 interrupt flag and resets the Timer0 register.
  • Increments a counter

Advantages of Using Interrupt-Based Delay Timer

  • Code Efficiency: The interrupt-based approach uses less code space compared to the __delay_ms() macro.
  • Flexibility: The delay timer can be dynamically adjusted based on various inputs, such as analog values, making it suitable for more complex applications.


By replacing the __delay_ms() macro with an interrupt-based delay timer using Timer0, you can create a more efficient and flexible timing solution with the PIC10F322 microcontroller. This method reduces code space usage and allows for variable delays, enhancing the versatility of your microcontroller . In the next post, we will explore using the analog input functions of the PIC10F322 to further enhance our delay timer.

Have a Project or Idea!?

Seeking Bespoke Technology Solutions?

Pin It on Pinterest

Share This