Menu Close

I2C software engineering main function

1. fii_i2c0.c

 

Related reference articles:

RISC-V teaching plan

 

fii_i2c0.c mainly implements some functions declared in fii_i2c0.h.

#include <stdio.h>
#include <stdint.h>
#include "fii_i2c0.h"
#include "platform.h"


/*I2C0_IRQ_register function parameter list includes frequency division, 
read watermark threshold, write watermark threshold, interrupt type, 
interrupt enable, restart enable
*/
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)
{
    //Set the data transfer rate, such as 100k bits/s
    I2C0_REG(I2C0_DIV) = (i2c0_div & 0XFFFF); //Only the lower 16 bits of the DIV register are valid

    //Set the corresponding read/write watermark threshold, before setting, clear the previous watermark threshold
    I2C0_REG(I2C0_CTRL) &= ~(I2C0_3_MASK << I2C0_CTRL_WWM);
    I2C0_REG(I2C0_CTRL) &= ~(I2C0_3_MASK << I2C0_CTRL_RWM);

    //Write watermark threshold
    I2C0_REG(I2C0_CTRL) |= ((wwm & I2C0_3_MASK) << I2C0_CTRL_WWM);

    // read watermark threshold
    I2C0_REG(I2C0_CTRL) |= ((rwm & I2C0_3_MASK) << I2C0_CTRL_RWM);

    //interrupt enable
    if (i2c0_sw == I2C0_IRQ_DIS )//Disable interrupt
        I2C0_REG(I2C0_IE) &= ~(1UL << irq_mask);
    else //enable interrupt
        I2C0_REG(I2C0_IE) |= (1UL << irq_mask);
        
    // restart enable
    if (restart == I2C0_N_RESTART )//Disable restart
        I2C0_REG(I2C0_CTRL) &= ~(1UL << I2C0_CTRL_RESTART);
    else // enable restart
        I2C0_REG(I2C0_CTRL) |= (1UL << I2C0_CTRL_RESTART);

    return ;
}

//The parameter list of the I2C0_RD_DATA function includes register address, slave device address, read data pointer, data length
void  I2C0_RD_DATA (u32_t reg_addr, u32_t slave_addr, u8_t* data, u32_t i2c0_data_len)
{
    //There is no limit to reading, you can read up to 15 bytes/the maximum value of the count register
    u32_t len_tmp;
    u32_t data_offset = 0;

    //Clear the lower 16 bits of the slave address register before writing
    I2C0_REG(I2C0_SADDR) &= ~(0xFFFF);

    //Set the lowest bit of the slave address register to 'read'
    I2C0_REG(I2C0_SADDR) |= 1UL;

    //Write to slave address
    I2C0_REG(I2C0_SADDR) |= ((slave_addr & 0X7F) << I2C0_DADDR); //0X7F = 111_1111

    //Main loop, as long as data_len > 0, the data needs to be read out
    while (i2c0_data_len > 0)
    {

        //Read up to 15 bytes of data at a time, because the maximum value of the count register is 15
        len_tmp = (i2c0_data_len > 0xf) ? 0xf : i2c0_data_len;

        //Write the current data length to the count register, and subtract the current data length read this time from data_len
        I2C0_REG(I2C0_CNT) = len_tmp;
        i2c0_data_len -= len_tmp;

        // Before writing to the register address, clear the previous value
        I2C0_REG(I2C0_SADDR) &= ~(0xFF << I2C0_SREG);
        I2C0_REG(I2C0_SADDR) |= ((reg_addr & 0xFF) << I2C0_SREG);

        // Advance the register address to avoid repeated writing on the same register address in the EEPROM
        reg_addr += len_tmp;
        data_offset = len_tmp;

        //After setting the registers needed to read the data, start
        I2C0_START();

        //read data
        while (data_offset > 0)
        {
            //If the read FIFO is empty, do not continue reading
            while ((I2C0_REG(I2C0_STATUS) & I2C0_ST_FIFO_EMPTY_MASK))
                continue ;

            //Read the data and write the data to the address pointed to by the data pointer
            *data = I2C0_REG(I2C0_RDATA);
            printf ("%02x ",*data);

            // pointer advance
            data++;

            //After reading a byte of data, data_offset is decremented by 1, until at most 15 bytes of data are read at a time
            data_offset--;
        }
    }

    printf ("\r\n\r\n");

    return ;
}

// start function
void  I2C0_START ()
{
    //Before the initialization register is completed, the start bit needs to be disabled
    // After all settings are done, enable the start bit
    I2C0_REG(I2C0_CTRL) |= (1UL << I2C0_CTRL_START);
    return ;
}

//Write all 0xff in EEPROM, I2C0_WR_FF function parameter list only has slave address
void  I2C0_WR_FF ( u32_t slave_addr)
{
    u32_t len_tmp;
    u32_t data_offset = 0;

    //Clear the lower 16 bits of the slave address register before writing
    I2C0_REG(I2C0_SADDR) &= ~(0xFFFF);
    I2C0_REG(I2C0_SADDR) &= ~ 1UL; //Set the lowest bit of the slave address register to 'write'
    I2C0_REG(I2C0_SADDR) |= ((slave_addr & 0X7F) << I2C0_DADDR); 、

    //Set data_len to the size of the EEPROM
    u32_t i2c0_data_len = 256;
    u32_t reg_addr = 0;

    while (i2c0_data_len > 0)
    {

        len_tmp = (i2c0_data_len > 0x8) ? 0x8 : i2c0_data_len; //AT24C02 can only write up to 8 bytes of data at a time

        //Write the current data_len to the count register, and subtract the current data length read from data_len
        I2C0_REG(I2C0_CNT) = len_tmp;
        i2c0_data_len -= len_tmp;

        // Before writing to the register address, clear the previous value
        I2C0_REG(I2C0_SADDR) &= ~(0xFF << I2C0_SREG);
        I2C0_REG(I2C0_SADDR) |= ((reg_addr & 0xFF) << I2C0_SREG);

        // Advance the register address to avoid repeated writing on the same register address in the EEPROM
        reg_addr += len_tmp;
        data_offset = len_tmp;
        printf ("%02x\r\n",reg_addr);

        //write data
        while (data_offset > 0)
        {
            //When the write FIFO is full, do not continue to write
            while ((I2C0_REG(I2C0_STATUS) & I2C0_ST_FIFO_FULL_MASK))
                continue ;

            //write 0xff
            I2C0_REG(I2C0_WDATA) = 0xff;

            //After writing a byte of data, the data_offset is reduced by 1 until at most 8 bytes of data are written at one time
            data_offset--;
        }

        // start function
        I2C0_START();
        
        // Determine whether the write FIFO is empty (actually detect the word count of the FIFO), and the next operation can only be performed after confirmation
        while ((I2C0_REG(I2C0_STATUS) >> I2C0_ST_FIFO_WCNT) & 0x0f)
            continue ;
    }

    return ;
}

//The parameter list of the I2C0_WR_DATA function includes register address, slave device address, write data pointer, data length
void  I2C0_WR_DATA (u32_t reg_addr, u32_t slave_addr, u8_t* data, u32_t i2c0_data_len)
{
    u32_t len_tmp;
    u32_t data_offset = 0;
    u32_t reg_tmp = 0;

    //If the write data pointer is not null
    if (data != NULL)
    {
        //Clear the lower 16 bits of the slave address register before writing
        I2C0_REG(I2C0_SADDR) &= ~(0xFFFF);
        I2C0_REG(I2C0_SADDR) &= ~ 1UL; //Set the lowest bit of the slave address register to 'write'
        I2C0_REG(I2C0_SADDR) |= ((slave_addr & 0X7F) << I2C0_DADDR);

        if (reg_addr % 8 == 0) //if the register address is 8-byte aligned
        {
            while (i2c0_data_len > 0)//Main loop
            {
                //AT24C02 can only write up to 8 bytes of data at a time
                len_tmp = (i2c0_data_len > 0x8) ? 0x8 : i2c0_data_len;

                //Write the current data_len to the count register, and subtract the current data length read from data_len
                I2C0_REG(I2C0_CNT) = len_tmp;
                i2c0_data_len -= len_tmp;

                // Before writing to the register address, clear the previous value
                I2C0_REG(I2C0_SADDR) &= ~(0xFF << I2C0_SREG);
                I2C0_REG(I2C0_SADDR) |= ((reg_addr & 0xFF) << I2C0_SREG);

                // Advance the register address to avoid repeated writing on the same register address in the EEPROM
                reg_addr += len_tmp;
                data_offset = len_tmp;

                //write data
                while (data_offset > 0)
                {
                    //When the write FIFO is full, do not continue to write
                    while ((I2C0_REG(I2C0_STATUS) & I2C0_ST_FIFO_FULL_MASK))
                        continue ;

                    //write data
                    I2C0_REG(I2C0_WDATA) = *data;
                    printf ("%02x ",*data);
                    data++; //write pointer move
                    data_offset--; //After writing a byte of data, data_offset is reduced by 1, until at most 8 bytes of data are written at one time
                }

                // start function
                I2C0_START();

                // Determine whether the write FIFO is empty (actually detect the word count of the FIFO), and the next operation can only be performed after confirmation
                while ((I2C0_REG(I2C0_STATUS) >> I2C0_ST_FIFO_WCNT) & 0x0f)
                    continue ;
            }
        }
        else             // (reg_addr % 8 != 0), the register address is not 8-byte aligned
        //Write the unaligned part first, (except for the last operation) subsequent write operations are all aligned with 8 bytes
        {
            reg_tmp = reg_addr - ((reg_addr/8)*8); //The register address is not aligned
            len_tmp = 8 - reg_tmp; //current data length

            //Write the current data_len to the count register, and subtract the current data length read from data_len
            I2C0_REG(I2C0_CNT) = len_tmp;
            i2c0_data_len -= len_tmp;

            // Before writing to the register address, clear the previous value
            I2C0_REG(I2C0_SADDR) &= ~(0xFF << I2C0_SREG);
            I2C0_REG(I2C0_SADDR) |= ((reg_addr & 0xFF) << I2C0_SREG);

            // Advance the register address to avoid repeated writing on the same register address in the EEPROM
            reg_addr += len_tmp;
            data_offset = len_tmp;

            //When the write FIFO is full, do not continue to write
            while ((I2C0_REG(I2C0_STATUS) & I2C0_ST_FIFO_FULL_MASK))
                continue ;

            //write data
            while (data_offset > 0)
            {
                I2C0_REG(I2C0_WDATA) = *data;
                printf ("%02x ",*data);
                data++; //ptr moves up
                data_offset--;
            }

            I2C0_START();

            //If the write FIFO is empty, continue to the next write
            while ((I2C0_REG(I2C0_STATUS) >> I2C0_ST_FIFO_WCNT) & 0x0f)
                continue ;

            //The rest of the alignment data is written
            while (i2c0_data_len > 0)
            {
                len_tmp = (i2c0_data_len > 0x8) ? 0x8 : i2c0_data_len; //AT24C02 can only write up to 8 bytes of data at a time
                //Write the current data_len to the count register, and subtract the current data length read from data_len
                I2C0_REG(I2C0_CNT) = len_tmp;
                i2c0_data_len -= len_tmp;

                // Before writing to the register address, clear the previous value
                I2C0_REG(I2C0_SADDR) &= ~(0xFF << I2C0_SREG);
                I2C0_REG(I2C0_SADDR) |= ((reg_addr & 0xFF) << I2C0_SREG);

                // Advance the register address to avoid repeated writing on the same register address in the EEPROM
                reg_addr += len_tmp;
                data_offset = len_tmp;

                //When the write FIFO is full, do not continue to write
                while ((I2C0_REG(I2C0_STATUS) & I2C0_ST_FIFO_FULL_MASK))
                    continue ;

                //write data
                while (data_offset > 0)
                {
                    I2C0_REG(I2C0_WDATA) = *data;
                    printf ("%02x ",*data);
                    data++; //ptr moves up
                    data_offset--;
                }

                I2C0_START();

                //If the write FIFO is empty, continue to the next write
                while ((I2C0_REG(I2C0_STATUS) >> I2C0_ST_FIFO_WCNT) & 0x0f)
                    continue ;
            }
        }
    }

    printf ("\r\n\r\n");

    return ;
}

 

 

2. main.c

The main function’s processing of interrupts temporarily only prints the current interrupt type after entering the interrupt. Only the newly added parts in the main function compared to the previous ones will be described 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 1.

 

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

 

 

#include <stdio.h>
#include <stdlib.h> //malloc is a subfunction in the standard library
#include "umm_heap.h" //For malloc function
#include "fii_types.h"
#include "platform.h"
#include "plic_driver.h"

#define NOP_DELAY 0x400000
#define MEM_SIZE 0x120

//declare global variables, two string pointers
u8_t* wdata;
u8_t* wwdata;

//I2C0 interrupt handler
void  i2c0_handler ( void ) {
    unsigned  int irq_case;

    //Determine what caused the I2C0 interrupt
    irq_case = I2C0_REG(I2C0_IP);
    switch (irq_case)
    {
        case 0x1: //Write watermark threshold interrupt
            printf ("\r\n I2C0 write data watermark interrupt\r\n");

            //Clear wwm interrupt
            I2C0_REG(I2C0_IC) |= 1 << I2C0_IRQ_WWM_MASK;

            // disable wwm interrupts
            I2C0_REG(I2C0_IE) &= ~(1UL << I2C0_IRQ_WWM_MASK);
            break ;
        case 0x2: //Read watermark threshold interrupt
            printf ("\r\n I2C0 read data watermark interrupt\r\n");

            //Clear rwm interrupt
            I2C0_REG(I2C0_IC) |= 1 << I2C0_IRQ_RWM_MASK;

            // disable rwm interrupts
            I2C0_REG(I2C0_IE) &= ~(1UL << I2C0_IRQ_RWM_MASK);
            break ;
        case 0x4: //No response interrupt
            printf ("\r\n I2C0 slave no ack error\r\n");

            //Clear the nack interrupt
            I2C0_REG(I2C0_IC) |= 1 << I2C0_IRQ_NACK_MASK;

            // disable nack interrupt
            I2C0_REG(I2C0_IE) &= ~(1UL << I2C0_IRQ_NACK_MASK);
            break ;
        case 0x8: //Write data count interrupt
            printf ("\r\n I2C0 data count mismatch error\r\n");

            //Clear DERROR interrupt
            I2C0_REG(I2C0_IC) |= 1 << I2C0_IRQ_DERROR_MASK;

            // disable DERROR interrupt
            I2C0_REG(I2C0_IE) &= ~(1UL << I2C0_IRQ_DERROR_MASK);
            break ;
        default :     
            printf ("\r\n No I2C0 interrupt\r\n");
            break ;
    }

    return ;
};


//initialization function
void  _init ( void )
{
    time_cnt = 0;
    curr_IRQ_id = 0;

    //Disable start in I2C0 control register
    I2C0_REG(I2C0_CTRL) &= ~(1UL << I2C0_CTRL_START);

    //Enable i2c0 module enable
    I2C0_REG(I2C0_CTRL) |= 0x80000000;

    //Disable I2C0 interrupt
    I2C0_REG(I2C0_IE) = 0;
    write_csr(mtvec, &trap_entry); //Global interrupt entry 

    return ;
}


//interrupt configuration function
void  IRQ_register (){
    // Until setup is complete, disable machine and timer interrupts.
    clear_csr(mie, MIP_MEIP); // disable external interrupt
    clear_csr(mie, MIP_MTIP); // disable timer interrupt

    for ( int ii = 0; ii < PLIC_NUM_INTERRUPTS; ii++){
        g_ext_interrupt_handlers[ii] = no_interrupt_handler;
    }

    //I2C0 interrupt handling
    g_ext_interrupt_handlers[INT_I2C0_BASE] = i2c0_handler;

    // Interrupts must be enabled at both the UART1 level and the PLIC level
    enable_plic_int(INT_I2C0_BASE);

    // The interrupt priority must be set above 0 to trigger an interrupt
    PLIC_set_priority(&g_plic, INT_I2C0_BASE, 3);

    //==================Interrupt setting variable==================
    int rwm = 3;                    // read watermark threshold setting
    int wwm = 3;                    // write watermark threshold setting
    int reg_addr = 9;               // register address
    int slave_addr = 0x50;          // slave address
    u32_t data_len = 50;            // read/write data length
    u8_t* rdata = malloc (data_len);// Allocate read data pointer

    //Call functions
    I2C0_IRQ_register(I2C0_100K, rwm, wwm, I2C0_IRQ_RWM_MASK, I2C0_IRQ_DIS , I2C0_RESTART );
    I2C0_WR_FF(slave_addr);
    I2C0_WR_DATA(reg_addr, slave_addr, wdata, data_len);
    I2C0_RD_DATA(reg_addr, slave_addr, rdata, data_len);

    // Enable external interrupt bit in machine mode in MIE
    set_csr(mie, MIP_MEIP);

    // Enable machine mode global interrupts
    set_csr(mstatus, MSTATUS_MIE);
}


// main function
int  main ( void )
{
    mm_heap_initialize();
    // allocate write data pointer
    wdata = malloc (MEM_SIZE);
    wwdata = wdata;

    // fill the allocated space with numbers
    for ( int ll = 0; ll < 256 ; ll++)
    {
        *wwdata = ll;
        wwdata++;
    }

    //call the initialization function
    _init();

    //Call the interrupt configuration function
    IRQ_register();

    while ( 1 )
    {
        for (i = 0; i < NOP_DELAY ; i ++ )
            asm ("nop");
    }
}

 

source code download

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!