Self Test

This sample demonstrates using I2C for self-transmitting and self-receiving communication.

I2C0 acts as the master and reads data length and information transmitted by I2C1, while I2C1 acts as the slave and receives data length and information sent by I2C0, thereby implementing I2C communication functionality.

Requirements

The sample supports the following development kits:

Development Kits

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

For more requirements, please refer to Quick Start.

Wiring

Connect P4_0(I2C0 SCL) to P4_2(I2C1 SCL), P4_1(I2C0 SDA) to P4_3(I2C1 SDA) on the EVB, need to connect 2.2k pull-up resistor.

The connection schematic of the pull-up resistor is shown below:

Here should be the I2C pull-up resistor schematic

I2C pull-up resistor schematic

Building and Downloading

This sample can be found in the SDK folder:

Project file: board\evb\io_sample\i2c\Self_Test\mdk

Project file: board\evb\io_sample\i2c\Self_Test\gcc

Please follow these steps to build and run the example:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

When the I2C_MASTER_SEND_SLAVE_RECEIVE macro is enabled, this example demonstrates the I2C master sending and the slave receiving.

  1. When the data received by the Slave reaches the set threshold level, it triggers the I2C_INT_RX_FULL interrupt and prints the log.

    I2C1 Rx Full detected
    
  2. When the Master finishes sending data, the Slave detects the stop signal, both Master and Slave trigger the I2C_INT_STOP_DET interrupt and print the log.

    I2C0 Stop signal detected
    I2C1 Stop signal detected
    
  3. Within the interrupt function on the Slave, print the length and content of the received data.

    Slave Recv Date Lenth = 24
    I2C1_Slave_ReceiveData=0
    ...
    I2C1_Slave_ReceiveData=23
    

When the I2C_MASTER_RECEIVE_SLAVE_SEND macro is enabled, this example demonstrates the I2C master receiving and the slave sending.

  1. The Slave receives a read request sent by the Master, triggering the I2C_INT_RD_REQ interrupt and printing a log.

    Enter I2C1 interrupt I2C1_INT_RD_REQ!
    
  2. Print the data received by the Master.

    Master Read data = 10
    ...
    Master Read data = 33
    

When the I2C_MASTER_REPEAT_READ macro is enabled, this example demonstrates the I2C master continuously sending and receiving data.

  1. The Slave receives a read request from the Master, triggering the I2C_INT_RD_REQ interrupt, and logs it.

    Enter I2C1 interrupt I2C1_INT_RD_REQ!
    
  2. The Slave receives data from the Master, prints the length and content of the received data within the Slave’s interrupt function.

    Slave Recv Date Lenth = 4
    I2C1_Slave_ReceiveData=1
    I2C1_Slave_ReceiveData=2
    I2C1_Slave_ReceiveData=3
    I2C1_Slave_ReceiveData=4
    
  3. Slave receives the data and prints a log.

    I2C1 RX_DONE detected
    
  4. Slave sends the data back to Master. After Master receives the data, it prints a log.

    I2C0 Stop signal detected
    
  5. Print the data received by Master.

    Master Repeat Read data = 10
    ...
    Master Repeat Read data = 33
    

Code Overview

This chapter will be introduced according to the following several parts:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. Functional implementation after initialization will be introduced in chapter Function Implementation.

Source Code Directory

  • Project directory: sdk\board\evb\io_sample\i2c\Self_Test

  • Source code directory: sdk\src\sample\io_sample\i2c\Self_Test

Source files are currently categorized into several groups as below.

└── Project: self_test
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            └── ROM.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            └── rtl876x_i2c.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            └── main.c

Initialization

When the EVB reset, the main function is executed, following these steps:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    __enable_irq();
    i2c_demo();

    while (1)
    {
        __NOP();
        __NOP();
        __NOP();
        __NOP();
        __NOP();
        __NOP();
    }
}

In i2c_demo, it includes PAD/PINMUX settings, I2C peripheral initialization, and interrupt configuration initialization.

void i2c_demo(void)
{
    /* Configure pad and pinmux firstly! */
    board_i2c_master_init();
    board_i2c_slave_init();

    /* Initialize i2c peripheral */
    driver_i2c_master_init();
    driver_i2c_slave_init();

    /* Config i2c nvic */
    nvic_i2c_config();

    ...
}

board_i2c_master_init and board_i2c_slave_init are for configuring the PAD/PINMUX settings of I2C0 and I2C1, including the following processes:

  1. Configure PAD: Set pins, PINMUX mode, PowerOn, internal pull-up, output enable, and output high.

  2. Configure PINMUX: Assign pins to I2C0_CLK, I2C0_DAT, I2C1_CLK, and I2C1_DAT functions respectively.

driver_i2c_master_init is for the initialization of the I2C0 peripheral, including the following processes:

  1. Enable the RCC clock source.

  2. Set the I2C clock frequency to 100kHz.

  3. Set the device mode to master mode.

  4. Set the I2C address to 7-bit address mode.

  5. Set the slave address to 0x50.

  6. Enable the ACK function.

  7. Enable I2C0.

void driver_i2c_master_init(void)
{
    /* Initialize I2C peripheral */
    RCC_PeriphClockCmd(APBPeriph_I2C0, APBPeriph_I2C0_CLOCK, ENABLE);

    I2C_InitTypeDef  I2C_InitStruct;
    I2C_StructInit(&I2C_InitStruct);

    I2C_InitStruct.I2C_ClockSpeed       = 100000;
    I2C_InitStruct.I2C_DeviveMode       = I2C_DeviveMode_Master;
    I2C_InitStruct.I2C_AddressMode      = I2C_AddressMode_7BIT;
    I2C_InitStruct.I2C_SlaveAddress     = 0x50;
    I2C_InitStruct.I2C_RxThresholdLevel = 8;
    I2C_InitStruct.I2C_Ack              = I2C_Ack_Enable;

    I2C_Init(I2C0, &I2C_InitStruct);
    I2C_Cmd(I2C0, ENABLE);
}

driver_i2c_slave_init is the initialization for the I2C1 peripheral, which includes the following steps:

  1. Enable the RCC clock.

  2. Set the I2C clock frequency to 100kHz.

  3. Set the device mode to slave mode.

  4. Set the I2C address to 7-bit address mode.

  5. Set the slave address to 0x50.

  6. Enable the ACK function.

  7. Enable I2C1.

void driver_i2c_slave_init(void)
{
    /* Initialize I2C peripheral */
    RCC_PeriphClockCmd(APBPeriph_I2C1, APBPeriph_I2C1_CLOCK, ENABLE);

    I2C_InitTypeDef  I2C_InitStruct;
    I2C_StructInit(&I2C_InitStruct);

    I2C_InitStruct.I2C_ClockSpeed        = 100000;
    I2C_InitStruct.I2C_DeviveMode        = I2C_DeviveMode_Slave;
    I2C_InitStruct.I2C_AddressMode       = I2C_AddressMode_7BIT;
    I2C_InitStruct.I2C_SlaveAddress      = 0x50;
    I2C_InitStruct.I2C_RxThresholdLevel  = 8;
    I2C_InitStruct.I2C_Ack               = I2C_Ack_Enable;

    I2C_Init(I2C1, &I2C_InitStruct);
    I2C_Cmd(I2C1, ENABLE);
}

nvic_i2c_config is for the interrupt configuration under different macro settings, including the following process:

  1. Clear all interrupt pending bits.

  2. If the macro I2C_MASTER_SEND_SLAVE_RECEIVE is enabled:

    1. Configure the I2C_INT_STOP_DET interrupt for the Master.

    2. Configure the I2C_INT_RX_FULL, I2C_INT_STOP_DET, and I2C_INT_RX_DONE interrupts for the Slave.

  3. If the macro I2C_MASTER_RECEIVE_SLAVE_SEND is enabled, configure the I2C_INT_RD_REQ interrupt for the Slave.

  4. If the macro I2C_MASTER_REPEAT_READ is enabled:

    1. Configure the I2C_INT_STOP_DET interrupt for the Master.

    2. Configure the I2C_INT_RX_FULL, I2C_INT_STOP_DET, I2C_INT_RX_DONE, and I2C_INT_RD_REQ interrupts for the Slave.

void nvic_i2c_config(void)
{
    /* Detect stop signal */
    I2C_ClearINTPendingBit(I2C0, I2C_INT_STOP_DET);
    I2C_ClearINTPendingBit(I2C1, I2C_INT_STOP_DET);
    /* Detect read require signal */
    I2C_ClearINTPendingBit(I2C1, I2C_INT_RD_REQ);
    /* Detect rx fifo full signal */
    I2C_ClearINTPendingBit(I2C1, I2C_INT_RX_FULL);
    /* Detect rx transmission finished signal */
    I2C_ClearINTPendingBit(I2C1, I2C_INT_RX_DONE);

#if I2C_MASTER_SEND_SLAVE_RECEIVE
    I2C_INTConfig(I2C0, I2C_INT_STOP_DET, ENABLE);
    I2C_INTConfig(I2C1, I2C_INT_RX_FULL, ENABLE);
    I2C_INTConfig(I2C1,  I2C_INT_STOP_DET, ENABLE);
    I2C_INTConfig(I2C1,  I2C_INT_RX_DONE, ENABLE);
#endif

#if I2C_MASTER_RECEIVE_SLAVE_SEND
    I2C_INTConfig(I2C1, I2C_INT_RD_REQ, ENABLE);
#endif

#if I2C_MASTER_REPEAT_READ
    I2C_INTConfig(I2C0, I2C_INT_STOP_DET, ENABLE);
    I2C_INTConfig(I2C1, I2C_INT_RX_FULL, ENABLE);
    I2C_INTConfig(I2C1,  I2C_INT_STOP_DET, ENABLE);
    I2C_INTConfig(I2C1,  I2C_INT_RX_DONE, ENABLE);
    I2C_INTConfig(I2C1, I2C_INT_RD_REQ, ENABLE);
#endif

    /* Config I2C interrupt */
    NVIC_InitTypeDef NVIC_InitStruct;

    NVIC_InitStruct.NVIC_IRQChannel = I2C0_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = I2C1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}

Functional Implementation

When the I2C_MASTER_SEND_SLAVE_RECEIVE macro is enabled, this example demonstrates the I2C master sending and the slave receiving.

  1. After the initialization stage is completed, execute I2C_MasterWrite(), the master sends data to the slave, and the slave receives the data sent by the master.

  2. When the amount of data received by the slave reaches the set threshold, it triggers the I2C_INT_RX_FULL interrupt and enters the interrupt handler function I2C1_Handler.

    1. Obtain the length of the received data in the RX FIFO and save the received data.

    2. Record the length of the received data and clear the interrupt flag.

    void I2C1_Handler(void)
    {
        ...
        if (I2C_GetINTStatus(I2C1, I2C_INT_RX_FULL) == SET)
        {
            DBG_DIRECT("I2C1 Rx Full detected");
            lenth = I2C_GetRxFIFOLen(I2C1);
    
            /*read I2C data*/
            for (uint32_t i = 0; i < lenth; i++)
            {
                I2C_Rev_Data[I2C_Rev_Index++] = I2C_ReceiveData(I2C1);
            }
            I2C_Rev_Data_Lenth += lenth;
    
            I2C_ClearINTPendingBit(I2C1, I2C_INT_RX_FULL);
        }
    
    }
    
  3. When the slave receives data and the stop signal is detected, it triggers the I2C_INT_STOP_DET interrupt and enters the interrupt handler function I2C1_Handler.

    1. Obtain the length of the received data in the RX FIFO and save the received data.

    2. Record the length of the data received and clear the interrupt flag.

    3. Print all the received data.

    void I2C1_Handler(void)
    {
        ...
        if (I2C_GetINTStatus(I2C1, I2C_INT_STOP_DET) == SET)
        {
            DBG_DIRECT("I2C1 Stop signal detected");
    
            /*read I2C receive data*/
            lenth = I2C_GetRxFIFOLen(I2C1);
            for (uint32_t i = 0; i < lenth; i++)
            {
    
                I2C_Rev_Data[I2C_Rev_Index++] = I2C_ReceiveData(I2C1);
            }
            I2C_Rev_Data_Lenth += lenth;
            I2C_ClearINTPendingBit(I2C1, I2C_INT_STOP_DET);
    
            DBG_DIRECT("Slave Recv Date Lenth = %d", I2C_Rev_Data_Lenth);
    
            for (uint32_t i = 0; i < I2C_Rev_Data_Lenth; i++)
            {
                DBG_DIRECT("I2C1_Slave_ReceiveData=%d\n", I2C_Rev_Data[i]);
            }
    
        }
    
    }
    

When the I2C_MASTER_RECEIVE_SLAVE_SEND macro is enabled, this example demonstrates the I2C master receiving and the slave sending.

  1. After the initialization phase is completed, execute I2C_MasterRead() to send a read request command to the Slave and wait to receive data.

  2. When the Slave detects the read request command, it triggers the I2C_INT_RD_REQ interrupt and enters the interrupt handler function I2C1_Handler.

    1. Execute the I2C_SendCmd() command to send data to the Slave.

    2. After sending the data, clear the I2C_INT_RD_REQ interrupt pending bit.

    void I2C1_Handler(void)
    {
        uint16_t lenth;
    
        uint8_t send_data_buffer[100];
        for (uint32_t i = 0; i < TransferLength; i++)
        {
            send_data_buffer[i] = i + 10;
        }
        if (I2C_GetINTStatus(I2C1, I2C_INT_RD_REQ) == SET)
        {
            DBG_DIRECT("Enter I2C1 interrupt I2C1_INT_RD_REQ!");
    
            for (uint32_t i = 0; i < TransferLength; i++)
            {
                I2C_SendCmd(I2C1, I2C_WRITE_CMD, send_data_buffer[i], I2C_STOP_DISABLE);
            }
    
            I2C_ClearINTPendingBit(I2C1, I2C_INT_RD_REQ);
    
        }
        ...
    }
    
  3. After the Master end has received the complete data, it prints the data.

    void i2c_demo(void)
    {
        ...
    #if I2C_MASTER_RECEIVE_SLAVE_SEND
        /* I2C master read */
        uint8_t data[TransferLength] = {0};
        I2C_MasterRead(I2C0, data, TransferLength);
    
        for (uint32_t i = 0; i < TransferLength ; i++)
        {
            DBG_DIRECT("Master Read data = %d\n", data[i]);
        }
    #endif
        ...
    }
    

When the I2C_MASTER_REPEAT_READ macro is enabled, this example demonstrates the I2C master continuously sending and receiving data.

  1. Master executes I2C_RepeatRead() to send data to the Slave and waits for the Slave to respond with data.

    void i2c_demo(void)
    {
        ...
    #if I2C_MASTER_REPEAT_READ
        /* I2C master repeat read */
        uint8_t tx_data[10] = {01, 02, 03, 04};
        uint8_t rx_data[TransferLength] = {0};
        I2C_RepeatRead(I2C0, tx_data, 4, rx_data, TransferLength);
    
        for (uint32_t i = 0; i < TransferLength ; i++)
        {
            DBG_DIRECT("Master Repeat Read data = %d\n", rx_data[i]);
        }
    #endif
    }
    
  2. Slave receives the data sent by the Master. When the Slave’s receive FIFO is full, it triggers the I2C_INT_RX_FULL interrupt, or when a stop signal is detected, it triggers the I2C_INT_STOP_DET interrupt, receiving the data sent by the Master.

  3. When the Slave detects an RD_REQ request, it triggers the I2C_INT_RD_REQ interrupt and sends data back to the Master.