233 lines
6.2 KiB
C

// MIT License
// Copyright (c) 2025 UniTheCat
// Tested with Ch32X03x and CH32V30x
#include "lib_i2c.h"
#define I2C_DEFAULT_TIMEOUT 100000
//! ####################################
//! I2C INIT FUNCTIONS
//! ####################################
void i2c_init(I2C_TypeDef* I2Cx, u32 PCLK, u32 i2cSpeed_Hz) {
// Enable I2C clock
if (I2Cx == I2C1) {
RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
}
#ifdef I2C2
else if (I2Cx == I2C2) {
RCC->APB1PCENR |= RCC_APB1Periph_I2C2;
}
#endif
// Disable I2C before configuration
I2Cx->CTLR1 &= ~I2C_CTLR1_PE;
// configure I2C clock
I2Cx->CTLR2 = (PCLK / 1000000);
I2Cx->CKCFGR = PCLK / (i2cSpeed_Hz << 1); // PeripheralClock / (100KHz * 2)
// Enable I2C
I2Cx->CTLR1 |= I2C_CTLR1_PE;
// Enable ACK
I2Cx->CTLR1 |= I2C_CTLR1_ACK;
}
u8 i2c_start(I2C_TypeDef* I2Cx, u8 i2cAddress, u8 isRead) {
//# Wait while BUSY, when BUSY is set to 0 then continue
u32 timeout = I2C_DEFAULT_TIMEOUT;
while((I2Cx->STAR2 & I2C_STAR2_BUSY) && --timeout);
// if (timeout == 0) { I2Cx->CTLR1 |= I2C_CTLR1_STOP; return 0x11; }
//# Generate START condition
I2Cx->CTLR1 |= I2C_CTLR1_START;
//# Wait while SB is 0, when SB is set to 1 then continue
timeout = I2C_DEFAULT_TIMEOUT;
while(!(I2Cx->STAR1 & I2C_STAR1_SB) && --timeout);
if (timeout == 0) { I2Cx->CTLR1 |= I2C_CTLR1_STOP; return 0x12; }
// printf("timeoutB: %d\n", I2C_DEFAULT_TIMEOUT - timeout);
//# Send address + read/write. Write = 0, Read = 1
I2Cx->DATAR = (i2cAddress << 1) | isRead;
//# Wait while ADDR is 0, if ADDR is set to 1 then continue
timeout = I2C_DEFAULT_TIMEOUT;
while(!(I2Cx->STAR1 & I2C_STAR1_ADDR) && --timeout);
if (timeout == 0) { I2Cx->CTLR1 |= I2C_CTLR1_STOP; return 0x13; }
// printf("timeoutC: %d\n", I2C_DEFAULT_TIMEOUT - timeout);
//! REQUIRED. Clear ADDR by reading STAR1 then STAR2
(void)I2Cx->STAR1;
(void)I2Cx->STAR2;
return 0;
}
void i2c_stop(I2C_TypeDef* I2Cx)
{
//# Generate STOP condition
I2Cx->CTLR1 |= I2C_CTLR1_STOP;
}
void i2c_scan(I2C_TypeDef* I2Cx, void (*onPingFound)(u8 address)) {
// mininum 0x08 to 0x77 (0b1110111)
for (int i = 0x08; i < 0x77; i++) {
u8 ping = i2c_start(I2Cx, i, 1);
//# Generate STOP condition
I2Cx->CTLR1 |= I2C_CTLR1_STOP;
if (ping == 0) onPingFound(i);
}
}
//! ####################################
//! I2C SEND FUNCTION
//! ####################################
u8 i2c_sendBytes_noStop(I2C_TypeDef* I2Cx, u8 i2cAddress, u8* buffer, u8 len) {
u8 err = i2c_start(I2Cx, i2cAddress, 0); // Write mode
if (err) return err;
u32 timeout;
for(u8 i = 0; i < len; i++) {
//# Wait for register empty
timeout = I2C_DEFAULT_TIMEOUT;
while(!(I2Cx->STAR1 & I2C_STAR1_TXE) && --timeout);
if (timeout == 0) { I2Cx->CTLR1 |= I2C_CTLR1_STOP; return 0x21; }
I2Cx->DATAR = buffer[i]; // Send data
}
//# Wait for transmission complete. Wait while BTF is 0, when set to 1 continue
timeout = I2C_DEFAULT_TIMEOUT;
while(!(I2Cx->STAR1 & I2C_STAR1_BTF) && --timeout);
if (timeout == 0) { I2Cx->CTLR1 |= I2C_CTLR1_STOP; return 0x22; }
return 0;
}
u8 i2c_sendBytes(I2C_TypeDef* I2Cx, u8 i2cAddress, u8* buffer, u8 len) {
u8 err = i2c_sendBytes_noStop(I2Cx, i2cAddress, buffer, len);
//# Generate STOP condition
I2Cx->CTLR1 |= I2C_CTLR1_STOP;
return err;
}
u8 i2c_sendByte(I2C_TypeDef* I2Cx, u8 i2cAddress, u8 data) {
return i2c_sendBytes(I2Cx, i2cAddress, &data, 1);
}
//! ####################################
//! I2C RECEIVE FUNCTIONS
//! ####################################
u8 i2c_readBytes(I2C_TypeDef* I2Cx, u8 i2cAddress, u8* buffer, u8 len) {
u8 err = i2c_start(I2Cx, i2cAddress, 1); // Read mode
if (err) return err;
//# Enable ACK at the beginning
I2Cx->CTLR1 |= I2C_CTLR1_ACK;
for(u8 i = 0; i < len; i++) {
//# Before reading the last bytes, disable ACK to signal the slave to stop sending
if(i == len-1) I2Cx->CTLR1 &= ~I2C_CTLR1_ACK;
//# Wait for data. Wait while RxNE is 0, when set to 1 continue
u32 timeout = I2C_DEFAULT_TIMEOUT;
while(!(I2Cx->STAR1 & I2C_STAR1_RXNE) && --timeout);
if (timeout == 0) { I2Cx->CTLR1 |= I2C_CTLR1_STOP; return 0x31; }
//# Read data
buffer[i] = I2Cx->DATAR;
}
//# Generate STOP condition
I2Cx->CTLR1 |= I2C_CTLR1_STOP;
return 0;
}
// Write to register and then do read data, no stop inbetween
u8 i2c_readRegTx_buffer(I2C_TypeDef* I2Cx, u8 i2cAddress,
u8 *tx_buf, u8 tx_len, u8 *rx_buf, u8 rx_len
) {
u8 err = i2c_sendBytes_noStop(I2Cx, i2cAddress, tx_buf, tx_len); // Send register address
if (err) return err;
err = i2c_readBytes(I2Cx, i2cAddress, rx_buf, rx_len); // Read data
return err;
}
//! ####################################
//! I2C SLAVE FUNCTIONS
//! ####################################
void i2c_slave_init(I2C_TypeDef* I2Cx, u16 self_addr, u32 PCLK, u32 i2cSpeed_Hz) {
i2c_init(I2Cx, PCLK, i2cSpeed_Hz);
// Configure the CH32 I2C slave address to make it an I2C slave
I2Cx->OADDR1 = (self_addr << 1);
I2Cx->OADDR2 = 0;
I2Cx->CTLR2 |= I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITERREN | I2C_CTLR2_ITBUFEN;
// Enable Event and Error Interrupts
if (I2Cx == I2C1) {
NVIC_EnableIRQ(I2C1_EV_IRQn); // I2C Event interrupt
NVIC_EnableIRQ(I2C1_ER_IRQn); // I2C Error interrupt
}
#ifdef I2C2
else if (I2Cx == I2C2) {
NVIC_EnableIRQ(I2C2_EV_IRQn); // I2C Event interrupt
NVIC_EnableIRQ(I2C2_ER_IRQn); // I2C Error interrupt
}
#endif
}
void I2C1_ER_IRQHandler(void) __attribute__((interrupt));
void I2C1_ER_IRQHandler(void) {
// get I2C status
uint16_t STAR1 = I2C1->STAR1;
// Obtain and clear Bus error
if (STAR1 & I2C_STAR1_BERR) {
I2C1->STAR1 &= ~(I2C_STAR1_BERR);
}
// Obtain and clear Arbitration lost error
if (STAR1 & I2C_STAR1_ARLO) {
I2C1->STAR1 &= ~(I2C_STAR1_ARLO);
}
// Obtain and clear Acknowledge failure error
if (STAR1 & I2C_STAR1_AF) {
I2C1->STAR1 &= ~(I2C_STAR1_AF);
}
}
#ifdef I2C2
void I2C2_ER_IRQHandler(void) __attribute__((interrupt));
void I2C2_ER_IRQHandler(void) {
// get I2C status
uint16_t STAR1 = I2C2->STAR1;
// Obtain and clear Bus error
if (STAR1 & I2C_STAR1_BERR) {
I2C2->STAR1 &= ~(I2C_STAR1_BERR);
}
// Obtain and clear Arbitration lost error
if (STAR1 & I2C_STAR1_ARLO) {
I2C2->STAR1 &= ~(I2C_STAR1_ARLO);
}
// Obtain and clear Acknowledge failure error
if (STAR1 & I2C_STAR1_AF) {
I2C2->STAR1 &= ~(I2C_STAR1_AF);
}
}
#endif