Menu Close

I2C0 module CPU implementation and project header file

1. I2C0 module design


Related reference articles:

RISC-V teaching plan


The structure of the RISC-V I2C0 module is shown in Figure 1. Because I2C is in Standard Mode/Low Speed ​​Mode, the speed is less than or equal to 100 kbits/s. The pipeline RISC-V CPU runs at the megahertz level. In order to prevent the loss of information caused by the mismatch of speeds at both ends of the communication, FIFOs are added at the read and write ends (here uses the self-made synchronous First Word Fall Through/ FIFO of FWFT, click here for details ), see rd_fifo and wr_fifo in Figure 1. The top-level module RIB_I2C0 is mainly a bus to read and write I2C0 registers. i2c_app and i2c_core are two-level control modules. The i2c_core module not only controls the transceiver (physical, phy) layer, but also divides the FPGA system clock signal through i2c_baudtick to generate the required I2C clock frequency. In addition, it can be seen that the data transmission between i2c_core and i2c_phy also uses a FIFO. phy_fifo is mainly used to assemble the commands and data of the control module to further ensure the correct reading and writing of data.

Similarly, I2C0 is a hierarchical module structure, and the module relationship is clear. Later, if you expand other I2C modules, or use them for other purposes, you only need to make corresponding modifications on the top layer of the current I2C0 module structure, such as modifying the bus chip selection, etc., and other parts can be used directly.

I2C0 module design

Figure 1 I2C0 module design

Add the I2C0 address and chip select to the bus, as shown in Figure 2. The base address parameters of I2C0 are shown in Figure 3.

I2C0 chip select and address

Figure 2 Adding a chip select in the bus


I2C0 base address

Figure 3 Add I2C0 base address


The I2C0 interrupt is also handled uniformly by the external platform-level interrupt handler (PLIC), so the I2C0 interrupt has a unique PLIC ID, as shown in Figure 4. In software, the I2C0 interrupt at the PLIC level can be controlled by configuring the enable and priority of this PLIC ID.


Figure 4 I2C0 PLIC ID


Figure 5 shows the interrupt request sent by I2C0 in the PLIC module.

I2C0 interrupt request

Figure 5 I2C0 interrupt request


2. I2C0 interrupt software engineering

On the basis of the PLIC software construction and the GPIO and PWM software engineering in the external interrupt source, only the newly added parts will be explained here. The previous PLIC software engineering details are shown in Sections 18-21 in the RISC-V teaching plan (click here ), as shown in Figure 6.


Figure 6 PLIC-related articles in the RISC-V teaching plan

Next, we will introduce the header files used in the I2C0 project: platform.h and fii_i2c0.h.

  • Platform.h

Because when setting interrupts, not only the interrupt-related registers of UART1 need to be set, but also need to open interrupts and set priorities at the PLIC layer, so the offset of UART1 needs to be added in platform.h, where the offset of UART1 is based on The definition of different external interrupt sources on the address map is shown in Figure 4.

#define INT_I2C0_BASE 60
#define INT_I2C1_BASE 61
#define INT_I2C2_BASE 62
#define INT_I2C3_BASE 63


  • fii_i2c0.h

fii_i2c0.h defines macros about I2C0 registers, as well as functions to initialize and read and write I2C

#ifndef __FII_I2C0_H
#define __FII_I2C0_H

#include "fii_types.h"

// define
// Newly added I2C0 base address, etc. For details, see the V3.04 address map
#define I2C0_BASE 0XF0004000      //I2C0 base address
#define I2C0_REG(offset) (*(volatile unsigned int *)(I2C0_BASE + offset)) //Access the register of I2C0

#define I2C0_VER 0X00             //I2C0 version register offset     
#define I2C0_SADDR 0X04           //I2C0 slave address register offset
#define I2C0_CTRL 0X08            //I2C0 control register offset
#define I2C0_STATUS 0X0C          //I2C0 status register offset
#define I2C0_WDATA 0X10           //I2C0 write data register offset
#define I2C0_RDATA 0X14           //I2C0 read data register offset
#define I2C0_CNT 0X18             //I2C0 count register offset
#define I2C0_IE 0X1C              //I2C0 interrupt enable register offset
#define I2C0_IP 0X20              //I2C0 interrupt suspension register offset
#define I2C0_IC 0X24              //I2C0 interrupt clear register offset
#define I2C0_DIV 0X28             //I2C0 DIV clock divider register offset

#define I2C0_4_MASK 0XF           //4-bit mask, 4'b1111
#define I2C0_3_MASK 0X7           //3-bit mask, 3'b111

//===================== slave address register =================================

#define I2C0_DADDR 0X1            //Device address displacement
#define I2C0_SREG 0X8             //slave register address displacement
#define I2C0_RW_MASK 0X0          //Read/write operation displacement

//====================== Control Register ======================================

#define I2C0_CTRL_START 0X00      //Start bit shift
#define I2C0_CTRL_WWM 0X01        //Write watermark threshold displacement
#define I2C0_CTRL_RWM 0X04        //Read watermark threshold displacement
#define I2C0_CTRL_RESTART 0X08    //Restart displacement
#define I2C0_CTRL_MSTART 0X31     //Module global enable displacement

//======================== Status Register =====================================

#define I2C0_ST_FIFO_FULL_MASK 0X01           //Write FIFO full mask
#define I2C0_ST_FIFO_WCNT 0X01                //write FIFO word count displacement
#define I2C0_ST_FIFO_EMPTY_MASK 0X20          //Read FIFO empty mask, 6'b10_0000
#define I2C0_ST_FIFO_RCNT 0X06                //Read FIFO word count displacement


//I2C0 interrupt
#define I2C0_IRQ_WWM_MASK 0X0            //Write watermark interrupt
#define I2C0_IRQ_RWM_MASK 0X1            //Read watermark interrupt
#define I2C0_IRQ_NACK_MASK 0X2           //No response interrupt
#define I2C0_IRQ_DERROR_MASK 0X3         //Data write count interrupt


//For 50 MHz CPU, convert to 100 kbit/s I2C rate,
#define I2C0_100K 0X1F3 //Decimal 499 "----100 k = 50 M/(DIV + 1), DIV = 499



//Enumeration defines interrupt enable
typedef enum {
    I2C0_IRQ_DIS = 0,
} E_I2C0_IRQ_SW;

//enum defines read/write operations
typedef enum {
    I2C0_WRITE = 0,
} E_I2C0_RW;

// Whether the enumeration definition is restarted
typedef enum {
    I2C0_N_RESTART = 0,


//Declare the I2C0_IRQ_register function for I2C0 interrupt initialization
void I2C0_IRQ_register( u32_t i2c0_div, u32_t rwm, u32_t wwm, u32_t irq_mask,
E_I2C0_IRQ_SW i2c0_sw, E_I2C0_RESTART restart);

//Declare the I2C0_RD_DATA function for reading data
void I2C0_RD_DATA( u32_t reg_addr, u32_t slave_addr, u8_t* data, u32_t i2c0_data_len);

//Declare the I2C0_WR_DATA function for writing data
void I2C0_WR_DATA(u32_t reg_addr, u32_t slave_addr, u8_t* data, u32_t i2c0_data_len);

//Declare the I2C0_WR_FF function for writing 0xff into AT24C02
void I2C0_WR_FF(u32_t slave_addr);

//Declare the I2C0_START function to start the I2C read/write operation
void I2C0_START();


#endif /* end __FII_I2C0_H */


Posted in FPGA, RISC-V, RISC-V Textbook, Textbook and Training Project

Related Articles

Leave a Reply

Your email address will not be published.

Leave the field below empty!