HM01B0 I2C register writes not applying on Raspberry Pi Pico MicroPython

Hi everyone, I’m stuck with an HM01B0 camera module on a Raspberry Pi Pico. I can read I2C registers fine through MicroPython, but writes don’t seem to be applied. For example, I write 0x42 to register 0x1003 (the black level target) and when I read it back it’s still 0x20. It’s like the write never happened. Has anyone run into this?

Hey, welcome. That does sound like the register isn’t being written properly. To help narrow it down, can you share a few environment details: I2C address, which pins you’re using for SCL/SDA, the IDE, and how the camera is wired up?

Sure. It’s an HM01B0 module with a Raspberry Pi Pico, running MicroPython in Thonny. The I2C device address is 0x24. scl=machine.Pin(5), sda=machine.Pin(4). The camera is connected according to the diagram in KB007.pdf.

Thanks. Could you post the exact script you’re running and the output from the Thonny console? That’ll help us see what’s actually happening.

Here’s the script:

from machine import Pin, I2C
import time

# MAIN
# assign Pico pins
LED = Pin(25, Pin.OUT)
Href = Pin(15,Pin.OUT)
Href.value(0)
Vsync = Pin(16,Pin.OUT)
Pclk = Pin(14,Pin.IN,Pin.PULL_UP)
D0 = Pin(6,Pin.IN,Pin.PULL_UP)
i2c = machine.I2C(0, scl=machine.Pin(5), sda=machine.Pin(4))

devices = i2c.scan()
if devices:
    for d in devices:
        print('Found device',hex(d))
else:
    print('No device found')

reglist = [0x0100,0x1003]  # mode select, black level target

for i in range(len(reglist)):
    val = i2c.readfrom_mem(0x24, reglist[i], 1 ,addrsize=16)
    val = hex(val[0])
    print('reg',hex(reglist[i]),'value',val)
print()

i2c.writeto_mem(0x24, 0x1003, b'\x42')
print('Black level target write 0x42')
time.sleep(0.1)

for i in range(len(reglist)):
    val = i2c.readfrom_mem(0x24, reglist[i], 1 ,addrsize=16)
    val = hex(val[0])
    print('reg',hex(reglist[i]),'value',val)

Got it. And what does that print exactly when you run it?

Here’s the console output:

MPY: soft reboot
Found device 0x24
reg 0x100 value 0x0
reg 0x1003 value 0x20

Black level target write 0x42
reg 0x100 value 0x0
reg 0x1003 value 0x20

As you can see, 0x1003 reads 0x20 before and after the write.

Thanks. And to confirm, the write you’re attempting is the i2c.writeto_mem() call in that script? Can you show the exact write line you tried after the initial read?

Yes, the write I used was:

i2c.writeto_mem(0x24, 0x1003, b’\x42’)

After re-reading, register 0x1003 is still 0x20. Could you show me an example of how to read register 0x1003 and write 0x42 to it using read_16_8 / write_16_8 style functions?

I think the issue is that i2c.writeto_mem() isn’t producing the write sequence the HM01B0 expects for a 16-bit register address plus 8-bit data. The register address has to be packed as a 2-byte value (big-endian by default), and the data as a single byte, then both sent together with i2c.writeto().

Here’s a helper pair for 16-bit address + 8-bit data reads/writes:

def write_16_8(self, reg, data, byteorder='big') -> None:
    addr_buf = bytearray(2)
    data_buf = bytearray(1)
    
    addr_encode = '<H' if byteorder == 'little' else '>H'
    data_encode = 'B'
    
    struct.pack_into(addr_encode, addr_buf, 0, reg)
    struct.pack_into(data_encode, data_buf, 0, data)
    
    buf = addr_buf + data_buf
    self.i2c.writeto(self.addr, buf)
    
def read_16_8(self, reg, byteorder='big'):
    addr_buf = bytearray(2)
    # data_buf = bytearray(1)
    
    addr_encode = '<H' if byteorder == 'little' else '>H'
    data_encode = 'B'
    
    struct.pack_into(addr_encode, addr_buf, 0, reg)
    self.i2c.writeto(self.addr, addr_buf)
    data_buf = self.i2c.readfrom(self.addr, 1)
    data = struct.unpack_from(data_encode, data_buf, 0)[0]
    
    return data

Note: make sure you have import struct at the top of your file.

Thanks, that makes sense. Just so I understand what I’m pasting in, could you explain how the packing works? And what should I use for byte order?

Sure. For writes: the code packs the 16-bit register address and the 1-byte data into a single buffer (addr_buf + data_buf), then calls self.i2c.writeto(self.addr, buf). For reads: it writes the 2-byte register address first, then reads 1 byte from the device and unpacks it with format ‘B’.

Byte order defaults to big-endian (‘>H’). Pass byteorder=‘little’ to use little-endian (‘<H’).

That fixed it. Writes are sticking now and 0x1003 reads back as 0x42 after the write. Thanks for the help!