STM32F103学习笔记(7.0)——模拟IIC

目录

预定义

引脚初始化

起始条件

结束条件

发送一个字节

接收应答

发送应答

接收一个字节


IIC的原理介绍移步B站江科大,本文介绍如何模拟IIC时序

IIC需要有起始条件、传输一个字节、应答、结束条件四个,将这四个时序组合起来就可以得到IIC完整的通讯时序。

IIC由SCL时钟线和SDA数据线组成,是同步半双工。

可以将一整个时序拆分开来。

预定义

为了提高代码可读性,提前定义一下SDA和SCL的操作,在头文件中定义好SDA和SCL是用哪两个引脚,就可以方便移植。

#define SCL_H GPIOB->BSRR|=1<<6
#define SCL_L GPIOB->BRR|=1<<6
#define SDA_H GPIOB->BSRR|=1<<7
#define SDA_L GPIOB->BRR|=1<<7
#define Read_SDA GPIOB->IDR&1<<7

/*
如果用野火的sys.h可以这么写
#define SCL_H PBout(6)=1
#define SCL_L PBout(6)=0
#define SDA_H PBout(7)=1
#define SDA_L PBout(7)=0
#define Read_SDA PBin(7)
*/

/*
如果写库函数,可以这么写
#define SCL_H GPIO_SetBits(GPIOB, GPIO_PIN_6)
#define SCL_L GPIO_ResetBits(GPIOB, GPIO_PIN_6)
#define SDA_H GPIO_SetBits(GPIOB, GPIO_PIN_7)
#define SDA_L GPIO_ResetBits(GPIOB, GPIO_PIN_7)
#define Read_SDA GPIO_ReadInputDataBit(GPIOB, GPIO_PIN_7)
*/

定义一个IIC_Delay()函数,用于时序的延时。

void IIC_Delay()
{
	delay_us(4);
}	

引脚初始化

这里PB6-SCL  PB7-SDA,外接了上拉电阻,所以这里使用开漏输出,开漏输出的时候直接读取ODR寄存器可以读到引脚的状态,不需要再转换引脚的输入输出状态。

void IIC_Init(void)		//初始化引脚
{
	RCC->APB2ENR |= 1<<3;
	GPIOB->CRL &= 0x00FFFFFF;
	GPIOB->CRL |= 0x77000000;	//通用开漏输出,PB6-SCL PB7-SDA
	GPIOB->ODR |= 3<<6;			//PB6 PB7给高
}

起始条件

主机发送一个起始条件,目的是告诉从机要准备开始传输数据了。

起始条件是在SCL高电平期间拉低SDA,如下图:

那把这个时序用代码写出来就是这样:

SCL_H;            //先释放总线
SDA_H;            //释放总线
IIC_Delay();
SDA_L;            //在SCL高电平期间拉低SDA
IIC_Delay();
SCL_L;            //为了和后面时序对齐

结束条件

主机发送一个结束条件,目的是告诉从机停止交流

结束条件是在SCL高电平期间拉高SDA,也就是说SCL本身要先处于高电平,SDA处于低电平,然后再拉高SDA。如下图:

 把这个时序用代码写出来就是这样:

void IIC_Stop(void)		//发送结束条件
{
	SDA_L;          //初始让SDA处于低电平
	SCL_H;		    //让SCL处于高电平
	IIC_Delay();	
	SDA_H;		    //在SCL高期间拉高SDA
}

发送一个字节

IIC发送字节很简单,就是在SCL高电平期间(主机或从机)读取SDA,在SCL低电平期间改变SDA状态。比如说想发送1,那就先把SCL拉低,然后再让SDA拉低,再把SCL拉高,接收方在SCL拉高之后读取SDA状态就可以了。依次发送八位就是一个字节。

IIC是高位先发送,用for循环依次发送即可,代码可以是这样:

void IIC_SendByte(unsigned char byte)	//发送一个字节
{
	int i;
	for(i=0; i<8; i++)
	{
		SCL_L;			//拉低时钟线,允许改变SDA
		if(byte&1<<(7-i))    //判断发送的那一位是0还是1
        {
            SDA_H;
        }else
        {
            SDA_L;
        }
		SCL_H;            //拉高SCL,让接收方读取
		IIC_Delay();      //延时一下,让接收方有足够时间读取SDA
	}
	SCL_L;			//发送结束,拉低SCL
}

接收应答

IIC通信上是有应答位的,目的是告诉发送方接收到了数据。应答位紧跟着数据位,一般主机在发送完一个字节之后,是要接收应答的。

读取应答位,首先需要释放SDA,让从机控制SDA,然后拉高SCL,等待,读取SDA,若从机有应答,SDA会被拉低,若从机无应答SDA将不会被拉低

代码可以是这样:

unsigned char IIC_ReadBck(void)	//接收应答
{
	u8 ack;
	SDA_H;		//释放SDA
	SCL_H;	    //拉高SCL
	IIC_Delay();       //等待
	ack = Read_SDA;    //读取
	SCL_L;      //这里拉低回来是为了对齐时序
	SDA_L;      
	return ack;
}

发送应答

同样的,从机每发送一个字节过来,主机也是要给应答的,除非不想再继续传输数据。

从机可以连续发送多个字节,这需要主机在接收到每个字节之后给应答,给应答的时序和接收应答差不多,区别只是这个应答是主机发从机读

代码可以是这样:

void IIC_SendBck(void)	//发送应答
{
	u8 ack;
    SCL_L;
	SDA_L;		//拉低SDA,表示应答
	SCL_H;	    //拉高SCL
	IIC_Delay();       //等待从机读取
	SCL_L;      //这里拉低回来是为了对齐时序  
}

接收一个字节

前面有发送字节,就会有接收字节,接收字节时序和发送字节时序一样,只不过是主机读,从机发,主机要释放SDA,将SDA交给从机控制

代码可以是这样:

unsigned char IIC_ReadByte()				//从总线上读一个字节
{
	int i;
	unsigned char byte=0;
	SDA_H;			//释放SDA
	for(i=0; i<8; i++)
	{
		SCL_H;		//拉高,然后读
		IIC_Delay();
		if(Read_SDA)
        {
            byte |= (1<<(7-i));
        }
		SCL_L;
		IIC_Delay();
	}
	return byte;
}

那么IIC总共就有这么些个函数:

void IIC_Delay(void);
void IIC_Init(void);						//初始化引脚
void IIC_Start(void);						//发送起始条件
void IIC_Stop(void);						//发送结束条件
unsigned char IIC_ReadBck(void);			//接收应答
void IIC_SendBck(void);						//发送应答
void IIC_SendByte(unsigned char byte);		//发送一个字节
unsigned char IIC_ReadByte(void);			//读一个字节

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天师电通电容爆破工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值