Arduino Nano 33 BLE Sense

Advertencia

Esta placa ya no recibe soporte. La última versión del firmware de OpenMV para la Arduino Nano 33 BLE Sense es la 4.7.0. No se publicarán más actualizaciones de firmware, correcciones de errores ni nuevas funciones para este objetivo. La información que figura a continuación se conserva para los usuarios que utilicen la 4.7.0 o versiones anteriores.

La Arduino Nano 33 BLE Sense es una placa de 45 × 18 mm con factor de forma Arduino-Nano construida en torno al nRF52840 de Nordic Semiconductor: un único ARM Cortex-M4 con FPU que funciona a 64 MHz con 256 KB de SRAM interna y 1 MB de memoria flash interna. El BLE proviene de la radio integrada en el chip, y la placa incorpora una IMU de 9 ejes, un barómetro LPS22HB, un sensor de temperatura/humedad HTS221 / HS3003, un sensor de luz ambiental / color / proximidad / gestos APDS9960 y un micrófono PDM MP34DT05. El firmware de OpenMV controla todos ellos desde MicroPython.

Arduino Nano 33 BLE Sense

Para la hoja de datos completa, fotos y dimensiones, consulta la página del producto Arduino Nano 33 BLE Rev2.

Aspectos destacados

  • Nordic nRF52840 Cortex-M4 con FPU a 64 MHz con 256 KB de SRAM interna y 1 MB de memoria flash interna.

  • Bluetooth LE 5.0 a través de la radio integrada en el chip y el Nordic SoftDevice s140.

  • IMU de 9 ejesLSM9DS1 en la Rev 1, BMI270 + BMM150 en la Rev 2. El controlador imu congelado detecta ambos en el arranque.

  • Barómetro LPS22HB, sensor de temperatura y humedad HTS221 / HS3003, sensor de luz ambiental / color / proximidad / gestos APDS9960 y micrófono PDM MP34DT05.

  • Conector Micro USB para alimentación, programación y un REPL por CDC.

  • 22 pines de E/S de usuario en los conectores estándar de la Nano: TX/RX, D2D13 (digitales), A0A7 (analógicos).

Distribución de pines

Distribución de pines de la Arduino Nano 33 BLE Sense

Referencia de pines

Nombre del pin

Referencia

Función

TX

3,3 V

UART1 TX

RX

3,3 V

UART1 RX

D2

3,3 V

PWM

D3

3,3 V

PWM

D4

3,3 V

PWM

D5

3,3 V

PWM

D6

3,3 V

PWM

D7

3,3 V

PWM

D8

3,3 V

PWM

D9

3,3 V

PWM

D10

3,3 V

PWM

D11

3,3 V

PWM / SPI0 MOSI

D12

3,3 V

PWM / SPI0 MISO

D13

3,3 V

PWM / SPI0 SCK

A0

3,3 V

ADC / PWM

A1

3,3 V

ADC / PWM

A2

3,3 V

ADC / PWM

A3

3,3 V

ADC / PWM

A4 / I2C_SDA

3,3 V

ADC / PWM / I2C0 SDA

A5 / I2C_SCL

3,3 V

ADC / PWM / I2C0 SCL

A6

3,3 V

ADC / PWM

A7

3,3 V

ADC / PWM

RESET

3,3 V

pulsa el botón RESET de la placa o conecta a GND para reiniciar

LED_BUILTIN

LED de usuario naranja en D13

LED_RED

Canal rojo del LED RGB (activo a nivel bajo)

LED_GREEN

Canal verde del LED RGB (activo a nivel bajo)

LED_BLUE

Canal azul del LED RGB (activo a nivel bajo)

Advertencia

Los pines de E/S de la Nano 33 BLE Sense son solo de 3,3 V: no toleran 5 V. Aplicar 5 V a ellos dañará el nRF52840.

Pines de alimentación

  • VIN — entrada de 4,5 – 21 V. Alimenta la placa a través del regulador integrado. También recibe alimentación mediante un diodo desde la línea de 5 V del USB, por lo que el USB y VIN pueden estar presentes al mismo tiempo sin realimentarse entre sí.

  • +5V — sin conectar de forma predeterminada.

  • +3V3 — salida del regulador de 3,3 V.

  • AREF — pin de referencia analógica. No está cableado al nRF52840 en esta placa: el ADC siempre se referencia a 3,3 V.

  • GND — tierra común.

La Nano 33 BLE Sense puede alimentarse por cualquiera de estas vías:

  • Micro USB — suministra 5 V al regulador integrado.

  • Pin VIN — aplica una fuente regulada de 4,5 – 21 V.

Nota

Un puente de soldadura en la parte inferior de la placa etiquetado VUSB conecta +5V con la línea de 5 V del USB. Ciérralo para que el pin del conector +5V realmente lleve 5 V.

Nota

Un puente de soldadura normalmente cerrado en la salida del regulador conmutado integrado de 4,5–21 V puede cortarse para deshabilitar el regulador, de modo que la placa pueda alimentarse directamente desde una fuente externa de 3,3 V en +3V3.

Pines de recuperación y depuración

  • RESET — tanto un pad expuesto como un botón RESET momentáneo en la parte superior de la placa, conectados a la línea de reinicio del nRF52840. Conecta a GND o pulsa el botón para reiniciar.

La Nano 33 BLE Sense utiliza el doble pulsado de reset estándar de Arduino para entrar en el bootloader de Arduino. Pulsa rápidamente el botón RESET dos veces: la placa entra en modo bootloader y OpenMV IDE puede grabar una nueva imagen de firmware.

Las señales SWD del nRF52840 están expuestas en pads chapados en la parte posterior de la placa. Todas las señales de depuración están referenciadas a 3,3 V.

Periféricos integrados

LEDs

La Nano 33 BLE Sense tiene un LED RGB de usuario —controlado a través de los canales serigrafiados LED_RED, LED_GREEN y LED_BLUE— más un LED_BUILTIN naranja independiente en D13. Los cuatro se pueden controlar por software mediante machine.LED:

from machine import LED

LED("LED_RED").on()
LED("LED_GREEN").on()
LED("LED_BLUE").on()
LED("LED_BUILTIN").on()

Un LED verde de alimentación independiente en la placa se enciende siempre que la línea de +3,3 V está activa y no es controlable por el usuario.

Sensor de cámara

El firmware de OpenMV en la Nano 33 BLE Sense admite el sensor CMOS paralelo OmniVision OV7670. La placa no tiene sensor de imagen integrado: conecta un módulo OV7670 a los pines del conector serigrafiados que se enumeran a continuación y contrólalo mediante el módulo csi — sensores de cámara:

import csi

cam = csi.CSI()
cam.reset()
cam.pixformat(csi.RGB565)
cam.framesize(csi.QVGA)
cam.snapshot(time=2000)       # let auto‑exposure settle

while True:
    img = cam.snapshot()

Nota

El OV7670 ocupa 14 pines. El firmware los conecta de la siguiente manera:

Señal del sensor

Pin de la Nano 33 BLE Sense

D0

D10

D1

TX

D2

RX

D3

D2

D4

D3

D5

D5

D6

D6

D7

D4

HSYNC

A1

VSYNC

D8

PXCLK

A0

MXCLK

D9

POWER

A3

RESET

A2

SCL

A5 (I²C 0)

SDA

A4 (I²C 0)

El bus de control I²C del OV7670 es el mismo I²C 0 externo expuesto en A5/A4. El sensor se encuentra en la dirección de 7 bits 0x21: los dispositivos de usuario en ese bus deben evitar esta dirección cuando la cámara está conectada.

IMU

La IMU de 9 ejes se expone a través del módulo imu congelado, que detecta automáticamente si la placa tiene el LSM9DS1 (Rev 1) o el BMI270 + BMM150 (Rev 2) y presenta una clase imu.IMU unificada. Los sensores están en el bus interno I²C 1 (P14 / P15):

import time
from machine import I2C, Pin
from imu import IMU

bus = I2C(1, scl=Pin("P15"), sda=Pin("P14"))
sensor = IMU(bus)

while True:
    print(sensor.accel())     # (x, y, z) in g
    print(sensor.gyro())      # (x, y, z) in deg/s
    print(sensor.magnet())    # (x, y, z) magnetometer
    time.sleep_ms(100)

Para acceder directamente a funciones como la detección de toques o la FIFO, importa el controlador congelado correspondiente (lsm9ds1, bmi270 o bmm150) e instáncialo en el mismo bus.

Sensores ambientales

El barómetro (LPS22HB) y el sensor de temperatura/humedad (HTS221 en la Rev 1, HS3003 en la Rev 2) comparten el mismo bus interno I²C 1 que la IMU:

import time
from machine import I2C, Pin
from lps22h import LPS22H
from hts221 import HTS221

bus = I2C(1, scl=Pin("P15"), sda=Pin("P14"))
lps = LPS22H(bus)
try:
    hts = HTS221(bus)
except OSError:
    from hs3003 import HS3003
    hts = HS3003(bus)

while True:
    print("pressure:    %.2f hPa" % lps.pressure())
    print("temperature: %.2f C"   % lps.temperature())
    print("humidity:    %.2f %%"  % hts.humidity())
    time.sleep_ms(500)

Luz / color / proximidad / gestos

El APDS9960 de Broadcom está en el mismo bus interno I²C 1 y proporciona detección de luz ambiental, color RGB, proximidad y gestos:

import time
from machine import I2C, Pin
from apds9960 import uAPDS9960 as APDS9960

bus = I2C(1, scl=Pin("P15"), sda=Pin("P14"))
apds = APDS9960(bus)
apds.enableLightSensor()

while True:
    print("ambient light:", apds.readAmbientLight())
    time.sleep_ms(250)

Micrófono

El micrófono PDM MP34DT05 integrado se captura mediante audio — Módulo de audio. Cada búfer llega como PCM de 16 bits con signo en un bytearray, listo para introducirlo en ulab/numpy para procesamiento de señales:

import audio
from ulab import numpy as np

def loudness(pcmbuf):
    samples = np.array(np.frombuffer(pcmbuf, dtype=np.int16), dtype=np.float)
    rms = np.sqrt(np.mean(samples ** 2))
    if rms > 10000:
        print("Loud!", int(rms))

audio.init(channels=1, frequency=16000, gain_db=24)
audio.start_streaming(loudness)

while True:
    pass

Bluetooth

La radio Bluetooth LE 5.0 del nRF52840 funciona sobre el Nordic SoftDevice s140 y se expone a través del módulo heredado ubluepy; las APIs modernas bluetooth / aioble — BLE asíncrono no están habilitadas en esta compilación. Están disponibles tanto el rol periférico (servidor GATT, anunciar) como el rol central (observador / escáner GAP + conexión).

Anúnciate como periférico con un único servicio Environmental Sensing y una característica de temperatura notificable: la función de retorno (callback) event_handler se activa en la conexión, la desconexión y las escrituras del CCCD:

from ubluepy import Service, Characteristic, UUID, Peripheral, constants
from machine import LED

def event_handler(event_id, handle, data):
    if event_id == constants.EVT_GAP_CONNECTED:
        LED("LED_GREEN").on()
    elif event_id == constants.EVT_GAP_DISCONNECTED:
        LED("LED_GREEN").off()
        periph.advertise(device_name="Nano 33", services=[svc])

svc = Service(UUID("181A"))                          # Environmental Sensing
char = Characteristic(UUID("2A6E"),                  # Temperature
                      props=Characteristic.PROP_NOTIFY | Characteristic.PROP_READ,
                      attrs=Characteristic.ATTR_CCCD)
svc.addCharacteristic(char)

periph = Peripheral()
periph.addService(svc)
periph.setConnectionHandler(event_handler)
periph.advertise(device_name="Nano 33", services=[svc])

Escanea dispositivos cercanos que anuncian en el rol central:

from ubluepy import Scanner

for entry in Scanner().scan(1_000):                  # 1 second window
    print(entry.addr(), entry.rssi(), "dBm")

Consulta la referencia de ubluepy para la API completa: UUID, Service, Characteristic, Peripheral, Scanner, ScanEntry y el espacio de nombres constants.

Referencia de buses

GPIO

Usa machine.Pin para leer o controlar cualquiera de los pines serigrafiados. Las salidas son CMOS de 3,3 V: 15 mA por pin, 25 mA en total entre todos los GPIO.

from machine import Pin

out = Pin("D2", Pin.OUT)
out.on()
out.off()
out.value(1)

inp = Pin("D3", Pin.IN, Pin.PULL_UP)
print(inp.value())

Cualquier pin de entrada también puede disparar una interrupción en las transiciones de flanco:

def handler(pin):
    print("triggered:", pin)

Pin("D3", Pin.IN, Pin.PULL_UP).irq(
    handler, Pin.IRQ_FALLING | Pin.IRQ_RISING,
)

UART

Bus

TX

RX

UART1

TX

RX

Usa los nombres serigrafiados TX/RX con machine.UART:

from machine import UART

uart = UART(1, baudrate=115200)
uart.write("hello")
uart.read(5)

I²C

Bus

SDA

SCL

I2C0

I2C_SDA / A4

I2C_SCL / A5

I2C1

P14

P15

Ambos buses necesitan que sus pines se pasen explícitamente a machine.I2C:

from machine import I2C, Pin

bus0 = I2C(0, scl=Pin("I2C_SCL"), sda=Pin("I2C_SDA"), freq=400_000)
bus0.scan()

bus1 = I2C(1, scl=Pin("P15"), sda=Pin("P14"), freq=400_000)
bus1.scan()

Nota

El bus 1 es el bus interno de sensores en P14/P15 (no está en los conectores de usuario): da servicio a la IMU, el barómetro, el sensor ambiental y el APDS9960. Los controladores de sensores congelados lo usan directamente; el código de usuario también puede escanearlo, pero las direcciones ya están ocupadas por los sensores integrados.

SPI

Bus

MOSI

MISO

SCK

CS

SPI0

D11

D12

D13

D10

La línea CS no la controla el periférico SPI: configura D10 como salida y conmútala manualmente alrededor de la transferencia:

from machine import SPI, Pin

spi = SPI(0, baudrate=10_000_000)
cs = Pin("D10", Pin.OUT, value=1)   # CS is not driven by the SPI peripheral

cs.value(0)
spi.write(b"hello")
cs.value(1)

Nota

D13 también funciona como el LED_BUILTIN naranja: controlar SPI en este bus hará parpadear el LED al ritmo del reloj del bus.

ADC

El nRF52840 tiene ocho canales ADC de 12 bits (SAADC) expuestos en A0–A7, todos referenciados a 3,3 V: read_u16 devuelve 0–65535 a lo largo de 0–3,3 V en el pin. El pin AREF de la placa no está cableado, por lo que la referencia siempre es 3,3 V:

from machine import ADC
import time

adc = ADC("A0")
while True:
    voltage = adc.read_u16() * 3.3 / 65535
    print(voltage)
    time.sleep_ms(100)

PWM

El nRF52840 expone cuatro periféricos PWM (PWM0PWM3), cada uno controlando cuatro canales, para un total de 16 ranuras de PWM por hardware. A diferencia de los puertos de función fija, los periféricos se enrutan a través de la matriz GPIOTE: cualquier GPIO puede ser una salida PWM, por lo que no hay una correspondencia fija entre pin y segmento. El precio de esa flexibilidad son dos restricciones grabadas en el silicio:

  • Los cuatro canales dentro de un módulo comparten un único período/frecuencia.

  • Cada canal tiene su propio ciclo de trabajo y polaridad.

Conceptualmente, las 16 ranuras se ven así:

Módulo

Canal 0

Canal 1

Canal 2

Canal 3

PWM0

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

PWM1

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

PWM2

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

PWM3

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

ciclo de trabajo

Cada fila funciona a una frecuencia; las cuatro celdas de una fila controlan cada una un pin elegido independientemente con su propio ciclo de trabajo. Filas distintas pueden funcionar a frecuencias completamente diferentes.

Controla cualquier pin serigrafiado (o los LEDs de la placa) mediante machine.PWM:

from machine import Pin, PWM

pwm = PWM(Pin("D3"), freq=1_000, duty_u16=32768)

Advertencia

La asignación automática consume un módulo entero por llamada. Cuando creas un PWM sin los argumentos device=/channel=, el controlador toma el primer módulo libre y vincula tu pin únicamente a su canal 0. Los tres canales restantes de ese módulo quedan inactivos y solo se pueden alcanzar mediante device=/channel= explícitos. Eso limita las llamadas PWM(Pin(...)) sin ayuda a cuatro antes de que el controlador genere ValueError: all PWM devices in use, aunque técnicamente sigan libres doce ranuras.

Para usar más de cuatro PWM, o para compartir deliberadamente una frecuencia entre varios pines, pasa device (0–3) y channel (0–3):

# Two PWMs on the same module → forced to share frequency,
# but each gets its own duty cycle.
pwm_a = PWM(Pin("D3"), device=0, channel=0,
            freq=1_000, duty_u16=32768)
pwm_b = PWM(Pin("D5"), device=0, channel=1,
            freq=1_000, duty_u16=16384)

# A third PWM on a separate module, free to pick any frequency.
pwm_c = PWM(Pin("D6"), device=1, channel=0,
            freq=20_000, duty_u16=49152)

El ciclo de trabajo acepta duty (0–100 %), duty_u16 (0–65535) o duty_ns. Añade invert=1 para invertir la polaridad de la salida (útil para el LED RGB activo a nivel bajo).

Nota

Como la frecuencia es una propiedad por módulo, llamar a pwm.freq(new_freq) en cualquier canal de un módulo vuelve a ejecutar nrfx_pwm_init para todo el módulo y cambia la frecuencia que ven todos los demás canales que lo comparten.

Nota

Las frecuencias permitidas abarcan aproximadamente de 4 Hz a 5,3 MHz, derivadas del reloj base de 16 MHz con preescaladores 1/2/4/8/16/32/64/128 y un contador de período de 15 bits. El controlador elige el divisor más cercano automáticamente: freq() informa del valor solicitado, no del valor exacto alcanzable.

Buses emulados por software (bit-banged)

machine.SoftI2C y machine.SoftSPI funcionan en cualquier GPIO si necesitas un bus adicional.

Sensor térmico (externo)

El firmware incluye el controlador fir — controlador de sensor térmico (fir == infrarrojo lejano) para sensores de imagen térmica cableados externamente:

  • MLX90621 — matriz IR de 16 × 4

  • MLX90640 — matriz IR de 32 × 24

  • MLX90641 — matriz IR de 16 × 12

  • AMG8833 — matriz IR de 8 × 8

Conecta el módulo al bus I²C de la placa y lee fotogramas con fir.init() + fir.snapshot():

import time
import image
import fir

fir.init()                          # auto‑detects the sensor
clock = time.clock()

while True:
    clock.tick()
    try:
        img = fir.snapshot(x_scale=5, y_scale=5,
                           color_palette=image.PALETTE_IRONBOW,
                           hint=image.BICUBIC,
                           copy_to_fb=True)
    except OSError:
        continue
    print(clock.fps())

El controlador fir solo se comunica con el sensor a través de I²C 0: conecta el módulo a los pads I2C_SCL / I2C_SDA (A5 / A4).

Temporización

time

El módulo time cubre retardos bloqueantes, ticks monótonos y la medición del tiempo transcurrido:

import time

time.sleep(1)              # seconds
time.sleep_ms(500)
time.sleep_us(10)

start = time.ticks_ms()
# ...do work...
elapsed = time.ticks_diff(time.ticks_ms(), start)

Temporizadores virtuales

machine.Timer programa funciones de retorno periódicas o de un solo disparo sin consumir una ranura de temporizador por hardware. Pasa -1 como id para usar un temporizador virtual (por software):

from machine import Timer

one_shot = Timer(-1)
one_shot.init(period=5_000, mode=Timer.ONE_SHOT,
              callback=lambda t: print("once"))

periodic = Timer(-1)
periodic.init(period=2_000, mode=Timer.PERIODIC,
              callback=lambda t: print("tick"))

Los valores de período están en milisegundos. Llama a deinit() para detener y liberar la ranura.

Reloj en tiempo real

machine.RTC mantiene la hora del reloj a lo largo de los reinicios. El RTC del nRF52840 está vinculado al oscilador del chip y no sobrevive a una pérdida total de alimentación: ajusta la hora en cada arranque en frío si es importante para tu aplicación:

from machine import RTC

rtc = RTC()
rtc.datetime((2026, 4, 30, 4, 12, 0, 0, 0))   # Y, M, D, weekday, h, m, s, subsec
print(rtc.datetime())

Watchdog

machine.WDT reinicia la placa si la aplicación se cuelga. Una vez iniciado, no se puede detener ni reconfigurar: aliméntalo periódicamente dentro de tu bucle principal:

from machine import WDT

wdt = WDT(timeout=5_000)   # 5 second window
while True:
    # ...do work...
    wdt.feed()

Información de arranque y de ejecución

Actualización del firmware

La Nano 33 BLE Sense utiliza el doble pulsado de reset estándar de Arduino para entrar en el bootloader de Arduino. Pulsa rápidamente el botón RESET dos veces: la placa entra en modo bootloader y OpenMV IDE puede grabar una nueva imagen de firmware.

Un script en ejecución puede volver a entrar en el bootloader bajo demanda llamando a machine.bootloader():

import machine

machine.bootloader()

Sistema de archivos y orden de arranque

El firmware de la Nano 33 BLE Sense monta un único sistema de archivos en el arranque:

  • Memoria flash interna — siempre montada en /flash y usada como directorio de trabajo. Contiene main.py y README.txt de forma predeterminada; se crea en el primer arranque.

Tras el montaje, el intérprete ejecuta entonces los scripts desde /flash:

  • boot.py se ejecuta en cada reinicio en caliente.

  • main.py se ejecuta solo en el arranque en frío, inmediatamente después de boot.py.

El main.py predeterminado que se incluye en una placa recién grabada simplemente hace parpadear el canal azul del LED RGB de usuario como latido (dos pulsos cortos, breve pausa), de modo que puedes saber que el firmware arrancó correctamente sin ningún host conectado.

/flash no se expone como una unidad de almacenamiento masivo USB en esta placa.

Tamaños de almacenamiento

La Nano 33 BLE Sense se entrega con:

  • /flash — sistema de archivos FAT de 64 KB, lectura/escritura.

La compilación de la Nano 33 BLE Sense no incluye un ROMFS; distribuye los módulos de Python directamente en /flash.

Bibliotecas de software

Consulta el índice de la biblioteca para ver la lista completa de módulos, incluidos los que son exclusivos de la compilación de la Nano 33 BLE Sense.