Repository: Nicolai-Electronics/rp2040-i2c-interface Branch: master Commit: de4a019076c7 Files: 19 Total size: 34.7 KB Directory structure: gitextract_0gfx8glm/ ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE_MIT ├── Makefile ├── README.md ├── example/ │ ├── 400kHz.sh │ ├── README.md │ ├── disable_kde_compositor.sh │ ├── run.sh │ └── ssd1306.py ├── kernel_i2c_flags.h ├── main.c ├── pico_sdk_import.cmake ├── tools/ │ └── 99-pico.rules ├── tusb_config.h ├── usb_descriptors.c ├── usb_descriptors.h └── version.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -1 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignConsecutiveBitFields: true AlignConsecutiveDeclarations: true AlignEscapedNewlines: Left AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes AttributeMacros: - __capability BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: true BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 160 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: true DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Regroup IncludeCategories: - Regex: '^' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '^<.*\.h>' Priority: 1 SortPriority: 0 CaseSensitive: false - Regex: '^<.*' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '.*' Priority: 3 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: '([-_](test|unittest))?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseLabels: true IndentCaseBlocks: true IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentRequires: false IndentWidth: 4 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false LambdaBodyIndentation: Signature MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Never ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PenaltyIndentedWhitespace: 0 PointerAlignment: Left PPIndentWidth: -1 RawStringFormats: - Language: Cpp Delimiters: - cc - CC - cpp - Cpp - CPP - 'c++' - 'C++' CanonicalDelimiter: '' BasedOnStyle: google - Language: TextProto Delimiters: - pb - PB - proto - PROTO EnclosingFunctions: - EqualsProto - EquivToProto - PARSE_PARTIAL_TEXT_PROTO - PARSE_TEST_PROTO - PARSE_TEXT_PROTO - ParseTextOrDie - ParseTextProtoOrDie - ParseTestProto - ParsePartialTestProto CanonicalDelimiter: pb BasedOnStyle: google ReferenceAlignment: Pointer ReflowComments: true ShortNamespaceLines: 1 SortIncludes: CaseSensitive SortJavaStaticImport: Before SortUsingDeclarations: true SpaceAfterCStyleCast: true SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false BitFieldColonSpacing: Both Standard: Auto StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE - NS_SWIFT_NAME - CF_SWIFT_NAME ... ================================================ FILE: .gitignore ================================================ build release generated ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) message("Build type: \"${CMAKE_BUILD_TYPE}\"") # Project name set(NAME i2c_adapter) # Board type set(PICO_BOARD none) # Fixes that allow some MCH2022 badges with a slowly starting oscillator to boot properly add_compile_definitions(PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H=1 PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64) # SDK include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake) project(${NAME} C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() pico_sdk_init() # Firmware add_executable(${NAME} main.c usb_descriptors.c ) target_include_directories(${NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(${NAME} pico_stdlib pico_unique_id hardware_watchdog hardware_flash hardware_uart hardware_pio hardware_pwm hardware_adc hardware_i2c tinyusb_device tinyusb_board cmsis_core ) pico_add_extra_outputs(${NAME}) ================================================ FILE: LICENSE_MIT ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # Copyright (c) 2022 Nicolai Electronics # SPDX-License-Identifier: MIT INSTALL_PREFIX := $PWD BUILD_DIR := build GENERATED_DIR := generated .PHONY: all firmware flash clean install_rules $(BUILD_DIR) format all: build flash @echo "All tasks completed" build: mkdir -p $(BUILD_DIR) mkdir -p $(GENERATED_DIR) cd $(BUILD_DIR); cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release .. $(MAKE) -C $(BUILD_DIR) --no-print-directory all debug: mkdir -p $(BUILD_DIR) mkdir -p $(GENERATED_DIR) cd $(BUILD_DIR); cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Debug .. $(MAKE) -C $(BUILD_DIR) --no-print-directory all flash: picotool load $(BUILD_DIR)/i2c_adapter.bin picotool reboot clean: rm -rf $(BUILD_DIR) rm -rf $(GENERATED_DIR) install_rules: cp tools/99-pico.rules /etc/udev/rules.d/ @echo "reload rules with:" @echo "\tudevadm control --reload-rules" @echo "\tudevadm trigger" format: find . -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' | grep -v '$(BUILD_DIR)' | xargs clang-format -i ================================================ FILE: README.md ================================================ # I2C interface This RP2040 firmware implements the USB protocol expected by the I2C-Tiny-USB kernel driver, allowing the use of a Raspberry Pi Pico as USB to I2C adapter. The original I2C-Tiny-USB project can be found at https://github.com/harbaum/I2C-Tiny-USB, this firmware is a complete re-implementation of the firmware for use with the RP2040. More testing is needed to verify that the firmware works correctly, this project currently has proof of concept status. ## Pinout * SDA (i2c data): GPIO 2 * SCL (i2c clock): GPIO 3 ================================================ FILE: example/400kHz.sh ================================================ #!/usr/bin/env bash # The kernel module accepts a delay in us as a parameter # the default value of 10us converts to a clock speed of 1000000 / 10 = 100 kHz # the RP2040 supports 400kHz too, setting a delay of 2 us results in # 1000000 / 2 = 500 kHz but the firmware automatically limits the speed to 400 kHz # # This script disables the kernel module and then enables it again in 400kHz mode. rmmod i2c-tiny-usb modprobe i2c-tiny-usb delay=2 ================================================ FILE: example/README.md ================================================ # Example A simple demonstration showing how to control an OLED display (SSD1306) via I2C. ## Contents * 400khz.sh - Run as root, this script switches I2C speed from 100kHz to 400kHz * disable_kde_compositor.sh - Run as your user, this script disables the compositor function of the KDE desktop, which causes problems when capturing the screen with FFMPEG * run.sh - Run as root, this script starts FFMPEG to record the screen, piping the raw video data to the Python script, run this script to start the demonstration * ssd1306.py - A very simple and ugly SSD1306 OLED driver in Python using the smbus python library ![photo](photo.jpg) [![Demonstration video](http://img.youtube.com/vi/PMtY5OU9V3Q/0.jpg)](http://www.youtube.com/watch?v=PMtY5OU9V3Q "Demonstration video") ## Linux tools You can easily list and access the I2C busses of your computer, including the bus exposed by this project by loading the i2c-dev kernel module. Please do watch out: some devices on internal I2C busses inside your computer may malfunction or cause damage when probing busses or performing read/write operations using these tools. You can find the device number of the RP2040 I2C adapter by running the dmesg command right after plugging in the device. You are looking for the following line of information: ``` $ sudo dmesg ... i2c i2c-1: connected i2c-tiny-usb device ``` In this example the I2C bus number assigned to the RP2040 I2C adapter is 1. This means the device file is `/dev/i2c-1` and the sysfs interface is `/sys/bus/i2c/devices/i2c-1`. To enable access to the I2C busses of the computer via the device file the i2c-dev kernel module needs to be loaded. ``` sudo modprobe i2c-dev ``` Busses can then be probed using i2cdetect: ``` sudo i2cdetect -y 1 ``` In which the number (1) is the bus number. To scan all I2C addresses add the `-a` flag before the bus number. To read a value from a register in an I2C device the i2cget command can be used: ``` sudo i2cget -y 1 0x28 0x00 ``` In which 1 is the bus number, 0x28 is the I2C device address and 0x00 is the register number. To write a value to a register in an I2C device the i2cset command can be used: ``` sudo i2cset -y 1 0x28 0x00 0x42 ``` In wich 1 is the bus number, 0x28 is the I2C device address, 0x00 is the register number and 0x42 is the value written to the register. ================================================ FILE: example/disable_kde_compositor.sh ================================================ #!/usr/bin/env bash # This script temporarily disables the compositor function of the KDE plasma desktop as it causes glitches when recording the screen with ffmpeg qdbus org.kde.KWin /Compositor suspend ================================================ FILE: example/run.sh ================================================ #!/usr/bin/env bash ffmpeg -framerate 16 -f x11grab -video_size 1920x1080 -i :0+0,0 -f image2pipe -vf scale=128x64 -pix_fmt monob -vcodec rawvideo - | python -u ssd1306.py ================================================ FILE: example/ssd1306.py ================================================ import smbus, sys, time bus = smbus.SMBus(1) def init(): bus.write_byte_data(0x3c, 0x00, 0xae) bus.write_byte_data(0x3c, 0x00, 0xd5) bus.write_byte_data(0x3c, 0x00, 0x80) bus.write_byte_data(0x3c, 0x00, 0xa8) bus.write_byte_data(0x3c, 0x00, 0x3f) bus.write_byte_data(0x3c, 0x00, 0xd3) bus.write_byte_data(0x3c, 0x00, 0x00) bus.write_byte_data(0x3c, 0x00, 0x40) bus.write_byte_data(0x3c, 0x00, 0x20) bus.write_byte_data(0x3c, 0x00, 0x00) bus.write_byte_data(0x3c, 0x00, 0xa1) bus.write_byte_data(0x3c, 0x00, 0xc8) bus.write_byte_data(0x3c, 0x00, 0xda) bus.write_byte_data(0x3c, 0x00, 0x12) bus.write_byte_data(0x3c, 0x00, 0x81) bus.write_byte_data(0x3c, 0x00, 0xcf) bus.write_byte_data(0x3c, 0x00, 0xd9) bus.write_byte_data(0x3c, 0x00, 0xf1) bus.write_byte_data(0x3c, 0x00, 0xdb) bus.write_byte_data(0x3c, 0x00, 0x30) bus.write_byte_data(0x3c, 0x00, 0x8d) bus.write_byte_data(0x3c, 0x00, 0x14) bus.write_byte_data(0x3c, 0x00, 0x2e) bus.write_byte_data(0x3c, 0x00, 0xa4) bus.write_byte_data(0x3c, 0x00, 0xa6) bus.write_byte_data(0x3c, 0x00, 0xaf) def position(x0 = 0, x1 = 127, y0 = 0, y1 = 7): bus.write_byte_data(0x3c, 0x00, 0x21) bus.write_byte_data(0x3c, 0x00, x0) bus.write_byte_data(0x3c, 0x00, x1) bus.write_byte_data(0x3c, 0x00, 0x22) bus.write_byte_data(0x3c, 0x00, y0) bus.write_byte_data(0x3c, 0x00, y1) init() position() def convert(data_in): data_out = [0x00] * 128 * 8 for x in range(128): for y in range(64): in_addr = x // 8 + (y * 128//8) in_bit = 7 - x % 8 out_addr = x + ((y // 8)*128) out_bit = y % 8 if data_in[in_addr] & 1 << in_bit: data_out[out_addr] |= 1 << out_bit return data_out for x in range(64): bus.write_i2c_block_data(0x3c, 0x40, [0x00] * 16) while True: data_in = list(sys.stdin.buffer.read(128*64//8)) data_out = convert(data_in) for position in range(128*8//32): out = data_out[position*32:(position*32)+32] bus.write_i2c_block_data(0x3c, 0x40, out) ================================================ FILE: kernel_i2c_flags.h ================================================ #pragma once /* linux kernel flags */ #define I2C_M_TEN 0x10 /* we have a ten bit chip address */ #define I2C_M_RD 0x01 #define I2C_M_NOSTART 0x4000 #define I2C_M_REV_DIR_ADDR 0x2000 #define I2C_M_IGNORE_NAK 0x1000 #define I2C_M_NO_RD_ACK 0x0800 /* To determine what functionality is present */ #define I2C_FUNC_I2C 0x00000001 #define I2C_FUNC_10BIT_ADDR 0x00000002 #define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_{REV_DIR_ADDR,NOSTART,..} */ #define I2C_FUNC_SMBUS_HWPEC_CALC 0x00000008 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_READ_WORD_DATA_PEC 0x00000800 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_WRITE_WORD_DATA_PEC 0x00001000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_PROC_CALL_PEC 0x00002000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BLOCK_PROC_CALL_PEC 0x00004000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_QUICK 0x00010000 #define I2C_FUNC_SMBUS_READ_BYTE 0x00020000 #define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 #define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000 #define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000 #define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 #define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 #define I2C_FUNC_SMBUS_PROC_CALL 0x00800000 #define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 #define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */ #define I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /* I2C-like block xfer */ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte reg. addr. */ #define I2C_FUNC_SMBUS_READ_BLOCK_DATA_PEC 0x40000000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC 0x80000000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BYTE I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE #define I2C_FUNC_SMBUS_BYTE_DATA I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA #define I2C_FUNC_SMBUS_WORD_DATA I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_WRITE_WORD_DATA #define I2C_FUNC_SMBUS_BLOCK_DATA I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_BLOCK_DATA #define I2C_FUNC_SMBUS_I2C_BLOCK I2C_FUNC_SMBUS_READ_I2C_BLOCK | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK #define I2C_FUNC_SMBUS_EMUL \ I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC | I2C_FUNC_SMBUS_I2C_BLOCK ================================================ FILE: main.c ================================================ /** * Copyright (c) 2022 Nicolai Electronics * * SPDX-License-Identifier: MIT */ #include #include #include #include "bsp/board.h" #include "hardware/adc.h" #include "hardware/i2c.h" #include "hardware/irq.h" #include "hardware/pwm.h" #include "hardware/structs/watchdog.h" #include "hardware/uart.h" #include "hardware/watchdog.h" #include "kernel_i2c_flags.h" #include "pico/bootrom.h" #include "pico/stdlib.h" #include "tusb.h" #include "usb_descriptors.h" #define I2C_INST i2c1 #define I2C_SDA 2 #define I2C_SCL 3 int main(void) { board_init(); tusb_init(); gpio_init(I2C_SDA); gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); gpio_pull_up(I2C_SDA); gpio_init(I2C_SCL); gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); gpio_pull_up(I2C_SCL); i2c_init(I2C_INST, 100000); while (1) { tud_task(); } return 0; } // Invoked when device is mounted void tud_mount_cb(void) {} // Invoked when device is unmounted void tud_umount_cb(void) {} // Invoked when usb bus is suspended void tud_suspend_cb(bool remote_wakeup_en) {} // Invoked when usb bus is resumed void tud_resume_cb(void) {} /* commands from USB, must e.g. match command ids in kernel driver */ #define CMD_ECHO 0 #define CMD_GET_FUNC 1 #define CMD_SET_DELAY 2 #define CMD_GET_STATUS 3 #define CMD_I2C_IO 4 #define CMD_I2C_BEGIN 1 // flag fo I2C_IO #define CMD_I2C_END 2 // flag fo I2C_IO const unsigned long i2c_func = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; #define STATUS_IDLE 0 #define STATUS_ADDRESS_ACK 1 #define STATUS_ADDRESS_NAK 2 static uint8_t i2c_state = STATUS_IDLE; uint8_t i2c_data[1024] = {0}; /*uint8_t buffer[256] = {0}; void debug_print(const char* buffer) { tud_cdc_n_write(0, buffer, strlen(buffer)); tud_cdc_n_write_flush(0); }*/ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR) { switch (request->bRequest) { case CMD_ECHO: if (stage != CONTROL_STAGE_SETUP) return true; return tud_control_xfer(rhport, request, (void*) &request->wValue, sizeof(request->wValue)); case CMD_GET_FUNC: if (stage != CONTROL_STAGE_SETUP) return true; return tud_control_xfer(rhport, request, (void*) &i2c_func, sizeof(i2c_func)); break; case CMD_SET_DELAY: if (stage != CONTROL_STAGE_SETUP) return true; if (request->wValue == 0) { i2c_set_baudrate(I2C_INST, 100000); // Use default: 100kHz } else { int baudrate = 1000000 / request->wValue; if (baudrate > 400000) baudrate = 400000; // Limit to 400kHz i2c_set_baudrate(I2C_INST, baudrate); } return tud_control_status(rhport, request); case CMD_GET_STATUS: if (stage != CONTROL_STAGE_SETUP) return true; return tud_control_xfer(rhport, request, (void*) &i2c_state, sizeof(i2c_state)); case CMD_I2C_IO: case CMD_I2C_IO + CMD_I2C_BEGIN: case CMD_I2C_IO + CMD_I2C_END: case CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END: { if (stage != CONTROL_STAGE_SETUP && stage != CONTROL_STAGE_DATA) return true; bool nostop = !(request->bRequest & CMD_I2C_END); //sprintf(buffer, "%s i2c %s at 0x%02x, len = %d, nostop = %d\r\n", (stage != CONTROL_STAGE_SETUP) ? "[D]" : "[S]", (request->wValue & I2C_M_RD)?"rd":"wr", request->wIndex, request->wLength, nostop); //debug_print(buffer); if (request->wLength > sizeof(i2c_data)) { return false; // Prevent buffer overflow in case host sends us an impossible request } if (stage == CONTROL_STAGE_SETUP) { // Before transfering data if (request->wValue & I2C_M_RD) { // Reading from I2C device int res = i2c_read_blocking(I2C_INST, request->wIndex, i2c_data, request->wLength, nostop); if (res == PICO_ERROR_GENERIC) { i2c_state = STATUS_ADDRESS_NAK; } else { i2c_state = STATUS_ADDRESS_ACK; } } else if (request->wLength == 0) { // Writing with length of 0, this is used for bus scanning, do dummy read uint8_t dummy = 0x00; int res = i2c_read_blocking(I2C_INST, request->wIndex, (void*) &dummy, 1, nostop); if (res == PICO_ERROR_GENERIC) { i2c_state = STATUS_ADDRESS_NAK; } else { i2c_state = STATUS_ADDRESS_ACK; } } tud_control_xfer(rhport, request, (void*) i2c_data, request->wLength); } if (stage == CONTROL_STAGE_DATA) { // After transfering data if (!(request->wValue & I2C_M_RD)) { // I2C write operation int res = i2c_write_blocking(I2C_INST, request->wIndex, i2c_data, request->wLength, nostop); if (res == PICO_ERROR_GENERIC) { i2c_state = STATUS_ADDRESS_NAK; } else { i2c_state = STATUS_ADDRESS_ACK; } } } return true; } default: if (stage != CONTROL_STAGE_SETUP) return true; break; } } else { if (stage != CONTROL_STAGE_SETUP) return true; } return false; // stall unknown request } bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_request_t const* request) { (void) rhport; (void) request; return true; } ================================================ FILE: pico_sdk_import.cmake ================================================ # This is a copy of /external/pico_sdk_import.cmake # This can be dropped into an external project to help locate this SDK # It should be include()ed prior to project() if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") endif () set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") if (NOT PICO_SDK_PATH) if (PICO_SDK_FETCH_FROM_GIT) include(FetchContent) set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) if (PICO_SDK_FETCH_FROM_GIT_PATH) get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") endif () FetchContent_Declare( pico_sdk GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk GIT_TAG master ) if (NOT pico_sdk) message("Downloading Raspberry Pi Pico SDK") FetchContent_Populate(pico_sdk) set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) endif () set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) else () message(FATAL_ERROR "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." ) endif () endif () get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") if (NOT EXISTS ${PICO_SDK_PATH}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") endif () set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") endif () set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) include(${PICO_SDK_INIT_CMAKE_FILE}) ================================================ FILE: tools/99-pico.rules ================================================ # /etc/udev/rules.d/99-pico.rules SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE="0666" SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0005", SYMLINK+="pico" ================================================ FILE: tusb_config.h ================================================ /* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #ifndef _TUSB_CONFIG_H_ #define _TUSB_CONFIG_H_ #ifdef __cplusplus extern "C" { #endif //-------------------------------------------------------------------- // COMMON CONFIGURATION //-------------------------------------------------------------------- // defined by board.mk #ifndef CFG_TUSB_MCU #error CFG_TUSB_MCU must be defined #endif // RHPort number used for device can be defined by board.mk, default to port 0 #ifndef BOARD_DEVICE_RHPORT_NUM #define BOARD_DEVICE_RHPORT_NUM 0 #endif // RHPort max operational speed can defined by board.mk // Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed #ifndef BOARD_DEVICE_RHPORT_SPEED #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || CFG_TUSB_MCU == OPT_MCU_NUC505 || \ CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED #else #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED #endif #endif // Device mode with rhport and speed defined by board.mk #if BOARD_DEVICE_RHPORT_NUM == 0 #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) #elif BOARD_DEVICE_RHPORT_NUM == 1 #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) #else #error "Incorrect RHPort configuration" #endif #ifndef CFG_TUSB_OS #define CFG_TUSB_OS OPT_OS_NONE #endif // CFG_TUSB_DEBUG is defined by compiler in DEBUG build // #define CFG_TUSB_DEBUG 0 /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. * Tinyusb use follows macros to declare transferring memory so that they can be put * into those specific section. * e.g * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) */ #ifndef CFG_TUSB_MEM_SECTION #define CFG_TUSB_MEM_SECTION #endif #ifndef CFG_TUSB_MEM_ALIGN #define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) #endif //-------------------------------------------------------------------- // DEVICE CONFIGURATION //-------------------------------------------------------------------- #ifndef CFG_TUD_ENDPOINT0_SIZE #define CFG_TUD_ENDPOINT0_SIZE 64 #endif //------------- CLASS -------------// #define CFG_TUD_HID 0 #define CFG_TUD_CDC 0 #define CFG_TUD_MSC 0 #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 1 #define CFG_TUD_CDC_RX_BUFSIZE 256 #define CFG_TUD_CDC_TX_BUFSIZE 256 #define CFG_TUD_VENDOR_EPSIZE 32 #define CFG_TUD_VENDOR_EP_BUFSIZE 512 #define CFG_TUD_VENDOR_RX_BUFSIZE 512 #define CFG_TUD_VENDOR_TX_BUFSIZE 512 // HID buffer size Should be sufficient to hold ID (if any) + Data #define CFG_TUD_HID_EP_BUFSIZE 16 // MSC Buffer size of Device Mass storage #define CFG_TUD_MSC_EP_BUFSIZE 512 #ifdef __cplusplus } #endif #endif /* _TUSB_CONFIG_H_ */ ================================================ FILE: usb_descriptors.c ================================================ /** * Copyright (c) 2022 Nicolai Electronics * Copyright (c) 2019 Ha Thach (tinyusb.org) * * SPDX-License-Identifier: MIT */ #include "usb_descriptors.h" #include "bsp/board.h" #include "pico/unique_id.h" #include "tusb.h" // String descriptors char const* string_desc_arr[] = { (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) "Nicolai Electronics", // 1: Manufacturer "I2C adapter", // 2: Product "I2C interface", // 3: I2C (vendor) interface //"Debug" // 4: Debug (cdc) interface }; enum { STRING_DESC = 0, STRING_DESC_MANUFACTURER, STRING_DESC_PRODUCT, STRING_DESC_VENDOR_0, // STRING_DESC_CDC_0, STRING_DESC_SERIAL // (Not in the string description array) }; static uint16_t _desc_str[32]; uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { (void) langid; uint8_t chr_count; if (index == STRING_DESC) { memcpy(&_desc_str[1], string_desc_arr[0], 2); chr_count = 1; } else if (index == STRING_DESC_SERIAL) { pico_unique_board_id_t id; pico_get_unique_board_id(&id); const uint8_t* str = id.id; chr_count = 16; for (uint8_t len = 0; len < chr_count; ++len) { uint8_t c = str[len >> 1]; c = ((c >> (((len & 1) ^ 1) << 2)) & 0x0F) + '0'; if (c > '9') { c += 7; } _desc_str[1 + len] = c; } } else { // Convert ASCII string into UTF-16 if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) { return NULL; } const char* str = string_desc_arr[index]; // Cap at max char chr_count = strlen(str); if (chr_count > 31) chr_count = 31; for (uint8_t i = 0; i < chr_count; i++) { _desc_str[1 + i] = str[i]; } } // first byte is length (including header), second byte is string type _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); return _desc_str; } // Device descriptors tusb_desc_device_t const desc_device = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0210, // Supported USB standard (2.1) .bDeviceClass = TUSB_CLASS_MISC, .bDeviceSubClass = MISC_SUBCLASS_COMMON, .bDeviceProtocol = MISC_PROTOCOL_IAD, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, // Endpoint 0 packet size .idVendor = 0x1c40, // Vendor identifier .idProduct = 0x0534, // Product identifier .bcdDevice = 0x0100, // Protocol version .iManufacturer = STRING_DESC_MANUFACTURER, // Index of manufacturer name string .iProduct = STRING_DESC_PRODUCT, // Index of product name string .iSerialNumber = STRING_DESC_SERIAL, // Index of serial number string .bNumConfigurations = 0x01 // Number of configurations supported }; uint8_t const* tud_descriptor_device_cb(void) { return (uint8_t const*) &desc_device; } // Configuration Descriptor #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN) #define EPNUM_VENDOR_0_OUT 0x01 // Endpoint 1 #define EPNUM_VENDOR_0_IN 0x81 /*#define EPNUM_CDC_0_NOTIF 0x82 // Endpoint 2: CDC serial port for ESP32 console, control #define EPNUM_CDC_0_OUT 0x03 // Endpoint 3: CDC serial port for ESP32 console, data #define EPNUM_CDC_0_IN 0x83*/ uint8_t const desc_fs_configuration[] = { // Config number, interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), // WebUSB: Interface number, string index, EP Out & IN address, EP size TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR_0, STRING_DESC_VENDOR_0, EPNUM_VENDOR_0_OUT, EPNUM_VENDOR_0_IN, 32), // CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. // TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, STRING_DESC_CDC_0, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 64), }; uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { (void) index; return desc_fs_configuration; } ================================================ FILE: usb_descriptors.h ================================================ #pragma once #include // enum { ITF_NUM_VENDOR_0, ITF_NUM_CDC_0, ITF_NUM_CDC_0_DATA, ITF_NUM_TOTAL }; enum { ITF_NUM_VENDOR_0, ITF_NUM_TOTAL }; ================================================ FILE: version.h ================================================ #pragma once #define FW_VERSION 0x01