hydrotower-mainboard/src/i2c/i2c.c

178 lines
5.2 KiB
C

// 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 <samd21.h>
#include <stddef.h>
#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);
}