// Copyright (C) 2025 Thies Lennart Alff // // 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 2 // 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, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, // USA #include "i2c.h" #include "common.h" #include "macros.h" #include #include #define DELIMITER 0 static uint8_t rx_buffer[64]; static uint8_t rx_index = 0; static uint8_t tx_buffer[64]; static uint8_t tx_index = 0; static i2c_data_received_cb_fun data_received_cb_fun = NULL; static inline uint8_t is_rx_buffer_full() { return !(rx_index < LEN(rx_buffer)); } static inline void i2c_ack() { I2C_SERCOM->I2CS.CTRLB.bit.ACKACT = 0; I2C_SERCOM->I2CS.CTRLB.bit.CMD = 0x03; } static inline void i2c_nack() { I2C_SERCOM->I2CS.CTRLB.bit.ACKACT = 1; I2C_SERCOM->I2CS.CTRLB.bit.CMD = 0x02; } static inline void i2c_handle_received_data(uint8_t data) { if (is_rx_buffer_full()) { rx_index = 0; i2c_nack(); return; } rx_buffer[rx_index++] = data; if (is_rx_buffer_full() && (data != DELIMITER)) { DEBUG_PRINT( "Receive buffer full (%hu bytes) but no delimiter. Discarding.\n", rx_index); rx_index = 0; i2c_nack(); return; } if (data == DELIMITER) { DEBUG_PRINT("Delimiter received!\n"); for (int i = 0; i < rx_index; ++i) { DEBUG_PRINT("%hu ", rx_buffer[i]); } DEBUG_PRINT("\n"); // we seem to have received a complete message. if (data_received_cb_fun != NULL) { if (data_received_cb_fun(rx_buffer, rx_index)) { DEBUG_PRINT("ACK valid msg\n"); i2c_ack(); return; } else { DEBUG_PRINT("NACK invalid crc\n"); i2c_nack(); return; } } i2c_nack(); rx_index = 0; return; } i2c_ack(); } void i2c_set_data_received_cb(i2c_data_received_cb_fun cb) { data_received_cb_fun = cb; } void I2C_SERCOM_HANDLER() { if (I2C_SERCOM->I2CS.INTFLAG.bit.AMATCH) { DEBUG_PRINT("i2c address match interrupt.\n"); if (I2C_SERCOM->I2CS.STATUS.bit.SR) { DEBUG_PRINT("i2c repeated start!\n"); } else { DEBUG_PRINT("i2c normal start\n"); } if (I2C_SERCOM->I2CS.STATUS.bit.DIR) { DEBUG_PRINT("i2c master read\n"); tx_index = 0; } else { DEBUG_PRINT("i2c master write\n"); rx_index = 0; } i2c_ack(); } if (I2C_SERCOM->I2CS.INTFLAG.bit.DRDY) { if (I2C_SERCOM->I2CS.STATUS.bit.DIR) { // slave write if (0) { I2C_SERCOM->I2CS.CTRLB.bit.ACKACT = 1; I2C_SERCOM->I2CS.CTRLB.bit.CMD = 0x02; } else { I2C_SERCOM->I2CS.DATA.bit.DATA = 0x11; // continue sending data to master I2C_SERCOM->I2CS.CTRLB.bit.ACKACT = 0; I2C_SERCOM->I2CS.CTRLB.bit.CMD = 0x03; } } else { i2c_handle_received_data(I2C_SERCOM->I2CS.DATA.reg); } } if (I2C_SERCOM->I2CS.INTFLAG.bit.PREC) { DEBUG_PRINT("i2c stop condition received\n"); rx_index = 0; I2C_SERCOM->I2CS.INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC; // we are done. cleanup? } } void i2c_init() { while (I2C_SERCOM->I2CS.SYNCBUSY.bit.ENABLE) ; I2C_SERCOM->I2CS.CTRLA.bit.ENABLE = 0; while (I2C_SERCOM->I2CS.SYNCBUSY.bit.SWRST) ; I2C_SERCOM->I2CS.CTRLA.bit.SWRST = 1; while (I2C_SERCOM->I2CS.SYNCBUSY.bit.SWRST) ; PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_WRPINCFG // declare pincfg shall be edited | PORT_WRCONFIG_WRPMUX // declare mux config shall be edited | PORT_WRCONFIG_PMUXEN // enable multiplexing | PORT_WRCONFIG_PMUX(MUX_PA08C_SERCOM0_PAD0 // choose mux C option ) | PORT_WRCONFIG_PINMASK((uint16_t)(I2C_SDA_PIN | I2C_SCL_PIN)); // enable peripheral clock PM->APBCMASK.reg |= PM_APBCMASK_SERCOM0; GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(SERCOM0_GCLK_ID_CORE) | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0); while (GCLK->STATUS.bit.SYNCBUSY) ; I2C_SERCOM->I2CS.CTRLA.reg = SERCOM_I2CS_CTRLA_SPEED(0) // 100kHz/400kHz | SERCOM_I2CS_CTRLA_SDAHOLD(0x2) // hold SDA line after SCL falling edge | SERCOM_I2CS_CTRLA_RUNSTDBY // keep running in standby | SERCOM_I2CS_CTRLA_MODE_I2C_SLAVE // run as slave ; I2C_SERCOM->I2CS.ADDR.reg = I2C_ADDRESS << 1; I2C_SERCOM->I2CS.INTENSET.reg = SERCOM_I2CS_INTENSET_PREC // stop received | SERCOM_I2CS_INTENSET_AMATCH // addr matched | SERCOM_I2CS_INTENSET_DRDY // data ready ; I2C_SERCOM->I2CS.CTRLA.reg |= SERCOM_I2CS_CTRLA_ENABLE; while (I2C_SERCOM->I2CS.SYNCBUSY.bit.ENABLE) ; NVIC_SetPriority(SERCOM0_IRQn, 3); NVIC_EnableIRQ(SERCOM0_IRQn); }