MM32F0140学习笔记——I2C

cathy的头像
cathy 发布于:周五, 05/20/2022 - 15:06 ,关键词:

1、I2C简介

I2C总线是一个两线串行接口,包含串行数据线(SDA)与串行时钟线(SCL),能够在连接到总线的器件间传递信息,每一个连接总线的设备都有独立的地址,主机可以通过该地址选择连接总线的设备并与之通信。I2C通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号,从而进行数据传输,微控制器可通过I2C总线接口实现芯片间串行互联。

I2C的功能框图如图1所示,当I2C采用主模式进行数据传输时,主机先发送从机设备地址与读写位数据,在从机地址匹配时可进行数据传输;I2C采用从模式时,从设备等待接收由主机发送来的地址数据,地址匹配时可进行数据传输。

“图1.I2C功能框图"
图1.I2C功能框图

主机与从机

主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时,任何被寻址的器件都被认为是从机,在主模式下只支持一个主机。

设备地址

在进行数据传输前需要发送从设备地址,只有当主机的发送设备地址与从机的设备地址匹配时才能进行数据传输。总线的每个器件都有一个唯一的地址识别,而且都可以作为一个发送或接收器。

I2C信号

● 起始信号

当总线处于空闲状态时,SCL和SDA同时被外部上拉电阻拉为高电平。当主机启动数据传输时,必须先产生一个起始信号。在SCL线是高电平时, SDA线从高电平向低电平切换表示起始信号,如图2所示。在起始信号产生后,总线处于忙碌状态,除当前数据传输的主从设备外,其他I2C器件无法访问总线。

“图2.起始/停止信号"
图2.起始/停止信号

● 停止信号

停止信号如图2所示,当主机结束传输时要发送停止信号,在SCL线为高电平、SDA线由低电平向高电平切换时,产生停止信号,从而释放总线,总线再次处于空闲状态。

● 应答信号

I2C具有完善的应答机制,当主机发送完成地址与读写位或者主机发送一个字节的数据到从机上时,从机会回应一个应答信号(ACK),即在第9个SCL时钟周期时拉低SDA,如图3所示。

“图3.应答/非应答信号"
图3.应答/非应答信号

● 非应答信号

如图3所示,当第9个SCL时钟周期时拉高SDA,表示为非应答信号,主机或从机将产生一个停止信号中止传输。

图4.读写位

2、I2C配置

I2C的配置包括传输速率、从设备地址、数据传输方向及主从模式选择的配置,从而进行数据传输。

工作速率

I2C存在两种工作速率模式:标准模式(数据传输速率为0 ~ 100Kbps),快速模式(数据传输速率最大为400Kbps);可通过I2C控制寄存器(I2C_CR)的SPEED位进行控制,由于I2C从机速率跟随主机速率,因此只需在I2C为主模式下配置工作速率,如图5所示。

“图5.I2C工作速率"
图5.I2C工作速率

地址格式

I2C有两种地址格式:7位地址格式和10位地址格式。

● 7位地址

如图6所示,在起始条件(S)后发送的一个字节的前7位(A6 ~ A0)为从机地址,最低位(R/W)是数据方向位,当R/W位为0,表示主机写数据到从机,R/W位为1表示主机从从机读数据。

“图6.I2C
图6.I2C 7位地址

● 10位地址

在10位的地址格式中,发送2个字节来传输10位地址。发送的第一个字节的起始位后的5位(bit7:3)用于告示从机接下来是10位的传输,第一个字节的后两位(bit2:1)是从机地址的bit9:8,最低位(bit 0)是数据方向位(R/W)。传输的第二个字节为10位地址的低八位,如图7所示。

“图7.I2C
图7.I2C 10位地址

写操作

I2C进行写操作时,先通过I2C目标地址寄存器(I2C_TAR)配置从设备地址,再配置读写位,令控制数据命令寄存器(I2C_DR)的CMD位为0,所需传输数据放入I2C_DR寄存器的DR位,进行写操作。

读操作

I2C进行读操作时,先由主机写从设备地址及设备内寄存器地址,当从设备地址及设备内寄存器地址发送完成后,将控制数据命令寄存器(I2C_DR)的CMD位为1,进行读操作,读出设备内寄存器地址中的数值。

“图8.I2C
图8.I2C 读操作

主模式

I2C主模式配置需在I2C未使能状态下进行,将I2C使能寄存器(I2C_IMR)的ENABLE位置0。通过配置控制寄存器(I2C_CR)的SPEED位选择速率模式,MASTER10位选择地址格式,DISSLAVE位为1使从模式禁止,MASTER位为1选择主模式。向目标地址寄存器(I2C_TAR)写入从设备地址,I2C_IMR寄存器的ENABLE位置1,使能I2C。

I2C 接口支持读写的动态切换,当发送数据时,写数据到I2C的RX/TX数据缓冲及命令寄存器(DR)的低字节中,配置CMD位为0产生写操作。接下来的读命令,需要确保CMD位为1。如果发送FIFO为空,I2C模块拉低SCL直到下个命令写入到发送FIFO中。

从模式

I2C做从机时,等待其他主机的访问,只有其他主机发送了I2C从机对应的设备地址,才能进行数据传输。配置I2C为从模式,将I2C使能寄存器(I2C_IMR)的ENABLE位置0,禁止I2C使能,将I2C自身从地址写入从机地址寄存器(I2C_SAR),配置控制寄存器(I2C_CR)的SLAVE10选择寻址格式,DISSLAVE位为0,选择从模式,MASTER位为0,禁止主模式;I2C_IMR寄存器的ENABLE位置1,使能I2C。

当其他主机发送地址与 SAR 寄存器中的从机地址匹配,I2C接口响应发送的地址,并识别传输方向;当I2C产生读请求,从机将数据发送至主机;当主机为写操作时,I2C从机接收到主机的数据并将数据存储在接收缓冲中,软件通过读DR寄存器中的bit7:0来获得接收到的数据。

3、实验

本实验使用轮询进行数据的写操作与读操作,初始化配置I2C为主模式,从设备地址为0x50,速率为100KHz,使用7位地址格式。I2C初始化后,按下一次任意按键,进行8字节数据的写操作,其中第一字节写入数据设置为设备内的寄存器地址,串口显示写入数据;写操作完成后,再次按下任意按键,进行7位数据的读操作,从设备的寄存器地址中读出相关数据。

启用I2C外设时钟 enable_clock()

实验使用I2C1,串口打印输出结果,串口使用引脚属于GPIOA组,I2C使用引脚属于GPIOB组,因此需要启用I2C1、UART1、GPIOA及GPIOB的外设时钟。

void enable_clock()
{
    /* Enable I2C1 clock. */
    RCC->APB1ENR |= RCC_APB1_PERIPH_I2C1;
    /* Enable GPIOA clock. */
    RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA;
    /* Enable GPIOB clock. */
    RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOB;
    /* Enable UART1 clock. */
    RCC->APB2ENR |= RCC_APB2_PERIPH_UART1;
}

配置引脚 pin_init()

配置I2C1引脚,I2C1_SCL为PB6引脚,I2C1_SDA为PB7引脚,复用通道为AF1;由于实验现象通过串口显示,故配置UART的TX(PA9)与RX(PA10)引脚。

void pin_init()
{
    /* PB6 - I2C1_SCL. */
    GPIOB->CRL &= ~GPIO_CRL_MODE6_MASK;
    GPIOB->CRL &= ~GPIO_CRL_CNF6_MASK;
    GPIOB->CRL |= GPIO_CRL_MODE6(GPIO_Speed_50MHz);
    GPIOB->CRL |= GPIO_CRL_CNF6(GPIO_PinMode_AF_OpenDrain);
    GPIOB->AFRL &= ~GPIO_AFRL_AFRY_MASK;
    GPIOB->AFRL |= (GPIO_AF_1 << GPIO_CRL_MODE6_SHIFT);

    /* PB7 - I2C1_SDA. */
    GPIOB->CRL &= ~GPIO_CRL_MODE7_MASK;
    GPIOB->CRL &= ~GPIO_CRL_CNF7_MASK;
    GPIOB->CRL |= GPIO_CRL_MODE7(GPIO_Speed_50MHz);
    GPIOB->CRL |= GPIO_CRL_CNF7(GPIO_PinMode_AF_OpenDrain);
    GPIOB->AFRL |= (GPIO_AF_1 << GPIO_CRL_MODE7_SHIFT);

    /* PA9 - UART_TX. */
    GPIOA->CRH &= ~GPIO_CRH_MODE9_MASK;
    GPIOA->CRH &= ~GPIO_CRH_CNF9_MASK;
    GPIOA->CRH |= GPIO_CRH_MODE9(GPIO_Speed_50MHz);
    GPIOA->CRH |= GPIO_CRH_CNF9(GPIO_PinMode_AF_PushPull);
    GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT);

    /* PA10 - UART_RX. */
    GPIOA->CRH &= ~GPIO_CRH_MODE10_MASK;
    GPIOA->CRH &= ~GPIO_CRH_CNF10_MASK;
    GPIOA->CRH |= GPIO_CRH_CNF10(GPIO_PinMode_In_Floating);
    GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT);
}

UART初始化 uart_init()

初始化UART,配置时钟频率、波特率、数据长度、停止位、传输模式及是否使用校验。

void uart_init()
{
    /* Clear the corresponding bit to be used. */
    UART1->CCR &= ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK );
    UART1->GCR &= ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK );
    /* WordLength. */
    UART1->CCR |= UART_CCR_CHAR_MASK;
    /* XferMode. */
    UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT);
    /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */
    UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u;
    UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u;
    /* Enable UART1. */
    UART1->GCR |= UART_GCR_UARTEN_MASK;
}

I2C初始化 i2c_init()

I2C通过操作配置寄存器(I2C_CR)实现初始化,配置I2C为主模式、速率为100KHz、7位地址格式、时钟频率为60000000u,设置从设备地址为0x50,使能I2C。

void i2c_init()
{
    I2C1->ENR &= ~I2C_ENR_ENABLE_MASK;
    uint32_t tmp = BOARD_I2C_FREQ / I2C_BaudRate_100K; /* BOARD_I2C_FREQ = 60000000u, I2C_BaudRate_100K = 100000u. */
    I2C1->SSHR = tmp / 2u - 12u; /* Configure high level count in normal speed. */
    I2C1->SSLR = tmp / 2u - 1u;  /* Configure low level count in normal speed. */
    I2C1->CR = I2C_CR_SPEED(1u);
    I2C1->CR &= ~I2C_CR_MASTER10_MASK;  /* Address format. */
    I2C1->CR |= I2C_CR_RESTART_MASK     /* Generate restart signal. */
             | I2C_CR_DISSLAVE_MASK     /* Disable slave module. */
             | I2C_CR_REPEN_MASK        /* Enable sending restart condition. */
             | I2C_CR_EMPINT_MASK       /* Control tx_empty interrupt generation. */
             | I2C_CR_MASTER_MASK;      /* Enable master module. */

    I2C1->RXTLR = 0u;  /* Configure the sending receive value. */
    I2C1->TXTLR = 0u;  /* Configure the sending threshold value. */

    /* Setup target address. */
    I2C1->TAR = I2C_TAR_ADDR(APP_I2C_TARGET_ADDR); /* APP_I2C_TARGET_ADDR = 0x50. */
    I2C1->ENR |= I2C_ENR_ENABLE_MASK;  /* Enable I2C. */
}

I2C写操作 i2c_write()

设置I2C发送数组i2c_tx_buf及发送数据长度i2c_tx_len,将数组i2c_tx_buf的第一个数值设置为寄存器地址,发送8字节数据。数据发送后读取状态寄存器(I2C_SR),等待发送缓冲空,再进行下一次发送;数据传输完成,操作使能寄存器的ABORT位为1,使I2C停止数据传输;读清除TX_ABRT中断寄存器(I2C_TXABRT),将TX FIFO从刷新/复位状态中释放。

void i2c_write()
{
    I2C1->DR = I2C_DR_DAT(i2c_tx_buf[0]);
    while (0u == (I2C1->SR & I2C_STATUS_TX_EMPTY) )
    {
    }
    for (uint32_t i = 1u; i < i2c_tx_len; i++)
    {
        I2C1->DR = I2C_DR_DAT(i2c_tx_buf[i]);

        while ( 0u == (I2C1->SR & I2C_STATUS_TX_EMPTY) )  /* Wait to tx fifo empty. */
        {
        }
    }
    I2C1->ENR |= I2C_ENR_ABORT_MASK;  /* Prepare for the stop. */
    I2C1->TXABRT;  /* Read register to release tx fifo. */
    while (I2C1->SR & I2C_STATUS_ACTIVE)  /* Wait to I2C not active, which means stop is taking effect. */
    {
    }
}

I2C读操作 i2c_read()

设置I2C接收数组i2c_rx_buf及接收长度i2c_rx_len,在进行数据读取前,需要发送从设备地址及寄存器地址,本实验设置发送数组的第一个数据为寄存器地址,发送完成后再将I2C_DR寄存器的读写位(CMD)置1,读取I2C_DR寄存器进行读操作。读操作结束后,操作使能寄存器的ABORT位为1,使I2C停止数据传输。

void i2c_read()
{
    I2C1->DR = I2C_DR_DAT(i2c_tx_buf[0]);
    while (0u == (I2C1->SR & I2C_STATUS_TX_EMPTY) )
    {
    }
    /* Read data from target device. */
    for (uint32_t i = 0u; i < i2c_rx_len; i++)
    {
        I2C1->DR = I2C_DR_CMD_MASK;  /* Swich read-write bit to read, prepare to get data. */

        while ( 0u == (I2C1->SR & I2C_STATUS_RX_NOTEMPTY) )  /* Wait to rx fifo not empty. */
        {
        }
        i2c_rx_buf[i] = (uint8_t)I2C1->DR;
    }
    I2C1->ENR |= I2C_ENR_ABORT_MASK;  /* Prepare for the stop. */
    I2C1->TXABRT;  /* Read register to release tx fifo. */
    while (I2C1->SR & I2C_STATUS_ACTIVE)  /* Wait to I2C not active, which means stop is taking effect. */
    {
    }
}

main()函数

main()函数结合上述操作,串口打印"i2c_master_basic example",初始化I2C后,将发送数组设置为0到7,按下一次任意按键,进行数据发送,串口显示发送数据;再次按下任意按键,进行数据读取,从寄存器地址中读出数值,设置寄存器地址设置为发送数据的首个数据,串口显示接收数据,实验结果如图9所示。

int main()
{
    enable_clock();
    pin_init();
    uart_init();
    printf("\r\ni2c_master_basic example\r\n");
    i2c_init();
    for (uint32_t i = 0u; i < i2c_tx_len; i++)
    {
        i2c_tx_buf[i] = i;
    }
    while (1)
    {
        printf("press any key to write i2c-eeprom.\r\n");
        getchar();
        i2c_write();
        printf("write data: ");
        for (uint32_t i = 0u; i < i2c_tx_len; i++)
        {
            printf("0x%02X, ", i2c_tx_buf[i]);
        }
        printf("\r\n");

        printf("press any key to read i2c-eeprom.\r\n");
        getchar();
        i2c_read();
        printf("read data from device register address: 0x%02X\r\n", i2c_tx_buf[0u]);
        printf("read data:  ");
        for (uint32_t i = 0u; i < i2c_rx_len; i++)
        {
            printf("0x%02X, ", i2c_rx_buf[i]);
        }
        printf("\r\n");
}

“图9.实验结果"
图9.实验结果

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 312