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.
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.
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¶
#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:
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.SelectDefaultIoTApplet()sendsSELECTwith the IoT applet's AID. It returns the raw response; the status words at its end must be90 00.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.