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信号

2902

被折叠的 条评论
为什么被折叠?



