Package: picoslave
PicoSlavePicoSlave is a USB controllable I2C slave device that features two independent I2C devices.
Each I2C slave works on 256-bytes register spaces, which can be read and modified via a
simple USB interface.PicoSlave is run on theRaspberry Pi Picoboard on theRP2040ARM Cortex-M0+ microcontroller. The two I2C interfaces of the board can be used independently or wired together to operate on the same I2C bus.Overview╓┉┉┉┉┉╖
                     ║     ║
               ╭─────║ USB ║─────╮
          (TX) ┥  1  ╙┉┉┉┉┉╜  40 ┝
          (RX) ┥  2 ╔╗        39 ┝
               ┥  3 ╚╝LED     38 ┝
               ┥  4           37 ┝
               ┥  5 ╔══╗      36 ┝
         ┌ SDA ┥  6 ║BS║      35 ┝
    I2C0 ┴ SCL ┥  7 ╚══╝      34 ┝
           GND ┥  8           33 ┝
         ┌ SDA ┥  9 ┏━━━━━━━┓ 32 ┝
    I2C1 ┴ SCL ┥ 10 ┃       ┃ 31 ┝
               ┥ 11 ┃RP2040 ┃ 30 ┝
               ┥ 12 ┃       ┃ 29 ┝
               ┥ 13 ┗━━━━━━━┛ 28 ┝
               ┥ 14           27 ┝
               ┥ 15           26 ┝
               ┥ 16           25 ┝
               ┥ 17           24 ┝
               ┥ 18           23 ┝
               ┥ 19           22 ┝
               ┥ 20   DEBUG   21 ┝
               ╰─────┰──┰──┰─────╯
                     S  G  S
                     W  N  W
                     C  D  D
                     L     I
                     K     ODependenciesPythonpyusbReferencesThe following sources are used in this project:I2C slave based onvmilea/pico_i2c_slaverxi/log.cis used as a submodule for loggingUsingTinyUSBInstallationProgram the Raspberry Pi PicoTo program the firmware onto the PicoSlave, follow these steps:obtain thelatest PicoSlave firmware builddisconnect the Raspberry Pi Pico from USBwhile holding theBOOTSELbutton of the board, connect USB to the PCthe Raspberry Pi Pico should load as a mass storage device to the systemcopy thepicoslave.uf2firmware file to the mass storage devicewhen copying is done, the Raspberry Pi Pico should reboot asPicoSlaveConfigure the PicoSlave USB deviceTo grant userspace access to the PicoSlave USB device, theudevrules must be added to the system. Make sure that the user is part of theplugdevuser group (default for Ubuntu and Raspberry Pi OS):sudocp./contrib/99-picoslave.rules/etc/udev/rules.d/
sudoudevadmcontrol--reload
sudoudevadmtrigger--attr-match=subsystem=usbAlternatively, the install script./util/install.shcan be executed (it will ask for sudo privileges).When installingpicoslaveas a pip package, thepicoslave-installscript is added to the path
and can be used to install the udev rules as well.Pythonpicoslavepackage installationPicoSlave comes as a python package (picoslave) which can be installed viapip. The package
contains a library for operating on PicoSlave devices as well as a CLI for manual operation. The
package can be installed from source only, currently:python-mpipinstallgit+https://gitlab.com/janoskut/picoslave.gitUsageThe PicoSlave allows the following operations to configure the two I2C interfaces slaves operations.
All functions are available via the CLI as well as the Python library/package, or via the vendor
specific USB interface.OperationArgsDescriptionconfigConfigure the I2C interface for slave operation on the specified I2C slave address on the specified memory. The I2C memory is configured as a 2-dimensional array of lengthsizeand widthwidth. Note that an interface that is not yet configured can still already be memory-programmed (read/write/clear).ifaceThe I2C interface (0 or 1).addrThe 7-bit I2C slave address to operate on.addr=0deactivates the interface.[size=256]The number of I2C adressable words in the memory, after which the internal address counter auto-increments. The maximum (and default) size is 256 words.[width=1]The word size, in bytes. Allowed values are 1 (default), 2 and 4. Note that internally as well as from the USB interface (seeread/write), the I2C memory is treated as a 1-dimensional array of sizesize*width. The word width defines the I2C-addressable sections. Hence when reading (or writing) from an addressNfrom I2C to a memory withwidth=2, then the first read will yieldmem[N*width]and the next read will yieldmem[N*width+1]. In terms of endianness, the I2C transmission order can hence be seen as little-endian byte order, as the smaller memory address will be transmitted first.readRead data from the I2C memory. When reading/writing data from/to the I2C memory, the memory is seen as a 1-dimensional array without respect to word widths and the result is a linear Byte array. Word interpretation has to be done by the API user.ifaceThe I2C interface (0 or 1).addrThe (raw) address to start reading from.sizeThe number of Bytes (not words) to read from the givenaddr.writeWrite data into the I2C memory. Seereadfor how the memory is seen as raw, 1-dimensional memory. As an example, with a I2C slave configured for wordwidth=2, in order to have an I2C master read the wordABCDfrom address0x24, one would have to write 2 bytes into the raw address0x48:write(iface, addr=0x48, data=bytes([0xAB, 0xCD])). Note that data can be written into the I2C memory already before the slave is configured to operate on an I2C address.ifaceThe I2C interface (0 or 1).addrThe (raw) address to start writingdatainto.dataThe data bytes (not words) to write into the givenaddr.clearReset all data in the I2C memory to 0 or a given value.ifaceThe I2C interface (0 or 1).value=0x00The reset value of the I2C memory.statGet a read/write statistics report for the selected memory section. The statistics report has a maximum ofsizeentries (seeconfigoperation) and has an entry for each addressable I2C memory address (not raw address). It gives a report for how often each I2C memory address has been read or written on the I2C interface. This report can be used to reverse-engineer or mock an I2C slave device in operation, when the internals of the I2C master or the specification of the to-be-mocked I2C slave are not known. The result data is a byte array withsizeentries, each in the (little-endian) format{#read}{#write}, where#readand#writeare numbers of size 4 each.ifaceThe I2C interface (0 or 1).addrThe I2C memory address (not raw address) to start reading the report for.sizeThe number of statistics report entries to read (fromaddr).resetReset the PicoSlave MCU. This leads to all I2C configuration and memory to be reset, and also the USB device to be re-enumerated.CLI UsageThe PicoSlave can be configured using the CLI, which can be run from the installed package, or from
source (cloned repository):# from source./picoslave/picocli.py-h
pythonpicoslave/picocli.py-h# from installed packagepicoslave-h
picoslave<command>-hSome example CLI usages:picoslavescan# scan for PicoSlave USB devicespicoslaveconfig00x16# configure I2C0 for 7-bit address 0x16picoslaveconfig10x23# configure I2C1 for 7-bit address 0x23picoslavewrite00x10aabbccddeeff# write 6 bytes to memory address 0x10 of I2C0 slavepicoslaveread00x106# read 6 bytes from memory address 0x10 from 12C0 slavepicoslaveclear0# clear the memory of I2C0 slavepicoslavestat0# get a full statistics dump for read/write access to I2C0 slavepicoslavereset# reset the PicoSlave USB deviceNote that the CLI allows abbreviations for commands, e.g.cforconfig, etc.Library UsageThe Python library to access PicoSlave devices can be used in an almost identical way as the CLI:frompicoslave.picoslaveimportPicoSlavepicoslave=PicoSlave()picoslave.config(iface=0,slave_address=0x16)picoslave.write(iface=0,mem_addr=0x10,data=b'aabbccddeeff')res:bytes=picoslave.read(iface=0,mem_addr=0x10,size=6)print(' '.join(f'{b:02X}'forbinres))picoslave.clear(iface=0)picoslave.statistics(iface=0)picoslave.reset()I2C - Raspberry PiFIXMEUSB ProtocolWire packetThe top level wire packet wraps the specific packets (host and response) into a simple header, which
consists of a length and a checksum:[LEN] [PAYLOAD] [CRC]FieldSizeDescriptionLEN4Length of the packet, including [CRC]PAYLOADNPayload data of variable length, seeHost packetandResponse packetCRC216-bit CRC-CCITT checksum over [PAYLOAD] initialized with0x8408Host packet:[CMD=config] [IFACE]   [ADDR]   [SIZE=N] [WIDTH]
[CMD=read]   [IFACE]   [ADDR]   [SIZE=N]
[CMD=write]  [IFACE]   [ADDR]   [SIZE=N] [DATA]
[CMD=clear]  [IFACE]   [ADDR]   [SIZE=0] [DATA]
[CMD=stat]   [IFACE]   [ADDR]   [SIZE=N]
[CMD=info]   [IFACE=0] [ADDR=0] [SIZE=0]
[CMD=reset]  [IFACE=0] [ADDR=0] [SIZE=0]FieldSizeDescriptionCMD1command to send to the deviceIFACE1I2C interface numberADDR27-bit address to assign to I2C interface, or (16 bit) memory address to read/writeSIZE2'config': memory size to use (max 256)'read':   number of bytes to read from'write':  number of bytes to write (must matchlen(DATA))DATANdata to writeWIDTH1I2C memory addressable word width. Allowed are 1, 2 and 4 bytes.Commands:CMDValueDescriptionconfig0xA0configure the slave given withIFACEto operate on the addressADDR, ordeactivate the slave whenADDR=0.SIZEspecifies the used memory atwhich the auto-increment overflows.read0xA1readSIZEbytes fromADDRfrom the slave given withIFACE.write0xA2writeSIZEbytes ofDATAto the slave given withIFACE.Note thatSIZEmust matchlen(DATA).clear0xA3clear all memory and statistics for the givenIFACE.Reset memory to the value given atADDR.stat0xA4readSIZEmany statistics fromADDR. Returns a statistics struct for each address.info0xB0obtain device information from picoslave, see"Info Response"reset0xBFreset the target device. Note thatIFACEandADDRare ignored,but need to be transmitted as 0.Response packet[CODE=0] [SIZE=N] [DATA]
[CODE>0] [SIZE=0]FieldSizeDescriptionCODE0response or error codeSIZE2number of received data bytes inDATADATANreceived data (optional)Response Codes:CodeDescription0OK(no error)1CRC_ERROR2INVALID_PACKET3INVALID_REQUEST4INVALID_INTERFACE5INVALID_ADDRESS6INVALID_SIZE7MEMORY_ERROR8OPERATION_FAILEDInfo ResponseWithCMD=infotheDATApart of the response is a semicolon separated ASCII string with the
following segments:[serial];[firmware];[protocol];[ifaces]SegmentDescriptionserialthe device unique serialfirmwareexact firmware versionprotocolversion of this USB protocolifacesnumber of I2C interfacesStatistics ResponseWithCMD=stattheDATApart of the response is structured data ofSIZEstatistics entries,
each corresponding to the memoryADDRrequested. A statistics entry has the format:[READ_CNT][WRITE_CNT]WhereREAD_CNTandWRITE_CNTare of size4each. They tell how many read/write accesses have
been made to that register address.DevelopmentToolchainsudoaptinstallcmakegcc-arm-none-eabiInstall SDKgitclonehttps://github.com/raspberrypi/pico-sdkexportPICO_SDK_PATH="$(pwd)/pico-sdk"Debugging Raspberry Pi PicoWiring:https://hackaday.io/project/177198-pi-pico-picoprobe-and-vs-code/detailshttps://www.digikey.be/en/maker/projects/raspberry-pi-pico-and-rp2040-cc-part-2-debugging-with-vs-code/470abc7efb07432b82c95f6f67f184c0Compiling and deployingpicoprobepicoprobeis the firmware which turns aRaspberry Pi Picointo a programmer for otherRaspberry Pi Pico's. Thepicoprobebinary needs to be compiled and loaded onto aRaspberry
Pi Picoonly once:# Picoprobe and picotoolgitclonehttps://github.com/raspberrypi/picoprobe.git--depth=1cmake-Spicoprobe-Bpicoprobe/build
cmake--buildpicoprobe/build-j$(nproc)# hold the BOOTSEL button and connect the Pico USB# assuming the Pico mounts at `/media/<user>/RPI-RP2`cppicoprobe/build/picoprobe.uf2/media/$(whoami)/RPI-RP2OpenOCD setupCompile & Install OpenOCDsudoaptinstalllibtoollibusb-1.0-0-dev
gitclone"https://github.com/raspberrypi/openocd.git"--branch"picoprobe"--depth=1cdopenocd
./bootstrap
./configure--enable-ftdi--enable-sysfsgpio--enable-bcm2835gpio--enable-picoprobe
make-j$(nproc)sudomakeinstallConfigure OpenOCDIn theopenocddirectory:sudocp./contrib/60-openocd.rules/etc/udev/rules.d/
sudoudevadmcontrol--reload
sudoudevadmtrigger--attr-match=subsystem=usbTest OpenOCDopenocd-finterface/picoprobe.cfg-ftarget/rp2040.cfg# should show something, but no errorsctrl+cProgram a binaryopenocd-finterface/picoprobe.cfg-ftarget/rp2040.cfg-c"program build/picoslave.elf verify reset exit"Or use the OpenOCD helper scriptpicoflash.sh:# program./util/picoflash.shbuild/picoslave.elf# or e.g. reset target./util/picoflash.sh--resetOr even easier, use the shell tools:sourceutil/shellutil.sh
flash
resetPython DevelopmentQA:pipinstall-rrequirements-dev.txt

flake8picoslave
pycodestylepicoslave
mypypicoslave--strictSystem Testing-> See./test/behave/README.md.