IIC协议

IIC协议,简而言之,就是SCL跟SDA两根总线

对于MCU而言,有硬件IIC跟模拟IIC,今天我们就来讲模拟IIC

首先,对于普通IO口而言,他们都需要有两个上拉电阻,来输出高电平

为什么要这样了,我认为,当我们不需要上拉电阻的时候,我们选择推挽输出,可以轻易输出高低电平,这不是很方便吗,但是,主机推挽输出强制高电平 → 从机无法拉低 → ACK检测失败

好了,现在开始代码讲解

#include "./BSP/IIC/iic.h"

/* 
iic 初始化
*/
GPIO_TypeDef * SCL_PORT=NULL;
GPIO_TypeDef * SDA_PORT=NULL;
uint16_t SCL_PIN =0;
uint16_t SDA_PIN =0;


void IIC_Init(GPIO_TypeDef * SCL_PORTX,GPIO_TypeDef *SDA_PORTX , uint16_t SCL_PINX , uint16_t SDA_PINX)
{
    GPIO_InitTypeDef GPIO_Init;

    SCL_PORT = SCL_PORTX;
    SDA_PORT = SDA_PORTX;
    SCL_PIN = SCL_PINX;
    SDA_PIN = SDA_PINX;

    /*使能时钟*/
    if(SCL_PORT == GPIOC)
    {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }else{
        __HAL_RCC_GPIOB_CLK_ENABLE();
    }

    if(SDA_PORT == GPIOC)
    {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }else{
        __HAL_RCC_GPIOB_CLK_ENABLE();
    }

    GPIO_Init.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_Init.Pull = GPIO_PULLUP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_Init.Pin = SCL_PIN;

    HAL_GPIO_Init(SCL_PORT, &GPIO_Init);

    GPIO_Init.Pin = SDA_PIN;
    HAL_GPIO_Init(SDA_PORT, &GPIO_Init);
}


GPIO_PinState IIC_READ_SCL_PIN_STATE()
{
    return HAL_GPIO_ReadPin(SCL_PORT, SCL_PIN);
}

GPIO_PinState IIC_READ_SDA_PIN_STATE()
{
    return HAL_GPIO_ReadPin(SDA_PORT, SDA_PIN);
}

void IIC_SCL_1(void)
{
    HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
}

void IIC_SCL_0(void)
{
    HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
}

void IIC_SDA_1(void)
{
    HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
}

void IIC_SDA_0(void)
{
    HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_RESET);
}

void IIC_Start(void)
{
    IIC_SDA_1();
    IIC_SCL_1();  
    
    IIC_Delay();
    IIC_SDA_0();
    IIC_Delay();
    IIC_SCL_0();//钳住总线,准备发送数据
    IIC_Delay();
}

uint8_t IIC_Wait_Ack(void)
{
    uint8_t flag=NACK;

    /*改成输入模式*/
    GPIO_InitTypeDef GPIO_Init;

    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_PULLUP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_Init.Pin = SDA_PIN;
    HAL_GPIO_Init(SDA_PORT, &GPIO_Init);
    IIC_Delay();

    IIC_SCL_1();//采样数据
    IIC_Delay();

    if(IIC_READ_SDA_PIN_STATE() == GPIO_PIN_RESET)
    {
        /*收到ACK*/
        flag = ACK;
    }
    else
    {
        flag = NACK;
    }

    IIC_SCL_0();//完成第九个周期
    IIC_Delay();

    //改成输出模式
    GPIO_Init.Mode = GPIO_MODE_OUTPUT_OD;
    
    HAL_GPIO_Init(SDA_PORT, &GPIO_Init);

    return flag;
}


void IIC_Stop(void)
{
    //先将SCL拉高,在将SDA拉高
    IIC_SCL_1();
    IIC_Delay();
    IIC_SDA_1();
    IIC_Delay();
    /*一个周期,表明发送了停止信号*/
}


void IIC_Delay(void)
{
    delay_us(5);
}

void IIC_Send_Byte(uint8_t byte)
{
    for(uint8_t i=0;i<8;i++)
    {
        IIC_SCL_0();//确保SCL为低电平
        IIC_Delay();

        if(byte & 0x80)
        {
            IIC_SDA_1();
        }
        else
        {
            IIC_SDA_0();
        }

        byte <<= 1;
        IIC_Delay();
        
        IIC_SCL_1();/*时钟上升沿(数据被采样)*/
        IIC_Delay();
        IIC_SCL_0();/*时钟下降沿*/
        IIC_Delay();
    }

    //第九个周期,准备接收ACK
    /*释放SDA总线,等待从设备拉低*/
    IIC_SDA_1();                        // 释放数据线
    IIC_Delay();
}


uint8_t IIC_Read_Byte(uint8_t ack_f)
{
    uint8_t byte=0;
    /*先切换成输入模式*/
    GPIO_InitTypeDef GPIO_Init;

    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_PULLUP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_Init.Pin = SDA_PIN;

    HAL_GPIO_Init(SDA_PORT, &GPIO_Init);

    for(uint8_t i=0;i<8;i++)
    {
        //确保SCL为低电平
        IIC_SCL_0();
        IIC_Delay();

        IIC_SCL_1();//时钟上升沿(数据被采样)
        IIC_Delay();

        byte <<= 1;//需要先移动,如果放在后面,会导致MSB丢失

        if(IIC_READ_SDA_PIN_STATE() == GPIO_PIN_SET)
        {
            byte |= 0x01;
        }

        IIC_SCL_0();//时钟下降沿
        IIC_Delay();
        //完成一个周期
    }

    //变成输出模式
    GPIO_Init.Mode = GPIO_MODE_OUTPUT_OD;

    HAL_GPIO_Init(SDA_PORT, &GPIO_Init);

    //是否发送ACK
    if(ack_f == ACK)
    {
       IIC_SDA_0();
    }
    else
    {
        IIC_SDA_1();
    }
    IIC_Delay();
    
    //完成第九个周期
    IIC_SCL_1();
    IIC_Delay();
    IIC_SCL_0();
    IIC_Delay();

    return byte;
}

我的IIC的0.96寸OLED跟GY_30光敏传感器都需要用到IIC,为了不重复编写IIC,我将通过全局变量通过init获取

简而言之,IIC就是一个起始信号+8位数据传输+应答信号+结束信号

起始信号,需要保证一开始SDA为高电平,SCL高电平,再将SDA拉低

对于发送8位数据而言,注意,只有当SCL为低电平的时候,SDA才能电平变化,当SCL为高电平的时候,就能开始采样了,传输8位数据,就要完成8个周期,再第九个周期的过程中,需要去检测从机发送的时ACK还是NACK,首先我们必须保证SCL为低电平,表示第九周期的开始,然后将SDA变成输入模式,再将SCL拉高,用来采样,去检测SDA的电平,再将SCL电平拉低,表示周期的结束,此时我们就能根据SDA的高低电平,来判断从机是否发送了ACK信号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值