LiquidCristal_I2C.h blocks execution in AVR project

Hi everyone,

I'm working on a project where I'm trying to read live data from a Honda motorcycle ECU using an AVR microcontroller via UART, and display some of the values (like RPM, temperature, etc.) on a 16x2 I2C LCD using the LiquidCrystal_I2C library. Inspired by Gonzos
I am still working on the code, therefore some of the variables are not used yet.
If needed i can add the usart header file i am using to this post!

Everything works fine individually:

  • The LCD works perfectly in a standalone test sketch.
  • The UART communication with the ECU works flawlessly on its own.

However, as soon as I add the line screen.init(); in the full project, the program blocks right there. Nothing proceeds past that point. No UART traffic, no display updates, no activity.

Below is the full code. I’ve translated the original variable names and comments from German to English for clarity. I'd really appreciate if someone can spot what's causing the conflict — I'm wondering if something in the USART setup is interfering with I2C.

Thanks in advance!

#include "USART.h"
#include <util/delay.h>
/*
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
*/

#include <LiquidCrystal_I2C.h>

// Handlebar buttons (Left & Right)
#define Pin_BtnL 0
#define Pin_BtnR 1
// Chain oiler output
#define PinOILER 5
#define PinOILER_on  PORTB |= (1 << PinOILER)
#define PinOILER_off PORTB &= ~(1 << PinOILER)
// UART RX / TX
#define PinRX 0
#define PinTX 1
#define PinTX_on PORTB |= (1 << PinTX)
#define PinTX_off PORTB &= ~(1 << PinTX)

// LCD
LiquidCrystal_I2C screen(0x27, 8, 2);

int main() {
    // Set pin directions
    DDRB &= ~(1 << Pin_BtnL);   // Input
    DDRB &= ~(1 << Pin_BtnR);   // Input
    DDRD &= ~(1 << PinRX);      // Input (UART Receive)
    DDRD |= (1 << PinTX);       // Output (UART Transmit)

    // Enable internal pullups for BtnL & BtnR
    PINB &= ~(1 << Pin_BtnL);
    PINB &= ~(1 << Pin_BtnR);

    // --- Startup animation ---
    // Initialize LCD
    screen.init();                                             // ### <-- CODE HANGS HERE ###

    // Wake up ECU
    PinTX_on;
    _delay_ms(70);
    PinTX_off;
    _delay_ms(120);

    USARTInit(0, 10400, 1, 0, 1, 0); // Start UART

    for (uint8_t i = 0; i <= 3; i++) {
        uint8_t Hex[] = {0xFE, 0x04, 0xFF, 0xFF};
        USARTTransmit(0, Hex[i]);
    }

    _delay_ms(200);

    for (uint8_t i = 0; i <= 4; i++) {
        uint8_t Hex[] = {0x72, 0x05, 0x00, 0xF0, 0x99};
        USARTTransmit(0, Hex[i]);
    }

    // Make data arrays globally available
    uint8_t Data_11[20];
    uint8_t Data_D1[10];

    while (1) {
        // --- Read values ---
        // Request table 11 from 00 to 16
        for (uint8_t i = 0; i <= 6; i++) {
            uint8_t Hex[] = {0x72, 0x07, 0x72, 0x11, 0x00, 0x16, 0xEE};
            USARTTransmit(0, Hex[i]);
        }

        // Store incoming values
        for (uint8_t i = 0; i <= 20; i++) {
            while (!USARTCharReceived(0));
            Data_11[i] = USARTReceive(0);
        }

        // Request table D1 from 00 to 06
        for (uint8_t i = 0; i <= 6; i++) {
            uint8_t Hex[] = {0x72, 0x07, 0x72, 0xD1, 0x00, 0x06, 0x3E};
            USARTTransmit(0, Hex[i]);
        }

        // Store incoming values
        for (uint8_t i = 0; i <= 10; i++) {
            while (!USARTCharReceived(0));
            Data_D1[i] = USARTReceive(0);
        }

        // --- Convert values ---
        uint16_t RPM = (Data_11[5] << 8) | Data_11[6];
        float TPS_Volt = Data_11[7] * (5.0 / 256.0);
        float TPS_Percent = Data_11[8] / 16.0;
        float ECT_Volt = Data_11[9] * (5.0 / 256.0);
        int8_t ECT_temp = Data_11[10] - 40;
        float IAT_Volt = Data_11[11] * (5.0 / 256.0);
        int8_t IAT_temp = Data_11[12] - 40;
        float MAP_Volt = Data_11[13] * (5.0 / 256.0);
        uint8_t MAP_kPa = Data_11[14];
        float Batt_Volt = Data_11[17] / 10.0;
        uint8_t Speed = Data_11[18];

        // Display logic would go here
    }
}

That looks a little suspicious. The PinTX_on/off stuff.

In this setup, the ECU doesn't respond to standard UART activity right away. It first requires a wake-up pulse:
You need to manually pull the TX line high for ~70 ms, then back to low, before starting any serial communication. This mimics the behavior of a diagnostics tool being connected, and it tells the ECU to begin talking.

That's why I'm doing this manually with:

PinTX_on;
_delay_ms(70);
PinTX_off;
_delay_ms(120);

After that, normal 10400 baud UART communication can begin, and the ECU responds as expected.
I appreciate your quick reply!

Look again. What port is the Tx line on? What port are you twiddling with the PinTx_on/off macros?

After going over the setup again, the exact issue with the current configuration is still unclear. It’s especially not obvious which port the Tx line should be assigned to, or precisely how the PinTx_on/off macros are meant to be used.

Right now, the method involves toggling the TX pin with these macros before the USART communication, and it seems to work as expected. At this point, there’s no known alternative approach for me

I am wondering if part of the problem is that you are not writing Arduino code. But you are trying to use an Arduino library in your non-Arduino code. Maybe that is causing the problem, but I don't know exactly why.

Maybe you could translate your code into Arduino code:

  • Use setup() and loop() instead of main()
  • Use pinMode(), digitalWrite() etc instead of direct port manipulation
  • Use Serial, or maybe Software serial objects
  • delay() instead of _delay_ms() etc.

Well of course it does. Arduino libraries depend on the Arduino framework, and you have bypassed that with your own main(). In this case it is probably because the millis() timer is not running, so delay() hangs.

If you want to use Arduino libraries, write an Arduino sketch.

If for some reason you absolutely need to not use the Arduino sketch framework, you need to find all the Arduino features the library depends on and implement those yourself, or modify the library to use your own versions of any Arduino API calls.

@daa_koda please post the updated working version of the code, it may be useful to others in the future.

Still a work in progress, but this code's doing the job for now.

#include "USART.h"
#include <LiquidCrystal_I2C.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>

// Pin definitions
const uint8_t pinBtnLeft = 2;
const uint8_t pinBtnRight = 3;
const uint8_t pinOiler = 5;
const uint8_t pinTX = 6;

// LCD display
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
#define maxScreenIndex 3

void setup() {
  // Initialize LCD
  u8g2.begin();  // Start the display
  uint8_t currentScreen = 2;

  // Inputs with pull-up resistors
  pinMode(pinBtnLeft, INPUT_PULLUP);
  pinMode(pinBtnRight, INPUT_PULLUP);

  // Output pins
  pinMode(pinOiler, OUTPUT);
  pinMode(pinTX, OUTPUT);

  digitalWrite(pinOiler, LOW); // Turn off chain oiler
  digitalWrite(pinTX, LOW);    // Set TX to LOW

  // Wake up ECU
  digitalWrite(pinTX, HIGH);
  delay(70);
  digitalWrite(pinTX, LOW);
  delay(120);

  // Initialize USART (custom function)
  USARTInit(0, 10400, 1, 0, 1, 0);

  // Send init frame
  uint8_t initFrame1[] = {0xFE, 0x04, 0xFF, 0xFF};
  for (uint8_t i = 0; i < sizeof(initFrame1); i++) {
    USARTTransmit(0, initFrame1[i]);
  }

  delay(200);

  uint8_t initFrame2[] = {0x72, 0x05, 0x00, 0xF0, 0x99};
  for (uint8_t i = 0; i < sizeof(initFrame2); i++) {
    USARTTransmit(0, initFrame2[i]);
  }
}

void loop() {
  uint8_t data11[21];
  uint8_t dataD1[11];

  // Request table 11
  uint8_t request11[] = {0x72, 0x07, 0x72, 0x11, 0x00, 0x16, 0xEE};
  for (uint8_t i = 0; i < sizeof(request11); i++) {
    USARTTransmit(0, request11[i]);
  }

  // Receive data from table 11
  for (uint8_t i = 0; i < sizeof(data11); i++) {
    while (!USARTCharReceived(0));  // Wait for next byte
    data11[i] = USARTReceive(0);
  }

  // Request table D1
  uint8_t requestD1[] = {0x72, 0x07, 0x72, 0xD1, 0x00, 0x06, 0x3E};
  for (uint8_t i = 0; i < sizeof(requestD1); i++) {
    USARTTransmit(0, requestD1[i]);
  }

  // Receive data from table D1
  for (uint8_t i = 0; i < sizeof(dataD1); i++) {
    while (!USARTCharReceived(0));  // Wait for next byte
    dataD1[i] = USARTReceive(0);
  }
}

Did you solve the problem with the LCD? you seem to be using a OLED now.

Actually, it is plain as day which port the Tx pin is on. It is shown in the processor datasheet. It is shown on any number of Arduino pinout diagrams. It is shown elsewhere in your code.

Your original code, with its odd mix of non Arduino structure, low level register manipulation, and an Arduino library, seemed off to me. As did your reaction. You did not seem to understand the basics of what the code was doing, and why it was wrong. Which seems strange for someone apparently comfortable with advanced techniques.

I have strong doubts that you, or any other human, wrote it. I think we're looking at AI generated code, and you were not upfront about that fact.