Add i2c split keyboard support for rp2040

This commit is contained in:
Charlotte 🦝 Delenk 2024-04-04 13:47:16 +01:00
parent 573d63a180
commit e8d2aca2e9
11 changed files with 279 additions and 4 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

1
.gitignore vendored
View file

@ -116,3 +116,4 @@ compile_commands.json
# VIA(L) files that don't belong in QMK repo
via*.json
/keyboards/**/keymaps/vial/*
.direnv

View file

@ -3,6 +3,10 @@
#pragma once
#define SPLIT_HAND_PIN GP21
#define SPLIT_HAND_PIN_LOW_IS_LEFT
#define SPLIT_USB_DETECT
/*
* Feature disable options
* These options are also useful to firmware size reduction.

View file

@ -0,0 +1,74 @@
// rp2040 implementation of i2c_master.c
#include "i2c_master.h"
#include "hardware/i2c.h"
#include "hardware/gpio.h"
#include <string.h>
void cos_i2c_init(void) {
static bool is_initialized = false;
if (!is_initialized) {
is_initialized = true;
gpio_set_function(24, GPIO_FUNC_I2C);
gpio_set_function(25, GPIO_FUNC_I2C);
gpio_pull_up(24);
gpio_pull_up(25);
}
}
static bool enabled = false;
static void init_i2c(void) {
if(!enabled) {
i2c_init(i2c0, 100 * 1000); // 100kb/s
enabled = true;
}
}
static uint8_t i2c_address;
static i2c_status_t pico_to_qmk(int result) {
if(result == PICO_ERROR_TIMEOUT)
return I2C_STATUS_TIMEOUT;
if(result < 0)
return I2C_STATUS_ERROR;
return I2C_STATUS_SUCCESS;
}
i2c_status_t cos_i2c_start(uint8_t address) {
i2c_address = address;
init_i2c();
return I2C_STATUS_SUCCESS;
}
i2c_status_t cos_i2c_transmit(uint8_t address, const uint8_t *data, uint16_t length, uint16_t timeout) {
cos_i2c_start(address);
return pico_to_qmk(i2c_write_timeout_us(i2c0, address, data, (size_t)length, false, (uint32_t)timeout * 1000));
}
i2c_status_t cos_i2c_receive(uint8_t address, uint8_t * data, uint16_t length, uint16_t timeout) {
cos_i2c_start(address);
return pico_to_qmk(i2c_read_timeout_us(i2c0, address, data, (size_t)length, false, (uint32_t)timeout * 1000));
}
i2c_status_t cos_i2c_writeReg(uint8_t devaddr, uint8_t regaddr, const uint8_t* data, uint16_t length, uint16_t timeout) {
cos_i2c_start(devaddr);
uint8_t complete_packet[length + 1];
memcpy(complete_packet + 1, data, (size_t)length);
complete_packet[0] = regaddr;
return cos_i2c_transmit(devaddr, complete_packet, length + 1, timeout);
}
i2c_status_t cos_i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout){
cos_i2c_start(devaddr);
// TODO: uses 2x timeout
i2c_status_t reg_result = cos_i2c_transmit(devaddr, &regaddr, 1, timeout);
if(reg_result != I2C_STATUS_SUCCESS)
return reg_result;
return cos_i2c_receive(devaddr, data, length, timeout);
}
void cos_i2c_stop(void) {
enabled = false;
i2c_deinit(i2c0);
}

119
keyboards/rkb1/i2c_slave.c Normal file
View file

@ -0,0 +1,119 @@
#include "i2c_slave.h"
#include "hardware/i2c.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"
#include <string.h>
#include "transactions.h"
// Code taken from here: https://github.com/harsha-vk/weeder_bot/blob/0bb2ac3f627dc2b3867480d210c0536ae0f684a5/assets/raspberry_pi_pico_code/src/i2c_slave.cpp
static volatile bool is_callback_executor = false;
volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
static volatile uint8_t buffer_address;
static volatile bool sub_has_register_set = false;
static volatile bool transfer_in_progress = false;
enum i2c_event_t {
I2C_SUB_RECEIVE,
I2C_SUB_REQUEST,
I2C_SUB_FINISH
};
static void i2c_sub_handler(enum i2c_event_t event) {
//uint8_t ack = 1;
switch(event) {
case I2C_SUB_RECEIVE: {
while(i2c_get_read_available(i2c0)) {
if(!sub_has_register_set) {
i2c_read_raw_blocking(i2c0, (uint8_t *)&buffer_address, 1);
if (buffer_address >= I2C_SLAVE_REG_COUNT) {
//ack = 0;
buffer_address = 0;
}
sub_has_register_set = true;
is_callback_executor = buffer_address == split_transaction_table[I2C_EXECUTE_CALLBACK].initiator2target_offset;
} else {
// find out how many bytes we can safely read
uint32_t bytes_left = I2C_SLAVE_REG_COUNT - buffer_address;
uint32_t bytes_to_read = i2c_get_read_available(i2c0);
uint32_t num_bytes = (bytes_left < bytes_to_read) ? bytes_left : bytes_to_read;
i2c_read_raw_blocking(i2c0, (uint8_t *)i2c_slave_reg + buffer_address, num_bytes);
buffer_address += num_bytes;
if(buffer_address >= I2C_SLAVE_REG_COUNT)
buffer_address = 0;
// If we're intending to execute a transaction callback, do so, as we've just received the transaction ID
if (is_callback_executor) {
split_transaction_desc_t *trans = &split_transaction_table[split_shmem->transaction_id];
if (trans->slave_callback) {
trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
}
}
}
}
}
break;
case I2C_SUB_REQUEST: {
i2c_write_raw_blocking(i2c0, (uint8_t*)i2c_slave_reg + buffer_address, 1);
buffer_address++;
break;
}
case I2C_SUB_FINISH:
sub_has_register_set = false;
is_callback_executor = false;
break;
}
// TODO: ack??
}
static void finish_transfer(void) {
transfer_in_progress = false;
i2c_sub_handler(I2C_SUB_FINISH);
}
static void i2c_handler(void) {
i2c_hw_t *hw = i2c_get_hw(i2c0);
uint32_t intr_stat = hw->intr_stat;
if(intr_stat == 0) return; // spurious
if(intr_stat & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) {
hw->clr_tx_abrt;
finish_transfer();
}
if(intr_stat & I2C_IC_INTR_STAT_R_START_DET_BITS) {
hw->clr_start_det;
finish_transfer();
}
if(intr_stat & I2C_IC_INTR_STAT_R_STOP_DET_BITS) {
hw->clr_stop_det;
finish_transfer();
}
if(intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS) {
transfer_in_progress = true;
i2c_sub_handler(I2C_SUB_RECEIVE);
}
if(intr_stat & I2C_IC_INTR_STAT_R_RD_REQ_BITS) {
hw->clr_rd_req;
transfer_in_progress = true;
i2c_sub_handler(I2C_SUB_REQUEST);
}
}
void cos_i2c_slave_init(uint8_t address) {
i2c_init(i2c0, 100 * 1000);
i2c_set_slave_mode(i2c0, true, address);
gpio_set_function(24, GPIO_FUNC_I2C);
gpio_set_function(25, GPIO_FUNC_I2C);
gpio_pull_up(24);
gpio_pull_up(25);
i2c_hw_t *hw = i2c_get_hw(i2c0);
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;
irq_set_exclusive_handler(I2C0_IRQ, i2c_handler);
irq_set_enabled(I2C0_IRQ, true);
}
void cos_i2c_slave_stop(void) {
i2c_set_slave_mode(i2c0, false, 0);
i2c_deinit(i2c0);
}

View file

@ -0,0 +1,41 @@
/* Copyright (C) 2019 Elia Ritterbusch
+
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* Library made by: g4lvanix
* GitHub repository: https://github.com/g4lvanix/I2C-slave-lib
Info: Inititate the library by giving the required address.
Read or write to the necessary buffer according to the opperation.
*/
#pragma once
#ifndef I2C_SLAVE_REG_COUNT
# if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
# include "transport.h"
# define I2C_SLAVE_REG_COUNT sizeof(split_shared_memory_t)
# else // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
# define I2C_SLAVE_REG_COUNT 30
# endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
#endif // I2C_SLAVE_REG_COUNT
_Static_assert(I2C_SLAVE_REG_COUNT < 256, "I2C target registers must be single byte");
extern volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
void cos_i2c_slave_init(uint8_t address);
void cos_i2c_slave_stop(void);

View file

@ -1 +1,9 @@
# This file intentionally left blank
SPLIT_KEYBOARD = yes
SPLIT_TRANSPORT = custom
RP2040_INTRINSICS_ENABLED = yes
SRC += i2c_slave.c i2c_master.c
QUANTUM_SRC += $(QUANTUM_DIR)/split_common/transport.c $(QUANTUM_DIR)/split_common/transactions.c
OPT_DEFS += -DSPLIT_COMMON_TRANSACTIONS
COMMON_VPATH += $(QUANTUM_PATH)/split_common
EXTRAINCDIRS += $(BOARD_PATH)

View file

@ -207,4 +207,4 @@ __attribute__((weak)) i2c_status_t i2c_ping_address(uint8_t address, uint16_t ti
// This approach may produce false negative results for I2C devices that do not respond to a register 0 read request.
uint8_t data = 0;
return i2c_readReg(address, 0, &data, sizeof(data), timeout);
}
}

View file

@ -3,6 +3,13 @@
#pragma once
#ifndef __ASSEMBLER__
#define _PICO_ASSERT_H
#include <stdbool.h>
#include <assert.h>
#define PARAM_ASSERTIONS_ENABLED(x) /* x */
#define invalid_params_if(x, test) /* x test */
#define valid_params_if(x, test) /* x test */
#define hard_assert_if(x, test) /* x test */
# include "hardware/flash.h"
#endif
@ -30,3 +37,4 @@
#ifndef WEAR_LEVELING_RP2040_FLASH_BASE
# define WEAR_LEVELING_RP2040_FLASH_BASE ((WEAR_LEVELING_RP2040_FLASH_SIZE) - (WEAR_LEVELING_BACKING_SIZE))
#endif

View file

@ -31,7 +31,20 @@ PICOSDKSRC = $(PICOSDKROOT)/src/rp2_common/hardware_clocks/clocks.c \
$(PICOSDKROOT)/src/rp2_common/hardware_claim/claim.c \
$(PICOSDKROOT)/src/rp2_common/hardware_watchdog/watchdog.c \
$(PICOSDKROOT)/src/rp2_common/hardware_xosc/xosc.c \
$(PICOSDKROOT)/src/rp2_common/pico_bootrom/bootrom.c
$(PICOSDKROOT)/src/rp2_common/hardware_i2c/i2c.c \
$(PICOSDKROOT)/src/rp2_common/hardware_irq/irq.c \
$(PICOSDKROOT)/src/rp2_common/hardware_irq/irq_handler_chain.S \
$(PICOSDKROOT)/src/rp2_common/hardware_timer/timer.c \
$(PICOSDKROOT)/src/rp2_common/pico_bootrom/bootrom.c \
$(PICOSDKROOT)/src/common/pico_time/time.c \
$(PICOSDKROOT)/src/common/pico_time/timeout_helper.c \
$(PICOSDKROOT)/src/common/pico_util/datetime.c \
$(PICOSDKROOT)/src/common/pico_util/pheap.c \
$(PICOSDKROOT)/src/common/pico_util/queue.c \
$(PICOSDKROOT)/src/common/pico_sync/critical_section.c \
$(PICOSDKROOT)/src/common/pico_sync/lock_core.c \
$(PICOSDKROOT)/src/common/pico_sync/mutex.c \
$(PICOSDKROOT)/src/common/pico_sync/sem.c \
PICOSDKINC = $(CHIBIOS)//os/various/pico_bindings/dumb/include \
$(PICOSDKROOT)/src/common/pico_base/include \
@ -49,10 +62,15 @@ PICOSDKINC = $(CHIBIOS)//os/various/pico_bindings/dumb/include \
$(PICOSDKROOT)/src/rp2_common/hardware_resets/include \
$(PICOSDKROOT)/src/rp2_common/hardware_watchdog/include \
$(PICOSDKROOT)/src/rp2_common/hardware_xosc/include \
$(PICOSDKROOT)/src/rp2_common/hardware_i2c/include \
$(PICOSDKROOT)/src/rp2_common/hardware_timer/include \
$(PICOSDKROOT)/src/rp2040/hardware_regs/include \
$(PICOSDKROOT)/src/rp2040/hardware_structs/include \
$(PICOSDKROOT)/src/boards/include \
$(PICOSDKROOT)/src/rp2_common/pico_bootrom/include
$(PICOSDKROOT)/src/rp2_common/pico_bootrom/include \
$(PICOSDKROOT)/src/common/pico_time/include \
$(PICOSDKROOT)/src/common/pico_util/include \
$(PICOSDKROOT)/src/common/pico_sync/include
PLATFORM_SRC += $(PICOSDKSRC)
EXTRAINCDIRS += $(PICOSDKINC)

View file

@ -128,3 +128,4 @@ bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[])
void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
transactions_slave(master_matrix, slave_matrix);
}