【I2C】软件I2C驱动

软件实现I2C:通过手动翻转普通GPIO电平实现通信
代码整体框架:
1、建立I2C通信层的.c和.h模块,编写I2C底层的GPIO初始化和6个时序基本单元(起始、终止、发送一个字节、接收一个字节、发送应答、接收应答 )
2、建立MPU6050的.c和.h模块,基于I2C通信模块实现指定地址读、指定地址写,并且写寄存器对芯片进行配置,读寄存器得到传感器数据
3、在main.c里调用MPU6050模块,初始化、拿到数据、显示数据

【基本驱动代码】
1、完成I2C驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【MyI2C】驱动文件放在【Hardware】文件夹中
//【MyI2C】名称是为了防止与库函数里面的I2C重复
2、在 MyI2C.c中初始化函数 MyI2C_Init
//本代码使用软件I2C,只需要GPIO的读写函数,不需要I2C库函数
/*给端口号换个名字————语义明确、方便删改
----------------------------------------
方法一        宏定义
#define SCL_PORT        GPIOB
#define SCL_PIN            GPIO_Pin_10
释放SCL:GPIO_SetBits(SCL_PORT,SCL_PIN);
----------------------------------------
方法二        将GPIO对端口的整个操作函数用宏定义替换
#define OLED_W_SCL(x)        GPIO_WriteBit(GPIOB,GPIO_Pin_8,(BitAction)(x))
#define OLED_W_SDA(x)        GPIO_WriteBit(GPIOB,GPIO_Pin_9,(BitAction)(x))
可以使用带参数的宏定义(有参宏)
----------------------------------------
方法三        定义一个函数,对操作端口的库函数进行封装
//另外本方法方便添加延时
void MyI2C_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
    Delay_us(10);
}
调用函数,参数给1或0,就可以释放或拉低SCL
----------------------------------------
//本例使用【方法三】
*/
//写SCL
void MyI2C_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
    Delay_us(10);
}
//写SDA
void MyI2C_W_SDA(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
    Delay_us(10);
}
//读SDA(读和写不是同一个寄存器)
//返回读到SDA线的电平
uint8_t MyI2C_R_SDA(void)
{
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
    Delay_us(10);
    return BitValue;
}
/*
PB10和PB11两个端口被初始化为开漏输出模式
释放总线,SCL和SDA处于高电平,此时I2C总线处于空闲状态
*/
void MyI2C_Init(void)
{
    //把SCL和SDA初始化为开漏输出模式
    //当前接线为SCL=PB10,SDA=PB11    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 |GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    
    //把SCL和SDA置高电平(初始时为空闲状态)
    GPIO_SetBits(GPIOB,GPIO_Pin_10 |GPIO_Pin_11);
}
//起始条件
void MyI2C_Start(void)
{
    //把SCL和SDA置高电平(初始时为空闲状态)
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);
    //先拉低SDA,再拉低SCL
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(0);
}
//终止条件
void MyI2C_Stop(void)
{
    //拉低SDA(确保之后SDA能产生上升沿)
    //结束时SCL一定为低电平
    MyI2C_W_SDA(0);
    //先拉高SCL,再拉高SDA
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);
    
    //只有【终止条件】SCL高电平结束,其余单元SCL都是低电平结束
}
//发送一个字节
/*
SCL低电平时,主机写数据到SDA线上
SCL高电平时,从机在SDA线上读取数据
字节高位先发送,先放最高位,再放次高位……
*/
void MyI2C_SendByte(uint8_t Byte)
{
    uint8_t i;
    for(i = 0; i < 8; i ++)
    {
        //默认SCL为低电平
        MyI2C_W_SDA(Byte & ( 0x80 >> i) );//主机由高到低写入数据
        MyI2C_W_SCL(1);//从机在SDA线上读取数据
        MyI2C_W_SCL(0);//主机写数据到SDA线上
    }
}
//接收一个字节
/*
SCL低电平时,从机写数据到SDA线上
SCL高电平时,主机在SDA线上读取数据
字节高位先发送,先放最高位,再放次高位……
*/
uint8_t MyI2C_ReceiveByte(void)
{
    uint8_t i, Byte = 0x00;
    //默认SCL为低电平
    MyI2C_W_SDA(1);//主机先释放SDA(将SDA控制权赋予从机)
    for(i = 0; i < 8; i ++)
    {
        MyI2C_W_SCL(1);//主机在SDA线上读取数据
        //如果主机读取SDA为1
        if( MyI2C_R_SDA() == 1)
        {
            Byte |= ( 0x80 >> i );//主机依次把Byte相应位置1
        }
        MyI2C_W_SCL(0);//从机写数据到SDA线上
    }
    return Byte;//返回主机接收的Byte
}
//发送应答
/*
SCL低电平时,主机写发送应答到SDA线上
SCL高电平时,从机在SDA线上读取应答
*/
void MyI2C_SendAck(uint8_t AckBit)
{
    //默认SCL为低电平
    MyI2C_W_SDA(AckBit);//主机向从机写入发送应答
    MyI2C_W_SCL(1);//从机在SDA线上读取发送应答
    MyI2C_W_SCL(0);//进入下一个时序单元
}
//接收应答
/*
SCL低电平时,从机写数据到SDA线上
SCL高电平时,主机在SDA线上读取数据
*/
uint8_t MyI2C_ReceiveAck(void)
{
    uint8_t AckBit;
    //默认SCL为低电平
    MyI2C_W_SDA(1);//主机先释放SDA(将SDA控制权赋予从机,从机在SDA写接收应答)
    MyI2C_W_SCL(1);//主机在SDA线上读取接收应答
    AckBit = MyI2C_R_SDA();//主机读取的值赋给接收应答
    MyI2C_W_SCL(0);//进入下一个时序单元
    return AckBit;//返回主机接收的AckBit
}
3、在MyI2C.h中声明初始化函数 MyI2C_Init及其 时序单元函数
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
4、在主程序main.c中 #include  "MyI2C .h "
#include "MyI2C.h"
5、在主循环中编写程序主体
int main(void)
{
    MyI2C_Init();
    OLED_Init();
    //测试方法:对照完成【指定地址写】、【指定地址读】完整时序
        
    //【指定地址写】
    //本例用于测试从机是否收到应答
    
    //调用Start产生起始条件开始传输
    MyI2C_Start();
    
    //第一个字节时序(必须是发送)——字节内容:从机地址(7位)+读写位(1位)
    MyI2C_SendByte(0xD0);    //1101 000 0    (0代表即将进行写入操作)
    //【接收应答】
    uint8_t Ack = MyI2C_ReceiveAck();
    //拓展:可以返回值变量判断从机是否收到主机发送的数据    
    //调用Stop产生终止条件结束传输
    MyI2C_Stop();
    
    OLED_ShowNum(1,1,Ack,3);
    
    while(1)
    {
    }
}
实现功能:上电后初始化I2C和OLED模块,通过软件I2C向从机发送寻址数据【0xD0】,MPU6050收到后返回接收应答【000】并通过OLED显示;此处如果修改寻址数据为不存在的从机,则返回接收应答【001】并通过OLED显示

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在STM32中,软件驱动I2C需要进行以下步骤: 1. 配置GPIO引脚:将I2C引脚对应的GPIO引脚配置为复用模式,并设置引脚输出模式和输出速度。 2. 配置I2C时钟:设置I2C时钟频率,包括SCL时钟频率和I2C总线时钟频率。 3. 初始化I2C外设:设置I2C工作模式、地址和ACK应答模式等参数。 4. 发送I2C数据:根据所需的数据传输方式,发送起始位、地址、数据和停止位等信号。 5. 接收I2C数据:通过读取I2C数据寄存器获取数据,并进行必要的处理。 在具体实现中,可以调用STM32提供的HAL库函数来完成以上步骤。以下是一个使用HAL库函数实现I2C读写的示例代码: ``` #include "stm32f1xx_hal.h" I2C_HandleTypeDef hi2c; void I2C_Init() { hi2c.Instance = I2C1; hi2c.Init.ClockSpeed = 100000; hi2c.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c.Init.OwnAddress1 = 0; hi2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c.Init.OwnAddress2 = 0; hi2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c) != HAL_OK) { Error_Handler(); } } void I2C_Write(uint8_t addr, uint8_t *data, uint16_t size) { HAL_I2C_Master_Transmit(&hi2c, (uint16_t)(addr << 1), data, size, 1000); } void I2C_Read(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t size) { HAL_I2C_Mem_Read(&hi2c, (uint16_t)(addr << 1), (uint16_t)reg, I2C_MEMADD_SIZE_8BIT, data, size, 1000); } ``` 在这个例子中,我们使用了I2C1作为I2C外设,并通过I2C_Init函数初始化了I2C外设。I2C_Write函数可以将数据写入到指定设备的寄存器中,而I2C_Read函数可以从指定设备的寄存器中读取数据。需要注意的是,在使用HAL库函数时,需要先将对应的GPIO引脚配置为复用模式,并将引脚输出模式和输出速度设置为合适的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值