the total execution-time of interrupt-service-routines (in short ISR) should be kept as short as possible.
you have coded in your isr
two commands that do output to the I2C-serial display.
It might be that this output takes more than 0,02 seconds which is too long.
the output to your LCD should be done outside the ISR
your ISR should have the the IRAM_ATTR attribute
code that is placed in internal RAM is executed much faster than from the flash
Variables that are shared between your ISR and your main-code need the attribute “volatile”
without the attribut volatile the compiler can “optimise” your code that results in non-functioning
so try this version
last but not least you should develop a habit of using selfexplaining names for all identifiers I did some namechanging in this way
LiquidCrystal_I2C lcd(0x27, 20, 4);
const byte zeroCross = 3;
volatile int NumberOfZeroCrossings = 0;
void IRAM_ATTR zero_ISR()
attachInterrupt(3, zero_ISR, LOW); // on Pin 3
this code compiles. But I don’t have your hardware. So I would like to hear from you if it working now
best regards Stefan