Yes, you can even see this right in the ArduinoCore-avr source-
#define HIGH 0x1
#define LOW 0x0
They use hexadecimal notation, but those numbers are still just
HIGH = 1 and
LOW = 0.
This is because digital IO pins basically abstract away all of the analog nonsense that a digital signal really consists off, and tend to be
HIGH when a signal passes a certain threshold and
LOW when that signal is below another threshold. These values can be found in the part datasheet, and will say something like
HIGH = 0.7VCC and
LOW = 0.3VCC.
With 5V power these values might be
5*0.7 = 3.5V and
5*0.3 = 1.5V.
The astute reader will notice that there’s a huge gap between “less than or equal to 0.3VCC” and “greater than or equal to 0.7VCC”. This gap is dubbed “hysteresis” or “undefined” and represents the voltage at which your signal might be HIGH, or might be LOW, but there’s no guarantee.
An interesting aside to this, is that the “3.5V” digital input some devices (like NeoPixels) require at 5V make them quite often work at 3.3V logic because the input pins tend toward reading as HIGH at 3.3V even if there’s absolutely no guarantee this is the case.
The chip on the Arduino UNO - the 328p - (described in great detail here: http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf) has:
- Input Low-voltage: -0.5V to 0.3 Vcc
- Input High-voltage: 0.7 Vcc to Vcc + 0.5
So at 5V that would give a range of -0.5V to 1.5V as “definitely LOW” and a range of “3.5V” to “5.5V” as “definitely HIGH” and everything between >1.5V and <3.5V is “uh, dunno!”.
TLDR: HIGH and LOW are literally drop-in replacements for 1 and 0, they are actually just placeholders that are replaced by a preprocessor which finds tokens like this and replaces them with their literal values before the compiler comes along. Heck you can even do maths with them:
5 * HIGH == 5 :D