I2C - Raspberry Pi, Pico and an Arduino

แชร์
ฝัง
  • เผยแพร่เมื่อ 15 มิ.ย. 2024
  • In this video I show how you can communicate between various devices using I2C serial communications. The example shows a Raspberry Pi as the controller and a Raspberry Pi Pico and an Arduino as peripheral devices.
    I2C is sometimes referred to as a Master, Slave relationship. This is now considered by many to be inappropriate as it is trivialising the suffering of slaves both historically and through modern day slavery affecting vulnerable people around the world. In addition the terminology isn't always correct.
    I have therefore used the terms controller to indicate the computer that is controlling the I2C bus, and peripheral device to indicate the device that the controllers communicates with.
    The main difference between SPI and I2C is that I2C needs only two wires (plus ground) whereas SPI needs 4 and an additional enable port on the controller for any additional devices. Some other differences are covered in more detail in the video above.
    Pull-up resistors and voltage differences
    I2C is implemented using open drain connections. This means that pull-up resistors are needed for both the SDA (data) and SCL (clock) lines. These are typically between about 2kΩ and 5kΩ.
    If using different voltages, then it is important not to allow a 5V signal (eg. Arduino) to go to a device for 3.3V (eg. Raspberry Pi or Pico). This can be achieved by connecting only to the 3.3V supply, but I recommend instead using a 3.3V to 5V MOSFET logic level shift circuit for voltage protection.
    Each peripheral must have a unique address. These are sometimes created in software and sometimes in hardware:
    Software (used here allows for flexibility)
    DIP switches / jumpers (limited address range, but good flexibility
    Solder pads / tracks (difficult to change more than once
    Fixed address (no flexibility - not recommended).
    I2C from the Raspberry Pi to the Pico
    The Raspberry Pi computer and Raspberry Pi Pico microcontroller both work at 3.3V. This makes connecting them together easy using:
    SDA - Raspberry Pi GPIO 2 to Pico GP2
    SCL - Raspberry Pi GPIO 3 to Pico GP3
    Gnd - Raspberry Pi Gnd to Pico Gnd
    I2C on the Raspberry Pi and the Arduino
    Warning! The Raspberry Pi and Arduino work may work at different voltages.
    The I2C ports on the Raspberry Pi are 3.3V only. They can be damaged by if a peripheral device pull-up resistor raises the bus to 5V. If connecting to a 5V device then a level shifter is recommended. The following can be used from a Raspberry Pi to an Arduino:
    SDA - Raspberry Pi GPIO 2 to Arduino A4
    SCL - Raspberry Pi GPIO 3 to Arduino A5
    Gnd - Raspberry Pi Gnd to Pico Gnd
    Software implementation of I2C
    I had some difficulties with getting I2C working due to differences in the implementations of I2C. In particular the Python that I used on the Raspberry Pi is for SMBus (System Management Bus) which is based on I2C but not the same. I needed to use different methods to retrieve data from the Pico compared to the Arduino. In both cases I only transferred data to the peripherals 2 bytes at a time, and retrieve data 2 bytes from the Pico and 1 byte from the Arduino at a time.
    For more details including the source code for the examples see:
    www.penguintutor.com/electroni...
    For more details of the logic-level shift circuit see:
    www.penguintutor.com/electroni...
    More details about SPI at:
    www.penguintutor.com/electroni...
    Chapters:
    00:00 Introduction to I2C for Pico Arduino
    01:43 What is I2C?
    02:19 Comparison with UART and SPI
    05:12 Which protocol is best?
    07:27 ATmega328p
    08:39 I2C connecting to multiple peripherals
    10:48 Pull-up resistors
    12:54 I2C peripheral addresses
    14:51 I2C to the Raspberry Pi Pico
    17:21 Raspberry Pi Pico C code
    24:22 Raspberry Pi Python code (to Pico)
    29:35 Testing the Pico
    32:28 I2c to the Arduino
    34:01 Arduino C code
    40:35 Raspberry Pi Python code (to Arduino)
    41:41 Testing the Arduino
    42:30 Raspberry Pi, Pico and Arduino together
    44:59 Summary
  • วิทยาศาสตร์และเทคโนโลยี

ความคิดเห็น • 19

  • @Steven-jf4cs
    @Steven-jf4cs ปีที่แล้ว

    Thanks for a well thought and presented tutorial.

  • @thrall1342
    @thrall1342 ปีที่แล้ว

    Thanks for the video !
    It's strange how hard this topic is to find online. Do you know whether this has had any updates in the meantime or where one would find these things ?

  • @mrezahoseiny9861
    @mrezahoseiny9861 3 ปีที่แล้ว

    Hi Many thanks for very useful tutorial. Just have a question, if we want to setup a cluster of 3 or more (up to 9) Pico devices, then can we use SPI protocol/pins to enable all picos communicate with each other? In such case, do you think does it matter which one should take the master role? so, what if one of the slave wants to send some data to another slave? What if we replace all Picos with Pi zero (w)?

    • @PenguinTutor
      @PenguinTutor  3 ปีที่แล้ว

      SPI is based on controller to peripheral (master / slave) relationship. Peripheral devices would need to communicate via the controller. The controller can probe each device and if one of them wants to transfer data to another then it would send data to the controller, which would then be resent to the other peripheral.
      In I2C direct transfer would be possible, by having multiple controllers. One device would need to become a controller and then communicate with another device. This is done by looking for no traffic on the line, declaring that it wants to be a controller and then taking over the control of the bus. When that device goes quiet another controller can come along. I didn't cover that on the video as it adds complexity.
      Unless you have a lot of data to send then I would say that you are better having one device identified as the controller which polls all the devices for any data. You would then have the devices state where they want to send the data to and the controller would resend the data on.
      The Raspberry Pi would be the same, but you do have the advantage of additional technologies, including Bluetooth (some peer to peer support) or wifi (able to communicate with any other device directly).

  • @ivanrojas1475
    @ivanrojas1475 2 ปีที่แล้ว +1

    Hi there, thanks for the tutorial, which version of Python are u using in that video and which command u used to install the SMBus library. "Pip3 SMBus or pip SMBus"?

    • @PenguinTutor
      @PenguinTutor  2 ปีที่แล้ว

      I use Python 3 for all my coding. I believe the current version on the Raspberry Pi is Python 3.9.2.
      I used the dpkg version from the Repository. I think SMBus was installed by default on Raspberry Pi OS, if not then you can install using:
      sudo apt install python3-smbus

  • @valloway
    @valloway ปีที่แล้ว

    Thanks, this helped!

  • @IanSMoyes
    @IanSMoyes 3 ปีที่แล้ว

    2:47. I'm sorry to pick fault but UART is NOT designed for connecting one pair of devices. With my pathetic Python coding skills I have programmed a Raspberry Pi 4 to communicate with 18 Serial Bus Servos and a Raspberry Pi Pico, as a lighting controller. All you have to do is set each "channel" up with a seperate frame header and all of the devices ignore any communication on the bus that doesn't start with their frame header. Many industrial applications stretch this format to connect MANY devices to the same UART bus.

    • @PenguinTutor
      @PenguinTutor  3 ปีที่แล้ว +1

      UART has a very long history, but was designed for point-to-point communications between two devices.
      It is possible using multiplexing or through agreed communication protocol to have multiple devices on a single connection, but that is not what the UART was originally designed for. In most cases UART is intended for point-to-point communications between one pair of devices.
      If looking to connect to multiple devices using a Raspberry Pi then I would recommend I2C or SPI rather than trying to connect multiple devices to a single UART.

  • @IanSMoyes
    @IanSMoyes 3 ปีที่แล้ว

    Sorry, I'm back again. The Raspberry Pi 4 has 6 UART buses All addressable from the GPIO, not 1.

    • @PenguinTutor
      @PenguinTutor  3 ปีที่แล้ว

      There is only one UART that is enabled by default. The second is used for Bluetooth and the others have to be configured using device tree overlays. The additional UARTs are also multiplexed with the I2C serial connections.

  • @j.p.denoyer7377
    @j.p.denoyer7377 3 ปีที่แล้ว

    No link for pico code. Too blurry to pause the video and type the code.

    • @PenguinTutor
      @PenguinTutor  3 ปีที่แล้ว

      Sorry about that. I'd put a link to the SPI page and not the I2C one.
      I've now updated the description.
      You can download the source code from: www.penguintutor.com/electronics/i2c

  • @GrantMeStrength
    @GrantMeStrength 3 ปีที่แล้ว +1

    This is a very helpful video - it's hard to find any information on setting up peripheral mode at all! I am trying to set up my Pico in peripheral mode, using the Pi 4 to control it. So far I can see the Pico in i2cdetect, and send date from the Pi to the Pico. However, I cannot get the Pico to return any date - it always leads to time-out or other communication errors (e.g. Read error:[Errno 110] Connection timed out) . I can't see how my code differs from your code. Can I ask what speed you have set the Pi i2c bus to operate at? I have also tried 10K physical pull-up resistors when connecting the Pi and Pico, as well as gpio_pull_up Thanks!
    /* Pico code */
    #include
    #include "pico/stdlib.h"
    #include "hardware/i2c.h"
    #define I2C_ADDR 0x3e // The address of this Pico on the i2c bus
    #define IC2_1 8
    #define IC2_2 9
    int main() {
    // i2c setup
    i2c_init(i2c0, 10000);
    i2c_set_slave_mode(i2c0, true, I2C_ADDR);
    gpio_set_function(IC2_1, GPIO_FUNC_I2C);
    gpio_set_function(IC2_2, GPIO_FUNC_I2C);
    //gpio_pull_up(IC2_1);
    //gpio_pull_up(IC2_2);
    // Data for i2c
    uint8_t rxdata[4];
    uint8_t txdata[2];
    // Counter just to have something to return
    uint8_t counter = 0;
    // Set up LED for status
    const uint LED_PIN = PICO_DEFAULT_LED_PIN;
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);
    uint8_t blink = 0;
    // Do some power-up blinking
    for (int i=0; i1) blink = 0;
    sleep_ms(50);
    }
    // Main loop
    while (true) {
    // Receive data from controller
    // 3 bytes received - byte 0 is cmd (used as lower byte) byte 2 is higher - byte 3 is 0
    // Wait here until some data arrives
    if (i2c_get_read_available(i2c0) < 3) continue;
    i2c_read_raw_blocking (i2c0, rxdata, 3);
    int input_value = rxdata[0]+(rxdata[1]

    • @PenguinTutor
      @PenguinTutor  3 ปีที่แล้ว +1

      I can't see anything obviously wrong with your code. In fact other than using different pins on the Pico (in my case using i2c1 and you are using i2c0) then the I2C part is almost identical. That shouldn't be a problem as long as you've not got anything else on the same bus.
      The speed of the i2c bus is the second parameter to i2c_init = 10000 Hz.
      There is some good documentation at: raspberrypi.github.io/pico-sdk-doxygen/group__hardware__i2c.html#i2c_example
      I used a lower value for the pull-ups. Around 2 to 5kohms is probably better, but over short distances 10k should work fine. Make sure that is connected to 3.3V (either the Raspberry Pi or Pico end).
      When I was testing I found it really useful to get feedback from the Pico. If you setup a UART on the Pico then you can print messages to confirm which part of the code is running. This is why there is additional UART code in my source code.
      The other thing I experimented with was different I2C read and write commands from Python. There are slight differences in the way that they format the instructions, but the ones you have used are the ones that worked for me.
      The other problem I had was that if the write wasn't sent successfully then it would hang at the Pi end, but that is the reason why the write_i2c_block_data is in a try loop and the continue means that it will wait a second before trying again.
      Sorry I can't give anything more definite. My best suggestion is adding the UART code so you can monitor what is happening on the Pico.

    • @GrantMeStrength
      @GrantMeStrength 3 ปีที่แล้ว

      @@PenguinTutor Thanks! Yes, I will set up a UART and monitor the Pico to see what it thinks is going on. Appreciate you taking the time to respond.

    • @GrantMeStrength
      @GrantMeStrength 3 ปีที่แล้ว +1

      @@PenguinTutor Thank you - it's working now. I'm not sure why. It could be that changing the allocated pins around to make room for the UART did something. It could be the UART code itself (unlikely). Or, it's something I did wrong with my choice of power supply - whatever I changed, it's working and I can send/receive data from the Pico in Peripheral mode, so cheers!

    • @PenguinTutor
      @PenguinTutor  3 ปีที่แล้ว +1

      @@GrantMeStrength Glad to hear it's working.

    • @denisbourqui7374
      @denisbourqui7374 2 ปีที่แล้ว +1

      @@GrantMeStrength Had the same problem, really strange. Removing the uart_puts() function made errors again. I added a sleep after the i2c_read_raw_blocking() of 2 ms and now it's working.
      Thank you for the hint :-)