Presto - connect to i2c slave pico

Wanting to hear some sound from my Presto, sadly the onboard buzzer is inaudible for my hearing, I got one of those Adafruit plug and play amp/speaker boards. Now I can give the hearing aid a nice workout :-)

Well that works very nicely, but there goes the Presto i2c port and I wanted to connect more stuff. After initially considering a GPIO extender I thought I would have a go at using the 12c_target that micropython came out with in their latest release and create an i2c slave on a pico board to link to the Presto.

Well that was quite easy, I have a test pico set up as an i2c slave to which I attach that speaker to, but also other sensors like and i2c based sht40 temperature sensor. Marvellous, and looking at my test code I’m left wondering why it took me all day to get it working, but it did. The lack of examples of setting up a pico as an i2c slave is my excuse.

Anyway flush with my now enhanced Presto, albeit now sporting a tangled array of i2c connector leads, a pico, a dangling speaker board and a temperature sensor, I shall now have to design some small pcb and 3d printed box to connect it all and discreetly hide it behind the Presto screen.

In case it helps anyone I give my small test snippets where a rpi pico is used as an i2c slave device, and a test snippet to run on a i2c master pico (or presto etc)

The Slave:

import asyncio
from machine import I2CTarget, I2C, Pin, PWM
import sht4x

alarm_event = asyncio.Event()

# mem bytearray: bytes 0-7 for being writen to, bytes 8 and 9 for reading temperature,
# bytes 10 and 11 for humidity
mem = bytearray(12)
mv = memoryview(mem)

# ----------------------------
# Create the i2c slave 
i2c_slave = I2CTarget(0, scl=Pin(1), sda=Pin(0), addr=67, mem=mem)


# -----------------------------
# Create i2c sht40 temp/hum sensor
i2c_sht40 = I2C(1, scl=Pin(3), sda=Pin(2))
#sht4x_address = 0x44 #defult address in driver
sht40_sensor = sht4x.SHT4X(i2c_sht40)


async def sound_alarm():
    SPEAKER_PIN = 7
    # create a Pulse Width Modulation Object on this pin
    speaker = PWM(Pin(SPEAKER_PIN)) 
    while True:
        speaker.duty_u16(0)
        await alarm_event.wait()
        speaker.duty_u16(30_000)
        while alarm_event.is_set():
            speaker.freq(810)
            await asyncio.sleep(0.5)
            speaker.freq(1000)
            await asyncio.sleep(0.5)


# check mem bytearray for i2c writes
async def check_mem():
    while True:
        if mem[0]:
            print('doing something because mem[0] was set')
            mem[0] = 0
        elif mem[1]:
            print('doing something because mem[1] was set')
            mem[1] = 0
        elif mem[2]: #trigger alarm sound
            asyncio.create_task(alarm_trigger())
            mem[2] = 0
        #etc
        await asyncio.sleep(0)


async def alarm_trigger():
    # sound alarm for 3 seconds
    alarm_event.set()
    await asyncio.sleep(3)
    alarm_event.clear()
    

async def update_temp_sensor():
    # read i2c sht40 temp/hum sensor every 30 seconds
    while True:
        temp, hum = sht40_sensor.measurements
        temp = int(round(temp,1)*10)
        hum = int(round(hum,1)*10)
        
        mv[8:10] = temp.to_bytes(2,'little')
        mv[10:12] = hum.to_bytes(2,'little')
        
        await asyncio.sleep(30)

        
async def main():
    asyncio.create_task(check_mem())
    asyncio.create_task(update_temp_sensor())
    asyncio.create_task(sound_alarm())
    while True:
        await asyncio.sleep(0)

try:
    print('Program i2c_target_test has started')
    asyncio.run(main())
except KeyboardInterrupt:
    print('Ctl C')
finally:
    print('Program i2c_target_test has ended')
    asyncio.new_event_loop()

The Master

from machine import Pin, I2C

i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400_000)

# write byte to i2c slave mem address 0
i2c.writeto_mem(67, 0, b'1')

# wite a non zero byte to slave address 2
# which will trigger alarm sound on speaker
i2c.writeto_mem(67, 2, b'1')

# read from i2c slave addresses containing temperature and humidity
# values from the sht40 sensor
temp_bytes = i2c.readfrom_mem(67,8,2)
hum_bytes = i2c.readfrom_mem(67,10,2)

temp = float((int.from_bytes(temp_bytes, 'little'))/10)
hum = float((int.from_bytes(hum_bytes, 'little'))/10)

print('Temperature / Humidity:', temp, hum)

I do admire the ingenuity, nicely done. And I may have missed something, but would something like this have worked for you?
SparkFun Qwiic MultiPort

I do believe Adafruit has their version as well.

Adafruit Qwiic / Stemma QT 5 Port Hub

If everything was running via i2c then an i2c hub would work. But the speaker needs a single pin that is modulated via PWM. I could use the Presto i2c connection to make a suitable connection to the speaker to send PWM pulses, but then I could not other i2c connections at the same time.
Using a Pico, or some other board as an i2c slave enables all the things that can be connected to a pico to be used by the master via the master/slave i2c link like i2c, analogue, uart, etc connected devices.
It overcomes the lack of ports available on the Presto though there are other options like a GPIO extender which would also work nicely. But I seem to have too many idle old gen 1 pico’s sitting in my drawers and I wanted to have go at seeing how the micropython i2c slave works. And as you can see its really easy to use so I though it may be of use to some Presto users.

1 Like

I should also remind anyone thinking of using an i2c master/slave arrangement on a Presto with a pico that a duplex connection via uart twixt the two is also a worthy consideration. I’ve been hacking around with that connection for the last couple of hours. Many other possibilities of course, and wifi comms twixt the boards would suffice in a lot of cases. But thats enough from me on the Presto I think. :)

Exactly what I was thinking when I read your post (but I really value that you shared the I2C-target code, since questions about that pop up once in a while).

You might want to have a look at GitHub - antonvh/SerialTalk: Simple symmetric MicroPython RPC protocol for serial lines like UART, Sockets, Bluetooth,.... This will give you RPC capabilities and you could even get rid of your cables and use WLAN. I don’t think this library makes full use of Python capabilities though, but it is there and easy to use.

@SirFico Thanks for the detailed reply. I had a feeling I had missed something. These days, some times I’m on the ball, and some times I’m the 8 ball, lol.