STM32(学习笔记3):IIC通信总结

STM32:IIC通信总结

说明

此篇博客为学习笔记,用来总结学到的知识。

IIC介绍:(来自百度百科)

IIC总线是飞利浦公司开发的一种双线、半双工的同步串行总线。

IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。

IIC总线是各种总线中使用信号线最少,并具有自动寻址、多主机时钟同步和仲裁等功能的总线。因此,使用I2C总线设计计算机系统十分方便灵活,体积也小,因而在各类实际应用中得到广泛应用。

IIC总线结构

STM32(学习笔记3):IIC通信总结_第1张图片
(图片来自百度)
MCU与其他IIC总线上的设备通过SCL(IIC时钟线)与SDA(IIC数据线)相连,只有两线就可以连接多个设备这就突出了IIC串口总线结构简单的优点。作为同步通信总线,所有IIC总线上的设备都由SCL时钟线提供时钟信号。IIC总线上的所有的数据都通过SDA数据线传输,若IIC总线连接多个设备,往往在数据发送前,先要发送片选地址。

IIC的数据传输

IIC通信由主机进行控制,遵循一定的时序。通常包含三种信号,开始信号、停止信号和应答信号,其中开始信号为必须,其余可省略。下面介绍三种信号,以及数据传输过程。

开始信号与停止信号

开始信号:当SCL为高电平期间,SDA由高到低跳变。由主机向从机发送开始信息数据开始传输。
停止信号:与开始信号相反,当SCL为高期间,SDA由低向高跳变。由主机向从机发送停止信号数据传输结束。
(对应的时序图)

STM32(学习笔记3):IIC通信总结_第2张图片
(图片来自百度)

代码实现:

开始信号

/*
函数名称:IIC_Strat()
函数功能:IIC总线发送开始信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Strat()
{
	
	//笔记
/*
	开始信号: SCL 为高电平时, 
	SDA 由高电平向低电平跳变,开始传送数据。
*/
	
	SDA_OUT(); //IIC数据线SDA输出
	
	//拉高两线
	IIC_SDA = 1;
	IIC_SCL = 1;
	
	//拉低SDA线发出起始信号
	delay_us(4);
	IIC_SDA = 0; 
	
	delay_us(4);
	IIC_SCL = 0;	//钳位SCL线为数据发送做准备
}

停止信号

/*
函数名称:IIC_Stop()
函数功能:IIC总线发送停止信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Stop()
{
	
	//笔记
/*
	结束信号: SCL 为高电平时, 
	SDA 由低电平向高电平跳变,结束传送数据。
*/
	
	SDA_OUT(); //IIC数据线SDA输出
	
	//拉低两线
	IIC_SCL = 0;
	IIC_SDA = 0;
	
	//拉高SCL
	IIC_SCL = 1;
	delay_us(4);
	//拉低SDA线产生停止信号
	IIC_SDA = 1;
	delay_us(4);	
}

注意:这里的延时是必须的,IIC通信对时序有较为严格的要求。

应答信号

分为两种:ACK和NACK,ACK代表传输的数据有效,NACK则认为传输的输出数据无效或者传输失败。

下面是对应答信号的解释:
发送器每发发送一个字节,就会在第九个时钟周期(发送一字节数据需要8个时钟周期应答信号在第九个时钟周期),由接收器反馈一个应答信号。应答信号为低电平时(ACK),规定为有效应答位(ACK),表示已经成功接收到该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器没有接收成功或数据无效。
(对应的时序图)
STM32(学习笔记3):IIC通信总结_第3张图片
(图片来自百度)

代码实现:

产生应答信号

/*
函数名称:IIC_Ack()
函数功能:IIC总线产生应答信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Ack()
{
	
	//笔记
/*
	发送器每发送一个字节(8个bit),
	就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
	应答信号为低电平时,规定为有效应答位(ACK,简称应答位),
	表示接收器已经成功地接收了该字节;
*/
	
	//先拉低SCL线作为第九个时钟周期的开始
	IIC_SCL = 0;
	SDA_OUT(); //设置SDA为输出
	//拉低SDA线产生应答信号
	IIC_SDA = 0;
	delay_us(2);
	
	//拉高SCL线
	IIC_SCL = 1;
	delay_us(2);
	//拉低SCL线作为第九个时钟周期的结束
	IIC_SCL = 0;
}

产生应答信号

/*
函数名称:IIC_NAck()
函数功能:IIC总线产生非应答信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_NAck()
{
	//笔记
/*
	发送器每发送一个字节(8个bit),
	就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
	应答信号为高电平时,规定为非应答位(NACK),
	一般表示接收器接收该字节没有成功。 
*/
	
	//先拉低SCL线作为第九个时钟周期的开始
	IIC_SCL = 0;
	SDA_OUT(); //设置SDA为输出
	//拉低SDA线产生非应答信号
	IIC_SDA = 1;
	delay_us(2);
	
	//拉高SCL线
	IIC_SCL = 1;
	delay_us(2);
	//拉低SCL线作为第九个时钟周期的结束
	IIC_SCL = 0;
}

检测应答或非应答信号

/*
函数名称:IIC_Wait_Ack()
函数功能:等待应答或非应答信号
输入:
输出:1:非应答信号 0:应答信号
修改日期:
*/
u8 IIC_Wait_Ack()
{
	
	/*
	等待应答
	等待SDA线被拉低若无则无应答有则应答
	*/
	
	//定义出计算时间的变量
	u8 ucErrTime = 0;
	SDA_IN(); //SDA线为输入
	
	IIC_SDA = 1;
	delay_us(1);
	IIC_SCL = 1;
	delay_us(1);
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime > 250)
		{
			IIC_Stop();
			return 1;
		}
	}
	//拉低SCL线
	IIC_SCL = 0;
	return 0;
}

IIC数据传输过程

(数据传输时序图)
STM32(学习笔记3):IIC通信总结_第4张图片
(图片来自百度)

从时序图中看到,IIC总线传输是MSB(也就是高位在前低位在后)形式按位发送的,在发送一字节数据后,在第九个时钟周期会等待应答,若为ACK则继续传输,若为NACK则发送停止信号(也可不发送)。
同样IIC数据接收时也是MSB形式按位接收,在接收一字节数据后,主机就会发送ACK或NACK,作为数据接收的标志,决定接下来是否继续进行接收数据。

数据的有效性

STM32(学习笔记3):IIC通信总结_第5张图片
(图片来自百度)
IIC通信协议规定:在SCL高电平期间,SDA上的数据必须稳定不能发生跳变:在SCL为低电平期间,SDA上的数据才能发生跳变。
可以理解为:数据必须在SCL上升沿之前准备好,在下降沿之前保持稳定,这样数据才是有效的。在一个SCL周期只能发送一位,而且必须在下一个周期来之前就准备好数据,拉低或拉高SDA。必须按照这个时序进行编写程序。

代码实现

MCU可作为从机或主机,但是遵循的时序是不变的

作为主机IIC发送一个字节并返回从机的应答情况。

/****************作为主机*****************/
/*
函数名称:IIC_Send_Byte(u8 txd)
函数功能:IIC发送一个字节并返回从机有无应答
输入:
输出:从机的应答情况 1,有 0,无
修改日期:2021.1.24
*/
void IIC_Send_Byte(u8 txd)
{
	u8 t;
	SDA_OUT();
	//拉低时钟线开始数据传输
	IIC_SCL = 0;
	
	//按从高位到低位顺序传输一个字节
	for(t = 0;t < 8;t++)
	{
		//发送最高位
		IIC_SDA = (txd & 0x80) >> 7;
		//移动下一位为最高位
		txd <<= 1;
		
		//保证数据的有效性
		//在SCL线为高电平时要保证SDA数据稳定
		//一个时钟周期
		delay_us(2);
		IIC_SCL = 1;
		delay_us(2);
		IIC_SCL = 0;
		delay_us(2);
	}
}

作为从机IIC接收一字节数据并给出应答或应答

/****************作为从机*****************/
/*
函数名称:IIC_Read_Byte(unsigned char ack)
函数功能:从机IIC接收一字节数据并给出应答或应答
输入:要给出的应答情况 1 为ACK 0 为NACK
输出:接收的数据
修改日期:2021.1.24
*/
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i, receive = 0;
	SDA_IN(); //SDA线为输入
	//按从高位到低位的顺序读取一个字节
	for(i = 0;i < 8;i++)
	{
		//拉低SCL线
		IIC_SCL = 0;
		delay_us(2);
		//拉高SCL线
		IIC_SCL=1;
		
		//把要接收的位移动至最高位
		receive <<= 1;
		//判断SDA线状态
		//若是高就加一
		//若是低则不加
		if(READ_SDA)
		{
			receive++;
		}
		delay_us(2);
	}
	
	//产生应答或非应答信号
	if(ack == 1)
	{
		IIC_Ack();
	}
	if(ack == 0)
	{
		IIC_NAck();
	}
	return receive;
}

总体代码

IIC总线在STM32是有硬件支持的,但是这里采用软件模拟的方式。
硬件IIC复杂且稳定性较差,所以采用软件模拟的方式。

IIC.H

/********************
文件名称:IIC.h
文件描述:作为IIC驱动的头文件
修改日期:
*********************/
#ifndef __IIC_H_
#define __IIC_H_

#include "sys.h"

//IO方向设置
#define SDA_IN() {GPIOB -> CRL &= 0X0FFFFFFF; GPIOB -> CRL |= (u32) 8 << 28;}
#define SDA_OUT() {GPIOB -> CRL &= 0X0FFFFFFF; GPIOB -> CRL |= (u32) 3 << 28;}

//IO操作
#define IIC_SCL  PBout(6) //IIC时钟线
#define IIC_SDA  PBout(7)	//IIC数据线(写)
#define READ_SDA PBin(7)	//IIC数据线(读)

//IIC所有操作函数
//IIC初始化函数
void IIC_Init(void);
//IIC起始信号
void IIC_Start(void);
//IIC停止信号
void IIC_Stop(void);
//IIC发送一个字节
void IIC_Send_Byte(u8 txd);
//IIC读取一个字节
u8 IIC_Read_Byte(unsigned char ack);
//IIC等待应答
u8 IIC_Wait_Ack(void);
//IIC应答信号
void IIC_Ack(void);
//IIC不应答信号
void IIC_NAck(void);
#endif

iic.c

/********************
文件名称:iic.c
文件描述:IIC驱动
修改日期:2021.1.24
*********************/
#include "IIC.h"
#include "delay.h"
#include "stm32f10x.h"

/*
函数名称:IIC_Init()
函数功能:IIC初始化GPIOB6为IIC时钟线GPIOB7为数据线
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Init()
{
	
	//笔记
/*
	IIC初始化
	空闲状态
	拉高时钟线和数据线
*/
	
	//使能GPIOB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	//GPIO初始化参数结构体
	GPIO_InitTypeDef GPIO_InitStruct;
	
	//GPIOB6 推挽输出
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	//GPIOB7 推挽输出
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);
	//设置GPIOB6 GPIOB7输出高
	GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);
}

/*
函数名称:IIC_Strat()
函数功能:IIC总线发送开始信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Strat()
{
	
	//笔记
/*
	开始信号: SCL 为高电平时, 
	SDA 由高电平向低电平跳变,开始传送数据。
*/
	
	SDA_OUT(); //IIC数据线SDA输出
	
	//拉高两线
	IIC_SDA = 1;
	IIC_SCL = 1;
	
	//拉低SDA线发出起始信号
	delay_us(4);
	IIC_SDA = 0; 
	
	delay_us(4);
	IIC_SCL = 0;	//钳位SCL线为数据发送做准备
}

/*
函数名称:IIC_Stop()
函数功能:IIC总线发送停止信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Stop()
{
	
	//笔记
/*
	结束信号: SCL 为高电平时, 
	SDA 由低电平向高电平跳变,结束传送数据。
*/
	
	SDA_OUT(); //IIC数据线SDA输出
	
	//拉低两线
	IIC_SCL = 0;
	IIC_SDA = 0;
	
	//拉高SCL
	IIC_SCL = 1;
	delay_us(4);
	//拉低SDA线产生停止信号
	IIC_SDA = 1;
	delay_us(4);	
}

/*
函数名称:IIC_Wait_Ack()
函数功能:等待应答或非应答信号
输入:
输出:1:非应答信号 0:应答信号
修改日期:
*/
u8 IIC_Wait_Ack()
{
	
	/*
	等待应答
	等待SDA线被拉低若无则无应答有则应答
	*/
	
	//定义出计算时间的变量
	u8 ucErrTime = 0;
	SDA_IN(); //SDA线为输入
	
	IIC_SDA = 1;
	delay_us(1);
	IIC_SCL = 1;
	delay_us(1);
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime > 250)
		{
			IIC_Stop();
			return 1;
		}
	}
	//拉低SCL线
	IIC_SCL = 0;
	return 0;
}


/**********************单片机作为从机或主机可产生应答与非应答信号****************************/

/*
函数名称:IIC_Ack()
函数功能:IIC总线产生应答信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_Ack()
{
	
	//笔记
/*
	发送器每发送一个字节(8个bit),
	就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
	应答信号为低电平时,规定为有效应答位(ACK,简称应答位),
	表示接收器已经成功地接收了该字节;
*/
	
	//先拉低SCL线作为第九个时钟周期的开始
	IIC_SCL = 0;
	SDA_OUT(); //设置SDA为输出
	//拉低SDA线产生应答信号
	IIC_SDA = 0;
	delay_us(2);
	
	//拉高SCL线
	IIC_SCL = 1;
	delay_us(2);
	//拉低SCL线作为第九个时钟周期的结束
	IIC_SCL = 0;
}

/*
函数名称:IIC_NAck()
函数功能:IIC总线产生非应答信号
输入:
输出:
修改日期:2021.1.24
*/
void IIC_NAck()
{
	//笔记
/*
	发送器每发送一个字节(8个bit),
	就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
	应答信号为高电平时,规定为非应答位(NACK),
	一般表示接收器接收该字节没有成功。 
*/
	
	//先拉低SCL线作为第九个时钟周期的开始
	IIC_SCL = 0;
	SDA_OUT(); //设置SDA为输出
	//拉低SDA线产生非应答信号
	IIC_SDA = 1;
	delay_us(2);
	
	//拉高SCL线
	IIC_SCL = 1;
	delay_us(2);
	//拉低SCL线作为第九个时钟周期的结束
	IIC_SCL = 0;
}

/***************************************************************/

/****************作为主机*****************/

/*
函数名称:IIC_Send_Byte(u8 txd)
函数功能:IIC发送一个字节并返回从机有无应答
输入:
输出:从机的应答情况 1,有 0,无
修改日期:2021.1.24表格7yv
*/
void IIC_Send_Byte(u8 txd)
{
	u8 t;
	SDA_OUT();
	//拉低时钟线开始数据传输
	IIC_SCL = 0;
	
	//按从高位到低位顺序传输一个字节
	for(t = 0;t < 8;t++)
	{
		//发送最高位
		IIC_SDA = (txd & 0x80) >> 7;
		//移动下一位为最高位
		txd <<= 1;
		
		//保证数据的有效性
		//在SCL线为高电平时要保证SDA数据稳定
		//一个时钟周期
		delay_us(2);
		IIC_SCL = 1;
		delay_us(2);
		IIC_SCL = 0;
		delay_us(2);
	}
}

/****************作为从机*****************/
/*
函数名称:IIC_Read_Byte(unsigned char ack)
函数功能:从机IIC接收一字节数据并给出应答或应答
输入:要给出的应答情况 1 为ACK 0 为NACK
输出:接收的数据
修改日期:2021.1.24
*/
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i, receive = 0;
	SDA_IN(); //SDA线为输入
	//按从高位到低位的顺序读取一个字节
	for(i = 0;i < 8;i++)
	{
		//拉低SCL线
		IIC_SCL = 0;
		delay_us(2);
		//拉高SCL线
		IIC_SCL=1;
		
		//把要接收的位移动至最高位
		receive <<= 1;
		//判断SDA线状态
		//若是高就加一
		//若是低则不加
		if(READ_SDA)
		{
			receive++;
		}
		delay_us(2);
	}
	
	//产生应答或非应答信号
	if(ack == 1)
	{
		IIC_Ack();
	}
	if(ack == 0)
	{
		IIC_NAck();
	}
	return receive;
}

结语

IIC总线通信协议这里就介绍完了。
如有错误还请指出。
共同进步。

你可能感兴趣的:(单片机,单片机,stm32,嵌入式)