Posted on Leave a comment

Reverse Engineering Pin Allocations

Sometimes you get a piece of hardware and you don’t have any documentation about it. Or even worse, the documentation is just wrong. How can you reverse engineer pin allocations of such a device? Here are a couple of tricks on how you can that sensor or actuator to work.

For our “You’ve Got Mail” IoT workshop we were using the TTGO T-CAM module. During the longest part of the preparation for the workshop, we were using the same camera module. Then for the workshop itself, we ordered the hardware for our participants. When ordering we quickly realized that there were many different revisions of this module in circulation. Some had a microphone, while others had a BME280 temperature/pressure sensor.

From the distance all boards looked the same. A closer look though revealed important differences. Not only had the TTGO engineers used different peripherals, but they had also completely changed pin allocation from one revision to the other.

When the boards for the participants finally arrived we realized that this was a version with a microphone. The sticker on the box neatly described the pin allocations. Adapting the pin numbers in the configuration file of our code was easy enough. But why the camera wouldn’t work? The output in the serial console revealed the first hint: Could not initialize the camera.

The boards came with the face detection software preinstalled so it was easy to confirm that the camera worked. So it must be something about our configuration! We played with several settings in the camera configuration struct to no avail. Let’s try the pin allocation from the board we used during preparation! This didn’t work either.

How to Reverse Engineer Pin Allocations

But how can we reverse engineer pin allocations in such a case? Maybe LillyGo, the manufacturer of the boards, had documented the pin allocation per revision somewhere? Intense research didn’t produce any working result either. But we could find something: Lewis He documented the pin allocation of several boards on github. Sadly this configuration also didn’t work.

Sometimes the solution is right in front of your eyes! The LillyGo engineers probably got confused by their own revisions and printed the used pins onto the PCB. The solution was right where it had to be all the time, but hidden by the camera.

The correct pin allocation was hidden right below the camera module.

And here is the complete configuration for the camera should you try to get the same board to run:


#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM     32
#define SIOD_GPIO_NUM     13
#define SIOC_GPIO_NUM     12
#define Y9_GPIO_NUM       4
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       23
#define Y6_GPIO_NUM       33
#define Y5_GPIO_NUM       15
#define Y4_GPIO_NUM       35
#define Y3_GPIO_NUM       2
#define Y2_GPIO_NUM       34
#define VSYNC_GPIO_NUM    5
#define HREF_GPIO_NUM     18
#define PCLK_GPIO_NUM     19

Scanning I2C Addresses

So far so good: the correct pins are printed on our little PCB. This is actually a really good idea and as long as the engineers update the silkscreen of the boards its safe for future revisions. But how about information which is not printed onto the boards? What about the addresses of devices connected to the I2C bus? Once we know the pins used for the I2C bus we can use a little program to find peripherals listening on the bus with a little scanner application:

// Abbreviated version of 
// https://gist.github.com/AustinSaintAubin/dc8abb2d168f5f7c27d65bb4829ca870
#include <Wire.h>

// We know the pins from the PCB
const uint8_t I2C_SDA_PIN = 21; //SDA;  // i2c SDA Pin
const uint8_t I2C_SCL_PIN = 22; //SCL;  // i2c SCL Pin

void setup()
{
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);

  // Serial
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}

void loop()
{
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");
  
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      nDevices++;
      
      Serial.print("Device found @ 0x");
      if (address<16) 
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");
    }
    else if (error==4) 
    {
      Serial.print("Unknow error @ 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);

    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  } else {
    Serial.println("done\n");
  }
  delay(5000);           // wait 5 seconds for next scan
}

This gave us the following device information:

Scanning...
Device found @ 0x3C  !
Device found @ 0x75  !
Device found @ 0x77  !
done

0x3C is most probably the address of the SSD1306 OLED display driver, while 0x75 or 0x77 probably are used by the microphone.

Manually Reverse Engineering the PIR pin

What can we do, if the pins of a certain peripheral are not printed on the PCB or hidden by the component? For some simple sensors, we can try to use a GPIO scanner.

In the case of the TTGO T-Cam module, the pin of the PIR motion sensor was nowhere to be found. So let’s try to figure out which pin it could be with a little program:

uint8_t pin = 0;
uint32_t lastUpdate = millis();

#define BTN_PIN 24

void setup() {
  Serial.begin(115200);
  pinMode(BTN_PIN, INPUT_PULLUP);
}

void loop() {
  // lastUpdate is a software button debouncer
  if (digitalRead(BTN_PIN) == 0 && millis() - lastUpdate > 1000) {
    pin++;
    if (pin == 6) {
      pin++;
    }
    lastUpdate = millis();
  }
  pinMode(pin, INPUT_PULLUP);
  
  Serial.printf("State of pin %d: %d\n", pin, digitalRead(pin));
  delay(100);
  
}

To avoid reflashing the ESP32 for every possible pin we use the onboard user button to switch the currently active PIN with every press. the lastUpdate variable is used to debounce the button. Only if the button was pressed the last time 1000ms before we accept a new button press. Setting pin 6 to INPUT_PULLUP crashed the ESP32 so it is skipped in our scanner application.

Summary

In this blog post, we showed you how we struggled to figure out the correct pin allocation of hardware with wrong or missing documentation and how to resolve this problem. Reverse engineering pin allocations can be hard in such a case. Of course, the described methods won’t help you in every situation. The I2C and pin scanner applications wouldn’t have been of help to find the pins used for the camera. Then you probably would have to use a line tracer to see where each of the camera pins leads too.

If you like this post about reverse engineering pin allocations, then you might also like the other articles about what you learn if you attend a ThingPulse ESP32 workshop. Subscribe to this site’s feed with your favorite RSS reader or follow us on Twitter to never miss new content.

Leave a Reply

Your email address will not be published. Required fields are marked *