233 lines
6.2 KiB
C
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
|