Skip to content

Instantly share code, notes, and snippets.

@hexeguitar
Last active November 14, 2023 10:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hexeguitar/3231f3c2b55ebd330124e4cbe56fe3b5 to your computer and use it in GitHub Desktop.
Save hexeguitar/3231f3c2b55ebd330124e4cbe56fe3b5 to your computer and use it in GitHub Desktop.
Single header file i2c library for ch32v003 - work in progress!
#ifndef _CH32V003_I2C_H
#define _CH32V003_I2C_H
/**
* @file ch32v003_i2c.h
* @author Piotr Zapart (www.hexefx.com)
* @brief i2c driver for the CH32V003 MCU
* @version 0.1
* @date 2023-07-01
*
* 07-2023 - for now only the master and 7 bit address mode is implemented
* ------ WORK IN PROGRESS !!! -------
*
* I2C pin mapping:
* SCL=PC2, SDA=PC1, AFIO_PCFR1.I2C1REMAP1= 0, I2C1RM = 0
* SCL=PD1, SDA=PD0, AFIO_PCFR1.I2C1REMAP1= 0, I2C1RM = 1 <-- PD1 = SWIO !!!
* SCL=PC5, SDA=PC6, AFIO_PCFR1.I2C1REMAP1= 1, I2C1RM = X
*
* Usage:
*
* 1. Optionally choose one of the altenate configurations, if not,
* the default setting (PC2+PC1) will be used
* #define I2C_PINS_PD1PD0
* #define I2C_PINS_PC5PC6
*
* #define I2C_MODE_IRQ // if using the interrrupt based driver
* #define CH32V003_I2C_IMPLEMENTATION
* #include "ch32v003_i2c.h"
*
* in the main function:
*
* call i2c_init();
*
* To write data to a device with 1 byte register address range:
* uint16_t err = i2c_write( devAddr,
* regAddr,
* I2C_REGADDR_1B,
* pointerToSrcBuffer, howManyBytes);
*
* * To write data to a device with 2 bytes register address range:
* uint16_t err = i2c_write( devAddr,
* regAddr,
* I2C_REGADDR_2B,
* pointerToSrcBuffer, howManyBytes);
* To read data;
* uint16_t err = i2c_read( devAddr,
* regAddr,
* I2C_REGADDR_1B,
* pointerToDstBuffer, howManyBytes);
*
* 2 byte address range, ie.: 24LC32 eeprom
* uint16_t err = i2c_read( devAddr,
* regAddr,
* I2C_REGADDR_2B,
* pointerToDstBuffer, howManyBytes);
*
* To scan the address space for devices:
* void i2c_scan();
*
* To ping a device:
* uint8_t detected = i2c_ping(devAddr);
*
* TODO: make the interrupt mode default?
* TODO: error reporting
*/
#include <string.h>
#include "ch32v003fun.h"
#define I2C_AFIO_PCFR1_RESET_MASK (0xFFBFFFFD) // AFIO bit 1 & bit 22
#if defined (I2C_PINS_SCLPD1_SDAPD0)
#define I2C_PORT_RCC RCC_APB2Periph_GPIOD
#define I2C_PORT GPIOD
#define I2C_PIN_SCL 1
#define I2C_PIN_SDA 0
#define I2C_AFIO_PCFR1 (1<<1) //remap 01
#elif defined(I2C_PINS_SCLPC5_SDAPC6)
#define I2C_PORT_RCC RCC_APB2Periph_GPIOC
#define I2C_PORT GPIOC
#define I2C_PIN_SCL 5
#define I2C_PIN_SDA 6
#define I2C_AFIO_PCFR1 (1<<22) //remap 01
#else
#define I2C_PINS_SCLPC2_SDAPC1
#define I2C_PORT_RCC RCC_APB2Periph_GPIOC
#define I2C_PORT GPIOC
#define I2C_PIN_SCL 2
#define I2C_PIN_SDA 1
#define I2C_AFIO_PCFR1 I2C_AFIO_PCFR1_RESET_MASK //default 00
#endif
// use default 100kHz if nothing else is declared
#if !defined (I2C_CLKRATE)
#define I2C_CLKRATE 100000
#endif
// I2C Logic clock rate - must be higher than Bus clock rate
#define I2C_PRERATE 2000000
// uncomment this for high-speed 36% duty cycle, otherwise 33%
#define I2C_DUTY
// I2C Timeout count
#define I2C_TIMEOUT_MAX 2000
// STAR1 mas for all i2c errors
#define I2C_STAR1_ERR_MASK ( I2C_STAR1_PECERR | \
I2C_STAR1_OVR | \
I2C_STAR1_AF | \
I2C_STAR1_ARLO | \
I2C_STAR1_BERR )
#define I2C_INT_EN_MASK (I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITERREN)
typedef enum
{
I2C_RESULT_OK = 0,
I2C_TIMEOUT_NOT_BUSY,
I2C_TIMEOUT_NO_ACK
}i2c_result_e;
typedef enum
{
I2C_REGADDR_1B = 0, // register address range is 1 byte
I2C_REGADDR_2B // 2 bytes
}i2c_regAddr_bytes_e;
void i2c_init();
void i2c_setup();
static inline uint32_t i2c_chk_evt(uint32_t event_mask);
i2c_result_e i2c_write( uint16_t devAddr,
uint16_t regAddr,
i2c_regAddr_bytes_e regAddrBytes,
uint8_t *data, uint8_t sz);
i2c_result_e i2c_read( uint16_t devAddr,
uint16_t regAddr,
i2c_regAddr_bytes_e regAddrBytes,
uint8_t *data, uint8_t sz);
i2c_result_e i2c_scan();
i2c_result_e i2c_ping(uint8_t addr, uint8_t *found) ;
/*----------------------------------------------------------*/
#ifdef CH32V003_I2C_IMPLEMENTATION
#ifdef I2C_MODE_IRQ
volatile uint8_t *i2c_data_ptr; // pointer to the data buffer
volatile uint8_t i2c_xfer_sz;
volatile uint8_t i2c_irq_state;
#endif
volatile uint16_t i2c_err_flags;
static inline uint16_t i2c_get_last_err(void);
static inline uint16_t i2c_get_last_err(void)
{
uint16_t result = i2c_err_flags;
i2c_err_flags = 0;
return result;
}
/*----------------------------------------------------------*/
void i2c_init()
{
RCC->APB2PCENR |= I2C_PORT_RCC | RCC_APB2Periph_AFIO;
RCC->APB1PCENR |= RCC_APB1Periph_I2C1; // enable I2C
// Setup SDA
I2C_PORT->CFGLR &= ~(0x0F<<(4*I2C_PIN_SDA));
I2C_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*I2C_PIN_SDA);
// Setup SCL
I2C_PORT->CFGLR &= ~(0x0F<<(4*I2C_PIN_SCL));
I2C_PORT->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*I2C_PIN_SCL);
// alternate pin mapping
AFIO->PCFR1 &= I2C_AFIO_PCFR1_RESET_MASK;
AFIO->PCFR1 |= I2C_AFIO_PCFR1;
i2c_setup();
}
/*----------------------------------------------------------*/
void i2c_setup()
{
uint16_t tmpu16;
i2c_err_flags = 0;
RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; // Reset I2C1 to init all regs
RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1;
tmpu16 = I2C1->CTLR2;
tmpu16 &= ~I2C_CTLR2_FREQ;
tmpu16 |= (FUNCONF_SYSTEM_CORE_CLOCK/I2C_PRERATE)&I2C_CTLR2_FREQ;
I2C1->CTLR2 = tmpu16;
#if (I2C_CLKRATE <= 100000) // standard mode good to 100kHz
tmpu16 = ( FUNCONF_SYSTEM_CORE_CLOCK / (2*I2C_CLKRATE) ) & I2C_CKCFGR_CCR;
#else // fast mode over 100kHz
#ifndef I2C_DUTY
// 33% duty cycle
tmpu16 = ( FUNCONF_SYSTEM_CORE_CLOCK / ( 3*I2C_CLKRATE ) ) & I2C_CKCFGR_CCR;
#else
// 36% duty cycle
tmpu16 = ( FUNCONF_SYSTEM_CORE_CLOCK / ( 25*I2C_CLKRATE ) ) & I2C_CKCFGR_CCR;
tmpu16 |= I2C_CKCFGR_DUTY;
#endif
tmpu16 |= I2C_CKCFGR_FS;
#endif
I2C1->CKCFGR = tmpu16;
#ifdef I2C_MODE_IRQ
NVIC_EnableIRQ(I2C1_EV_IRQn); // enable IRQ driven operation
i2c_irq_state = 0; // initialize the state
I2C1->CTLR1 |= I2C_NACKPosition_Next;
#endif
I2C1->CTLR1 |= I2C_CTLR1_PE; // Enable I2C
}
/*----------------------------------------------------------*/
/**
* @brief Check event status against a mask
*
* @param event_mask mask to apply on the status word
* @return uint32_t masked event status
*/
static inline uint32_t i2c_chk_evt(uint32_t event_mask)
{
/* read order matters here! STAR1 before STAR2!! */
uint32_t status = I2C1->STAR1 | ( I2C1->STAR2<<16 );
return ( status & event_mask ) == event_mask;
}
/*----------------------------------------------------------*/
/**
* @brief ping device address to check if it's responding
*
* @param addr i2c device address
* @param found pointer to return value, 1=found, 0=not found
* @return i2c_result_e operation result
*/
i2c_result_e i2c_ping(uint8_t addr, uint8_t *found)
{
i2c_result_e result = I2C_RESULT_OK;
int32_t timeout = I2C_TIMEOUT_MAX;
uint8_t reply = 1;
while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
if(timeout==-1) return I2C_TIMEOUT_NOT_BUSY;
I2C1->CTLR1 |= I2C_CTLR1_START;
while(!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT));
I2C1->DATAR = (addr<<1) & 0xFE; // send 7-bit address + write flag
timeout = I2C_TIMEOUT_MAX; // wait for transmit condition
while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
if(timeout==-1) reply = 0; // if time out - no ack from the device
I2C1->CTLR1 |= I2C_CTLR1_STOP; // set STOP condition
if (found) *found = reply;
return result;
}
/*----------------------------------------------------------*/
/**
* @brief Scan the full range 0x00-0x7F of i2c addresses
* and report back detected devices
*/
i2c_result_e i2c_scan()
{
uint8_t i, found;
i2c_result_e result = I2C_RESULT_OK;
printf(" ");
for (i = 0; i < 16; i++) { printf("%3x", i); }
for (i = 0; i <= 127; i++)
{
if (i % 16 == 0)
{
printf("\n%02x:", i & 0xF0);
}
result = i2c_ping(i, &found);
if (found) { printf(" %02x", i);}
else { printf(" XX"); }
}
printf("\n\n");
return result;
}
/*----------------------------------------------------------*/
/**
* @brief sequential write
*
* @param devAddr i2c device address
* @param regAddr register address (u16)
* @param regAddrBytes reg address size in bytes: I2C_REGADDR_1B | I2C_REGADDR_2B
* @param data pointer to a uint8_t source buffer
* @param sz how many bytes to write
* @return i2c_result_e
*/
i2c_result_e i2c_write( uint16_t devAddr,
uint16_t regAddr,
i2c_regAddr_bytes_e regAddrBytes,
uint8_t *data, uint8_t sz)
{
int32_t timeout;
i2c_err_flags = 0x00;
#ifdef I2C_MODE_IRQ
while(i2c_irq_state); // wait for previous packet to finish
i2c_xfer_sz = sz; // init buffer for sending
i2c_data_ptr = data;
#endif
// wait for not busy
timeout = I2C_TIMEOUT_MAX;
while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
if(timeout==-1) return I2C_TIMEOUT_NOT_BUSY;
// Set START condition
I2C1->CTLR1 |= I2C_CTLR1_START;
// wait for master mode select
while(!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT));
I2C1->DATAR = (devAddr<<1) & 0xFE; // send 7-bit address + write flag
while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
if(timeout==-1) return I2C_TIMEOUT_NO_ACK;
if (regAddrBytes)
{
I2C1->DATAR = (regAddr>>8) & 0xFF; // send hi address
while(!(I2C1->STAR1 & I2C_STAR1_TXE));
}
I2C1->DATAR = regAddr & 0xFF; // send low address
while(!(I2C1->STAR1 & I2C_STAR1_TXE));
#ifdef I2C_MODE_IRQ
// Enable interrupts
I2C1->CTLR2 |= I2C_INT_EN_MASK;
i2c_irq_state = 1;
#else
while(sz--)
{
while(!(I2C1->STAR1 & I2C_STAR1_TXE));
I2C1->DATAR = *data++; // send command
}
while(!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C1->CTLR1 |= I2C_CTLR1_STOP; // set STOP condition
#endif
return I2C_RESULT_OK;
}
/*----------------------------------------------------------*/
/**
* @brief sequential read
*
* @param devAddr i2c device address
* @param regAddr register address (u16)
* @param regAddrBytes reg address size in bytes: I2C_REGADDR_1B | I2C_REGADDR_2B
* @param data pointer to a uint8_t receiver buffer
* @param sz how many bytes to read
* @return i2c_result_e
*/
i2c_result_e i2c_read( uint16_t devAddr,
uint16_t regAddr,
i2c_regAddr_bytes_e regAddrBytes,
uint8_t *data, uint8_t sz)
{
int32_t timeout;
#ifdef I2C_MODE_IRQ
while(i2c_irq_state); // wait for previous packet to finish
i2c_xfer_sz = sz; // init buffer for sending
i2c_data_ptr = data;
#endif
// wait for not busy
timeout = I2C_TIMEOUT_MAX;
while((I2C1->STAR2 & I2C_STAR2_BUSY) && (timeout--));
if(timeout == -1) return I2C_TIMEOUT_NOT_BUSY;
// Set START condition
I2C1->CTLR1 |= I2C_CTLR1_START;
// wait for master mode select
while(!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT));
I2C1->DATAR = (devAddr<<1) & 0xFE; // send 7-bit address + write flag
timeout = I2C_TIMEOUT_MAX; // wait for transmit condition
while((!i2c_chk_evt(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) && (timeout--));
if(timeout == -1) return I2C_TIMEOUT_NO_ACK;
if (regAddrBytes)
{
I2C1->DATAR = (regAddr>>8) & 0xFF; // send hi address
while(!(I2C1->STAR1 & I2C_STAR1_TXE));
}
I2C1->DATAR = regAddr & 0xFF; // send low address
while(!(I2C1->STAR1 & I2C_STAR1_TXE));
if (sz > 1) I2C1->CTLR1 |= I2C_CTLR1_ACK;
// Set repeat START condition
I2C1->CTLR1 |= I2C_CTLR1_START;
// wait for master mode select
while(!i2c_chk_evt(I2C_EVENT_MASTER_MODE_SELECT));
I2C1->DATAR = (devAddr<<1) | 0x01; // send 7-bit address + read flag
timeout = I2C_TIMEOUT_MAX; // wait for transmit condition
while((!i2c_chk_evt(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) && (timeout--));
if(timeout == -1) return I2C_TIMEOUT_NO_ACK;
#ifdef I2C_MODE_IRQ
// Enable interrupts
I2C1->CTLR2 |= I2C_INT_EN_MASK;
i2c_irq_state = 1;
while (i2c_irq_state);
#else
while(sz--)
{
if (!sz) I2C1->CTLR1 &= ~I2C_CTLR1_ACK; //signal it's the last byte
while(!(I2C1->STAR1 & I2C_STAR1_RXNE));
*data++ = I2C1->DATAR;
}
I2C1->CTLR1 |= I2C_CTLR1_STOP; // set STOP condition
#endif
return I2C_RESULT_OK;
}
/*----------------------------------------------------------*/
/**
* @brief IRQ handler for I2C events
*
* Side note about receiving the bytes: in order to avoid reading dummy bytes
* caused by too late reset of the ACK bit in CTLR1 the ACK is enabled only for
* transfer sizes > 1 and while receving the one before last byte combined with
* I2C_NACKPosition_Next setting.
* Otherwise even if the read size is set to n, the i2c bus will try to read more
* because the Master does not send NACK in time. This unnecessarily blocks the i2c
* bus.
*/
#ifdef I2C_MODE_IRQ
void I2C1_EV_IRQHandler(void) __attribute__((interrupt));
void I2C1_EV_IRQHandler(void)
{
uint16_t star1, err __attribute__((unused));
uint8_t sz = i2c_xfer_sz;
// read status, clear any events
star1 = I2C1->STAR1;
// check for any errors
err = star1 & I2C_STAR1_ERR_MASK;
if (err)
{
i2c_err_flags = err; // store the error flags
i2c_setup(); // reset the i2c bus
return;
}
else i2c_err_flags = 0; // reset error flags;
if(star1 & I2C_STAR1_TXE) // byte teansmitted
{
if(sz--)
I2C1->DATAR = *i2c_data_ptr++;
if(!sz)
{
I2C1->CTLR2 &= ~I2C_INT_EN_MASK;
i2c_irq_state = 0;
while(!i2c_chk_evt(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C1->CTLR1 |= I2C_CTLR1_STOP;
}
}
if (star1 & I2C_STAR1_RXNE) // data received
{
if(sz--)
{
if (sz == 1) I2C1->CTLR1 &= ~I2C_CTLR1_ACK; //signal it's the last byte
*i2c_data_ptr++ = I2C1->DATAR;
}
if (!sz)
{
I2C1->CTLR2 &= ~I2C_INT_EN_MASK;
i2c_irq_state = 0;
I2C1->CTLR1 |= I2C_CTLR1_STOP; // set STOP condition
}
}
i2c_xfer_sz = sz;
}
#endif // CH32V003_I2C_MODE_IRQ
#endif // CH32V003_I2C_IMPLEMENTATION
#endif // _CH32V003_I2C_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment