Pull up, down or give up?

I'm primarily not an Arduino coder. I'm trying to vibe-code my way out of a problem and my complete lack of understanding of electronics.

The situation... I had a remote control with a decent amount of buttons that I wanted to get into Home Assistant. Despite being a cheap Chinese remote commonly used for LED controllers, it turned out to be proprietary RF and I couldn't sniff it.

That meant replacing the electronics with an ESP32 board and using that instead. However, restrictions with BTHome codes don't lend themselves well to what I was trying to do. That ended trying to use ESPHome directly.

So... I ended up with an ESP32 C6 Zero that I built into the remote matrix which uses Arduino code to hack a button code into the manufacturer code of a BLT transmission. A separate C3 Zero listens for transmissions from that specific MAC address, extracts the code and passes it to HA. - that part is all good.

What isn't good is the sleep function.

I tried to get the C6 to wake on GPIO 1, 2, 3 or 22 going high... but that didn't work. So I tried to put diodes and get it to wake on GPIO 0. That failed.

My belief was that when the C6 Zero was in deep sleep, that the matrix wouldn't be powered, so there'd be nothing to oppose the resistor from 3.3v and GPIO 0 wouldn't get pulled down.

When I took an oscilloscope to it, it appears that all the matrix pins are at 3.3v... so I stepped back, cursed AI and decided I needed to ask actual people for help.

Grateful for any advice please, as I'm clueless and have vibe-hacked myself into a corner.

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEAdvertising.h>
#include "esp_sleep.h"

// ---------------- KEYPAD ----------------
const int ROWS = 5;
const int COLS = 4;

int rowPins[ROWS] = {18, 19, 20, 21, 14};
int colPins[COLS] = {1, 2, 3, 22};

char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'},
  {'E','F','G','H'}
};

// ---------------- BLE ----------------
BLEAdvertising *pAdvertising;

// ---------------- STATE ----------------
unsigned long lastActivity = 0;
const unsigned long sleepDelay = 5000;

// ---------------- BLE SEND ----------------
void sendBLE(uint8_t key_id) {

  uint8_t data[] = {0xFF, 0xFF, key_id};

  String md = "";
  for (int i = 0; i < 3; i++) {
    md += (char)data[i];
  }

  BLEAdvertisementData adv;
  adv.setFlags(0x06);
  adv.setManufacturerData(md);

  pAdvertising->setAdvertisementData(adv);

  // fast burst (low power, low latency)
  pAdvertising->start();
  delay(12);
  pAdvertising->stop();
}

// ---------------- SLEEP ----------------
void goToSleep() {

  Serial.println("SLEEP: preparing");

  delay(50);

  pinMode(0, INPUT_PULLUP);
  delay(20);

  // EXT1 wake on GPIO0 LOW
  esp_sleep_enable_ext1_wakeup(
    (1ULL << GPIO_NUM_0),
    ESP_EXT1_WAKEUP_ANY_LOW
  );

  Serial.println("SLEEP: entering");
  delay(50);

  esp_deep_sleep_start();
}

// ---------------- SETUP ----------------
void setup() {

  Serial.begin(115200);
  delay(1000);

  Serial.println("BOOT");

  // rows
  for (int r = 0; r < ROWS; r++) {
    pinMode(rowPins[r], OUTPUT);
    digitalWrite(rowPins[r], HIGH);
  }

  // columns
  for (int c = 0; c < COLS; c++) {
    pinMode(colPins[c], INPUT_PULLUP);
  }

  // BLE init
  BLEDevice::init("BLE-Remote-1");
  pAdvertising = BLEDevice::getAdvertising();

  // low power BLE tuning
  BLEDevice::setPower(ESP_PWR_LVL_N12);

  pAdvertising->setMinInterval(160);
  pAdvertising->setMaxInterval(160);
  pAdvertising->setScanResponse(false);

  // FIXED: ESP32 core does NOT define ADV_TYPE_NONCONN_IND
  pAdvertising->setAdvertisementType(0x03);

  Serial.print("BLE MAC: ");
  Serial.println(BLEDevice::getAddress().toString().c_str());

  pinMode(0, INPUT_PULLUP);

  lastActivity = millis();

  Serial.println("READY");
}

// ---------------- LOOP ----------------
void loop() {

  bool keyPressed = false;
  int key_id = -1;

  // scan keypad
  for (int r = 0; r < ROWS; r++) {

    digitalWrite(rowPins[r], LOW);

    for (int c = 0; c < COLS; c++) {

      if (digitalRead(colPins[c]) == LOW) {

        delay(20);

        if (digitalRead(colPins[c]) == LOW) {

          key_id = (r * COLS) + c + 1;

          Serial.print("KEY: ");
          Serial.println(key_id);

          keyPressed = true;
          lastActivity = millis();
        }
      }
    }

    digitalWrite(rowPins[r], HIGH);
  }

  // BLE send (isolated from scan)
  if (keyPressed && key_id > 0) {

    sendBLE(key_id);
    delay(30);
    sendBLE(0);
  }

  // sleep logic
 if (!keyPressed && (millis() - lastActivity > sleepDelay)) {

  // DEBUG: check wake line state BEFORE sleeping
  Serial.print("GPIO0 before sleep: ");
  Serial.println(digitalRead(0));

  if (digitalRead(0) == LOW) {
    Serial.println("Wake line already LOW - will sleep and immediately wake again risk");
  }

  goToSleep();
}
}

Thank You for posting code in code tags.

But.... please translate the picture inte schematics. Utterly few helpers will take on that picture.

have you had a look at ESP32C6 sleep modes and ESP32 Deep Sleep with Arduino IDE and Wake Up Sources

I have read a few things and tried using EXT1 wake, and then when I thought I knew where I was going, I tripped up over AI hallucinations until I wanted to scream at the window.

I thought EXT1 was failing because the C6 Zero couldn't handle multiple pins and that there was no power in the matrix to bring everything up.

That was when I focused on GPIO0 and the resistor, only to find that everything was at 3.3v anyway... and apparently it is possible to leave some lines up at 3.3v even when the processor is in deep sleep.

When I came to that, I thought about leaving either the row, or the column, powered, and then waking on the other side... but then I wasn't sure whether that would be banjaxed by the boards limitations and I lost track of what I actually knew, and what I knew that I didn't know any more. ... if you know what I mean.

So I came here in the hope that someone could tell me a solution that would work for this board and point me in the direction of how to do it.

Belief based on what? What resistor? Pulled down by what?

try downloading the example code ZIP from ESP32-C6-Deep-Sleep-with-External-Wake-Up

Thanks. I'll give that a read.

Hi @msknight !

How about considering what @Railroader posted?

A simple drawing of your wiring might provide much more information than the picture ...

Well, the problem is that the wiring is wrong. The picture was where I was, but in the nature of the old joke of the people stopping to ask for directions, "Well, if I was you, I wouldn't start from here."

Where I've gone has been a mess, so I need someone to say, "Ah. This is what you need to do." ... because I have been going around in circles. Eg. I wired 18, 19, 20, 21 and 14 in the hope I could keep them high at 3.3v .... and I'm being told that only pin 14 actually supports staying at 3.3v, and I now need to wire the others to 6-10 which are underneath the board.

...and things like that. So I've been reading documentation, only to find out that it doesn't apply to the board I'm using... or at least that's what I believe.

It's to the point where I'm completely lost and... effectively... as far as the board is concerned... just wipe it out and start from scratch, because I now believe that everything I've done is wrong.

... but if I'm going to re-do it again... I need to have faith that I'm wiring the right things, to the right pins.

I understood that

  • the original device provides five rows and four columns of keys
  • which you separated electrically from its surrounding parts
  • and connected to the rowPins and colPins as implemented in your sketch

So far it seems to be clear.

If I am not mistaken the picture shows:

  • A resistor that connects 3V3 to the cathode of (probably?) 1N4148 diodes.
  • The anodes of the diodes are connected to the column pins.

What's your intention for this part?

[Edit:] I just saw that there might be a connection from GPIO0 to the wire that connects all diodes cathodes and the resistor ... Is that right?

try

// ESP32C6 Dev Board wake from deep sleep - take GPIO0 HIGH

// adapted from https://github.com/Carla-Guo/ESP32-C6-Deep-Sleep-with-External-Wake-Up

/*
Hardware Connections
======================
Push Button to GPIO 0 pulled down with a 10K Ohm
resistor

NOTE:
======
Bit mask of GPIO numbers which will cause wakeup. Only GPIOs
                which have RTC functionality can be used in this bit map.
                For different SoCs, the related GPIOs are:
                  - ESP32: 0, 2, 4, 12-15, 25-27, 32-39
                  - ESP32-S2: 0-21
                  - ESP32-S3: 0-21
                  - ESP32-C6: 0-7
                  - ESP32-H2: 7-14
*/

#define BUTTON_PIN_BITMASK (1ULL << GPIO_NUM_0) // GPIO 0 bitmask for ext1

RTC_DATA_ATTR int bootCount = 0;

/*
Method to print the reason by which ESP32
has been awaken from sleep
*/
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

void setup(){
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  /*
  First we configure the wake up source
  We set our ESP32 to wake up for an external trigger.
  There are two types for ESP32, ext0 and ext1, ext0 
  don't support ESP32C6 so we use ext1.
  */

  //If you were to use ext1, you would use it like
  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  //Go to sleep now
  Serial.println("Going to sleep now");
  delay(1000);
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop(){
  //This is not going to be called
}

pull GPIO0 down - when taken HIGH wakes from deep sleep

serial monitor output

Boot number: 1
Wakeup was not caused by deep sleep: 0
Going to sleep now
ESP-ROM:esp32c6-20220919
Build:Sep 19 2022
rst:0x5 (SLEEP_WAKEUP),boot:0x1c (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x40875730,len:0x1278
load:0x4086b910,len:0xdd0
load:0x4086e610,len:0x3154
entry 0x4086b910
Boot number: 2
Wakeup caused by external signal using RTC_CNTL
Going to sleep now
….
Boot number: 3
Wakeup caused by external signal using RTC_CNTL
Going to sleep now
….
Boot number: 4
Wakeup caused by external signal using RTC_CNTL
Going to sleep now

Hi, @msknight

From what I see and understand you are using the old remote keyboard to input your microcontroller.

Have you verified the rows and columns connections?
If so please draw a schematic to indicate how you have down this.

A diagram will keep us all on one page.

Thanks.. Tom.... :smiley: :+1: :coffee: :australia:

Hi Tom,

You understand correctly. I have code that doesn't do sleep, doesn't use any diodes, and it works fine. It detects all keypresses and transmits normally.

I think what I need, is to scrap what I currently have and for someone to point me at a schematic that works, and then I can wire it up... basically... because what I've done at present, might as well be torched.

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEAdvertising.h>

// -------- KEYPAD CONFIG --------
const int ROWS = 5;
const int COLS = 4;

int rowPins[ROWS] = {18, 19, 20, 21, 14};
int colPins[COLS] = {1, 2, 3, 5};

char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'},
  {'E','F','G','H'}
};

// --------------------
// BLE
// --------------------
BLEAdvertising *pAdvertising;

// --------------------
// SEND BTHOME PACKET
// --------------------
void sendBTHome(uint8_t key_id) {
  // Manufacturer Data (2 bytes vendor ID + 1 byte button ID)
  // Vendor ID can be any 2 bytes; here 0xFF 0xFF is a placeholder.
  uint8_t raw_md[] = {
    0xFF, 0xFF,   // manufacturer ID (example)
    key_id        // button ID 1–20
  };

  // Convert to String (BLE Arduino API wants String)
  String manufacturerData = "";
  for (int i = 0; i < 3; i++) {
    manufacturerData += (char)raw_md[i];
  }

  BLEAdvertisementData advData;
  advData.setFlags(0x06);
  advData.setManufacturerData(manufacturerData);   // ← now compiles
  pAdvertising->setAdvertisementData(advData);
  pAdvertising->start();

  Serial.print("BLE sent key ID: ");
  Serial.println(key_id);
}


// --------------------
// SETUP
// --------------------
void setup() {
  Serial.begin(115200);
  Serial.println("Starting keypad + BLE...");

  // Setup rows
  for (int r = 0; r < ROWS; r++) {
    pinMode(rowPins[r], OUTPUT);
    digitalWrite(rowPins[r], HIGH);
  }

  // Setup columns
  for (int c = 0; c < COLS; c++) {
    pinMode(colPins[c], INPUT_PULLUP);
  }

  // Init BLE
  BLEDevice::init("BLE-Remote-1");
  pAdvertising = BLEDevice::getAdvertising();
  BLEAddress addr = BLEDevice::getAddress();
  Serial.print("BLE MAC address: ");
  Serial.println(addr.toString().c_str());


  Serial.println("Ready.");
}

// --------------------
// LOOP
// --------------------
void loop() {
  for (int r = 0; r < ROWS; r++) {
    digitalWrite(rowPins[r], LOW);

    for (int c = 0; c < COLS; c++) {

      if (digitalRead(colPins[c]) == LOW) {
        delay(20); // debounce

        if (digitalRead(colPins[c]) == LOW) {

          char key = keys[r][c];

          Serial.print("Key pressed: ");
          Serial.println(key);

          // Convert to numeric ID (1–20)
          uint8_t key_id = (r * COLS) + c + 1;

          sendBTHome(key_id);
          
          // Small delay so receiver sees the press
          delay(500);

          // Send "no button pressed"
          sendBTHome(0);

          // Wait for release
          while (digitalRead(colPins[c]) == LOW) {
            delay(10);
          }
        }
      }
    }

    digitalWrite(rowPins[r], HIGH);
  }
}

Schematics are something I don't draw every day. I'm working on it now.

Well, AI has been completely useless at drawing a schematic, so I'm going to have to do this in Paint. Please bear with me.

Just draw it using pencil and paper, take a photo of it and post it here

OK - this is more like it, electrically.

Every grey dot is a piece of metal that makes a contact and thus bridges the row and column.

This was the foundation of my thinking... that if I can set a voltage high on the rows, then only when a button is pressed, is a voltage actually passed through to a column, and thus wake up on EXT1 - but it turns out this is failing.

If I was a guessing person, it's because the column lines are left floating? But this is where my complete lack of experience is showing. I just don't know what I'm dealing with.

I think one of my problems is that diodes between each pin and ground wouldn't work because the diode would prevent the pins from grounding and they would remain floating. So I need a strong enough resistor between each pin and ground... to prevent the current in one, from roaming to another?

Am I going the right way?

It doesn't look like you are using

https://docs.arduino.cc/libraries/keypad/

Which is used here

and should let you test with just a single jumper shorting a row and a column to simulate a key press.

That is to say use a pre-rolled algorithm, lose the little keyboard for the moment until you get the code working, which it just should.

Then worry about one real row and column (one button) om the keypad.

Then add rows and columns testing the newly included buttons with each addition.

HTH hope I read your current circumstances right.

a7