(1)IIC总线(Inter Integrated Circuit Bus):是Philips公司推出的串行总线标准(为二线制)。总线上扩展的外围器件及外设接口通过总线寻址,是具备总线仲裁和高低速设备同步等功能的高性能多主机总线。
(2)数据线—SDA ??时钟线—SCL
(3)特性:
半双工同步串行通信总线--一问一答
仲裁----主从模式----任何信息都需要主机主动控制
从属设备地址----器件地址,通过地址去找到对应发送数据的设备
(4)IIC:半双工一问一答、一次只能发送1个位数据,发送一个数据包(连续发送多个位-8位)
IIC速度慢,IO口就能达到数据传输要求。
结论:IIC的通信速度是比较慢的。
? 在STM32中,一般都是使用IO模拟IIC通信,而不使用它的硬件IIC.
IIC总时序图
(1)空闲状态:SDA 和SCL都为高电平。
(2)起始位(起始条件、起始信号):在SCL为高电平期间,SDA由高变为低电平。
(3)停止位(停止条件、停止信号):在SCL为高电平期间,SDA由低变为高电平。
(4)应答信号:在IIC通信中,每发送或者接收到一个字节后,都会有一个应答信号(非应答信号)
(5)发送过程(发数据):在第9个时钟周期,SCL为高电平期间,读取SDA的电平(低à应答,高à非应答)。
?? ??起始信号用于开始I2C总线通信。其中,起始信号是在时钟线SCL为高电平期间,数据SDA上高电平向低电平变化的下降沿信号。起始信号出现以后,才可以进行后续的I2C总线寻址或数据传输等。起始信号时序图如下:
??
配置代码如下:
//产生IIC起始信号
void IIC_Start(void){
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
终止信号用于终止I2C总线通信。其中,终止信号是在时钟线SCL为高电平期间,数据线SDA上低电平到高电平变化的上升沿信号。终止信号一出现,所有I2C总线操作都结束,并释放总线控制权。终止信号的时序图如下:
配置代码如下:
//产生IIC终止信号
void IIC_Stop(void){
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
IIC_SDA=1; //发送I2C总线结束信号
delay_us(4);
}
应答信号用于表明I2C总线数据传输的结束。 I2C总线数据传送时,一个字节数据传送完毕后都必须由主器件产生应答信号。主器件在第9个时钟位上释放数据总线SDA,使其处于高电平状态,此时从器件输出低电平拉低数据总线SDA为应答信号。应答信号的时序图如下:
配置代码如下:
//等待应答
unsigned char IIC_Wait_ACK(void){
unsigned char ret; //用于返回值,存储是否应答的变量
IIC_SCL = 1; //SCL拉高电平
IIC_SDA = 1; //SDA拉高电平
delay_us(5);
//读取 SDA 数据线的状态。
if (SDA_READ() == 0) {
ret = 0; // 返回应答
}
else{
ret = 1; // 返回非应答
}
//产生ACK应答/非应答
void IIC_ACK_NACK(unsigned char ack){
IIC_SCL = 0; //SCL拉低电平
delay_us(5);
// 判断发送应答
if (ack == 0) {
IIC_SDA = 0; // 应答
}
else {
IIC_SDA = 1; // 非应答
}
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
写操作分为字节写和页面写两种操作:在字节写模式下,主器件发送起始命令和从器件地址信息 R/W位置零给从器件,在从器件产生应答信号后,主器件发送16的字节地址,主器件在收到从器件的另一个应答信号后,再发送数据到被寻址的存储单元再次应答,并在主器件产生停止信号后,开始内部数据的擦写,在内部擦写过程中从器件不再应答主器件的任何请求。写操作代码如下:
//写操作
void IIC_WriteByte(unsigned char data){
u8 i = 0;
//循环发送8位数据
for (i = 0; i < 8; i++) {
IIC_SCL = 0; //SCL拉低电平
//判断高位先发
if (data & 0x80) {
IIC_SDA = 1; //发送高位数据
}
else{
IIC_SDA = 0; // 发送低位数据
}
data = data << 1; //移位数据
delay_us(5);
IIC_SCL = 1; //SCL拉高时钟
delay_us(5);
}
IIC_SCL = 0; //拉低时钟
delay_us(1);
}
? ? 有三种基本操作:当前地址读、随机读和顺序读。下图给出的是顺序读的时序图。应当注意的是,为了结束读操作,主机必须在第9个周期间发出停止条件或者在第9个时钟周期内保持SDA为高电平,然后发出停止条件。 配置代码如下:
//读操作
unsigned char IIC_ReadByte(unsigned char ack){
unsigned char i = 0, data = 0;
// 循环接收数据
for (i = 0; i < 8; i++) {
IIC_SCL = 0; // 拉低时钟SCL等待接收数据
delay_us(5);
IIC_SCL = 1; // 延时等待数据线的改变
delay_us(5);
data <<= 1; // 拉高时钟读取数据
if (SDA_READ() == 1) {
data |= 0x01; // 判断读到的数据并保存
}
delay_us(5);
IIC_SCL = 0; // 延时函数
delay_us(5);
}
IIC_SCL = 0; // 拉低时钟SCL
delay_us(5);
IIC_ACK_NACK(ack); // 发送给从机应答或非应答
return data;
}
(1)myiic.c
#include "myiic.h"
#include "math.h"
/**********
函 数 名 : IIC_Start
功能说明 : 产生IIC起始信号
形 参 : 无
返 回 值 : 无
备 注 : SCL为高电平期间,SDA由高电平向低电平的跳变。
**********/
// 起始函数
void IIC_Start(void)
{
IIC_SCL = 1; //SCL拉高电平
IIC_SDA = 1; //SDA拉高电平
delay_us(5); //时序图中延迟时间要大于4.7us
IIC_SDA = 0; //SDA拉低电平
delay_us(5);
IIC_SCL = 0; //SCL拉低电平
delay_us(5);
}
/**********
函 数 名 : IIC_Stop
功能说明 : 产生IIC结束信号
形 参 : 无
返 回 值 : 无
备 注 : SCL为高电平期间,SDA由低电平向高电平的跳变。
**********/
// 停止函数
void IIC_Stop(void)
{
IIC_SDA = 0; //SCL拉低电平
IIC_SDA = 0; //SDA拉低电平
delay_us(5);
IIC_SCL = 1; //SCL拉高电平
delay_us(5);
IIC_SDA = 1; //SDA拉高电平
delay_us(5);
}
/**********
函 数 名 : IIC_ACK_NACK
功能说明 : 作为接收方时,每个字节(8bit)传输完成后的下一个时钟信号,发起应答或非应答信号
形 参 : ack:0应答,1非应答
返 回 值 : 无
备 注 : 在SCL为高电平期间,SDA为低,则表示一个应答信号(ACK)
SDA为高,则表示一个非应答信号(NACK)。
**********/
void IIC_ACK_NACK(unsigned char ack)
{
IIC_SCL = 0; //SCL拉低电平
delay_us(5);
if (ack == 0) // 判断发送应答
{
IIC_SDA = 0; // 应答
}
else
{
IIC_SDA = 1; // 非应答
}
delay_us(5);
IIC_SCL = 1; // SCL拉高电平
delay_us(5);
IIC_SCL = 0; // SCL拉低电平
}
/**********
函 数 名 : IIC_Wait_ACK
功能说明 : 作为发送方时,检测从机返回的应答信号
形 参 : 无
返 回 值 : 0:应答,1:非应答
备 注 : 接收从机返回的应答
**********/
// 等待应答
unsigned char IIC_Wait_ACK(void)
{
unsigned char ret; //用于返回值,存储是否应答的变量
IIC_SCL = 1; //SCL拉高电平
IIC_SDA = 1; //SDA拉高电平
delay_us(5);
if (SDA_READ() == 0) //读取 SDA 数据线的状态。
{
ret = 0; // 返回应答
}
else
{
ret = 1; // 返回非应答
}
IIC_SCL = 0; //SCL拉低电平
delay_us(5);
return ret;
}
/**********
函 数 名 : IIC_WriteByte
功能说明 : 发送一个字节的数据到IIC总线上
形 参 : data:需要发送的数据
返 回 值 : 无
备 注 : MSB(高位在前)
**********/
void IIC_WriteByte(unsigned char data)
{
u8 i = 0;
for (i = 0; i < 8; i++) //循环发送8位数据
{
IIC_SCL = 0; //SCL拉低电平
if (data & 0x80) //判断高位先发
{
IIC_SDA = 1; //发送高位数据
}
else
{
IIC_SDA = 0; // 发送低位数据
}
data = data << 1; //移位数据
delay_us(5);
IIC_SCL = 1; //SCL拉高时钟
delay_us(5);
}
IIC_SCL = 0; //拉低时钟
delay_us(5);
}
/*********************************************************************************************************
* 函 数 名 : IIC_ReadByte
* 功能说明 : 读取IIC总线上的一个字节
* 形 参 : ack:0应答,1非应答
* 返 回 值 : 读取到的数据
* 备 注 : MSB(高位在前)
*********************************************************************************************************/
unsigned char IIC_ReadByte(unsigned char ack)
{
unsigned char i = 0, data = 0;
// 循环接收数据
for (i = 0; i < 8; i++)
{
IIC_SCL = 0; // 拉低时钟SCL等待接收数据
delay_us(5);
IIC_SCL = 1; // 延时等待数据线的改变
delay_us(5);
data <<= 1; // 拉高时钟读取数据
if (SDA_READ() == 1)
{
data |= 0x01; // 判断读到的数据并保存
}
delay_us(5);
IIC_SCL = 0; // 延时函数
delay_us(5);
}
IIC_SCL = 0; // 拉低时钟SCL
delay_us(5);
IIC_ACK_NACK(ack); // 发送给从机应答或非应答
return data;
}
(2)myiic.h
#ifndef _MYIIC_H_
#define _MYIIC_H_
#include "stm32f4xx.h"
#include "delay.h"
#include "io_bit.h"
#define SDA_IN() {GPIOB->MODER&=~(3<<(7*2));GPIOB->MODER|=0<<7*2;} //PB7输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(7*2));GPIOB->MODER|=1<<7*2;} //PB7输出模式
#define SDA_READ() (GPIOB->IDR & (1 << 7)) // 读取PB7引脚的状态
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
void IIC_Start(void);
void IIC_Stop(void);
void IIC_ACK_NACK(unsigned char ack);
unsigned char IIC_Wait_ACK(void);
void IIC_WriteByte(unsigned char data);
unsigned char IIC_ReadByte(unsigned char ack);
#endif
IIC时序中的延迟时间会存在误差,如果上述的延迟不准,可用delay_ms函数来进行延迟。
所写文章均为记录个人学习的过程!!!!!