From abffe47e5228c8b342613b57c26b1b412f8026bb Mon Sep 17 00:00:00 2001 From: Thomas Daede Date: Tue, 7 Feb 2023 02:01:57 -0800 Subject: [PATCH] Add edid emulation support. Uses third party pico_i2c_slave library. --- firmware/CMakeLists.txt | 6 +- firmware/edid.c | 46 ++++ firmware/td-io.c | 4 + .../third_party/pico_i2c_slave/.clang-format | 135 +++++++++++ .../third_party/pico_i2c_slave/.gitignore | 4 + .../third_party/pico_i2c_slave/CMakeLists.txt | 14 ++ .../pico_i2c_slave/LICENSES/MIT.txt | 21 ++ firmware/third_party/pico_i2c_slave/README.md | 30 +++ .../pico_i2c_slave/example_mem/CMakeLists.txt | 10 + .../pico_i2c_slave/example_mem/example_mem.c | 133 +++++++++++ .../example_mem_wire/CMakeLists.txt | 10 + .../pico_i2c_slave/example_mem_wire/Wire.cpp | 180 ++++++++++++++ .../pico_i2c_slave/example_mem_wire/Wire.h | 226 ++++++++++++++++++ .../example_mem_wire/example_mem_wire.cpp | 136 +++++++++++ .../pico_i2c_slave/i2c_slave/CMakeLists.txt | 16 ++ .../pico_i2c_slave/i2c_slave/i2c_slave.c | 115 +++++++++ .../i2c_slave/include/i2c_fifo.h | 53 ++++ .../i2c_slave/include/i2c_slave.h | 65 +++++ .../pico_i2c_slave/pico_sdk_import.cmake | 62 +++++ 19 files changed, 1264 insertions(+), 2 deletions(-) create mode 100644 firmware/edid.c create mode 100644 firmware/third_party/pico_i2c_slave/.clang-format create mode 100644 firmware/third_party/pico_i2c_slave/.gitignore create mode 100644 firmware/third_party/pico_i2c_slave/CMakeLists.txt create mode 100644 firmware/third_party/pico_i2c_slave/LICENSES/MIT.txt create mode 100644 firmware/third_party/pico_i2c_slave/README.md create mode 100644 firmware/third_party/pico_i2c_slave/example_mem/CMakeLists.txt create mode 100644 firmware/third_party/pico_i2c_slave/example_mem/example_mem.c create mode 100644 firmware/third_party/pico_i2c_slave/example_mem_wire/CMakeLists.txt create mode 100644 firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.cpp create mode 100644 firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.h create mode 100644 firmware/third_party/pico_i2c_slave/example_mem_wire/example_mem_wire.cpp create mode 100644 firmware/third_party/pico_i2c_slave/i2c_slave/CMakeLists.txt create mode 100644 firmware/third_party/pico_i2c_slave/i2c_slave/i2c_slave.c create mode 100644 firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_fifo.h create mode 100644 firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_slave.h create mode 100644 firmware/third_party/pico_i2c_slave/pico_sdk_import.cmake diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 7fcfc52..1566f6f 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -9,14 +9,16 @@ project(td-io) # initialize the Raspberry Pi Pico SDK pico_sdk_init() +add_subdirectory(third_party/pico_i2c_slave/i2c_slave) + # rest of your project add_executable(td-io - td-io.c + td-io.c edid.c ) # Pull in our pico_stdlib which aggregates commonly used features -target_link_libraries(td-io pico_stdlib hardware_adc) +target_link_libraries(td-io pico_stdlib hardware_adc i2c_slave) # enable usb output, disable uart output pico_enable_stdio_usb(td-io 1) diff --git a/firmware/edid.c b/firmware/edid.c new file mode 100644 index 0000000..6d8d09b --- /dev/null +++ b/firmware/edid.c @@ -0,0 +1,46 @@ +#include "edid.h" +#include "pico/stdlib.h" +#include "i2c_slave.h" +#include "i2c_fifo.h" + +#define PIN_SDA 4 +#define PIN_SCL 5 + +static const uint8_t edid_value[128] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x31\xd8\x00\x00\x00\x00\x00\x00" + "\x05\x21\x01\x03\x6d\x10\x0c\x78\xea\x5e\xc0\xa4\x59\x4a\x98\x25" + "\x20\x50\x54\x00\x00\x00\x31\x40\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\x8c\x0a\x80\xda\x20\xe0\x2d\x10\x18\x60" + "\xa4\x00\xa6\x7d\x00\x00\x00\x18\x00\x00\x00\xff\x00\x74\x64\x2d" + "\x69\x6f\x20\x20\x20\x0a\x20\x20\x20\x20\x00\x00\x00\xfd\x00\x3b" + "\x3d\x1e\x20\x03\x00\x0a\x20\x20\x20\x20\x20\x20\x00\x00\x00\xfc" + "\x00\x74\x64\x2d\x69\x6f\x0a\x20\x20\x20\x20\x20\x20\x20\x00\xf6"; + +static int16_t edid_address = 0; + +static void edid_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { + switch (event) { + case I2C_SLAVE_RECEIVE: + edid_address = i2c_read_byte(i2c) & 0x7F; + break; + case I2C_SLAVE_REQUEST: + i2c_write_byte(i2c, edid_value[edid_address]); + edid_address++; + if (edid_address > 127) edid_address = 0; + break; + default: + break; + } +} + +void edid_init(void) { + gpio_init(PIN_SDA); + gpio_init(PIN_SCL); + gpio_pull_up(PIN_SDA); + gpio_pull_up(PIN_SCL); + gpio_set_function(PIN_SDA, GPIO_FUNC_I2C); + gpio_set_function(PIN_SCL, GPIO_FUNC_I2C); + + i2c_init(i2c0, 100000); + i2c_slave_init(i2c0, 0x50, &edid_handler); +} diff --git a/firmware/td-io.c b/firmware/td-io.c index 564da80..f4fae0c 100644 --- a/firmware/td-io.c +++ b/firmware/td-io.c @@ -1,6 +1,7 @@ #include #include "pico/stdlib.h" #include "hardware/adc.h" +#include "edid.h" #include const uint PIN_JVS_RE = 2; @@ -265,6 +266,9 @@ int main() { gpio_set_dir(PIN_DIP1, GPIO_IN); gpio_pull_up(PIN_DIP1); + // edid emulation + edid_init(); + update_termination(); while (true) { diff --git a/firmware/third_party/pico_i2c_slave/.clang-format b/firmware/third_party/pico_i2c_slave/.clang-format new file mode 100644 index 0000000..3365024 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/.clang-format @@ -0,0 +1,135 @@ +--- +BasedOnStyle: LLVM +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: true +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AlignOperands: DontAlign +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: No +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Never + AfterEnum: true + AfterFunction: false + AfterNamespace: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentCaseBlocks: false +IndentExternBlock: NoIndent +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE +... diff --git a/firmware/third_party/pico_i2c_slave/.gitignore b/firmware/third_party/pico_i2c_slave/.gitignore new file mode 100644 index 0000000..6b9075c --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.vscode +build +*.bat diff --git a/firmware/third_party/pico_i2c_slave/CMakeLists.txt b/firmware/third_party/pico_i2c_slave/CMakeLists.txt new file mode 100644 index 0000000..eb53604 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.13) + +include(pico_sdk_import.cmake) + +project(i2c_examples C CXX ASM) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +add_subdirectory(i2c_slave) +add_subdirectory(example_mem) +add_subdirectory(example_mem_wire) diff --git a/firmware/third_party/pico_i2c_slave/LICENSES/MIT.txt b/firmware/third_party/pico_i2c_slave/LICENSES/MIT.txt new file mode 100644 index 0000000..8287cc4 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/LICENSES/MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Valentin Milea + +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. diff --git a/firmware/third_party/pico_i2c_slave/README.md b/firmware/third_party/pico_i2c_slave/README.md new file mode 100644 index 0000000..9cf859d --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/README.md @@ -0,0 +1,30 @@ +# I2C slave library for the Raspberry Pi Pico + +The Raspberry Pi Pico C/C++ SDK has all you need to write an I2C master, but is curiously lacking when it comes to I2C in slave mode. This library fills that gap to easily turn the Pico into an I2C slave. + +## Examples + +An example program is included where the slave acts as a 256 byte external memory. See `example_mem`. + +For those who prefer the Wire API commonly used with Arduino, there is a second version on top of a Wire wrapper. See `example_mem_wire`. + +To keep it simple, both master and slave run on the same board. Just add jumpers between the two I2C instances: GP4 to GP6 (SDA), and GP5 to GP7 (SCL). + +### Setup + +Follow the instructions in [Getting started with Raspberry Pi Pico](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) to setup your build environment. Then: + +- `git clone https://github.com/vmilea/pico_i2c_slave` +- `cd pico_i2c_slave` +- `mkdir build`, `cd build`, `cmake ../`, `make` +- copy `example_mem/example_mem.uf2` to Raspberry Pico +- open a serial connection and check output + +## Links + +- [Pico as I2C slave](https://www.raspberrypi.org/forums/viewtopic.php?t=304074) - basic setup using raw registers +- [DroneBot Workshop](https://dronebotworkshop.com/i2c-part-2-build-i2c-sensor/) - building an I2C sensor + +## Authors + +Valentin Milea diff --git a/firmware/third_party/pico_i2c_slave/example_mem/CMakeLists.txt b/firmware/third_party/pico_i2c_slave/example_mem/CMakeLists.txt new file mode 100644 index 0000000..d8431a7 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/example_mem/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(example_mem example_mem.c) + +pico_enable_stdio_uart(example_mem 1) +pico_enable_stdio_usb(example_mem 1) + +pico_add_extra_outputs(example_mem) + +target_compile_options(example_mem PRIVATE -Wall) + +target_link_libraries(example_mem i2c_slave pico_stdlib) diff --git a/firmware/third_party/pico_i2c_slave/example_mem/example_mem.c b/firmware/third_party/pico_i2c_slave/example_mem/example_mem.c new file mode 100644 index 0000000..07712f5 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/example_mem/example_mem.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +static const uint I2C_SLAVE_ADDRESS = 0x17; +static const uint I2C_BAUDRATE = 100000; // 100 kHz + +// For this example, we run both the master and slave from the same board. +// You'll need to wire pin GP4 to GP6 (SDA), and pin GP5 to GP7 (SCL). +static const uint I2C_SLAVE_SDA_PIN = PICO_DEFAULT_I2C_SDA_PIN; // 4 +static const uint I2C_SLAVE_SCL_PIN = PICO_DEFAULT_I2C_SCL_PIN; // 5 +static const uint I2C_MASTER_SDA_PIN = 6; +static const uint I2C_MASTER_SCL_PIN = 7; + +// The slave implements a 256 byte memory. To write a series of bytes, the master first +// writes the memory address, followed by the data. The address is automatically incremented +// for each byte transferred, looping back to 0 upon reaching the end. Reading is done +// sequentially from the current memory address. +static struct +{ + uint8_t mem[256]; + uint8_t mem_address; + bool mem_address_written; +} context; + +// Our handler is called from the I2C ISR, so it must complete quickly. Blocking calls / +// printing to stdio may interfere with interrupt handling. +static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { + switch (event) { + case I2C_SLAVE_RECEIVE: // master has written some data + if (!context.mem_address_written) { + // writes always start with the memory address + context.mem_address = i2c_read_byte(i2c); + context.mem_address_written = true; + } else { + // save into memory + context.mem[context.mem_address] = i2c_read_byte(i2c); + context.mem_address++; + } + break; + case I2C_SLAVE_REQUEST: // master is requesting data + // load from memory + i2c_write_byte(i2c, context.mem[context.mem_address]); + context.mem_address++; + break; + case I2C_SLAVE_FINISH: // master has signalled Stop / Restart + context.mem_address_written = false; + break; + default: + break; + } +} + +static void setup_slave() { + gpio_init(I2C_SLAVE_SDA_PIN); + gpio_set_function(I2C_SLAVE_SDA_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SLAVE_SDA_PIN); + + gpio_init(I2C_SLAVE_SCL_PIN); + gpio_set_function(I2C_SLAVE_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SLAVE_SCL_PIN); + + i2c_init(i2c0, I2C_BAUDRATE); + // configure I2C0 for slave mode + i2c_slave_init(i2c0, I2C_SLAVE_ADDRESS, &i2c_slave_handler); +} + +static void run_master() { + gpio_init(I2C_MASTER_SDA_PIN); + gpio_set_function(I2C_MASTER_SDA_PIN, GPIO_FUNC_I2C); + // pull-ups are already active on slave side, this is just a fail-safe in case the wiring is faulty + gpio_pull_up(I2C_MASTER_SDA_PIN); + + gpio_init(I2C_MASTER_SCL_PIN); + gpio_set_function(I2C_MASTER_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_MASTER_SCL_PIN); + + i2c_init(i2c1, I2C_BAUDRATE); + + for (uint8_t mem_address = 0;; mem_address = (mem_address + 32) % 256) { + char msg[32]; + snprintf(msg, sizeof(msg), "Hello, I2C slave! - 0x%02X", mem_address); + uint8_t msg_len = strlen(msg); + + uint8_t buf[32]; + buf[0] = mem_address; + memcpy(buf + 1, msg, msg_len); + // write message at mem_address + printf("Write at 0x%02X: '%s'\n", mem_address, msg); + int count = i2c_write_blocking(i2c1, I2C_SLAVE_ADDRESS, buf, 1 + msg_len, false); + if (count < 0) { + puts("Couldn't write to slave, please check your wiring!"); + return; + } + hard_assert(count == 1 + msg_len); + + // seek to mem_address + count = i2c_write_blocking(i2c1, I2C_SLAVE_ADDRESS, buf, 1, true); + hard_assert(count == 1); + // partial read + uint8_t split = 5; + count = i2c_read_blocking(i2c1, I2C_SLAVE_ADDRESS, buf, split, true); + hard_assert(count == split); + buf[count] = '\0'; + printf("Read at 0x%02X: '%s'\n", mem_address, buf); + hard_assert(memcmp(buf, msg, split) == 0); + // read the remaining bytes, continuing from last address + count = i2c_read_blocking(i2c1, I2C_SLAVE_ADDRESS, buf, msg_len - split, false); + hard_assert(count == msg_len - split); + buf[count] = '\0'; + printf("Read at 0x%02X: '%s'\n", mem_address + split, buf); + hard_assert(memcmp(buf, msg + split, msg_len - split) == 0); + + puts(""); + sleep_ms(2000); + } +} + +int main() { + stdio_init_all(); + puts("\nI2C slave example"); + + setup_slave(); + run_master(); +} diff --git a/firmware/third_party/pico_i2c_slave/example_mem_wire/CMakeLists.txt b/firmware/third_party/pico_i2c_slave/example_mem_wire/CMakeLists.txt new file mode 100644 index 0000000..1a7cfd4 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/example_mem_wire/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(example_mem_wire Wire.cpp example_mem_wire.cpp) + +pico_enable_stdio_uart(example_mem_wire 1) +pico_enable_stdio_usb(example_mem_wire 1) + +pico_add_extra_outputs(example_mem_wire) + +target_compile_options(example_mem_wire PRIVATE -Wall) + +target_link_libraries(example_mem_wire i2c_slave pico_stdlib) diff --git a/firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.cpp b/firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.cpp new file mode 100644 index 0000000..674c9e4 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#include "Wire.h" +#include + +TwoWire Wire; +TwoWire Wire1; + +// +// TwoWire +// + +TwoWire::TwoWire() = default; + +i2c_inst_t *TwoWire::i2c() const { + assert(this == &Wire || this == &Wire1); + + return this == &Wire ? i2c0 : i2c1; +} + +void TwoWire::begin() { + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + + if (mode_ != Unassigned) { + i2c_slave_deinit(i2c()); + } + mode_ = Master; + bufLen_ = 0; + bufPos_ = 0; +} + +void TwoWire::begin(uint8_t selfAddress) { + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + + if (mode_ != Unassigned) { + i2c_slave_deinit(i2c()); + } + mode_ = Slave; + bufLen_ = 0; + bufPos_ = 0; + i2c_slave_init(i2c(), selfAddress, &handleEvent); +} + +void TwoWire::beginTransmission(uint8_t address) { + assert(mode_ == Master); // not allowed for slave + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + assert(address != NO_ADDRESS); + + txAddress_ = address; + bufLen_ = 0; + bufPos_ = 0; +} + +uint8_t TwoWire::endTransmission(bool sendStop) { + assert(mode_ == Master); // not allowed for slave + assert(txAddress_ != NO_ADDRESS); // must follow beginTransmission() + assert(bufPos_ == 0); + + int result = i2c_write_blocking(i2c(), txAddress_, buf_, bufLen_, !sendStop); + txAddress_ = NO_ADDRESS; + bufLen_ = 0; + if (result < 0) { + return 4; // other error + } else if (result < bufLen_) { + return 3; // data transfer interrupted after NACK + } else { + return 0; // success; + } +} + +uint8_t TwoWire::requestFrom(uint8_t address, size_t count, bool sendStop) { + assert(mode_ == Master); // not allowed for slave + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + + count = MIN(count, WIRE_BUFFER_LENGTH); + int result = i2c_read_blocking(i2c(), address, buf_, count, !sendStop); + if (result < 0) { + result = 0; + } + bufLen_ = (uint8_t)result; + bufPos_ = 0; + return (uint8_t)result; +} + +size_t TwoWire::write(uint8_t value) { + assert(mode_ != Unassigned); // begin not called + + if (mode_ == Master) { + assert(txAddress_ != NO_ADDRESS); // allowed between begin...end transmission + if (bufLen_ == WIRE_BUFFER_LENGTH) { + return 0; // buffer is full + } + buf_[bufLen_++] = value; + } else { + auto i2c = this->i2c(); + auto hw = i2c_get_hw(i2c); + while ((hw->status & I2C_IC_STATUS_TFNF_BITS) == 0) { + tight_loop_contents(); // wait until Tx FIFO is no longer full + } + i2c_write_byte(i2c, value); + } + return 1; +} + +size_t TwoWire::write(const uint8_t *data, size_t size) { + assert(mode_ != Unassigned); // missing begin + + if (mode_ == Master) { + assert(txAddress_ != NO_ADDRESS); // allowed between begin...end transmission + size = MIN(size, WIRE_BUFFER_LENGTH - (size_t)bufLen_); + for (size_t i = 0; i < size; i++) { + buf_[bufLen_++] = data[i]; + } + } else { + auto i2c = this->i2c(); + auto hw = i2c_get_hw(i2c); + for (size_t i = 0; i < size; i++) { + while ((hw->status & I2C_IC_STATUS_TFNF_BITS) == 0) { + tight_loop_contents(); // wait until Tx FIFO is no longer full + } + i2c_write_byte(i2c, data[i]); + } + } + return size; +} + +void TwoWire::onReceive(WireReceiveHandler handler) { + receiveHandler_ = handler; +} + +void TwoWire::onRequest(WireRequestHandler handler) { + requestHandler_ = handler; +} + +// +// private members +// + +void __not_in_flash_func(TwoWire::handleEvent)(i2c_inst_t *i2c, i2c_slave_event_t event) { + auto &wire = (i2c_hw_index(i2c) == 0 ? Wire : Wire1); + assert(wire.mode_ == TwoWire::Slave); + assert(wire.bufPos_ == 0); + + switch (event) { + case I2C_SLAVE_RECEIVE: + for (size_t n = i2c_get_read_available(i2c); n > 0; n--) { + uint8_t value = i2c_read_byte(i2c); + if (wire.bufLen_ < WIRE_BUFFER_LENGTH) { + wire.buf_[wire.bufLen_++] = value; + } else { + // we can't respond with NACK when the buffer is full on DW_apb_i2c, + // so the excess data is simply discarded + } + } + break; + case I2C_SLAVE_REQUEST: + assert(wire.bufLen_ == 0); + assert(wire.bufPos_ == 0); + if (wire.requestHandler_ != nullptr) { + wire.requestHandler_(); + } + break; + case I2C_SLAVE_FINISH: + if (0 < wire.bufLen_) { + if (wire.receiveHandler_ != nullptr) { + wire.receiveHandler_(wire.bufLen_); + } + wire.bufLen_ = 0; + wire.bufPos_ = 0; + } + assert(wire.bufPos_ == 0); + break; + default: + break; + } +} diff --git a/firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.h b/firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.h new file mode 100644 index 0000000..33a28d9 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/example_mem_wire/Wire.h @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#ifndef _WIRE_H_ +#define _WIRE_H_ + +#include + +/** \file Wire.h + * + * \brief Wire API wrapper for I2C. + */ + +#ifndef WIRE_BUFFER_LENGTH +#define WIRE_BUFFER_LENGTH 32 +#endif + +/** + * \brief Called in slave mode after receiving data from master. + * + * The received data is buffered internally, and the handler is called once the transfer has + * completed (after the master sends a Stop or Start signal). + * + * The maximum transfer size is determined by WIRE_BUFFER_LENGTH. Because of how the I2C + * hardware operates on RP2040, there is no way to NACK once the buffer is full, so excess + * data is simply discarded. + * + * \param count The number of bytes available for reading. + */ +using WireReceiveHandler = void (*)(int count); + +/** + * \brief Called in slave mode when the master is requesting data. + */ +using WireRequestHandler = void (*)(); + +/** + * \brief Wire API wrapper. + */ +class TwoWire final +{ +public: + /** + * \brief Don't call, use the global Wire and Wire1 instances instead. + */ + TwoWire(); + + /** + * \brief Get the associated I2C instance (i2c0 or i2c1). + */ + i2c_inst_t *i2c() const; + + /** + * \brief Initialize in master mode. + * + * Note: The Wire library typically initializes predefined I2C pins and enables internal + * pull-up resistors here. In this implementation, the user is responsible for setting + * up the I2C instance and GPIO pins in advance. + */ + void begin(); + + /** + * \brief Initialize in slave mode. + * + * Note: The Wire library typically initializes predefined I2C pins and enables internal + * pull-up resistors here. In this implementation, the user is responsible for setting + * up the I2C instance and GPIO pins in advance. + * + * \param selfAddress Slave address. + */ + void begin(uint8_t selfAddress); + + /** + * \brief Begin writing to a slave. + * + * Available in master mode. + * + * The maximum transfer size is determined by WIRE_BUFFER_LENGTH. Excess data will be ignored. + * + * \param address Slave address. + */ + void beginTransmission(uint8_t address); + + /** + * \brief Finish writing to the slave. + * + * Available in master mode. + * + * Flushes the write buffer and blocks until completion. + * + * \param sendStop Whether to send Stop signal at the end. + * \return 0 on success. + * 3 if transfer was interrupted by the slave via NACK. + * 4 on other error. + */ + uint8_t endTransmission(bool sendStop = true); + + /** + * \brief Read data from a slave. + * + * Available in master mode. + * + * \param address Slave address. + * \param count Amount of data to read. + * \param sendStop Whether to send Stop signal at the end. + * \return 0 on error. + * `count` on success. + */ + uint8_t requestFrom(uint8_t address, size_t count, bool sendStop); + + /** + * \brief Get the amount of data in the read buffer. + */ + size_t available() const; + + /** + * \brief Get the next byte from the read buffer without removing it. + * + * \return the buffer value, or -1 if the read buffer is empty. + */ + int peek() const; + + /** + * \brief Get the next byte from the read buffer. + * + * \return the buffer value, or -1 if the read buffer is empty. + */ + int read(); + + /** + * \brief Write a single byte. + * + * \param value Byte value. + * \return 1 on success. + * 0 if the buffer is full (when transmitting in master mode). + */ + size_t write(uint8_t value); + + /** + * \brief Write data. + * + * \param data Data pointer. + * \param size Data size. + * \return The amount of data written. May be less than len if the buffer fills up (when + * transmitting in master mode). + */ + size_t write(const uint8_t *data, size_t size); + + /** + * \brief Set the receive handler for slave mode. + * + * \param handler Receive handler. + */ + void onReceive(WireReceiveHandler handler); + + /** + * \brief Set the request handler for slave mode. + * + * \param handler Request handler. + */ + void onRequest(WireRequestHandler handler); + +private: + static constexpr uint8_t NO_ADDRESS = 255; + + enum Mode : uint8_t + { + Unassigned, + Master, + Slave, + }; + + TwoWire(const TwoWire &other) = delete; // not copyable + + TwoWire &operator=(const TwoWire &other) = delete; // not copyable + + static void handleEvent(i2c_inst_t *i2c, i2c_slave_event_t event); + + WireReceiveHandler receiveHandler_ = nullptr; + WireRequestHandler requestHandler_ = nullptr; + Mode mode_ = Unassigned; + uint8_t txAddress_ = 255; + uint8_t buf_[WIRE_BUFFER_LENGTH]; + uint8_t bufLen_ = 0; + uint8_t bufPos_ = 0; +}; + +/** + * \brief Wire instance for i2c0 + */ +extern TwoWire Wire; + +/** + * \brief Wire instance for i2c1 + */ +extern TwoWire Wire1; + +// +// inline members +// + +inline size_t TwoWire::available() const { + assert(mode_ != Unassigned); // missing begin + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + + return bufLen_ - bufPos_; +} + +inline int TwoWire::peek() const { + assert(mode_ != Unassigned); // missing begin + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + + return bufPos_ < bufLen_ ? (int)buf_[bufPos_] : -1; +} + +inline int TwoWire::read() { + assert(mode_ != Unassigned); // missing begin + assert(txAddress_ == NO_ADDRESS); // not allowed during transmission + + return bufPos_ < bufLen_ ? (int)buf_[bufPos_++] : -1; +} + +#endif diff --git a/firmware/third_party/pico_i2c_slave/example_mem_wire/example_mem_wire.cpp b/firmware/third_party/pico_i2c_slave/example_mem_wire/example_mem_wire.cpp new file mode 100644 index 0000000..a129627 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/example_mem_wire/example_mem_wire.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#include "Wire.h" +#include +#include +#include + +static const uint I2C_SLAVE_ADDRESS = 0x17; +static const uint I2C_BAUDRATE = 100000; // 100 kHz + +// For this example, we run both the master and slave from the same board. +// You'll need to wire pin GP4 to GP6 (SDA), and pin GP5 to GP7 (SCL). +static const uint I2C_SLAVE_SDA_PIN = PICO_DEFAULT_I2C_SDA_PIN; // 4 +static const uint I2C_SLAVE_SCL_PIN = PICO_DEFAULT_I2C_SCL_PIN; // 5 +static const uint I2C_MASTER_SDA_PIN = 6; +static const uint I2C_MASTER_SCL_PIN = 7; + +// The slave implements a 256 byte memory. To write a series of bytes, the master first +// writes the memory address, followed by the data. The address is automatically incremented +// for each byte transferred, looping back to 0 upon reaching the end. Reading is done +// sequentially from the current memory address. +static struct +{ + uint8_t mem[256]; + uint8_t mem_address; +} context; + +static void slave_on_receive(int count) { + // writes always start with the memory address + hard_assert(Wire.available()); + context.mem_address = (uint8_t)Wire.read(); + + while (Wire.available()) { + // save into memory + context.mem[context.mem_address++] = (uint8_t)Wire.read(); + } +} + +static void slave_on_request() { + // load from memory + uint8_t value = context.mem[context.mem_address++]; + Wire.write(value); +} + +static void setup_slave() { + gpio_init(I2C_SLAVE_SDA_PIN); + gpio_set_function(I2C_SLAVE_SDA_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SLAVE_SDA_PIN); + + gpio_init(I2C_SLAVE_SCL_PIN); + gpio_set_function(I2C_SLAVE_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SLAVE_SCL_PIN); + + i2c_init(i2c0, I2C_BAUDRATE); + + // setup Wire for slave mode + Wire.onReceive(slave_on_receive); + Wire.onRequest(slave_on_request); + // in this implementation, the user is responsible for initializing the I2C instance and GPIO + // pins before calling Wire::begin() + Wire.begin(I2C_SLAVE_ADDRESS); +} + +static void run_master() { + gpio_init(I2C_MASTER_SDA_PIN); + gpio_set_function(I2C_MASTER_SDA_PIN, GPIO_FUNC_I2C); + // pull-ups are already active on slave side, this is just a fail-safe in case the wiring is faulty + gpio_pull_up(I2C_MASTER_SDA_PIN); + + gpio_init(I2C_MASTER_SCL_PIN); + gpio_set_function(I2C_MASTER_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_MASTER_SCL_PIN); + + i2c_init(i2c1, I2C_BAUDRATE); + + // setup Wire for master mode on i2c1 + Wire1.begin(); + + for (uint8_t mem_address = 0;; mem_address = (mem_address + 32) % 256) { + char msg[32]; + snprintf(msg, sizeof(msg), "Hello, I2C slave! - 0x%02X", mem_address); + uint8_t msg_len = strlen(msg); + + // write message at mem_address + printf("Write at 0x%02X: '%s'\n", mem_address, msg); + Wire1.beginTransmission(I2C_SLAVE_ADDRESS); + Wire1.write(mem_address); + Wire1.write((const uint8_t *)msg, msg_len); + uint8_t result = Wire1.endTransmission(true /* sendStop */); + if (result != 0) { + puts("Couldn't write to slave, please check your wiring!"); + return; + } + + // seek to mem_address + Wire1.beginTransmission(I2C_SLAVE_ADDRESS); + Wire1.write(mem_address); + result = Wire1.endTransmission(false /* sendStop */); + hard_assert(result == 0); + uint8_t buf[32]; + // partial read + uint8_t split = 5; + uint8_t count = Wire1.requestFrom(I2C_SLAVE_ADDRESS, split, false /* sendStop */); + hard_assert(count == split); + for (size_t i = 0; i < count; i++) { + buf[i] = Wire1.read(); + } + buf[count] = '\0'; + printf("Read at 0x%02X: '%s'\n", mem_address, buf); + hard_assert(memcmp(buf, msg, split) == 0); + // read the remaining bytes, continuing from last address + count = Wire1.requestFrom(I2C_SLAVE_ADDRESS, msg_len - split, true /* sendStop */); + hard_assert(count == (size_t)(msg_len - split)); + for (size_t i = 0; i < count; i++) { + buf[i] = Wire1.read(); + } + buf[count] = '\0'; + printf("Read at 0x%02X: '%s'\n", mem_address + split, buf); + hard_assert(memcmp(buf, msg + split, msg_len - split) == 0); + + puts(""); + sleep_ms(2000); + } +} + +int main() { + stdio_init_all(); + puts("\nI2C slave example with Wire API"); + + setup_slave(); + run_master(); +} diff --git a/firmware/third_party/pico_i2c_slave/i2c_slave/CMakeLists.txt b/firmware/third_party/pico_i2c_slave/i2c_slave/CMakeLists.txt new file mode 100644 index 0000000..02e8454 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/i2c_slave/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(i2c_slave INTERFACE) + +target_include_directories(i2c_slave + INTERFACE + ./include) + +target_sources(i2c_slave + INTERFACE + i2c_slave.c +) + +target_link_libraries(i2c_slave + INTERFACE + hardware_i2c + hardware_irq +) diff --git a/firmware/third_party/pico_i2c_slave/i2c_slave/i2c_slave.c b/firmware/third_party/pico_i2c_slave/i2c_slave/i2c_slave.c new file mode 100644 index 0000000..16e2213 --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/i2c_slave/i2c_slave.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +typedef struct i2c_slave_t +{ + i2c_inst_t *i2c; + i2c_slave_handler_t handler; + bool transfer_in_progress; +} i2c_slave_t; + +static i2c_slave_t i2c_slaves[2]; + +static inline void finish_transfer(i2c_slave_t *slave) { + if (slave->transfer_in_progress) { + slave->handler(slave->i2c, I2C_SLAVE_FINISH); + slave->transfer_in_progress = false; + } +} + +// Improved version of the IRQ handler above, reduces the error rate +#pragma GCC push_options +#pragma GCC optimize ("O0") +static void __not_in_flash_func(i2c_slave_irq_handler)(i2c_slave_t *slave) { + i2c_inst_t *i2c = slave->i2c; + i2c_hw_t *hw = i2c_get_hw(i2c); + + uint32_t intr_stat = hw->intr_stat; + if (intr_stat == 0) { + return; + } + if (intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS) { + slave->transfer_in_progress = true; + slave->handler(i2c, I2C_SLAVE_RECEIVE); + } + if (intr_stat & I2C_IC_INTR_STAT_R_RD_REQ_BITS) { + hw->clr_rd_req; + slave->transfer_in_progress = true; + slave->handler(i2c, I2C_SLAVE_REQUEST); + } + if (intr_stat & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) { + hw->clr_tx_abrt; + finish_transfer(slave); + } + if (intr_stat & I2C_IC_INTR_STAT_R_START_DET_BITS) { + hw->clr_start_det; + finish_transfer(slave); + } + if (intr_stat & I2C_IC_INTR_STAT_R_STOP_DET_BITS) { + hw->clr_stop_det; + finish_transfer(slave); + } +} +#pragma GCC pop_option + +static void __not_in_flash_func(i2c0_slave_irq_handler)() { + i2c_slave_irq_handler(&i2c_slaves[0]); +} + +static void __not_in_flash_func(i2c1_slave_irq_handler)() { + i2c_slave_irq_handler(&i2c_slaves[1]); +} + +void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler) { + assert(i2c == i2c0 || i2c == i2c1); + assert(handler != NULL); + + uint i2c_index = i2c_hw_index(i2c); + i2c_slave_t *slave = &i2c_slaves[i2c_index]; + slave->i2c = i2c; + slave->handler = handler; + + // Note: The I2C slave does clock stretching implicitly after a RD_REQ, while the Tx FIFO is empty. + // There is also an option to enable clock stretching while the Rx FIFO is full, but we leave it + // disabled since the Rx FIFO should never fill up (unless slave->handler() is way too slow). + i2c_set_slave_mode(i2c, true, address); + + i2c_hw_t *hw = i2c_get_hw(i2c); + // unmask necessary interrupts + hw->intr_mask = I2C_IC_INTR_MASK_M_RX_FULL_BITS | I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_RAW_INTR_STAT_TX_ABRT_BITS | I2C_IC_INTR_MASK_M_STOP_DET_BITS | I2C_IC_INTR_MASK_M_START_DET_BITS; + + // enable clock stretching for when RX FIFI is full -> give more time to ISR + hw->con = hw->con | I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL_VALUE_ENABLED; + + // enable interrupt for current core + uint num = I2C0_IRQ + i2c_index; + irq_set_exclusive_handler(num, i2c_index == 0 ? i2c0_slave_irq_handler : i2c1_slave_irq_handler); + irq_set_enabled(num, true); +} + +void i2c_slave_deinit(i2c_inst_t *i2c) { + assert(i2c == i2c0 || i2c == i2c1); + + uint i2c_index = i2c_hw_index(i2c); + i2c_slave_t *slave = &i2c_slaves[i2c_index]; + assert(slave->i2c == i2c); // should be called after i2c_slave_init() + + slave->i2c = NULL; + slave->handler = NULL; + slave->transfer_in_progress = false; + + uint num = I2C0_IRQ + i2c_index; + irq_set_enabled(num, false); + irq_remove_handler(num, i2c_index == 0 ? i2c0_slave_irq_handler : i2c1_slave_irq_handler); + + i2c_hw_t *hw = i2c_get_hw(i2c); + hw->intr_mask = I2C_IC_INTR_MASK_RESET; + + i2c_set_slave_mode(i2c, false, 0); +} diff --git a/firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_fifo.h b/firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_fifo.h new file mode 100644 index 0000000..805c00e --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_fifo.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#ifndef _I2C_FIFO_H_ +#define _I2C_FIFO_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file i2c_fifo.h + * + * \brief I2C non-blocking r/w. + */ + +/** + * \brief Pop a byte from I2C Rx FIFO. + * + * This function is non-blocking and assumes the Rx FIFO isn't empty. + * + * \param i2c I2C instance. + * \return uint8_t Byte value. + */ +static inline uint8_t i2c_read_byte(i2c_inst_t *i2c) { + i2c_hw_t *hw = i2c_get_hw(i2c); + assert(hw->status & I2C_IC_STATUS_RFNE_BITS); // Rx FIFO must not be empty + return (uint8_t)hw->data_cmd; +} + +/** + * \brief Push a byte into I2C Tx FIFO. + * + * This function is non-blocking and assumes the Tx FIFO isn't full. + * + * \param i2c I2C instance. + * \param value Byte value. + */ +static inline void i2c_write_byte(i2c_inst_t *i2c, uint8_t value) { + i2c_hw_t *hw = i2c_get_hw(i2c); + assert(hw->status & I2C_IC_STATUS_TFNF_BITS); // Tx FIFO must not be full + hw->data_cmd = value; +} + +#ifdef __cplusplus +} +#endif + +#endif // _I2C_FIFO_H_ diff --git a/firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_slave.h b/firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_slave.h new file mode 100644 index 0000000..48ac3fb --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/i2c_slave/include/i2c_slave.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Valentin Milea + * + * SPDX-License-Identifier: MIT + */ + +#ifndef _I2C_SLAVE_H_ +#define _I2C_SLAVE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file i2c_slave.h + * + * \brief I2C slave setup. + */ + +/** + * \brief I2C slave event types. + */ +typedef enum i2c_slave_event_t +{ + I2C_SLAVE_RECEIVE = 0, /**< Data from master is available for reading. Slave must read from Rx FIFO. */ + I2C_SLAVE_REQUEST = 1, /**< Master is requesting data. Slave must write into Tx FIFO. */ + I2C_SLAVE_FINISH = 2, /**< Master has sent a Stop or Restart signal. Slave may prepare for the next transfer. */ +} i2c_slave_event_t; + +/** + * \brief I2C slave event handler + * + * The event handler will run from the I2C ISR, so it should return quickly (under 25 us at 400 kb/s). + * Avoid blocking inside the handler and split large data transfers across multiple calls for best results. + * When sending data to master, up to `i2c_get_write_available()` bytes can be written without blocking. + * When receiving data from master, up to `i2c_get_read_available()` bytes can be read without blocking. + * + * \param i2c Slave I2C instance. + * \param event Event type. + */ +typedef void (*i2c_slave_handler_t)(i2c_inst_t *i2c, i2c_slave_event_t event); + +/** + * \brief Configure I2C instance for slave mode. + * + * \param i2c I2C instance. + * \param address 7-bit slave address. + * \param handler Called on events from I2C master. It will run from the I2C ISR, on the CPU core + * where the slave was initialized. + */ +void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler); + +/** + * \brief Restore I2C instance to master mode. + * + * \param i2c I2C instance. + */ +void i2c_slave_deinit(i2c_inst_t *i2c); + +#ifdef __cplusplus +} +#endif + +#endif // _I2C_SLAVE_H_ diff --git a/firmware/third_party/pico_i2c_slave/pico_sdk_import.cmake b/firmware/third_party/pico_i2c_slave/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/firmware/third_party/pico_i2c_slave/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# 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})