Skip to content

Berry I2C

Jak ve firmware 0.12.11 používat obecné I2C API v Berry Scriptu pro čtení a zápis vlastních zařízení.

This content is not available in your language yet.

Berry I2C zpřístupňuje v kontroleru obecný I2C bus jako objekt io["I2C"].

To znamená, že pro mnoho I2C zařízení už není potřeba přidávat nový hardcoded driver do firmware. Místo toho lze:

  • nadefinovat I2C v Controller Configu
  • v Berry Scriptu zařízení najít, číst a zapisovat jeho registry
  • postavit si vlastní jednoduchý driver přímo v projektu

Typický scénář je například:

  • senzor okolního světla
  • akcelerometr nebo IMU
  • kapacitní touch kontroler
  • jiný I2C periferní čip se známým datasheetem

Berry I2C funguje nad jedním IO typu I2C, které je definované v Controller Configu.

Příklad:

{
"I2C": {
"type": "I2C",
"frequency": 400000,
"sda": 27,
"scl": 14
}
}

Důležité:

  • v aktuální verzi firmware je podporovaný jeden Berry I2C bus na kontroler
  • pokud I2C v configu není, io["I2C"] nebude pro váš script použitelný

Berry Script získá I2C bus takto:

var bus = io["I2C"]

Jakmile máte bus, můžete nad ním volat obecné I2C metody.

Vrátí seznam nalezených 7bit I2C adres.

var bus = io["I2C"]
print("scan:", bus.scan())

Ověří, jestli zařízení na dané adrese odpovídá.

if bus.probe(0x10)
print("device found")
end

Provede čisté I2C čtení o délce len.

Použijte ho ve chvíli, kdy zařízení očekává samotné čtení bez register prefixu.

read(addr, len) samo o sobě nenastavuje adresu registru. Pokud potřebujete nejprve zapsat registr a potom číst, použijte writeRead(...) nebo readReg(...).

Zapíše na danou adresu byte payload a vrátí počet zapsaných bytů.

var tx = bytes().append(0x03).append(0x00).append(0x00)
bus.write(0x10, tx)

Provede transakci typu write-then-read. To je běžný způsob práce s registry u mnoha I2C zařízení.

var data = bus.writeRead(0x10, bytes().append(0x04), 2)

Zjednodušený helper pro čtení registru.

var data = bus.readReg(0x10, 0x04, 2)

Zjednodušený helper pro zápis registru.

data může být:

  • jedno byte číslo
  • objekt bytes
bus.writeReg(0x10, 0x03, 0x00)
bus.writeReg(0x10, 0x00, bytes().append(0x00).append(0x00))

Berry I2C vrstva pracuje schválně jen s bytovým payloadem.

Interpretaci hodnot si řešíte v Berry Scriptu podle datasheetu zařízení:

  • little-endian vs. big-endian
  • signed vs. unsigned
  • převod registrů na fyzikální jednotky
  • vlastní inicializační sekvence

To je záměr. Firmware dává obecný transport a projektový driver si skládáte v Berry.

Tento příklad:

  • zkontroluje, že na adrese 0x10 něco odpovídá
  • přečte 16bit little-endian hodnotu z registru 0x04
  • vypíše ji do logu
var bus = io["I2C"]
if !bus.probe(0x10)
print("I2C device 0x10 not found")
return
end
var raw = bus.readReg(0x10, 0x04, 2)
var value = raw[0] | (raw[1] << 8)
print("raw:", value)

Pro VEML7700 lze v Berry Scriptu udělat i velmi krátký projektový driver.

Následující příklad:

  • ověří přítomnost zařízení
  • nastaví základní konfiguraci
  • jednou za sekundu vypisuje ALS lux hodnotu
var bus = io["I2C"]
var ok = false
var last = -1000
try
if bus != nil && bus.probe(0x10)
# ALS_CONF = 0x0000 => gain 1x, integration 100 ms
bus.writeReg(0x10, 0x00, bytes().append(0x00).append(0x00))
# Power saving off
bus.writeReg(0x10, 0x03, 0x00)
ok = true
end
except ..
ok = false
end
Plugin(def()
if !ok || controller.millis() - last < 1000
return
end
last = controller.millis()
try
var raw = bus.readReg(0x10, 0x04, 2)
var als = raw[0] | (raw[1] << 8)
var lux = als * 0.0576
print("VEML7700 lux:", lux)
except ..
print("VEML7700 read failed")
end
end)

Níže jsou příklady ve stylu, který se v projektech Spectoda osvědčuje:

  • komentáře vysvětlují, co se děje
  • samotný kód zůstává krátký a úsporný
  • názvy proměnných jsou záměrně stručné, aby script zbytečně nezabíral RAM

Tento plugin:

  • ověří VEML7700 na adrese 0x10
  • nastaví základní konfiguraci
  • jednou za sekundu vypisuje lux hodnotu
var b = io["I2C"]
var ok = false
var last = -1000
# ALS_CONF = 0x0000 => gain 1x, integration 100 ms.
# PSM = 0x00 => normal mode.
try
if b != nil && b.probe(0x10)
b.writeReg(0x10, 0x00, bytes().append(0x00).append(0x00))
b.writeReg(0x10, 0x03, 0x00)
ok = true
end
except ..
ok = false
end
Plugin(def()
if !ok || controller.millis() - last < 1000
return
end
last = controller.millis()
try
var d = b.readReg(0x10, 0x04, 2)
var raw = d[0] | (d[1] << 8)
var lux = raw * 0.0576
print("VEML7700 lux:", lux)
except ..
print("VEML7700 read failed")
end
end)

VEML7700: Daylight plugin jako drop-in replacement

Section titled “VEML7700: Daylight plugin jako drop-in replacement”

Tento příklad je kompaktní náhrada za starší plugin, který používal firmware-specific helpery.

Chování zůstává stejné:

  • čte stejné NUMBER hodnoty v milli-lux jako původní helper
  • publikuje DAYLI
  • při chybě emituje E_INI nebo E_REA
  • upravuje brigh podle cílové lux hodnoty
def Daylight(S)
import controller, math
var id = S["id"]
var target = S["target"]
var toggl = EVS("toggl", id)
var brigh = EVS("brigh", id)
var L_ena = EVS("L_ena", id)
# Původní helper používal pevně gain 1x a integration 100 ms.
# Pokud to změníte, musí zůstat odpovídající i přepočet raw -> milli-lux.
var als_conf = 0x0000
var mlux_per_count_x10 = 576
var bus = nil
var ok = false
var err_init = EVT("DAYLI", "E_INI", id, LABEL)
var err_read = EVT("DAYLI", "E_REA", id, LABEL)
# Přímá náhrada za staré __vmin("I2C", 1.0, 100):
# jeden fixní init senzoru bez auto-range.
try
bus = io["I2C"]
if bus != nil
var cfg = bytes()
cfg.append(als_conf & 0xff)
cfg.append((als_conf >> 8) & 0xff)
bus.writeReg(0x10, 0x00, cfg)
bus.writeReg(0x10, 0x03, 0x00)
ok = true
end
except ..
ok = false
end
if !ok
err_init()
end
# Přímá náhrada za staré __vmre():
# čte ALS register a vrací NUMBER v milli-lux.
var rd = def()
if !ok
return nil
end
try
var d = bus.readReg(0x10, 0x04, 2)
var raw = d[0] | (d[1] << 8)
return int((raw * mlux_per_count_x10) / 10)
except ..
return nil
end
end
EVS("DAYLI", id).cb = def(v)
if v.is(NULL)
var lux = rd()
if lux == nil
err_read()
else
EVS.emit("DAYLI", lux, id, NUMBER)
end
return
end
end
var last = controller.millis()
return Plugin(def()
var now = controller.millis()
if now - last < 1000 || !L_ena || !toggl
return
end
last = now
var lux = rd()
if lux == nil
err_read()
return
end
var dis = math.abs(lux - target)
if dis == 0 || lux == 0
return
end
var dif = real(dis) / real(lux)
if dif < 0.025 || dis < 10
return
end
var b0 = brigh.get(PERCENTAGE)
if lux < target
if dif > 0.1
b0 += 5
else
b0 += 1
end
else
if dif > 0.1
b0 -= 5
else
b0 -= 1
end
end
if b0 < 0
b0 = 0
elif b0 > 100
b0 = 100
end
brigh.set(b0, PERCENTAGE)
end)
end

Tento plugin ukazuje, jak přes Berry I2C připojit LSM6DS3TR.

Příklad:

  • používá výchozí I2C adresu 0x6A
  • ověří WHO_AM_I
  • zapne akcelerometr a gyroskop na 104 Hz
  • průběžně vypisuje akceleraci v g a gyroskop v dps

Pokud máte pin SA0 zapojený jinak, může být potřeba změnit adresu na 0x6B.

var b = io["I2C"]
var ok = false
var last = -1000
# Convert two bytes into signed int16.
var s16 = def(lo, hi)
var v = lo | (hi << 8)
if v >= 0x8000
v -= 0x10000
end
return v
end
# Default address is 0x6A when SA0 is low.
# WHO_AM_I should return 0x6A.
# CTRL3_C = 0x44 enables BDU and register auto-increment.
# CTRL1_XL = 0x40 enables accelerometer at 104 Hz, ±2 g.
# CTRL2_G = 0x40 enables gyroscope at 104 Hz, ±245 dps.
try
if b != nil && b.probe(0x6A)
if b.readReg(0x6A, 0x0F, 1)[0] == 0x6A
b.writeReg(0x6A, 0x12, 0x44)
b.writeReg(0x6A, 0x10, 0x40)
b.writeReg(0x6A, 0x11, 0x40)
ok = true
end
end
except ..
ok = false
end
Plugin(def()
if !ok || controller.millis() - last < 500
return
end
last = controller.millis()
try
# Read gyro XYZ and accel XYZ in one burst.
var d = b.readReg(0x6A, 0x22, 12)
var gx = s16(d[0], d[1]) * 0.00875
var gy = s16(d[2], d[3]) * 0.00875
var gz = s16(d[4], d[5]) * 0.00875
var ax = s16(d[6], d[7]) * 0.000061
var ay = s16(d[8], d[9]) * 0.000061
var az = s16(d[10], d[11]) * 0.000061
print("LSM6DS3TR acc[g]:", ax, ay, az, "gyro[dps]:", gx, gy, gz)
except ..
print("LSM6DS3TR read failed")
end
end)

Když připojujete nové I2C zařízení, osvědčený postup je:

  1. Nejprve ověřit scan() a probe(addr).
  2. Potom ručně přečíst nebo zapsat první registry přes readReg(...) a writeReg(...).
  3. Až potom si kolem toho obalit malý Berry driver pro konkrétní zařízení.

Prakticky to znamená:

  • obecný I2C transport řeší firmware
  • device-specific protokol řeší váš Berry Script
  • změna zařízení často nevyžaduje změnu C++ firmware

To je vhodné hlavně pro projekty, kde potřebujete rychle doplnit podporu konkrétního senzoru nebo periferního čipu.

Ve většině případů pracujte s registry, takže dává smysl:

  • readReg(...)
  • writeReg(...)
  • případně writeRead(...)

Samotné read(...) použijte tehdy, když zařízení opravdu očekává čisté čtení bez register adresy nebo když přesně víte, jak se daný čip v této situaci chová.