element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Raspberry Pi Projects
  • Products
  • Raspberry Pi
  • Raspberry Pi Projects
  • More
  • Cancel
Raspberry Pi Projects
Blog USB-to-I2C with a Pi Pico: Building an Easy I2C Adapter for PC control of I2C Devices
  • Blog
  • Documents
  • Events
  • Polls
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Raspberry Pi Projects to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: shabaz
  • Date Created: 21 Aug 2024 10:57 PM Date Created
  • Views 4003 views
  • Likes 15 likes
  • Comments 17 comments
  • python
  • i2c
  • pico
  • pi pico
  • rpi-beginner
Related
Recommended

USB-to-I2C with a Pi Pico: Building an Easy I2C Adapter for PC control of I2C Devices

shabaz
shabaz
21 Aug 2024

Table of Contents

  • Introduction
  • Building It
  • Using it from a Terminal (Console)
  • Using it from Python
  • Using Multiple Adapters
  • Known Devices Database
  • Summary


Introduction

This simple project converts a Pi Pico into a USB-to-I2C adapter! It is useful for sending commands from a PC to talk to low-level I2C hardware.

Sometimes, when trying out new sensors, sending commands from a PC rather than a microcontroller could be convenient. I had a requirement to program a device which accepts I2C communication, so it made sense to communicate to it from a PC.

I searched around for existing USB-to-I2C options and was disappointed; a lot of code didn’t work. A project that actually works is Bus Pirate. This project is slightly similar in that respect, but my project is very much cut down since it only supports I2C. I might extend it to SPI one day at a stretch, but it will never replace the more flexible Bus Pirate. My project is more geared toward programmable control (from Python), whereas the Bus Pirate is aimed at user command-prompt interaction.

This blog post is short because there’s not much to this project. It's a 30-minute project to replicate from start to finish!

In terms of features, this project supports attaching up to eight Pi Pico boards to a PC, and you can control them all. As well as I2C control, you can also read and write GPIO.

Building It

I soldered some wires to a Pi Pico so that it could attach to the target I2C device. Only three wires are needed (SDA, SCL, and GND); however, for a bit more convenience, I also soldered wires to VBUS and 3.3V so that I can power the target from them if desired. There are optional pull-up resistors, but it's advised to add them. I used two ten kohm resistors, which are fairly weak I2C pull-ups, but means that even if the target I2C has pull-up resistors already, it should still work with the 10k resistor additions.

image

The firmware file is called easy_i2c_adapter.uf2 (available from the easy_pico_adapter GitHub repo) and is programmed into the Pico by first holding down the BOOTSEL button on it, inserting the USB plug, and then releasing BOOTSEL. A drive letter appears on the PC, and the firmware file is dragged into it. The Pico is programmed within seconds. If you've never done this before, the 1-minute video here shows how it's done.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

When the Easy I2C Adapter firmware is running, you’ll know this because the green LED on the Pico will flicker dimly.

Using it from a Terminal (Console)

The main commands are listed here. After typing them, press return to execute the commands:

Command Syntax Example(s) Description
addr:<val>

addr:104

addr:0x5a

Used to set the I2C address 0-128. The value can be decimal, or hexadecimal, e.g., 0x10
bytes:<val> bytes:2 Used to set the desired number of bytes for sending or receiving. The value must be decimal. The value is reset to zero afterward.
recv recv This performs an I2C read and dumps the output to the terminal
send <val1> <val2> ... <valN>

send 01 02 03 04

send 01 02↵

03 04

This command sends I2C data. The values must be space-separated, in hexadecimal format, and must be two characters per value. 

If you wish to send a lot of data, then you can press return and continue typing bytes on the next line (up to 256 bytes total today, but I hope to extend this to 4096 bytes at some point). The I2C send command will occur once the expected number of bytes (set with the bytes: command) has been typed and the user has pressed return after the last one

send+hold <val1> <val2> ... <valN>

send+hold 0a

This command behaves like the send command, except that the I2C bus is held and not released afterwards. This allows for I2C ‘repeated starts’ with the next command.

tryaddr:<val>

tryaddr:104

tryaddr:0x0c

This function initiates an I2C read operation at the specified address, but deliberately aborts after the address is acknowledged (if the I2C target device exists at that address). This is useful for probing the bus to see what address devices exist, without actually reading or writing any data bytes. 

iowrite:<port>,<val>

iowrite:6,1

This function sets a GPIO port to output mode, and then sets the logic level according to the val parameter (0 or 1).

ioread:<port>

ioread:6

This function sets a GPIO port as input, and then reads and displays the logic value (0 or 1).


The screenshot here shows example interaction, where the user sent one byte to I2C address 0x0b, and then the user performed a repeated start and read 16 bytes. Next, the user made a command error and then tried receiving one byte from I2C address 0x50, which didn’t exist.

Please note that it will be easy to break things by sending poorly formatted commands. There’s not a lot of syntax checking currently.

image

Using it from Python

This is actually easier than the terminal (console) command line. Download the easyadapter.py Python code from the GitHub repo. You’ll also need to install pyserial.

To use the code to send I2C data, you can type the following (either directly in a Python command prompt or in a Python file); this example sends the values 0x01, 0x02, 0x03, 0x04 to I2C address 0x50:

import easyadapter as ea

adapter = ea.EasyAdapter()
result = adapter.init(0)
data = [0x01, 0x02, 0x03, 0x04]
adapter.i2c_write(0x50, data[0], data[1:])

You’ll notice that the first byte is split from the rest. This may be useful because often the first byte is a sub-address or register value.

The repeated start is performed by appending hold=1 to the parameters in the write function.

Here's how to send just a single data byte:

val = 0x01;
address = 0x50;
adapter.i2c_write(address, val, [])

The following example shows how to read 4 bytes from the I2C address 0x50:

buffer = adapter.i2c_read(0x50, 4)

If you wish to probe the I2C bus to see if an I2C device exists, use:

result = adapter.i2c_try_address(0x0b)

It will return True if the device exists at the provided address.

To set GPIO # 5 to logic level 1:

adapter.io_write(5,1)

To set GPIO 6 to input mode, and read the logic level:

val = adapter.io_read(6)

As you can see, the functionality is identical to what can be achieved with the command prompt but with friendlier Python functions. In the background, the Python adapter.init() function issues a command to switch the Pico into an ‘M2M’ mode, which is less verbose and easier to parse, but that functionality is hidden from the user.

For those interested in extending the code, the M2M protocol looks just like the normal ASCII protocol in the direction from the PC to the Pico, but in the Pico-to-PC direction, the Pico condenses responses to a single character. All ‘positive’ responses are condensed to a single period (.). The error response is the character X. If more input is required, the character ‘&’ is issued. The character ‘~’ indicates a wire-level protocol error, i.e., the lower-level I2C command failed.

Finally, if you wish to display the data in a user-friendly format, type:


adapter.print_data(buffer)

The data will be displayed as hex bytes on the left and ASCII characters on the right.

Using Multiple Adapters

Short the BOARD_ID connections (visible in the earlier diagram) to ground, to set unique board identifier values per board. A total of 8 boards can be configured in this manner (identifiers 0 to 7). The BOARD_ID GPIO pins are read upon startup. They float high, and the resultant default board identifier value is zero. To select a board identifier value of 1, short GPIO2 to ground. Here is how the identifiers are used with Python:

import easyadapter as ea
firstAdapter = ea.EasyAdapter()
secondAdapter = ea.EasyAdapter()
result = firstAdapter.init(0)   # this is board ID 0
result = secondAdapter.init(1)  # this is board ID 1

Known Devices Database

The Python capability also supports a known device database. Here's how to use it. Suppose you've done the following:
result = adapter.i2c_try_address(0x77)
If the result came True, then you know that the attached I2C device is responding to address 0x77, but you might not know what that device could be. This command will look it up:
adapter.get_known_device_names(0x77, adapter.db)
That example will return the following Python list:
['PCA9685', 'TCA9548', 'HT16K33', 'IS31FL3731', 'BME280', 'BMP280', 'MS5607', 'BMP180', 'BMP085', 'BMA180', 'MS5611', 'BME680', 'BME688', 'PCA9541', 'SPL06-007', 'TCA9548A', 'XD8574', 'PCA9539']
You can also do a reverse lookup. For instance, if you what to find the I2C address of a Bosch BMP series sensor:
adapter.get_known_device_address('BMP', adapter.db)
That will return a Python list containing:
[118, 119]
 

Summary

This is a very basic project and very prototype grade, but it might be handy occasionally when you need to access an I2C device from a PC. Although the Pico-based USB-I2C adapter only supports a slightly user-friendly command-line mode, the device is better suited to access from a programming language such as Python.

This project was tested on Windows. Some slight tweaks may be necessary to the Python code if you are using Linux or Mac. If you try the code, it would be great if you could report whether it works or fails. EDIT: I have now tested on Linux, and confirmed it works.

Thanks for reading!

  • Sign in to reply

Top Comments

  • shabaz
    shabaz 8 months ago +1
    Project updated! Now it is possible to use up to eight Pi Pico boards simultaneously, i.e. eight completely independent I2C buses. Each board can be given a different identifier, by shorting particular…
  • an79881
    an79881 5 months ago +1
    One hint: as the easyadapter.py supersedes the easy_interface.py your description may be updated for using print_data: from easy_interface import print_data print_data(buffer) You don't need this.…
  • shabaz
    shabaz 5 months ago in reply to shabaz +1
    Done.
  • shabaz
    shabaz 5 months ago

    New functionality added to the Python interface (and the blog content is updated). See the section titled "Known Devices Database".

    It basically allows a lookup of any I2C address against a known list of about 700 IC part names.

    A reverse-lookup on sub-strings is also possible, i.e. if you know the part name but not the I2C address.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz 5 months ago in reply to shabaz

    Done.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz 5 months ago in reply to an79881

    Thanks! I forgot to update that. I'll get that done.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • an79881
    an79881 5 months ago

    One hint: as the easyadapter.py supersedes the easy_interface.py your description may be updated for using print_data:

    from easy_interface import print_data
    print_data(buffer)

    You don't need this. Instead of print_data(buffer) use adapter.print_data(buffer)

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz 5 months ago in reply to an79881

    Hi, regarding the command reference for Python, the main documentation is this blog post, but otherwise the easyadapter.py code can be examined, each function there has notes explaining it. But the most user-friendly way is to check the blog post, which should cover all the functionality.

    The I2C speed is set to 100 kHz, it is hard-coded. To modify it, the main.c code needs changing and the code rebuilt. See the red highlighted line below.

    image 

    The Pico does support two I2C interfaces, but this project code only supports one. For more I2C interfaces, additional Pi Pico boards could be connected to the PC (up to 8 are supported with the Easy I2C adapter software, see the section titled "Using Multiple Adapters" in the blog). To me this was cleaner than trying to use both I2C interfaces per Pico (and Pico boards are cheap anyway) but if there was a strong need to use both I2C interfaces, then unfortunately the code would need a lot more altering and testing.

    I hope this helps, let me know if there's any issue.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube