一、概述
IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。
IIC是 由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。
二、基本原理
(IIC总线的时序图)
IIC总线在传输数据的过程中共有三种类型的信号,分别为:开始信号、结束信号和应答信号。
由时序图可知:
1、开始信号:在SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据。
2、结束信号:在SCL为高电平时,SDA由低电平向高电平跳变,结束传输数据。
3、应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未接收到信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号都可以不要。
注意:当scl为高电平的时候,sda为出现下降沿为start位,当生成了为低电平的时候,sda出现上升沿为stop位,所以在scl为高电平的时候sda应该保持稳定不能随意乱动(传输数据时)。
写入数据状态:
scl为高电平的时候sda的数据被写入从机,
scl为低电平的时候sda的数据被写入主机
读取数据状态:
scl为低电平的时候器件的数据被读出到sda总线上,在scl高电平器件保持数据稳定
scl为高电平的时候主机将sda总线上的数据读出被存储,在scl低电平时间保持稳定
应答信号ACK:
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK),表示接收器已成功地接收了该字节,应答信号为高电平时,规定为非应答(NACK),表示接收未成功
三、STM32上的IIC
在正点原子的教程中说,STM32的硬件IIC设计比较复杂,而且稳定性不佳(貌似是ST为了规避飞利浦IIC的版权问题),所以建议使用软件模拟IIC的方式。
用软件模拟IIC也有不少的优点,最大的好处当然是方便移植,同一个代码兼容各种的MCU,无论是51,32还是430还是别的微控制器,在移植的时候只要将引脚的设置一更改即可,但如果使用硬件IIC,那基本就是推倒重建了。。。。其次,用硬件模拟IIC更能够理解IIC的时序以及流程,更加有利于学习。
当然硬件IIC也不是一无是处的,若使用硬件IIC,代码的可靠性会提高,同时,对于实时性要求较高的模块还是使用硬件IIC更好,使用硬件IIC就可以使用中断以及DMA,可以使数据的传输速率大大提高。
但是。。。本人现在还没有研究硬件IIC的用法,以后使用后会再写一篇博客。
四、IIC的代码实现
我借助正点原子的代码来分析一下软件IIC的实现
头文件:
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//IO方向设置
#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;} //PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;} //PB9输出模式
//IO操作函数
#define IIC_SCL PBout(8) //SCL
#define IIC_SDA PBout(9) //SDA
#define READ_SDA PBin(9) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
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等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
#endif
SDA_IN()与SDA_OUT() 是用来设置IIC的传输方向 CPU接收数据与输出数据
.c文件
void IIC_Init(void)
{
RCC->AHB1ENR|=1<<1; //使能PORTB时钟
GPIO_Set(GPIOB,PIN8|PIN9,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PB8/PB9设置
IIC_SCL=1;
IIC_SDA=1;
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
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;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
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;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
注意:当SDA和SCL同时为高电平时,IIC总线为空闲状态。
初始化:使能IO口时钟,设置SDA和SCL为推挽输出,拉高SDA和SCL。
产生起始信号:设置SDA为输出,拉高SDA和SCL,延时4us,拉低SDA,延时4us,拉低SCL钳住IIC总线,准备发送或接收数据。
产生停止信号:设置SDA为输出,拉低SDA和SCL,延时4us,依次拉高SCL和SDA,发送结束信号。
产生应答信号:拉低SCL,设置SDA为输出,拉低SDA,延时2us,拉高SCL,延时2us后拉低
不产生应答引号:与产生应答信号基本相同,区别在于将SDA拉高。
发送一个字节:设置SDA为输出,然后从左往右将8bit的数据一位一位的发送,发送时要先将SCL拉高延时2us后拉低
读一个字节:设置SDA为输入,定义一个unsigned char类型的变量来存放接收到的数据,拉低时钟线,延时2us后拉高,读SDA上的数据,按这个步骤依次读出8个位上的数据,数据读取完毕后可以选择是否发送应答
等待应答的到来:设置SDA为输入,依次拉高SDA和SCL,等待SDA上低电平的到来(应答信号),可以设置等待时间,超过等待时间可以停止IIC总线,最后拉低SCL。