1
0
mirror of https://github.com/tdaede/td-io.git synced 2024-11-30 17:34:33 +01:00

Add edid emulation support.

Uses third party pico_i2c_slave library.
This commit is contained in:
Thomas Daede 2023-02-07 02:01:57 -08:00
parent 9cc85b8020
commit abffe47e52
19 changed files with 1264 additions and 2 deletions

View File

@ -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)

46
firmware/edid.c Normal file
View File

@ -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);
}

View File

@ -1,6 +1,7 @@
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "edid.h"
#include <string.h>
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) {

View File

@ -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
...

View File

@ -0,0 +1,4 @@
.DS_Store
.vscode
build
*.bat

View File

@ -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)

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
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.

View File

@ -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 <valentin.milea@gmail.com>

View File

@ -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)

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <i2c_fifo.h>
#include <i2c_slave.h>
#include <pico/stdlib.h>
#include <stdio.h>
#include <string.h>
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();
}

View File

@ -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)

View File

@ -0,0 +1,180 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include "Wire.h"
#include <i2c_fifo.h>
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;
}
}

View File

@ -0,0 +1,226 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef _WIRE_H_
#define _WIRE_H_
#include <i2c_slave.h>
/** \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

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include "Wire.h"
#include <pico/stdlib.h>
#include <stdio.h>
#include <string.h>
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();
}

View File

@ -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
)

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <i2c_slave.h>
#include <hardware/irq.h>
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);
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef _I2C_FIFO_H_
#define _I2C_FIFO_H_
#include <hardware/i2c.h>
#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_

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2021 Valentin Milea <valentin.milea@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef _I2C_SLAVE_H_
#define _I2C_SLAVE_H_
#include <hardware/i2c.h>
#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_

View File

@ -0,0 +1,62 @@
# This is a copy of <PICO_SDK_PATH>/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})