- 基于stm32f407
- 小结关于iic的内容,附模拟iic代码
一、IIC简介
- IIC(也叫I2C)是一种两线式串行总线,由数据线SDA和时钟线SCL组成
- 属于串行同步半双工通信方式。(即带时钟同步信号;允许双向传输,但同一个时刻只允许单方向传输
- iic总线的物理补拓结构如下,SCL和SDA都有上拉电阻,所以总线空闲时通常被拉高
- 总线上控制SCL电平变化的设备为主设备,每一个设备都可以作为主设备或从设备,任何时刻只允许有一个主机
- 数据传输以字节为单位
- 总线上可挂载多个设备,为了避免收发错误,绝大多数iic芯片都带有固定地址。一般通信时,主设备先发地址激活对应从设备,再进行数据传输
- 通常单片机上都带有硬件IIC,直接使用可以不用深究通信过程,但这会导致程序移植困难,且有些单片机硬件IIC设计不好,所以通常用操作IO口模拟软件IIC使用
- 可在CPU与IC;IC与IC间传输
二、IIC通信协议
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始,以一个结束信号作为结束。
1、三种信号
(1) 开始信号:
- SCL为高电平时,SDA从低电平向高电平跳变。
- 由主机发送
- 从设备收到这个就知道要即将开始数据传输
- 在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线回归空闲状态
(2)结束信号:
- SCL为高电平时,SDA从高电平向低电平跳变。
- 由主机发送
- 从设备接到后即停止数据接收,这时主从之间就没什么关系了,从机可以进行独立的数据处理或进入休眠等,甚至可以作为主机发起下一次数据传输
(3) 应答信号
- 从机收到8bit数据后,向主机发送一个特定脉冲,分为应答信号ACK(从机发送0)和非应答信号NACK(从机发送1)
- 由收信方(主或从)发送
- 从机收到应答后根据情况判断是否继续传输数据,若没有收到应答,可判断为受控单元故障
2、数据传输
主从机建立联系后,一字节数据传输过程如下
- 先由主机拉低SCL
- 当SCL为低时,发信方将要发送的位,按从高到低的顺序,以高低电平形式写入SDA
- 主机拉高SCL,收信方检测SDA电平,记录该位。这段时间内要保证SCL高电平稳定
- 8位数据传输完成后,发信方释放SDA,由收信方操作SDA发出一个应答信号
需要注意以下几点:
- 数字电路电平变化需要建立时间和保持时间,在模拟IIC时必须留够延时,保证电平已经变化并稳定。具体时间应查看iic设备手册,若没有详细说明就延时稍微长一点
- 收信方只在SCL为高时检测SDA电平
- 除了起始和结束信号外,SDA只有在SCL为低时才能变化,否则会被误判为起始/结束信号。所以传输间隙,主机完成一位数据收发后都要立刻拉低SCL,避免SCL为高时SDA抖动造成误判
- 某些IIC设备支持快速设备等待慢速设备(看手册)。对于这种设备,如果一接收器件在完成其他功能(如一内部中断)前不能接收另一数据的完整字节时,它可以保持时钟线SCL为低,以促使发送器进入等待状态;当接收器准备好接受数据的其它字节并释放时钟SCL后,数据传输继续进行。若收发双方硬件IIC都支持,那么两者可以自行协调;若其中一方硬件IIC不支持,可以不做处理;若一方硬件IIC支持,另一边是软件模拟IIC,则要检测SCL电平,响应慢速方的延时要求。(通常不用)
了解了三种信号和数据的传输方式后,我们已经了解了如何进行完整的一个最小包IIC数据(8bit数据+1bit应答)传输,比如:
- 其中SCL全部、SDA的起始/结束信号由主机控制
- SDA的8bit数据由发送方(主或从)控制
- SDA的1bit应答由接收方(主或从)控制
3、主从设备配对
前面提到过,为了在总线上的总多设备中匹配主机和从机,需要进行配对,方法如下:
- 主机发送起始信号
- 主机发送8bit寻址数据,从高到低为:7bit从机地址+1bit读写标志。读写标志0表示主设备向从设备写数据,1表示主设备向从设备读数据
- 第9个周期,对应从设备发送应答信号,标志已建立通信
4、通信协议
接下来可以看一看设备间的通信协议了。特别注意的是模拟IIC一定要按照从机的硬件IIC要求设计。具体的通信时序大同小异,一般分为以下三种:
(1)主机向从机写入n字节
- 主机发送
起始信号
- 主机发送
7bit从机地址+写标志
- 从机应答
ACK
(配对完成) - 主机发送
8bit数据
- 从机应答
ACK
- …
- 主机发送
停止信号
(2)主机从从机读出n字节
主机发送 起始信号
- 主机发送
7bit从机地址+读标志
- 从机应答
ACK
(配对完成) - 从机发送
8bit数据
- 主机应答
ACK
- …
- 从机发送
8bit数据
- 主机应答
NACK
(主机作为接受方时,这个应答可省略) - 主机发送
停止信号
(3)混合读写
- 其实就是把读写过程混合了,中间的停止改成了重复的开始信号,这样效率较高
注意:这些只是抽象出来一般化的三种情况,不代表适用所有iic器件,但是所有iic器件的三种信号和通信方式是一样的。具体的数据发送格式和时序一定要按照iic器件手册中的读写时序图来编程
三、模拟iic代码
#include "myiic.h"
#include "delay.h"
//-----------------------------------------------------------
//IO方向(应在myiic.h里)
#define SDA_IN() {GPIOC->MODER&=~(3<<(9*2));GPIOC->MODER|=0<<9*2;} //PB9ÊäÈëģʽ
#define SDA_OUT() {GPIOC->MODER&=~(3<<(9*2));GPIOC->MODER|=1<<9*2;} //PB9Êä³öģʽ
//IO操作(应在myiic.h里)
#define IIC_SCL PCout(8) //SCL
#define IIC_SDA PCout(9) //SDA
#define READ_SDA PCin(9) //ÊäÈëSDA
//-----------------------------------------------------------
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
IIC_SCL=1; //stm32f4位带操作,拉高PC8
IIC_SDA=1; //stm32f4位带操作,拉高PC9
}
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; //发送完成后拉低SCL,避免SDA抖动出现误判
}
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);
}
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设为输入,由于上拉电阻,这是SDA实际为高
IIC_SDA=1;delay_us(1); //位带操作ODR寄存器,只有SDA输出模式才有用,这句其实没用,就是方便看状态
IIC_SCL=1;delay_us(1);
while(READ_SDA) //读SDA电平,若从机应答ACK应受到低
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
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ÕâÈý¸öÑÓʱ¶¼ÊDZØÐëµÄ
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//IIC读一个字节
//ack非零发应答ACK,否则发非应答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;
}
上述程序在stm32f407平台测试通过