Skip to content

Quickstart

Talk to an SE050 from your MCU: wire four signals, write a small port class, read 32 random bytes from the chip.

1. Wire it

MCU SE050 pin Notes
I2C SDA 9, I2C_SDA Pull-up to VIN
I2C SCL 10, I2C_SCL Pull-up to VIN
GPIO (optional) 11, ENA Low: power-down
1.8 V or 3.3 V 12 VIN, 18 VCC 1.62 to 3.6 V
GND 19, VSS Common ground

ENA lets the host power-cycle the chip: the port's HardwareReset() pulses it low. It works when VCC is fed from VOUT (pin 15); tie ENA to VCC if you do not use it. Size the pull-ups for your bus speed.

The bus must be idle high when the SE050 powers up: it picks I2C only if SDA and SCL are both high at boot. Hardware has the full pin list, bus speeds and power modes.

2. Add the driver

Add the repository as a submodule and link the hf::se050 CMake target (or include inc/ and build with C++17). Installation covers CMake and ESP-IDF.

git submodule add https://github.com/hardfoc/hf-se050-driver.git external/hf-se050-driver

3. Write the port

The port is the only platform code. It writes one block, reads a given number of bytes, waits and, if wired, pulses ENA. Replace the four platform_* calls with your SDK's.

my_i2c.hpp
class MyI2c : public se050::I2cTransceiveInterface<MyI2c> {
public:
  explicit MyI2c(std::uint8_t addr7 = se050::kDefaultI2cAddress7, int ena_gpio = -1) noexcept
      : addr7_(addr7), ena_gpio_(ena_gpio) {}

  // Bus and pins are set up by your board code; report whether they are ready.
  bool EnsureInitialized() noexcept {
    return true;
  }

  // One complete T=1 block in one I2C write (START, address + W, bytes, STOP). Never zero-length.
  se050::Error I2cWrite(const std::uint8_t* tx, std::size_t tx_len,
                        std::uint32_t timeout_ms) noexcept {
    if (tx == nullptr || tx_len == 0U) {
      return se050::Error::InvalidArgument;
    }
    return platform_i2c_write(addr7_, tx, tx_len, timeout_ms) ? se050::Error::Ok
                                                              : se050::Error::Transport;
  }

  // Exactly rx_len bytes in one I2C read. A NACK (the SE050 is still busy) is Transport: the driver
  // retries.
  se050::Error I2cRead(std::uint8_t* rx, std::size_t rx_len, std::uint32_t timeout_ms) noexcept {
    if (rx == nullptr || rx_len == 0U) {
      return se050::Error::InvalidArgument;
    }
    return platform_i2c_read(addr7_, rx, rx_len, timeout_ms) ? se050::Error::Ok
                                                             : se050::Error::Transport;
  }

  // Bring-up helper only (Device::TransceiveRaw); T=1 uses I2cWrite / I2cRead.
  se050::Error Transceive(const std::uint8_t* tx, std::size_t tx_len, std::uint8_t* rx,
                          std::size_t rx_cap, std::size_t* rx_len_out,
                          std::uint32_t timeout_ms) noexcept {
    *rx_len_out = 0;
    se050::Error e = I2cWrite(tx, tx_len, timeout_ms);
    if (e != se050::Error::Ok || rx == nullptr || rx_cap == 0U) {
      return e;
    }
    delay_ms_impl(2);
    e = I2cRead(rx, rx_cap, timeout_ms);
    if (e == se050::Error::Ok) {
      *rx_len_out = rx_cap;
    }
    return e;
  }

  // Power-cycle through ENA (deep power-down while low). Return Ok when ENA is not wired.
  se050::Error HardwareReset() noexcept {
    if (ena_gpio_ < 0) {
      return se050::Error::Ok;
    }
    platform_gpio_write(ena_gpio_, false);
    delay_ms_impl(2);
    platform_gpio_write(ena_gpio_, true);
    delay_ms_impl(10);
    return se050::Error::Ok;
  }

  void delay_ms_impl(std::uint32_t ms) noexcept {
    platform_delay_ms(ms);
  }

private:
  std::uint8_t addr7_;
  int ena_gpio_;
};

Porting explains each method and the ESP-IDF reference port.

4. Read random bytes

main.cpp
#include "se050_device.hpp"

int read_32_random_bytes(MyI2c& i2c, std::uint8_t (&random)[32]) {
  se050::Device<MyI2c> se(i2c);

  // 1. Power-cycle through ENA (a no-op when ENA is not wired), then bring the T=1 link up.
  (void)se.HardwareReset();
  if (se.T1().ChipWarmReset(100) != se050::Error::Ok) {
    return -1; // wiring, pull-ups, supply or address
  }

  // 2. Select the IoT applet: the call succeeds and the applet answers SW 90 00.
  std::uint8_t rsp[32]{};
  std::size_t rsp_len = 0;
  if (se.SelectDefaultIoTApplet(rsp, sizeof(rsp), &rsp_len, 300) != se050::Error::Ok ||
      rsp_len < 2 || rsp[rsp_len - 2] != 0x90 || rsp[rsp_len - 1] != 0x00) {
    return -2;
  }

  // 3. Use it: 32 bytes from the SE050's random number generator.
  std::size_t n = 0;
  if (se.GetRandom(32, random, sizeof(random), &n, 300) != se050::Error::Ok || n != 32) {
    return -3;
  }
  return 0;
}

What each step does:

  1. HardwareReset() calls your port; with ENA wired it puts the chip through deep power-down and back. ChipWarmReset() sends the T=1 chip-reset S-block and resets the block sequence numbers on both sides.
  2. SelectDefaultIoTApplet() sends SELECT with the IoT applet's AID. It returns the raw response; the status words at its end must be 90 00.
  3. GetRandom() asks the applet for 32 bytes from the on-chip random number generator.

This example is built by the host CI, so it compiles against the current headers.

If it does not work

Symptom First thing to check
ChipWarmReset() returns Transport or Timeout Address 0x48, pull-ups, VIN, ENA high, bus idle high at power-up
ChipWarmReset() returns Crc Bus speed (start at 100 kHz), long wires, a port that splits one block into several writes
SELECT answers a status other than 90 00 The applet in your configuration; see Troubleshooting
Works once, then Sequence or Protocol A previous exchange failed half-way: call ResetSequenceState() and ChipWarmReset()

Next

  • API guide: objects, key generation, signatures, raw APDUs, errors.
  • Security model: what to provision, SCP03, and what the driver does not protect.
  • Examples: ESP32 applications from bring-up to a full IoT lifecycle.