STM32f103 驱动之I2C

目录

一、简介。

(1)数据有效性

 (2)开始信号和结束信号

 (3)应答信号

 (4)传输时序

(5)字节写模式时序

(6)页写模式时序

(7)当前地址读

 (8)随机地址读

(9)顺序地址读

(10) 硬件设计

 二、模拟I2C编程

 三、硬件I2C编程

【说明】:文章摘自韦东山老师《100ASK_STM32F103_MINI用户手册V1.1》

一、简介。

     I²C(Inter-Integrated Circuit),是一种多主从架构串行通信总线,半双工同步类型传输总线。由两条线组成:一条双向数据线SDA,一条串行时钟线SCL。

(1)数据有效性

        >SCL为高电平时,SDA的数据才有效。

        >SCL为低电平时,SDA高低电平才能改变。

        

 (2)开始信号和结束信号

        I²C起始信号(S):当SCL高电平时,SDA由高电平向低电平转换;
        I²C停止信号(P):当SCL高电平时,SDA由低电平向高电平转换;

 (3)应答信号

        I2C每次传输8位数据(7位数据位+1位读写位),每次传输后需从机返回一个应答位,以确认从机是否正常接收数据。

        主机每发送8位数据后,再产生一个时钟。此时主机放开SDA的控制,读取SDA电平。

 (4)传输时序

(5)字节写模式时序

(6)页写模式时序

 

(7)当前地址读

 (8)随机地址读

(9)顺序地址读

 

(10) 硬件设计

 二、模拟I2C编程

/* 1、I2C 硬件相关定义********************************************/
#define ACK (0)
#define NACK (1)

#define SCL_PIN                 GPIO_PIN_6
#define SCL_PORT                GPIOB
#define SCL_PIN_CLK_EN()        __HAL_RCC_GPIOB_CLK_ENABLE()

#define SDA_PIN                 GPIO_PIN_7
#define SDA_PORT                GPIOB
#define SDA_PIN_CLK_EN()        __HAL_RCC_GPIOB_CLK_ENABLE()

#define SCL_H()                 HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET)
#define SCL_L()                 HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET)
#define SCL_INPUT()             HAL_GPIO_ReadPin(SCL_PORT, SCL_PIN)

#define SDA_H()                 HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET)
#define SDA_L()                 HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_RESET)
#define SDA_INPUT()             HAL_GPIO_ReadPin(SDA_PORT, SDA_PIN)

/* 2、I2C引脚初始化***********************************************/
/*
* 函数名:void I2C_Init(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:初始化 模拟I2C 的引脚为输出状态且SCL/SDA都初始为高电平
*/
void I2C_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    SCL_PIN_CLK_EN();    //
    SDA_PIN_CLK_EN();
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    //推挽输出
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;    //高速模式

    GPIO_InitStruct.Pin = SCL_PIN;
    HAL_GPIO_Init(SCL_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = SDA_PIN;
    HAL_GPIO_Init(SDA_PORT, &GPIO_InitStruct);

    SCL_H();        //初始化SCL高电平
    SDA_H();        //初始化SDA高电平
}

/*
* 函数名:static void I2C_SDA_OUT(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:配置SDA引脚为输出
*/
static void I2C_SDA_OUT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;       
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Pin = SDA_PIN;
    HAL_GPIO_Init(SDA_PORT, &GPIO_InitStruct);
}

/*
* 函数名:static void I2C_SDA_IN(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:配置SDA引脚为输入
*/
static void I2C_SDA_IN(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Pin = SDA_PIN;
    HAL_GPIO_Init(SDA_PORT, &GPIO_InitStruct);
}

/*3、I2C开始信号和结束信号 *************************************************/
/*
* 函数名:void I2C_Start(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:I2C开始信号
*/
void I2C_Start(void)
{
    I2C_SDA_OUT();   //SDA输出
    SCL_H();        //SCL高电平
    I2C_Delay();    //延迟大于5us   
    SDA_H();
    I2C_Delay();    
                    
    SDA_L();        //先拉高,再拉低
    I2C_Delay();
    SCL_L();
    I2C_Delay();
}

/*
* 函数名:void I2C_Stop(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:I2C停止信号
*/
void I2C_Stop(void)
{
    I2C_SDA_OUT();
    SDA_L();        //终止时先有应答信号,所以SCL被拉低
    I2C_Delay();
    
    SCL_H();    //SCL先拉低,SDA后拉高
    I2C_Delay();
    SDA_H();
    I2C_Delay();
}

/*4、应答/非应答/等待应答信号**********************************************/
/*
* 函数名:void I2C_ACK(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:I2C发出应答信号
*/
void I2C_ACK(void)
{
    I2C_SDA_OUT();
    SCL_L();        //SCL和SDA都拉低
    I2C_Delay();
    SDA_L();
    I2C_Delay();

    SCL_H();        //SCL拉高
    I2C_Delay();
    SCL_L();       //SCL拉低
    I2C_Delay();
}

/*
* 函数名:void I2C_NACK(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:I2C发出非应答信号
*/
void I2C_NACK(void)
{
    I2C_SDA_OUT();
    SCL_L();        // SCL 先拉低,然后SDA和SCL都拉高,SCL再拉低
    I2C_Delay();

    SDA_H();
    I2C_Delay();

    SCL_H();
    I2C_Delay();
    SCL_L();
    I2C_Delay();
}

/*
* 函数名:uint8_t I2C_GetACK(void)
* 输入参数:
* 输出参数:无
* 返回值:1无应答,0有应答
* 函数作用:I2C等待从机的应答信号
*/
uint8_t I2C_GetACK(void)
{
    uint8_t time = 0;
    I2C_SDA_IN();        //切换为输入
    SCL_L();
    I2C_Delay();
    SDA_H();
    I2C_Delay();

    SCL_H();
    I2C_Delay();

    while(SDA_INPUT())
    {
        time++;
        if(time>250)
        {
            SCL_L();
            return 1;
        }
    }
    SCL_L();
    return 0;
}

/*5、发送/接收函数**************************************************/
/*
* 函数名:void I2C_SendByte(uint8_t data)
* 输入参数:data->发送的数据
* 输出参数:无
* 返回值:无
* 函数作用:I2C发送一个字节
*/
void I2C_SendByte(uint8_t data)
{
    uint8_t cnt = 0;
    I2C_SDA_OUT();                //切换为输出
    for(cnt=0; cnt<8; cnt++)
    {
        SCL_L();                //每发一位,先SCL拉低
        I2C_Delay();
        if(data & 0x80)
        {
            SDA_H();
        }
        else
        {
            SDA_L();
        }
        data = data<<1;
        SCL_H();            //发完一位,再SCL拉高
        I2C_Delay();
    }
    SCL_L();
    I2C_Delay();
    I2C_GetACK();           //发完一个字节,获取ACK
}


/*
* 函数名:uint8_t I2C_ReadByte(uint8_t ack)
* 输入参数:ack->发送的应答标志,1应答,0非应答
* 输出参数:无
* 返回值:返回读到的字节
* 函数作用:I2C读出一个字节
*/
uint8_t I2C_ReadByte(uint8_t ack)
{
    uint8_t cnt;
    uint8_t data = 0xFF;
    SCL_L();
    I2C_Delay();
    for(cnt=0; cnt<8; cnt++)
    {
        SCL_H(); //SCL高(读取数据)
        I2C_Delay();
        data <<= 1;
        if(SDA_INPUT())    //切换位输入
        {
            data |= 0x01; //SDA高(数据为1)
        }
        SCL_L();
        I2C_Delay();
    }
    //发送应答信号,为低代表应答,高代表非应答
    if(ack == 0)
    {
        I2C_ACK();
    }
    else
    {
        I2C_NACK();
    }
    return data; //返回数据
}


/*EEPROM I2C读写函数**********************************************/

/*
* 函数名:uint8_t EEPROM_WriteByte(uint16_t addr, uint8_t data)
* 输入参数:addr -> 写一个字节的EEPROM初始地址
* data -> 要写的数据
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM写一个字节
*/
void EEPROM_WriteByte(uint16_t addr, uint8_t data)
{
    /* 1. Start */
    I2C_Start();
    /* 2. Write Device Address */
    I2C_SendByte( EEPROM_DEV_ADDR | EEPROM_WR );
    /* 3. Data Address */
    if(EEPROM_WORD_ADDR_SIZE==0x08)
    {
        I2C_SendByte( (uint8_t)(addr & 0x00FF) );
    }
    else
    {
        I2C_SendByte( (uint8_t)(addr>>8) );
        I2C_SendByte( (uint8_t)(addr & 0x00FF) );
    }
    /* 4. Write a byte */
    I2C_SendByte(data);
    /* 5. Stop */
    I2C_Stop();
}

/*
* 函数名:uint8_t EEPROM_ReadByte(uint16_t addr, uint8_t *pdata)
* 输入参数:addr -> 读一个字节的EEPROM初始地址
* data -> 要读的数据指针
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM读一个字节
*/
void EEPROM_ReadByte(uint16_t addr, uint8_t *pdata)
{
    /* 1. Start */
    I2C_Start();
    /* 2. Write Device Address */
    I2C_SendByte( EEPROM_DEV_ADDR | EEPROM_WR );
    /* 3. Data Address */
    if(EEPROM_WORD_ADDR_SIZE==0x08)
    {
    I2C_SendByte( (uint8_t)(addr & 0x00FF) );
    }
    else
    {
        I2C_SendByte( (uint8_t)(addr>>8) );
        I2C_SendByte( (uint8_t)(addr & 0x00FF) );
    }
    /* 4. Start Again */
    I2C_Start();
    /* 5. Write Device Address Read */
    I2C_SendByte( EEPROM_DEV_ADDR | EEPROM_RD );
    /* 6.Read a byte */
    *pdata = I2C_ReadByte(NACK);
    /* 7. Stop */
    I2C_Stop();
}

/*读写EEPROM多字节数据*/
/*
* 函数名:void EEPROM_Write_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
* 输入参数:addr -> 写一个字节的EEPROM初始地址
* data -> 要写的数据指针
* sz -> 要写的字节个数
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM写N个字节
*/
void EEPROM_Write_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
{
    uint16_t i = 0;
    for(i=0; i<sz; i++)
    {
        EEPROM_WriteByte(addr, pdata[i]);
        addr++;
        HAL_Delay(10); // Write Cycle Time 5ms
    }
}
/*
* 函数名:void EEPROM_Read_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
* 输入参数:addr -> 读一个字节的EEPROM初始地址
* data -> 要读的数据指针
* sz -> 要读的字节个数
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM读N个字节
*/
void EEPROM_Read_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
{
    uint16_t i = 0;
    for(i=0; i<sz; i++)
    {
        EEPROM_ReadByte(addr, &pdata[i]);
        addr++;
    }
}

 三、硬件I2C编程

/************************* I2C 硬件相关定义 *************************/
#define I2Cx                          I2C1
#define I2Cx_CLK_EN()                 __HAL_RCC_I2C1_CLK_ENABLE()

#define I2Cx_ClockSpeed               (400000)    
#define I2Cx_FORCE_RESET()            __HAL_RCC_I2C1_FORCE_RESET()
#define I2Cx_RELEASE_RESET()          __HAL_RCC_I2C1_RELEASE_RESET()

#define SCL_PIN              GPIO_PIN_6
#define SCL_PORT             GPIOB
#define SCL_PIN_CLK_EN()     __HAL_RCC_GPIOB_CLK_ENABLE()

#define SDA_PIN              GPIO_PIN_7
#define SDA_PORT             GPIOB
#define SDA_PIN_CLK_EN()    __HAL_RCC_GPIOB_CLK_ENABLE()


/*22C协议初始化***************************************************/
I2C_HandleTypeDef hi2c;
/*
* 函数名:void I2C_Init(void)
* 输入参数:
* 输出参数:无
* 返回值:无
* 函数作用:初始化I2C速率和地址格式
*/
void I2C_Init(void)
{
    hi2c.Instance = I2Cx;
    hi2c.Init.ClockSpeed = I2Cx_ClockSpeed; // 设置SCL时钟频率(最高400000)
    hi2c.Init.DutyCycle = I2C_DUTYCYCLE_2; // 设置I2C的SCL时钟的占空比(都可以)
    hi2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 设置广播呼叫模式(关闭)
    hi2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 设置禁止时钟延长模式(关闭)
    hi2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 设置I2C寻址长度模式(通常7bit)
    hi2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 是否使用两个STM32的设备地址(关闭)
    hi2c.Init.OwnAddress1 = 0x0A; // STM32的设备地址1(支持7bit或10bit)
    hi2c.Init.OwnAddress2 = 0; // STM32的设备地址2(只支持7bit)
    if(HAL_I2C_Init(&hi2c) != HAL_OK)
    {
    Error_Handler();
    }
}

/* 3 I2C硬件初始化********************************************************/
/*
* 函数名:void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
* 输入参数:hi2c-I2C句柄
* 输出参数:无
* 返回值:无
* 函数作用:使能I2C的时钟,使能引脚时钟,并配置引脚的复用功能
*/
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    if(hi2c->Instance==I2Cx)
    {
        I2Cx_CLK_EN();
        SCL_PIN_CLK_EN();
        SDA_PIN_CLK_EN();
        GPIO_InitStruct.Pin = SCL_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(SCL_PORT, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = SDA_PIN;
        HAL_GPIO_Init(SDA_PORT, &GPIO_InitStruct);

        I2Cx_FORCE_RESET(); // 强制复位
        I2Cx_RELEASE_RESET(); // 释放复位
    }
}

/* 4 读写EEPROM 1字节数据******************************************/
/*
* 函数名:uint8_t EEPROM_WriteByte(uint16_t addr, uint8_t data)
* 输入参数:addr -> 写一个字节的EEPROM初始地址
* data -> 要写的数据
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM写一个字节
*/
void EEPROM_WriteByte(uint16_t addr, uint8_t data)
{
    uint8_t write_data[10];
    uint8_t data_num;
    //AT24C01/02的数据地址长度为8位
    //AT24C04/08/16数据地址长度为16位
    if(EEPROM_WORD_ADDR_SIZE==0x08)
    {
        write_data[0] = (uint8_t)(addr & 0x00FF);
        write_data[1] = data;
        data_num = 2;
    }
    else
    {
        write_data[0] = (uint8_t)(addr>>8);
        write_data[1] = (uint8_t)(addr & 0x00FF);
        write_data[2] = data;
        data_num = 3;
    }
    //发送数据和地址
    if(HAL_I2C_Master_Transmit(&hi2c, EEPROM_DEV_ADDR | EEPROM_WR, write_data, data_num, 300) != HAL_OK)
    {
        Error_Handler();
    }
    while (HAL_I2C_GetState(&hi2c) != HAL_I2C_STATE_READY);
}

/*
* 函数名:uint8_t EEPROM_ReadByte(uint16_t addr, uint8_t *pdata)
* 输入参数:addr -> 读一个字节的EEPROM初始地址
* data -> 要读的数据指针
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM读一个字节
*/
void EEPROM_ReadByte(uint16_t addr, uint8_t *pdata)
{
    uint8_t write_data[10];
    uint8_t data_num;
    //AT24C01/02的数据地址长度为8位
    //AT24C04/08/16数据地址长度为16位
    if(EEPROM_WORD_ADDR_SIZE==0x08)
    {
        write_data[0] = (uint8_t)(addr & 0x00FF);
        data_num = 1;
    }
    else
    {
        write_data[0] = (uint8_t)(addr>>8);
        write_data[1] = (uint8_t)(addr & 0x00FF);
        data_num = 2;
    }
    //发送地址
    if(HAL_I2C_Master_Transmit(&hi2c, EEPROM_DEV_ADDR | EEPROM_WR , (uint8_t*)write_data, data_num, 300) != HAL_OK)
    {
        Error_Handler();
    }
    while (HAL_I2C_GetState(&hi2c) != HAL_I2C_STATE_READY);
    //接收数据
    if(HAL_I2C_Master_Receive(&hi2c, EEPROM_DEV_ADDR | EEPROM_RD , (uint8_t*)pdata, 1, 300) != HAL_OK)
    {
        Error_Handler();
    }
}


/* 5 读写EEPORM多字节****************************************************/
/*
* 函数名:void EEPROM_Write_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
* 输入参数:addr -> 写一个字节的EEPROM初始地址
* data -> 要写的数据指针
* sz -> 要写的字节个数
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM写N个字节
*/
void EEPROM_Write_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
{
    uint16_t i = 0;
    for(i=0; i<sz; i++)
    {
        EEPROM_WriteByte(addr, pdata[i]);
        addr++;
        HAL_Delay(10); // Write Cycle Time 5ms
    }
}

/*
* 函数名:void EEPROM_Read_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
* 输入参数:addr -> 读一个字节的EEPROM初始地址
* data -> 要读的数据指针
* sz -> 要读的字节个数
* 输出参数:无
* 返回值:无
* 函数作用:EEPROM读N个字节
*/
void EEPROM_Read_NBytes(uint16_t addr, uint8_t *pdata, uint16_t sz)
{
    uint16_t i = 0;
    for(i=0; i<sz; i++)
    {
    EEPROM_ReadByte(addr, &pdata[i]);
    addr++;
    }
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值