/* * Single-File-Header for using the I2C peripheral in slave mode * * MIT License * * Copyright (c) 2023 Renze Nicolai * * 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 __I2C_SLAVE_H #define __I2C_SLAVE_H #include <stdint.h> #define APB_CLOCK SYSTEM_CORE_CLOCK struct _i2c_slave_state { uint8_t first_write; uint8_t offset; uint8_t position; volatile uint8_t* volatile registers; uint8_t size; } i2c_slave_state; void SetupI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size) { i2c_slave_state.first_write = 1; i2c_slave_state.offset = 0; i2c_slave_state.position = 0; i2c_slave_state.registers = registers; i2c_slave_state.size = size; // Enable GPIOC and I2C RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; RCC->APB1PCENR |= RCC_APB1Periph_I2C1; // PC1 is SDA, 10MHz Output, alt func, open-drain GPIOC->CFGLR &= ~(0xf<<(4*1)); GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1); // PC2 is SCL, 10MHz Output, alt func, open-drain GPIOC->CFGLR &= ~(0xf<<(4*2)); GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2); // Reset I2C1 to init all regs RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; I2C1->CTLR1 |= I2C_CTLR1_SWRST; I2C1->CTLR1 &= ~I2C_CTLR1_SWRST; // Set module clock frequency uint32_t prerate = 2000000; // I2C Logic clock rate, must be higher than the bus clock rate I2C1->CTLR2 |= (APB_CLOCK/prerate) & I2C_CTLR2_FREQ; // Enable interrupts I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN; I2C1->CTLR2 |= I2C_CTLR2_ITEVTEN; // Event interrupt I2C1->CTLR2 |= I2C_CTLR2_ITERREN; // Error interrupt NVIC_EnableIRQ(I2C1_EV_IRQn); // Event interrupt NVIC_SetPriority(I2C1_EV_IRQn, 2 << 4); NVIC_EnableIRQ(I2C1_ER_IRQn); // Error interrupt // Set clock configuration uint32_t clockrate = 1000000; // I2C Bus clock rate, must be lower than the logic clock rate I2C1->CKCFGR = ((APB_CLOCK/(3*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_FS; // Fast mode 33% duty cycle //I2C1->CKCFGR = ((APB_CLOCK/(25*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_DUTY | I2C_CKCFGR_FS; // Fast mode 36% duty cycle //I2C1->CKCFGR = (APB_CLOCK/(2*clockrate))&I2C_CKCFGR_CCR; // Standard mode good to 100kHz // Set I2C address I2C1->OADDR1 = address << 1; // Enable I2C I2C1->CTLR1 |= I2C_CTLR1_PE; // Acknowledge the first address match event when it happens I2C1->CTLR1 |= I2C_CTLR1_ACK; } void I2C1_EV_IRQHandler(void) __attribute__((interrupt)); void I2C1_EV_IRQHandler(void) { uint16_t STAR1, STAR2 __attribute__((unused)); STAR1 = I2C1->STAR1; STAR2 = I2C1->STAR2; I2C1->CTLR1 |= I2C_CTLR1_ACK; if (STAR1 & I2C_STAR1_ADDR) { // Start event i2c_slave_state.first_write = 1; // Next write will be the offset i2c_slave_state.position = i2c_slave_state.offset; // Reset position } if (STAR1 & I2C_STAR1_RXNE) { // Write event if (i2c_slave_state.first_write) { // First byte written, set the offset i2c_slave_state.offset = I2C1->DATAR; i2c_slave_state.position = i2c_slave_state.offset; i2c_slave_state.first_write = 0; } else { // Normal register write if (i2c_slave_state.position < i2c_slave_state.size) { i2c_slave_state.registers[i2c_slave_state.position] = I2C1->DATAR; i2c_slave_state.position++; } } } if (STAR1 & I2C_STAR1_TXE) { // Read event if (i2c_slave_state.position < i2c_slave_state.size) { I2C1->DATAR = i2c_slave_state.registers[i2c_slave_state.position]; i2c_slave_state.position++; } else { I2C1->DATAR = 0x00; } } } void I2C1_ER_IRQHandler(void) __attribute__((interrupt)); void I2C1_ER_IRQHandler(void) { uint16_t STAR1 = I2C1->STAR1; if (STAR1 & I2C_STAR1_BERR) { // Bus error I2C1->STAR1 &= ~(I2C_STAR1_BERR); // Clear error } if (STAR1 & I2C_STAR1_ARLO) { // Arbitration lost error I2C1->STAR1 &= ~(I2C_STAR1_ARLO); // Clear error } if (STAR1 & I2C_STAR1_AF) { // Acknowledge failure I2C1->STAR1 &= ~(I2C_STAR1_AF); // Clear error } } #endif