The Arduino-Pico framework [[url]https://github.com/earlephilhower/arduino-pico[/url]] now provides a simple and easy Arduino based approach to programming the Raspberry Pi Pico Arduino core [and all RP2040 based boards] . . . in response to the original posters question, the code below demonstrates how the SDA & SCA pins can be specified in the Arduino-Pico (earlephilhower) "Raspberry Pi RP2040 Boards"framework/library using Wire.setSDA & Wire.setSCL for I2C0 pins and Wire1.setSDA & Wire1.setSCL for I2C1 pins.
I have used Arduino-Pico to create a simple, but highly responsive, interrupt triggered i2c Slave/Responder [in about 30 lines of code], as show below, to illustrate how easy this is [based on smithg400's idea for a 256 byte i2c RAM - [url]
Pico as I2C slave (solved) - Raspberry Pi Forums]
#include <Wire.h>
const byte PICO_I2C_ADDRESS = 0x55;
const byte PICO_I2C_SDA = 20;
const byte PICO_I2C_SCL = 21;
const byte PICO_LED = 25;
byte volatile rx_flag = 0;
byte volatile tx_flag = 0;
uint8_t volatile ram_addr = 0;
uint8_t volatile ram[256];
void setup() {
pinMode (PICO_LED, OUTPUT);
Wire.setSDA(PICO_I2C_SDA);
Wire.setSCL(PICO_I2C_SCL);
Wire.begin(PICO_I2C_ADDRESS); // join i2c bus as slave
Wire.onReceive(i2c_receive); // i2c interrupt receive
Wire.onRequest(i2c_transmit); // i2c interrupt send
}
void loop() {
if (rx_flag) {
rx_flag = 0;
digitalWrite (PICO_LED, HIGH);
delay(1);
digitalWrite (PICO_LED, LOW);
}
if (tx_flag) {
tx_flag = 0;
digitalWrite (PICO_LED, HIGH);
delay(1);
digitalWrite (PICO_LED, LOW);
}
delay(1);
}
void i2c_receive(int bytes_count) { // bytes_count gives number of bytes in rx buffer
if (bytes_count == 0) {
return;
}
ram_addr = (uint8_t)Wire.read(); // first byte is ram offset address (0-255)
for (byte i=1; i<bytes_count; i++) {
ram[ram_addr] = (uint8_t)Wire.read();
ram_addr++;
}
rx_flag = bytes_count;
}
void i2c_transmit() {
tx_flag = 1;
Wire.write((uint32_t)ram[ram_addr]);
ram_addr++;
}
This code turns the Raspberry Pico into an i2c Slave/Responder that emulates a 256 byte RAM memory. The first byte written to the Pico over i2c sets an address/offset in the 256 byte RAM and subsequent bytes written are then stored into the RAM, with the address auto-incremented after each write. Similarly, each request for a byte from the Pico over i2c retrieves a byte from memory and sends it back to the Master/Controller over i2c [again with the address auto-incremented after each read] . Setting the address/offset before a request for a byte from the Pico determines which address/offset the next read will use [NOTE: the address/offset automatically wraps back to zero when it exceeds 255 as a result of an auto-increment]
In testing the code I identified a problem specific to multi controller i2c systems that others may have experienced. This is where one Master/Controller uses the i2c restart functionality, rather than issuing a stop and then start, between the write of a register address and the read of it's content to ensure that another Master/Controller does not gain control of the i2c bus . . . this issue is detailed here [[url]https://github.com/earlephilhower/arduino-pico/issues/585[/url]] and has already been fixed in the master branch of Arduino-Pico framework, and is included in the release [as of version 2.0.2] which ensures this functions as expected in a multi-Master/Controller system.
The code, referenced above, needs to be compiled and uploaded to a Raspberry Pi Pico with GPIO 20 & 21 (SDA & SCL) connected to a suitable controller device [e.g. a Raspberry Pi] . . . NOTE: pull-up resistors to +3.3v will be needed on SDA & SCL, if using a Raspberry Pi & Raspberry Pico for this.
i2cTools under the Pi-OS can then be used to interact with the i2c Pico Slave/Responder . . . examples of this are shown below
i2cdetect -y 1 \\ i2c device found at 0x55
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- 55 -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
i2cset -y 1 0x55 0x00 \\ set RAM address/offset to 0x00
i2cdump -y 1 0x55 \\ dump 256 bytes from Pico
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Load 10 bytes ("0123456789") into Pico starting at address 00
i2ctransfer -y 1 w11@0x55 0x00 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39
i2cset -y 1 0x55 0x00 \\ set RAM address/offset to 0x00
i2cdump -y 1 0x55 \\ dump 256 bytes from Pico
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 30 31 32 33 34 35 36 37 38 39 00 00 00 00 00 00 0123456789......
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
i2cset -y 1 0x55 0x00 \\ set RAM address/offset to 0x00
i2ctransfer -y 1 r10@0x55 \\ read 10 bytes from Pico
0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39
i2ctransfer -y 1 w1@0x55 0x00 r10@0x55 \\ set RAM address/offset to 0x00, i2c restart and read 10 bytes
0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39
Below is a more complex i2cTools command that combines the write of 10 bytes [auto incrementing from 0x00 to 0x09] to address 0x00, then resets the address/offset to 0x00 and reads back 10 bytes.
i2ctransfer -y 1 w11@0x55 0x00 0x00+ w1@0x55 0x00 r10@0x55
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09
In terms of i2c protocol it actually generates;
- an i2c ‘start’
- an i2c Slave/Responder device address of 0x55
- a write of 11 bytes of data [byte 1 is the RAM address/offset followed by 10 bytes of actual data to be loaded into the Pico]
- an i2c ‘restart’
- an i2c Slave/Responder device address of 0x55
- 1 byte of data [the RAM address/offset]
- an i2c ‘restart
- an i2c Slave/Responder device address of 0x55
- a read request for 10 bytes of data
- an i2c ‘stop’
NOTE; each i2c ‘restart’ is actioned by the Slave/Responder device as though it were a ‘stop’ followed by a ‘start’ [i.e. a combined ‘end of message’ followed by a ‘start of message’]
Alternatively, the Raspberry Pico as an i2c Slave/Responder can also be demonstrated using the python program shown below, which is designed to run using CircuitPython [i.e. Adafruit Blinka - Overview | CircuitPython on Linux and Raspberry Pi | Adafruit Learning System] on a Raspberry Pi.
#!/usr/bin/env python
import time
import board
import busio
I2C_ADDRESS = 0x55
i2cbyte = bytearray(1)
buffer = bytearray(64)
print("Hello CircuitPython blinka!")
# Try to create an I2C device
i2c = busio.I2C(board.SCL, board.SDA)
print("... I2C ok!")
while not i2c.try_lock():
pass
# i2c functions
# -------------
# writeto(I2C_ADDRESS, buffer, *, start=0, end=len(buffer), stop=True)
# readfrom_into(I2C_ADDRESS, buffer, *, start=0, end=len(buffer))
# writeto_then_readfrom(address, out_buffer, in_buffer, *, out_start=0, out_end=None, in_start=0, in_end=None)
try:
# write multiple bytes [first byte is initial address/register to write]
i2c.writeto(I2C_ADDRESS, bytes([0x00,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39]) )
# read a single byte from a specific address/register
i2c.writeto(I2C_ADDRESS, bytes([0x01]) ) # address/register 0x01
i2c.readfrom_into(I2C_ADDRESS, i2cbyte)
print(i2cbyte)
i2c.writeto(I2C_ADDRESS, bytes([0x03]) ) # address/register 0x03
i2c.readfrom_into(I2C_ADDRESS, buffer, start=0, end=1)
print(buffer[0:1])
# read multiple bytes [6] from specified address/register
i2c.writeto(I2C_ADDRESS, bytes([0x00]) ) # address/register 0x00
i2c.readfrom_into(I2C_ADDRESS, buffer, start=0, end=6)
print(buffer[0:6])
# the code below combines writing an address with reading 6 byes using 'restart' rather than
# 'stop' & 'start' betwwen the write and the read [as executed in the examples above]
i2c.writeto_then_readfrom(I2C_ADDRESS, bytes([0x00]), buffer, in_start=0, in_end=6)
print(buffer[0:6])
finally: # unlock the i2c bus when ctrl-c'ing out
i2c.unlock()