178 lines
5.2 KiB
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);
|
|
}
|