OK I’ve cracked it. There’s definitively a gap in my knowledge or some really subtle timing that made C code work in some circumstances… Here’s what I have done for reference if anyone else decides not to use Python but C on Pi Pico and not to use slightly overcomplicated code but go straight to the simplest way of using neokey 1x4.
For Python on Raspberry Pi I’ve got:
...
def write(self, buf: List[int]) -> None:
i2c_bus.i2c_rdwr(i2c_msg.write(self.i2c_address, buf))
def read(self, reg_base: int, reg: int, number_of_bytes_to_read: int) -> List[int]:
buf = [reg_base, reg]
read_data = i2c_msg.read(self.i2c_address, number_of_bytes_to_read)
i2c_bus.i2c_rdwr(i2c_msg.write(self.i2c_address, buf))
i2c_bus.i2c_rdwr(read_data)
return list(read_data)
def init(self) -> None:
self.write([self._NEOPIXEL_BASE, self._NEOPIXEL_PIN, 3])
self.write([self._NEOPIXEL_BASE, self._NEOPIXEL_BUF_LENGTH, 0, 12])
pins = self._BUTTON_MASK # _BUTTON_MASK = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7)
cmd = [(pins & 0xff000000) >> 24, (pins & 0xff0000) >> 16, (pins & 0xff00) >> 8, pins]
self.write([self._GPIO_BASE, self._GPIO_DIRCLR_BULK] + cmd)
self.write([self._GPIO_BASE, self._GPIO_PULLENSET] + cmd)
self.write([self._GPIO_BASE, self._GPIO_BULK_SET] + cmd)
self.write([self._GPIO_BASE, self._GPIO_INTENSET] + cmd)
def update_leds(self) -> None:
self.write(
[self._NEOPIXEL_BASE, self._NEOPIXEL_BUF, 0, 0,
self.leds[0][0], self.leds[0][1], self.leds[0][2],
self.leds[1][0], self.leds[1][1], self.leds[1][2],
self.leds[2][0], self.leds[2][1], self.leds[2][2],
self.leds[3][0], self.leds[3][1], self.leds[3][2]])
self.write([self._NEOPIXEL_BASE, self._NEOPIXEL_SHOW])
def read_keys(self, num: int) -> List[int]:
return self.read(self._GPIO_BASE, self._GPIO_BULK, 4)
...
while True:
...
neokey.update_leds()
...
time.sleep(0.01)
keys = neokey.read_keys(4)
...
Attention is here on smbus2’s i2c_rdwr() - which I though is one of i2c’s start/write/continue/read/stop combinations. And all works well - LEDs are updated, keys are read.
For pi pico’s code I’ve got the same init structure and then:
void write_leds() {
buf[0] = NEOPIXEL_BASE;
buf[1] = NEOPIXEL_BUF;
buf[2] = 0;
buf[3] = 0;
for (int i = 0; i < 12; i++) { buf[i + 4] = leds[i]; }
int ret = i2c_write_blocking(i2c_default, NEOKEY_I2C_ADDRESS, buf, 16, false);
...
}
void show_leds() {
...
buf[0] = NEOPIXEL_BASE;
buf[1] = NEOPIXEL_SHOW;
int ret = i2c_write_blocking(i2c_default, NEOKEY_I2C_ADDRESS, buf, 2, false);
...
}
void read_keys_raw() {
buf[0] = GPIO_BASE;
buf[1] = GPIO_BULK;
int ret = i2c_write_blocking(i2c_default, NEOKEY_I2C_ADDRESS, buf, 2, true);
...
sleep_ms(2);
uint8_t rec[4];
ret = i2c_read_blocking(i2c_default, NEOKEY_I2C_ADDRESS, rec, 4, false);
}
Which didn’t work. Or worse - calling read_keys_raw() worked well as long as I never called show_leds().
But when I changed:
int ret = i2c_write_blocking(i2c_default, NEOKEY_I2C_ADDRESS, buf, 2, true);
to
int ret = i2c_write_blocking(i2c_default, NEOKEY_I2C_ADDRESS, buf, 2, false);
I completely failed to realise that (by mistake?) I did:
i2c_bus.i2c_rdwr(i2c_msg.write(self.i2c_address, buf))
i2c_bus.i2c_rdwr(read_data)
In Raspberry Pi’s Python code instead of:
i2c_bus.i2c_rdwr(i2c_msg.write(self.i2c_address, buf), read_data)
which I really meant to do and hence change behaviour of i2c from start/write/continue/read/stop to start/write/stop - startread/stop. The moment I did the same in C code all settled down to the right place.