Skip to content

i2c: support writes with a list of buffers (v2) #4763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
9 changes: 3 additions & 6 deletions drivers/display/ssd1306.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b'\x40', None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)

def write_cmd(self, cmd):
Expand All @@ -104,12 +105,8 @@ def write_cmd(self, cmd):
self.i2c.writeto(self.addr, self.temp)

def write_data(self, buf):
self.temp[0] = self.addr << 1
self.temp[1] = 0x40 # Co=0, D/C#=1
self.i2c.start()
self.i2c.write(self.temp)
self.i2c.write(buf)
self.i2c.stop()
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)


class SSD1306_SPI(SSD1306):
Expand Down
233 changes: 148 additions & 85 deletions extmod/machine_i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ STATIC int mp_hal_i2c_read_byte(machine_i2c_obj_t *self, uint8_t *val, int nack)
}

// return value:
// >=0 - number of acks received
// >=0 - success; for read it's 0, for write it's number of acks received
// <0 - error, with errno being the negative of the return value
int mp_machine_soft_i2c_writeto(mp_obj_base_t *self_in, uint16_t addr, const uint8_t *src, size_t len, bool stop) {
int mp_machine_soft_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) {
machine_i2c_obj_t *self = (machine_i2c_obj_t*)self_in;

// start the I2C transaction
Expand All @@ -192,7 +192,7 @@ int mp_machine_soft_i2c_writeto(mp_obj_base_t *self_in, uint16_t addr, const uin
}

// write the slave address
ret = mp_hal_i2c_write_byte(self, addr << 1);
ret = mp_hal_i2c_write_byte(self, (addr << 1) | (flags & MP_MACHINE_I2C_FLAG_READ));
if (ret < 0) {
return ret;
} else if (ret != 0) {
Expand All @@ -201,69 +201,102 @@ int mp_machine_soft_i2c_writeto(mp_obj_base_t *self_in, uint16_t addr, const uin
return -MP_ENODEV;
}

// write the buffer to the I2C memory
int num_acks = 0;
while (len--) {
ret = mp_hal_i2c_write_byte(self, *src++);
if (ret < 0) {
return ret;
} else if (ret != 0) {
// nack received, stop sending
break;
int transfer_ret = 0;
for (; n--; ++bufs) {
size_t len = bufs->len;
uint8_t *buf = bufs->buf;
if (flags & MP_MACHINE_I2C_FLAG_READ) {
// read bytes from the slave into the given buffer(s)
while (len--) {
ret = mp_hal_i2c_read_byte(self, buf++, (n | len) == 0);
if (ret != 0) {
return ret;
}
}
} else {
// write bytes from the given buffer(s) to the slave
while (len--) {
ret = mp_hal_i2c_write_byte(self, *buf++);
if (ret < 0) {
return ret;
} else if (ret != 0) {
// nack received, stop sending
n = 0;
break;
}
++transfer_ret; // count the number of acks
}
}
++num_acks;
}

// finish the I2C transaction
if (stop) {
if (flags & MP_MACHINE_I2C_FLAG_STOP) {
ret = mp_hal_i2c_stop(self);
if (ret != 0) {
return ret;
}
}

return num_acks;
return transfer_ret;
}

// return value:
// 0 - success
// <0 - error, with errno being the negative of the return value
int mp_machine_soft_i2c_readfrom(mp_obj_base_t *self_in, uint16_t addr, uint8_t *dest, size_t len, bool stop) {
machine_i2c_obj_t *self = (machine_i2c_obj_t*)self_in;

// start the I2C transaction
int ret = mp_hal_i2c_start(self);
if (ret != 0) {
return ret;
}

// write the slave address
ret = mp_hal_i2c_write_byte(self, (addr << 1) | 1);
if (ret < 0) {
return ret;
} else if (ret != 0) {
// nack received, release the bus cleanly
mp_hal_i2c_stop(self);
return -MP_ENODEV;
}

// read the bytes from the slave
while (len--) {
ret = mp_hal_i2c_read_byte(self, dest++, len == 0);
if (ret != 0) {
return ret;
/******************************************************************************/
// Generic helper functions

// For use by ports that require a single buffer of data for a read/write transfer
int mp_machine_i2c_transfer_adaptor(mp_obj_base_t *self, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) {
size_t len;
uint8_t *buf;
if (n == 1) {
// Use given single buffer
len = bufs[0].len;
buf = bufs[0].buf;
} else {
// Combine buffers into a single one
len = 0;
for (size_t i = 0; i < n; ++i) {
len += bufs[i].len;
}
buf = m_new(uint8_t, len);
if (!(flags & MP_MACHINE_I2C_FLAG_READ)) {
len = 0;
for (size_t i = 0; i < n; ++i) {
memcpy(buf + len, bufs[i].buf, bufs[i].len);
len += bufs[i].len;
}
}
}

// finish the I2C transaction
if (stop) {
ret = mp_hal_i2c_stop(self);
if (ret != 0) {
return ret;
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
int ret = i2c_p->transfer_single(self, addr, len, buf, flags);

if (n > 1) {
if (flags & MP_MACHINE_I2C_FLAG_READ) {
// Copy data from single buffer to individual ones
len = 0;
for (size_t i = 0; i < n; ++i) {
memcpy(bufs[i].buf, buf + len, bufs[i].len);
len += bufs[i].len;
}
}
m_del(uint8_t, buf, len);
}

return 0; // success
return ret;
}

STATIC int mp_machine_i2c_readfrom(mp_obj_base_t *self, uint16_t addr, uint8_t *dest, size_t len, bool stop) {
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
mp_machine_i2c_buf_t buf = {.len = len, .buf = dest};
unsigned int flags = MP_MACHINE_I2C_FLAG_READ | (stop ? MP_MACHINE_I2C_FLAG_STOP : 0);
return i2c_p->transfer(self, addr, 1, &buf, flags);
}

STATIC int mp_machine_i2c_writeto(mp_obj_base_t *self, uint16_t addr, const uint8_t *src, size_t len, bool stop) {
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
mp_machine_i2c_buf_t buf = {.len = len, .buf = (uint8_t*)src};
unsigned int flags = stop ? MP_MACHINE_I2C_FLAG_STOP : 0;
return i2c_p->transfer(self, addr, 1, &buf, flags);
}

/******************************************************************************/
Expand Down Expand Up @@ -318,11 +351,10 @@ MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_init_obj, 1, machine_i2c_obj_init);

STATIC mp_obj_t machine_i2c_scan(mp_obj_t self_in) {
mp_obj_base_t *self = MP_OBJ_TO_PTR(self_in);
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
mp_obj_t list = mp_obj_new_list(0, NULL);
// 7-bit addresses 0b0000xxx and 0b1111xxx are reserved
for (int addr = 0x08; addr < 0x78; ++addr) {
int ret = i2c_p->writeto(self, addr, NULL, 0, true);
int ret = mp_machine_i2c_writeto(self, addr, NULL, 0, true);
if (ret == 0) {
mp_obj_list_append(list, MP_OBJ_NEW_SMALL_INT(addr));
}
Expand Down Expand Up @@ -407,12 +439,11 @@ MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_write_obj, machine_i2c_write);

STATIC mp_obj_t machine_i2c_readfrom(size_t n_args, const mp_obj_t *args) {
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(args[0]);
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
mp_int_t addr = mp_obj_get_int(args[1]);
vstr_t vstr;
vstr_init_len(&vstr, mp_obj_get_int(args[2]));
bool stop = (n_args == 3) ? true : mp_obj_is_true(args[3]);
int ret = i2c_p->readfrom(self, addr, (uint8_t*)vstr.buf, vstr.len, stop);
int ret = mp_machine_i2c_readfrom(self, addr, (uint8_t*)vstr.buf, vstr.len, stop);
if (ret < 0) {
mp_raise_OSError(-ret);
}
Expand All @@ -422,12 +453,11 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_i2c_readfrom_obj, 3, 4, machine_i2c_

STATIC mp_obj_t machine_i2c_readfrom_into(size_t n_args, const mp_obj_t *args) {
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(args[0]);
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
mp_int_t addr = mp_obj_get_int(args[1]);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE);
bool stop = (n_args == 3) ? true : mp_obj_is_true(args[3]);
int ret = i2c_p->readfrom(self, addr, bufinfo.buf, bufinfo.len, stop);
int ret = mp_machine_i2c_readfrom(self, addr, bufinfo.buf, bufinfo.len, stop);
if (ret < 0) {
mp_raise_OSError(-ret);
}
Expand All @@ -437,12 +467,11 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_i2c_readfrom_into_obj, 3, 4, machine

STATIC mp_obj_t machine_i2c_writeto(size_t n_args, const mp_obj_t *args) {
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(args[0]);
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
mp_int_t addr = mp_obj_get_int(args[1]);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
bool stop = (n_args == 3) ? true : mp_obj_is_true(args[3]);
int ret = i2c_p->writeto(self, addr, bufinfo.buf, bufinfo.len, stop);
int ret = mp_machine_i2c_writeto(self, addr, bufinfo.buf, bufinfo.len, stop);
if (ret < 0) {
mp_raise_OSError(-ret);
}
Expand All @@ -451,53 +480,87 @@ STATIC mp_obj_t machine_i2c_writeto(size_t n_args, const mp_obj_t *args) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_i2c_writeto_obj, 3, 4, machine_i2c_writeto);

STATIC mp_obj_t machine_i2c_writevto(size_t n_args, const mp_obj_t *args) {
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(args[0]);
mp_int_t addr = mp_obj_get_int(args[1]);

// Get the list of data buffer(s) to write
size_t nitems;
const mp_obj_t *items;
mp_obj_get_array(args[2], &nitems, (mp_obj_t**)&items);

// Get the stop argument
bool stop = (n_args == 3) ? true : mp_obj_is_true(args[3]);

// Extract all buffer data, skipping zero-length buffers
size_t alloc = nitems == 0 ? 1 : nitems;
size_t nbufs = 0;
mp_machine_i2c_buf_t *bufs = mp_local_alloc(alloc * sizeof(mp_machine_i2c_buf_t));
for (; nitems--; ++items) {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(*items, &bufinfo, MP_BUFFER_READ);
if (bufinfo.len > 0) {
bufs[nbufs].len = bufinfo.len;
bufs[nbufs++].buf = bufinfo.buf;
}
}

// Make sure there is at least one buffer, empty if needed
if (nbufs == 0) {
bufs[0].len = 0;
bufs[0].buf = NULL;
nbufs = 1;
}

// Do the I2C transfer
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
int ret = i2c_p->transfer(self, addr, nbufs, bufs, stop ? MP_MACHINE_I2C_FLAG_STOP : 0);
mp_local_free(bufs);

if (ret < 0) {
mp_raise_OSError(-ret);
}

// Return number of acks received
return MP_OBJ_NEW_SMALL_INT(ret);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_i2c_writevto_obj, 3, 4, machine_i2c_writevto);

STATIC int read_mem(mp_obj_t self_in, uint16_t addr, uint32_t memaddr, uint8_t addrsize, uint8_t *buf, size_t len) {
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(self_in);
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
uint8_t memaddr_buf[4];
size_t memaddr_len = 0;
for (int16_t i = addrsize - 8; i >= 0; i -= 8) {
memaddr_buf[memaddr_len++] = memaddr >> i;
}
int ret = i2c_p->writeto(self, addr, memaddr_buf, memaddr_len, false);
int ret = mp_machine_i2c_writeto(self, addr, memaddr_buf, memaddr_len, false);
if (ret != memaddr_len) {
// must generate STOP
i2c_p->writeto(self, addr, NULL, 0, true);
mp_machine_i2c_writeto(self, addr, NULL, 0, true);
return ret;
}
return i2c_p->readfrom(self, addr, buf, len, true);
return mp_machine_i2c_readfrom(self, addr, buf, len, true);
}

#define MAX_MEMADDR_SIZE (4)
#define BUF_STACK_SIZE (12)

STATIC int write_mem(mp_obj_t self_in, uint16_t addr, uint32_t memaddr, uint8_t addrsize, const uint8_t *buf, size_t len) {
mp_obj_base_t *self = (mp_obj_base_t*)MP_OBJ_TO_PTR(self_in);
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;

// need some memory to create the buffer to send; try to use stack if possible
uint8_t buf2_stack[MAX_MEMADDR_SIZE + BUF_STACK_SIZE];
uint8_t *buf2;
size_t buf2_alloc = 0;
if (len <= BUF_STACK_SIZE) {
buf2 = buf2_stack;
} else {
buf2_alloc = MAX_MEMADDR_SIZE + len;
buf2 = m_new(uint8_t, buf2_alloc);
}

// create the buffer to send
// Create buffer with memory address
size_t memaddr_len = 0;
uint8_t memaddr_buf[4];
for (int16_t i = addrsize - 8; i >= 0; i -= 8) {
buf2[memaddr_len++] = memaddr >> i;
memaddr_buf[memaddr_len++] = memaddr >> i;
}
memcpy(buf2 + memaddr_len, buf, len);

int ret = i2c_p->writeto(self, addr, buf2, memaddr_len + len, true);
if (buf2_alloc != 0) {
m_del(uint8_t, buf2, buf2_alloc);
}
return ret;
// Create partial write buffers
mp_machine_i2c_buf_t bufs[2] = {
{.len = memaddr_len, .buf = memaddr_buf},
{.len = len, .buf = (uint8_t*)buf},
};

// Do I2C transfer
mp_machine_i2c_p_t *i2c_p = (mp_machine_i2c_p_t*)self->type->protocol;
return i2c_p->transfer(self, addr, 2, bufs, MP_MACHINE_I2C_FLAG_STOP);
}

STATIC const mp_arg_t machine_i2c_mem_allowed_args[] = {
Expand Down Expand Up @@ -584,6 +647,7 @@ STATIC const mp_rom_map_elem_t machine_i2c_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_readfrom), MP_ROM_PTR(&machine_i2c_readfrom_obj) },
{ MP_ROM_QSTR(MP_QSTR_readfrom_into), MP_ROM_PTR(&machine_i2c_readfrom_into_obj) },
{ MP_ROM_QSTR(MP_QSTR_writeto), MP_ROM_PTR(&machine_i2c_writeto_obj) },
{ MP_ROM_QSTR(MP_QSTR_writevto), MP_ROM_PTR(&machine_i2c_writevto_obj) },

// memory operations
{ MP_ROM_QSTR(MP_QSTR_readfrom_mem), MP_ROM_PTR(&machine_i2c_readfrom_mem_obj) },
Expand Down Expand Up @@ -625,8 +689,7 @@ STATIC const mp_machine_i2c_p_t mp_machine_soft_i2c_p = {
.stop = (int(*)(mp_obj_base_t*))mp_hal_i2c_stop,
.read = mp_machine_soft_i2c_read,
.write = mp_machine_soft_i2c_write,
.readfrom = mp_machine_soft_i2c_readfrom,
.writeto = mp_machine_soft_i2c_writeto,
.transfer = mp_machine_soft_i2c_transfer,
};

const mp_obj_type_t machine_i2c_type = {
Expand Down
Loading