Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ node_modules/
CLAUDE.md
.build/
.venv/
.claude/
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lib/apds9960",
"lib/bme280",
"lib/bq27441",
"lib/daplink_bridge",
"lib/daplink_flash",
"lib/gc9a01",
"lib/hts221",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ This repository contains all the drivers for the main components of the [STeaMi]
| Component | Driver | I2C Address | Description |
| ------------- | ---------------------------------------------- | ----------- | ------------------------------------- |
| BQ27441-G1 | [`bq27441`](lib/bq27441/README.md) | `0x55` | Battery fuel gauge |
| DAPLink Flash | [`daplink_flash`](lib/daplink_flash/README.md) | `0x3B` | I2C-to-SPI flash bridge + config zone |
| DAPLink Bridge | [`daplink_bridge`](lib/daplink_bridge/) | `0x3B` | I2C bridge to STM32F103 + config zone |
| DAPLink Flash | [`daplink_flash`](lib/daplink_flash/README.md) | — | Flash file operations via bridge |
| SSD1327 | [`ssd1327`](lib/ssd1327/README.md) | — (SPI) | 128x128 greyscale OLED display |
| MCP23009E | [`mcp23009e`](lib/mcp23009e/README.md) | `0x20` | 8-bit I/O expander (D-PAD) |
| VL53L1X | [`vl53l1x`](lib/vl53l1x/README.md) | `0x29` | Time-of-Flight distance sensor |
Expand Down
5 changes: 5 additions & 0 deletions lib/daplink_bridge/daplink_bridge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from daplink_bridge.device import DaplinkBridge

__all__ = [
"DaplinkBridge",
]
29 changes: 29 additions & 0 deletions lib/daplink_bridge/daplink_bridge/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from micropython import const

# I2C address (7-bit) — 0x76 in 8-bit (CODAL convention)
DAPLINK_BRIDGE_DEFAULT_ADDR = const(0x3B)

# WHO_AM_I expected value
DAPLINK_BRIDGE_WHO_AM_I_VAL = const(0x4C)

# Commands — bridge level
CMD_WHO_AM_I = const(0x01)
CMD_WRITE_CONFIG = const(0x30)
CMD_READ_CONFIG = const(0x31)
CMD_CLEAR_CONFIG = const(0x32)

# Registers
REG_STATUS = const(0x80)
REG_ERROR = const(0x81)

# Status register bits
STATUS_BUSY = const(0x80)

# Error register bits
ERROR_BAD_PARAM = const(0x01)
ERROR_CMD_FAILED = const(0x80)

# Protocol limits
MAX_WRITE_CHUNK = const(30)
SECTOR_SIZE = const(256)
CONFIG_SIZE = const(1024)
143 changes: 143 additions & 0 deletions lib/daplink_bridge/daplink_bridge/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from time import sleep_ms

from daplink_bridge.const import *


class DaplinkBridge(object):
"""Low-level I2C bridge to the STM32F103 DAPLink interface."""

def __init__(self, i2c, address=DAPLINK_BRIDGE_DEFAULT_ADDR):
self.i2c = i2c
self.address = address
self._buffer_1 = bytearray(1)

# --------------------------------------------------
# Low level I2C
# --------------------------------------------------

def _read_reg(self, reg, n=1):
"""Read n bytes from register."""
if n == 1:
self.i2c.readfrom_mem_into(self.address, reg, self._buffer_1)
return self._buffer_1[0]
return self.i2c.readfrom_mem(self.address, reg, n)

def _write_reg(self, reg, data):
"""Write data bytes to register."""
self.i2c.writeto_mem(self.address, reg, data)

def _write_cmd(self, cmd):
"""Write a single command byte (no payload)."""
self._buffer_1[0] = cmd
self.i2c.writeto(self.address, self._buffer_1)

def _writeto(self, data):
"""Write raw data frame to the bridge."""
self.i2c.writeto(self.address, data)

def _readfrom(self, n):
"""Read n raw bytes from the bridge."""
return self.i2c.readfrom(self.address, n)

# --------------------------------------------------
# Device identification
# --------------------------------------------------

def device_id(self):
"""Read WHO_AM_I register. Expected: 0x4C."""
return self._read_reg(CMD_WHO_AM_I)

# --------------------------------------------------
# Status and error registers
# --------------------------------------------------

def _status(self):
"""Read raw status register."""
return self._read_reg(REG_STATUS)

def _error(self):
"""Read raw error register."""
return self._read_reg(REG_ERROR)

def busy(self):
"""Return True if bridge is busy."""
return bool(self._status() & STATUS_BUSY)

def _wait_busy(self, timeout_ms=30000):
"""Poll busy bit until clear. Raises OSError on timeout."""
elapsed = 0
while self.busy():
sleep_ms(5)
elapsed += 5
if elapsed >= timeout_ms:
raise OSError("DAPLink bridge busy timeout")

# --------------------------------------------------
# Config zone (1 KB persistent storage in F103 internal flash)
# --------------------------------------------------

def clear_config(self):
"""Erase the 1 KB config zone."""
self._wait_busy()
self._write_cmd(CMD_CLEAR_CONFIG)
sleep_ms(100)
self._wait_busy()
err = self._error()
if err:
raise OSError("DAPLink config clear error: 0x{:02X}".format(err))

def write_config(self, data, offset=0):
"""Write data to the config zone at the given offset.

Args:
data: bytes or str to store.
offset: byte offset within the config zone (0-1023).
"""
if isinstance(data, str):
data = data.encode()
length = len(data)
if offset < 0 or offset >= CONFIG_SIZE:
raise ValueError("offset out of range: {}".format(offset))
if offset + length > CONFIG_SIZE:
raise ValueError("data exceeds config zone boundary")
buf = bytearray(MAX_WRITE_CHUNK + 2)
max_payload = len(buf) - 4
buf[0] = CMD_WRITE_CONFIG
pos = 0
while pos < length:
self._wait_busy()
chunk_len = min(max_payload, length - pos)
cur_offset = offset + pos
buf[1] = (cur_offset >> 8) & 0xFF
buf[2] = cur_offset & 0xFF
buf[3] = chunk_len
buf[4 : 4 + chunk_len] = data[pos : pos + chunk_len]
for i in range(4 + chunk_len, len(buf)):
buf[i] = 0
self.i2c.writeto(self.address, buf)
sleep_ms(50)
pos += chunk_len
self._wait_busy()
err = self._error()
if err:
raise OSError("DAPLink config write error: 0x{:02X}".format(err))

def read_config(self):
"""Read config zone content.

Returns:
bytes: config data up to first 0xFF, or b'' if empty.
"""
result = bytearray()
for page_offset in range(0, CONFIG_SIZE, SECTOR_SIZE):
self._wait_busy()
hi = (page_offset >> 8) & 0xFF
lo = page_offset & 0xFF
self._write_reg(CMD_READ_CONFIG, bytes([hi, lo]))
sleep_ms(100)
data = self.i2c.readfrom(self.address, SECTOR_SIZE)
for i in range(SECTOR_SIZE):
if data[i] == 0xFF:
return bytes(result)
result.append(data[i])
return bytes(result)
6 changes: 6 additions & 0 deletions lib/daplink_bridge/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
metadata(
description="Low-level I2C bridge driver for the STM32F103 DAPLink interface.",
version="0.0.1",
)

package("daplink_bridge")
26 changes: 1 addition & 25 deletions lib/daplink_flash/daplink_flash/const.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,15 @@
from micropython import const

# I2C address (7-bit) — 0x76 in 8-bit (CODAL convention)
DAPLINK_FLASH_DEFAULT_ADDR = const(0x3B)

# WHO_AM_I expected value
DAPLINK_FLASH_WHO_AM_I_VAL = const(0x4C)

# Commands
CMD_WHO_AM_I = const(0x01)
# Commands — flash level
CMD_SET_FILENAME = const(0x03)
CMD_GET_FILENAME = const(0x04)
CMD_CLEAR_FLASH = const(0x10)
CMD_WRITE_DATA = const(0x11)
CMD_READ_SECTOR = const(0x20)
CMD_WRITE_CONFIG = const(0x30)
CMD_READ_CONFIG = const(0x31)
CMD_CLEAR_CONFIG = const(0x32)

# Registers
REG_STATUS = const(0x80)
REG_ERROR = const(0x81)

# Status register bits
STATUS_BUSY = const(0x80)

# Error register bits
ERROR_BAD_PARAM = const(0x01)
ERROR_FILE_FULL = const(0x20)
ERROR_BAD_FILENAME = const(0x40)
ERROR_CMD_FAILED = const(0x80)

# Protocol limits
MAX_WRITE_CHUNK = const(30)
SECTOR_SIZE = const(256)
MAX_SECTORS = const(32768)
CONFIG_SIZE = const(1024)
FILENAME_LEN = const(8)
EXT_LEN = const(3)
Loading
Loading