Diferencia para definir entradas digitales

Yo tenía entendido que declarar variables aún con const sigue ocupando memoria RAM. De hecho, no es por nada que a algo declarado con const se le pueda extraer su puntero; mientras que declarado con #define no se puede. Por ejemplo:

const byte b = 64;
byte* p = (byte*)&b; // ¿Bug del compilador? Claro, sin el casting explícito no me deja

void setup() {
  Serial.begin(9600);
  *p = 32; // ¿Acabo de modificar una "constante"?
  Serial.println(b);

}

void loop() {
}

Si este código imprimiera "32", entonces el uso de const no es del todo "a prueba de tontos" :o

Sin embargo, esto definitivamente no me compila:

#define B 64
byte* p = (byte*)&B; // Dice que no está definido el operador unario & en macro constantes

void setup() {
  Serial.begin(9600);
  *p = 32; // ¿Acabo de modificar una "constante"?
  Serial.println(B);

}

void loop() {
}

Por esta razón es que llego a creer que #define es la manera más adecuada de declarar constantes. No ocupa del todo memoria RAM ya que en tiempo de compilación se traduce en valores "hard coded". Por ejemplo:

#define LED 2
#define TIEMPO_RETRASO 500

void setup() {
  pinMode(LED, OUTPUT);
}

void loop() {
  digitalWrite(LED, HIGH);
  delay(TIEMPO_RETRASO);
  digitalWrite(LED, LOW);
  delay(TIEMPO_RETRASO);
}

El compilador lo traduce en:

void setup() {
  pinMode(2, 1); // Si no me equivoco, OUTPUT vale 1
}

void loop() {
  digitalWrite(2, 1);
  delay(500);
  digitalWrite(2, 0);
  delay(500);
}

Dicho en otras palabras: los #define son como "piezas de rompecabezas" del código; por eso no necesitan de la RAM (y no se les puede extraer el puntero).
Es más, si algo se declara pero no se llega a usar en ninguna parte del código; dicho valor ni siquiera llega a formar parte del compilado (también aplica para vectores con PROGMEM).

Por esta razón es que:

const byte b = 64;

No le hallo mucho sentido hacerlo; aparte de que igual ocupa memoria RAM, el valor queda también en memoria flash (no es por arte de magia que, en el arranque, el CPU del micro recuerde cuál era).

Aclaro de una vez:

#define MENSAJE "Hola mundo de Arduino"

Estoy de acuerdo que se declara con #define, pero ESTO NO AHORRARÁ MEMORIA RAM SI SE USA.
Las cadenas de caracteres son otro cantar; los valores "hard coded" que introduciría son el puntero a dicha cadena, y hablar de puntero implica que los datos deben estar en RAM.
Declararlo así al final termina en lo que mencioné en el párrafo anterior: queda en RAM y en flash.

En este caso, la manera correcta de declarar cadenas de caracteres constantes, y que no queden en RAM, serían las siguientes:

const char MENSAJE[] PROGMEM = "Hola mundo de Arduino";

// También aplica para vectores de cualquier tipo.
// Si la función a utilizar no sabe manejar punteros de memoria flash, no quedaría de otra que hacer una copia en RAM; suena contradictorio al tema, pero la diferencia está en que debería ser en un único espacio y reutilizable
#define MENSAJE F("Hola mundo de Arduino")

// La macrofunción F() solo acepta literales (entrecomillados), no vectores de char ni objetos String
// Solo se puede utilizar en objetos que hereden de Print o funciones que sepan manejar el tipo de dato __FlashStringHelper

ArduMyth:
Tampoco tiene sentido para mí hacer esto y prefiero un array.

Si te sientes más cómodo programando así, no te lo voy a discutir; pero si pretendes ahorrar RAM de esta manera, estás equivocado.
Mira que lo acabo de decir: el const por sí solo no evita el consumo en RAM (y encima no es tan resistente a la modificación intencional); para eso está el #define en datos individuales, o el PROGMEM para conjuntos (vectores) de datos.

PD: los #define también sirven para ahorrarle cálculos al micro. Ejemplo:

#define GIGABYTE 1024 * 1024 * 1024
// El resultado al ser mayor que 65535, el compilador implícitamente entiende que la constante es de tipo long o unsigned long

En vez de:

const unsigned long GIGABYTE = 1024 * 1024 * 1024;
// Encima el programador debe escoger el tipo de dato más adecuado; un novato que opta ciegamente por int, después no tiene idea qué hizo mal
// Si el compilador no es lo suficientemente optimizado, este cálculo lo tiene que realizar la CPU del micro (en tiempo de ejecución)