Using ISR's with I2C and LCD and LED's

Hello, in my project, i want to use a water level sensor, LED's to represent water depth, and a LCD screen with I2C to represent the water level in words. However, i am having timing issues between the ISR's for the LED's and the writing of characters on the LCD. I have tried shifting the position of the codes but from what i can see, i cannnot seem to seperate the ISR's from the main loop code. my understanding is that the main loop runs automatically, and the ISR's are called accoring to their initialisaiton. However, is there a way to run both codes simultanesouly, so that the 1 ms of Timer0 does not affect the speed at which the I2C and LCD need to print out the characters? it seems that the LCD prints out characters but gets stuck after a while and does not continue looping. Thank you. The code dealing with the motor is an extension, however i want to understand how to fix this problem first.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>
#include <stdio.h>
#include <util/twi.h>
#include <stdlib.h>
#include <math.h>

#define LCD_RS 	0       // Register select (0-instruction, 1-data)
#define LCD_Rw 	1       // Read (1) or Write (0)
#define LCD_EN 	2       // Enable (1->0) transition fetchs data
#define LCD_BL 	3       // LCD Backlight

char line1[100];                // string buffers for varying chracter lines
char line2[100];
volatile int led_blink_time = 0;
volatile int adc_value;
unsigned int on_off = 0;



void init_twi();                // Initialise Atmegar328P-TWI(I2C, two wire interface)
int putchar_twi(uint8_t ch);    // ensures that byte information is corectly sent, stored and written for the LCD.

// LCD+TWI functions to send a byte/string 
void init_lcd();                // formats LCD to properly display characters.
void lcd_light(int on);         // turns on the backlight of the LCD.
void putchar_lcd(uint8_t ch, int rs); // Connects 2x 4bits into a byte to be sent to the LCD.
void puts_lcd_a(char *s);
void puts_lcd_b(char *s);

// Delay functions used to interface LCD
void delay1ms();                // delay functions used to properly set up LCD
void delay15ms();

void ADC_init(void)
{
  ADMUX |= (1<<6);
  ADCSRA |= 0b10111111;
  ADCSRA |= (1<<6);
}

void Timer0_Int(void)           
{
  TCCR0A = (1<<WGM01);          
  TCCR0B = (1<<CS00)|(1<<CS01); 
  TIMSK0 = (1<<OCIE0A);       
  OCR0A = 250-1;     
  
}

ISR(TIMER0_COMPA_vect)
{
  led_blink_time++;
}

ISR(ADC_vect)  
{
  adc_value = ADC;

  if(adc_value < 550 && led_blink_time > 500)
    {
      PORTD &= ~((1<<1)|(1<<2));
      PORTD ^= (1<<0);      
      led_blink_time = 0;
    }

    if(adc_value < 650 && adc_value > 550 && led_blink_time > 500)
    {
      PORTD |= (1<<0);
      PORTD &= ~(1<<2);
      PORTD ^= (1<<1);
      led_blink_time = 0;
    }

    if(adc_value < 690 && adc_value > 650 && led_blink_time > 500)
    {
      PORTD |= (1<<0)|(1<<1);
      PORTD ^= (1<<2);
      led_blink_time = 0;
    }

    if(adc_value > 690)
    {
      PORTD |= (1<<0)|(1<<1)|(1<<2);
    }
}

int main(void)
{
  DDRD = 0b00000111;

  init_twi();     // Initialise Atmegar328P-TWI(I2C, two wire interface)
  init_lcd();     // Initialise LCD HD44780 + PCF8574T (I2C)
  ADC_init();
  Timer0_Int();
  sei();          // Global variabe used for ISR.

  while(1)
  {
    if(on_off == 0)
    {
      putchar_lcd(0b10000000, 0);
      sprintf(line1, "Motor: OFF"); // stores first line of LCD in string line1
      puts_lcd_a(line1);             // sends string to for loop for LCD writing.
    }

    if(on_off == 1)
    {
      putchar_lcd(0b10000000, 0);
      sprintf(line1, "Motor: ON"); // stores first line of LCD in string line1
      puts_lcd_a(line1);             // sends string to for loop for LCD writing. 
    }

    if(adc_value < 550)
    {
      putchar_lcd(0b11000000, 0);
      sprintf(line2, "Water: 0/4-1/4"); // stores second line of LCD in string line2
      puts_lcd_b(line2);                // sends string to for loop for LCD writing
    }

    if(adc_value < 650 && adc_value > 550)
    {
      putchar_lcd(0b11000000, 0);
      sprintf(line2, "Water: 1/4-2/4"); // stores second line of LCD in string line2
      puts_lcd_b(line2);                // sends string to for loop for LCD writing
    }

    if(adc_value < 690 && adc_value > 650)
    {
      putchar_lcd(0b11000000, 0);
      sprintf(line2, "Water: 2/4-3/4"); // stores second line of LCD in string line2
      puts_lcd_b(line2);                // sends string to for loop for LCD writing
    }

    if(adc_value > 690)
    {
      putchar_lcd(0b11000000, 0);
      sprintf(line2, "Water: 3/4-4/4"); // stores second line of LCD in string line2
      puts_lcd_b(line2);                // sends string to for loop for LCD writing
    }
  }
}

void init_twi()
{
	PORTC = (1<<DDC5)|(1<<DDC4);	// Activate the pull-up resistors for SDA/SCL(TWI).
                                    // Otherwise, you need to attach external pull-ups
	
  TWSR = ((1<<TWPS1)|(1<<TWPS0));   // PS-64, sets TWI clock to 1Kbps, if ~ is used, then bps is faster.
	TWBR = 124;                     // Bit rate: TWBR = ((F_CPU / 1000) - 16) / (2*PS)
  TWDR = 0xFF;                    // Default content = SDA released.
	TWCR = (1<<TWEN);               // Enable TWI-interface and release TWI pins.
}

int putchar_twi(uint8_t ch)
{
	uint8_t addr = 0x27;            // LCD default address (0x27). Check your LCD board

	// 1. Start (From Table 26.2, page 270, Atmega328p datasheet (single chip))
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN) | (1<<TWEA);
	while (!(TWCR & (1<<TWINT)));
	if ((TWSR & 0xF8) != TW_START)
	return -1;
	
	// 2. Send SLA+W (Write Mode)
	TWDR = (addr << 1) | (TW_WRITE);	// SLA+W
	TWCR = (1<<TWINT)|(1<<TWEN);		// Start transmission
	while (!(TWCR & (1<<TWINT)));
	if ((TWSR & 0xF8) != TW_MT_SLA_ACK)
	return -2;

	//	3. Send Data #1 (actual data)
	TWDR = ch;							// Data (at the sub-address register)
	TWCR = (1<<TWINT)|(1<<TWEN);		// Start transmission
	while (!(TWCR & (1<<TWINT)));
	if ((TWSR & 0xF8) != TW_MT_DATA_ACK)
	return -4;

	// 4. Stop condition
	TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
	
	return 0;
}

void init_lcd()
{
    uint8_t ch; 
          
    // Atmega hardware reset doesn't affect LCD, so we do software reset here (32ms+)
    // function set 1 (repeat 3 times). Ref) Fig. 24 @ Page 46, HD44780U datasheet
    delay15ms();
    delay15ms();
    ch = 0x30|(1<<LCD_BL);      // reset (0x30)
    for (int i=0; i<3; i++){
        ch |= (1<<LCD_EN);
        putchar_twi(ch);
        delay15ms();
        ch &= ~(1<<LCD_EN);
        putchar_twi(ch);
        delay15ms();
    }
    
    // Send command (0x20) to set LCD to 4-bit mode
    // Ref) Table 12 @ Page 42, HD44780U datasheet
	ch = 0x20|(1<<LCD_BL);
 	ch |= (1<<LCD_EN);
	putchar_twi(ch);
	delay1ms();
	ch &= ~(1<<LCD_EN);
	putchar_twi(ch);
	delay1ms();
	
    // [TASK 2] Complete the code:
    // Send commands to instruction register (RS=0) 
    // to initialise LCD. A byte is splited into two 4-bits
    // Ref) Table 12 @ Page 42, HD44780U datasheet
    // Ref) Fig. 11 @ Page 28, HD44780U datasheet
    // The instructions to be sent in sequence are:

    // - (set 2-line mode with font size)
    putchar_lcd(0b00101000, 0);

    // - (display off)
    putchar_lcd(0b00001000, 0);

    // - (clear display)
    putchar_lcd(0b00000001, 0);

    // - (set entry mode)
    putchar_lcd(0b0000000110, 0);

    // - (display on and cursor blinking)
    putchar_lcd(0b00001110, 0);
    

    // Now you can send data to data register (RS=1)
    // To move the cursor to next line first column (0x40 in LCD)
    // you need to send an instruction with (RS=0)
    ;     
}

void putchar_lcd(uint8_t ch, int rs)
{
  uint8_t logi;
  logi = ((ch >> 4 & 0x0F) << 4)|(1<<LCD_BL)|rs; //High nibble
 	logi |= (1<<LCD_EN);
	putchar_twi(logi);
	delay1ms();
	logi &= ~(1<<LCD_EN);
	putchar_twi(logi);
	delay1ms();

    logi = ((ch & 0X0F) << 4)|(1<<LCD_BL)|rs; //LOW nibble
 	logi |= (1<<LCD_EN);
	putchar_twi(logi);
	delay1ms();
	logi &= ~(1<<LCD_EN);
	putchar_twi(logi);
	delay1ms();
}

void puts_lcd_a(char *s)
{
    unsigned int i = 0;

    for(i = 0; i < strlen(line1); i++)
    {
        putchar_lcd(*(s+i), 1);
    }
}

void puts_lcd_b(char *s)
{
    unsigned int i = 0;

    for(i = 0; i < strlen(line2); i++)
    {
        putchar_lcd(*(s+i), 1);
    }
}

void lcd_light(int on)
{
    char temp = 0x0|(1<<LCD_RS);   // write to data reg

	if (on==1){
        temp |= (1<<LCD_BL);      // back light bit on
    }
 	putchar_twi(temp);
	delay1ms();
 }

void delay1ms()
{
  TCCR2B = 0x04;
  while(!TIFR2);
  TCCR2B = 0x00; 
  TCNT2 = 0;  
  TIFR2 |= 0x01;
}

void delay15ms()
{
  TCCR2B = 0x07;
  while(!TIFR2);
  TCCR2B = 0x00;  
  TCNT2 = 0;  
  TIFR2 |= 0x01;
}

I'm having problems reconciling that phrase with this one

did you write this code yourself?

iam using Timer0 with a 1ms set up, that calls on the COMPA_vect ISR. whatever is in the ISR is then executed. In my code, it just increments the variable by 1 every ms. this allows me to time the blinking of my LED's to how many ms i need. the I2C and LCD printing of characters is in the while loop in the main code. does that clarify things?

everything other than the I2C initialisation, that code was given as part of my old uni course, as an example

Is it just me, or do replies here keep repeating?

no thats just me, i didnt tag your name when i responded. my bad

Pretty much. :grin:

You appear to be using ISRs for things that definitely should not be performed in ISRs.

This function is already provided for you. It is called "millis()". :astonished:

Study This tutorial and IIRC, this one and/ or this.

but isn't millis() an arduino code? i am using pure C and C++ to program my arduino, without using the arduino 'commands'. im open to using arduino, however, its coding using C that i want to utilise. (i know its counter intuitive, but this is what is required when i code in my future uni subjects). unless there's another way?

Yup.
And this is the Arduino forum.

Ta-daaa!

All the register definitions are not C or C++ and are platform specific. In order to make sense of your code one needs to look up specific processor (which I don’t think you mentioned) data sheet. Too much work, sorry.

Oh I thought the methodology would be interchangeable. I’ll read a bit more and see what I find.

Oh I forgot that. It’s an Ardunio UNO, ATmega 328 microchip. I’ll read a bit more on the data sheet.

What "methodology'?

You have the Arduino sources - use them.

Oh! You did not explain that to start with, did you. :thinking:

Well, in that case, you need to remove at least the following includes, don't you?

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <util/twi.h>
#include <math.h>

You should probably try AVRFreaks.net for pure AVR programming.