写一个IIC协议

Brief Introduction

说明:本文是参考bilibili up 江协科技 STM32入门教程
本文仅作为个人学习和思考记录,可能有些地方写的不是很到位,还请评论指出不足。

IIC作为一个(SDA、SCL)双线通讯协议,特点同步,半双工,带有数据应答,且支持一主多从、多主多从。形象来说该协议像我们小时候玩的木头人游戏,游戏开始时,主机开始发言123木头人…,此时从机可以随意行动,当主机突然发出不许动这句话时,所有从机必须保持不变,只有主机说123木头人时才可以动。SCL低电平时,SDA可以变化,但是SCL高电平时,SDA就必须保持不变。

START

开始之前先定义端口
首先IIC初始化函数,这里开启GPIOB的时钟,又配置了Pin10Pin11,最后把他们设置为高电平。加粗字体可以自行修改。

void IIC_Init(void)
{
  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);

  GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}

这里把SDA和SCL都封装成函数,函数里又包含延时函数,这样调用的时候,既方便改引脚又方便改延时时间。

void IIC_SCL(uint8_t BitValue)
{  
  GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
  Delay_us(10);
}

void IIC_SDA(uint8_t BitValue)
{
  GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
  Delay_us(10);
}

(BitAction)BitValue 是一个类型转换,它将一个变量 BitValue 转换成 BitAction 类型。这里BitValue取0或1,然后转换成下面枚举类型里面内容。

文件:stm32f10x_gpio.c

typedef enum
{ Bit_RESET = 0,
  Bit_SET
}BitAction;

这是读SDA引脚值函数,它还有一个返回值BitValue,用于接收字节和应答。

uint8_t IIC_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}

Start & Stop

在这里插入图片描述

IIC开始先把SDA、SCL都释放,然后拉低SDA,再拉低SCL;

IIC_Start()
{
    IIC_SDA(1);
    IIC_SCL(1);
    IIC_SDA(0);
    IIC_SCL(0);
}
这里建议先释放SDA,再释放SCL,目的是为了兼容重复起始条件。

IIC结束先拉低SDA,再释放SCL,再释放SDA;

void IIC_Stop
{
    IIC_SDA(0);
    IIC_SCL(1);
    IIC_SDA(1);
}

Send one Byte

在这里插入图片描述

SCL低电平,允许数据变换,SCL高电平,保持数据稳定;
在发送一个字节时,高位先行,因此变换数据的时候,先放最高位,再放次高位,…,最后最低位;
依次把一个字节每一位放在SDA线上,每放完一位,释放SCL,拉低SCL,驱动时钟运转;

首先SCL低电平期间,先把Byte的最高位放在SDA线上,然后判断该位是否为0,是就SDA低电平,否就是SDA高电平,然后SCL释放,SCL再拉低,接着判断次高位…循环8次就完成了一个字节的发送。

这里除了终止条件SCL以高电平结束,其他所有单元SCL以低电平结束,因此在后面的函数模块里不会在开始时先写IIC_SCL(0);

void IIC_SendByte(uint8_t Byte)
{
   uint8_t i;
   for (i = 0; i < 8; i ++)
   {
	 if((Byte & 0x80>>i) == 0){	
	 IIC_SDA(0);
	 }
	 else{
		IIC_SDA(1);
	 }
	 IIC_SCL(1);
	 IIC_SCL(0);
   }
}

Receive one Byte

在这里插入图片描述

在接受一个字节开始时,SCL低电平,此时从机需要把数据放到SDA上,(为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA相当于切换为输入模式),如果从机想发1,就释放SDA,如果从机想发0就拉低SDA,然后主机释放SCL,在SCL高电平期间读取SDA,再然后拉低SCL,从机把下一位数据放到SDA上,重复8次,主机就能读到一个字节了。

接受一个字节,进来之后SCL是低电平,主机释放SDA,从机把数据放到SDA,这时主机释放SCL,SCL高电平主机就能读取数据了,如果读到的数据为1,就把0x80右移一位,Byte和0x80或运算,再赋给Byte,如果读到数据为0,则进入下一次循环,默认这一位为0,循环8次返回Byte的值。

//整体函数内容不唯一可以用移位运算实现字节读取
uint8_t IIC_ReceiveByte(void)
{
  uint8_t i, Byte = 0x00;
  IIC_SDA(1);         //主机释放SDA
  for (i = 0; i < 8; i ++) 
  {
	IIC_SCL(1);       //主机在SCL高电平时,读取数据
	if (IIC_R_SDA() == 1){Byte |= (0x80 >> i);}
	IIC_SCL(0);
  }
  return Byte;
}

Send Ack & Receive Ack

在这里插入图片描述

发送应答
相当于发送一个字节简化版,函数进来时,SCL低电平,主机把AckBit放到SDA上,SCL高电平,从机读取应答,SCL低电平进入下一个时序单元。

void IIC_SendAck(uint8_t AckBit)
{
  IIC_SDA(AckBit);
  IIC_SCL(1);
  IIC_SCL(0);
}

接收应答

uint8_t IIC_ReceiveAck(void)
{
  uint8_t AckBit;
  IIC_SDA(1);
  IIC_SCL(1);
  AckBit = IIC_R_SDA() ;
  IIC_SCL(0);
  return AckBit;
}

END

验证
需要有支持IIC通信的器件,还有引脚要插对位置;
需要OLED来显示结果;

这里使用从机地址为0x68的IIC器件(如:MPU6050)
MPU6050的引脚AD0为低电平时从机地址为 110 1000;高电平时为 110 1001;
因此可以通过配置AD0引脚来更改从机地址,来避免地址重名。

把110 1000分成高三位和低四位的七位二进制,转换成十六进制就是0x68;
在IIC通信中高七位是从机地址,最后一位是读写位,一种方式是如果把0x68当作从机地址,在发送第一个字节时,那么需要0x68左移一位再按位或上读写位,读1写0;

另一种方式是直接把0x68左移一位后当作从机地址,即0xD0,如果要写就直接把0xD0当作第一个字节,如果读就把0xD0和0x01或运算,0xD1当作第一个字节。此方式将读写位融入到从机地址中。

int main(void)
{
  OLED_Init();

  IIC_Init();
  IIC_Start();
  IIC_SendByte(0xD0);
  uint8_t Ack = IIC_ReceiveAck();
  IIC_Stop();

  OLED_ShowNum(1,1,Ack,3);
}

在这里插入图片描述
显示为0表示MPU6050应答,否则会显示1
根据上面main函数,在该函数里面套一个for循环用于遍历从机地址,

//由于从机地址是一个7位二进制数,所有它的十六进制数范围0x00~0x7F
for(uint8_t address=0x00;address<0x7F;address++) 
{
	IIC_Start();
    IIC_SendByte(address<<1);      // I2C 地址的最低位为读写位,此处写入模式,读1写0
	uint8_t Ack = IIC_ReceiveAck();
    IIC_Stop();
	
	if(Ack==0)
	{
		OLED_ShowString(1,1,"Ack=");
        OLED_ShowNum(1,5,Ack,3);
		OLED_ShowString(2,1,"address:");    //移位后的从机地址
	    OLED_ShowHexNum(2,9,address<<1,4);  //没有移位的从机地址
		OLED_ShowString(3,1,"ID:");
	    OLED_ShowHexNum(3,4,address,4);
		
	Delay_s(1);
	}	
}

运行结果
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值