Un driver I2C es la capa que traduce el protocolo de un dispositivo a una interfaz que el programa puede utilizar. Para escribirlo necesitamos el datasheet y acceso al bus.
Si un sensor habla I2C (o SPI), podemos hablar con él. Solo necesitamos dos cosas:
- Su Datasheet (Hoja de datos).
- Las funciones de bajo nivel
i2c.writeyi2c.read.
Hoy vamos a aprender a crear un “Mini-Driver”. No será una especificación completa de Jacdac (eso es para nota), pero será una clase o función que nos permitirá usar el sensor en nuestro proyecto de forma limpia.
Leer el datasheet
Antes de escribir código, tenemos que saber qué decirle al sensor. Para este ejemplo, vamos a implementar un driver para el BH1750, un sensor de luz muy común.
Si abrimos su datasheet, encontraremos una tabla de comandos como esta:
| Instrucción | Opcode (Hex) | Descripción |
|---|---|---|
| Power Down | 0x00 | Apaga el sensor |
| Power On | 0x01 | Enciende el sensor |
| Continuos H-Res | 0x10 | Empieza a medir en alta resolución |
Y nos dice que, tras enviar el comando de medición, debemos esperar 120ms y luego leer 2 bytes (parte alta y parte baja) que forman el valor de luxes.
¡Ya tenemos el plan!
- Enviar
0x10(Start Measurement). - Esperar.
- Leer 2 bytes.
- Unirlos matemáticamente.
Usar I2CSensorDriver
El paquete @devicescript/i2c ofrece acceso directo al bus y operaciones sobre registros. Para un driver completo resulta más cómodo heredar de I2CSensorDriver, que conserva la dirección y proporciona writeBuf y readBuf.
Vamos a implementar la lectura en una clase. El literal hex`10` crea el búfer con el comando de medida sin convertirlo a texto.
import { sleep } from "@devicescript/core"
import { I2CSensorDriver } from "@devicescript/drivers"
export class BH1750Driver extends I2CSensorDriver<{ lux: number }> {
constructor(address = 0x23) {
super(address, { readCacheTime: 200 })
}
override async initDriver() {
await this.writeBuf(hex`01`) // Power On
}
override async readData() {
await this.writeBuf(hex`10`) // Continuous H-Resolution Mode
await sleep(180)
const data = await this.readBuf(2)
const raw = (data[0] << 8) | data[1]
return { lux: raw / 1.2 }
}
}Ahora inicializamos el driver y leemos el valor. read() ejecuta readData() y devuelve el objeto con la medida.
const miSensor = new BH1750Driver(0x23)
await miSensor.init()
setInterval(async () => {
const { lux } = await miSensor.read()
console.log(`Luz: ${lux.toFixed(1)} lx`)
}, 1000)Este ejemplo se centra en la comunicación. Un driver reutilizable debería validar respuestas, documentar el modo de medida y exponer un servicio estándar, por ejemplo LightLevel.