一、IIC连接实物示意图
二、IIC协议程序编写的要点:
1、空闲状态
2、开始信号
3、停止信号
4、应答信号
5、数据的有效位
6、数据传输
三、IIC驱动编写
1、硬件准备
此处使用正点原子Mini板STM32F103,使用的IO口为C11、C12。由于使用的IO口并不是自带硬件IIC口,所以在此我们使用软件模拟IIC传输。(没有硬件的同学也可以继续看下去,协议的实现与硬件没有太大关系)
2、程序编写
由上文得知IIC协议程序编写的要点:下面我们来依次实现
第一步、首先我们先来初始化一下IO口(只是理解IIC协议原理的同学,可直接跳过)
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOC_CLK_ENABLE(); //使能GPIOC时钟
//PC11,12初始化设置
GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
IIC_SDA=1;
IIC_SCL=1;
}
此处初始化需注意要上拉两个IO口,因为IIC协议设定SDA=1,SCL=1为空闲状态。
此处使用的是HAL库,HAL对IO口的结构体定义如下:
typedef struct
{
uint32_t Pin;/*引脚*/ /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode;/*模式*/ /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull;/*上拉或下拉*/ /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed;/*IO口速度*/ /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
第二步、对SDA、SCL进行宏定义(只是理解IIC协议原理的同学,可直接跳过)
//IO操作
#define IIC_SCL PCout(12) //SCL
#define IIC_SDA PCout(11) //SDA
#define READ_SDA PCin(11) //输入SDA
第三步、对各个要点进行实现
1、空闲状态
无数据发送接收时
void IIC_Leisure(void)
{
IIC_SDA=1;
IIC_SCL=1;
}
2、开始信号
根据时序图,可知,开始信号:SCL为高期间, SDA由高到低的跳变;
特别注意:启动信号是一种电平跳变时序信号,下面是程序编写:
void IIC_Start(void)
{
SDA_OUT(); /*设置C12为输出模式*/
IIC_SDA=1; /*拉高保持空闲状态*/
IIC_SCL=1; /*拉高保持空闲状态*/
delay_us(4); /*延时保证电平稳定*/
IIC_SDA=0; /*当SCL为高时,SDA由高到低的跳变*/
delay_us(4); /*延时保证电平稳定*/
IIC_SCL=0; /*钳住I2C总线,准备发送或接收数据*/
}
3、停止信号
根据时序图,可知,开始信号:SCL为高期间, SDA由低到高的跳变;
特别注意:停止信号是一种电平跳变时序信号,同时此处有个小细节,如时序图,SDA只有在SCL变高时才能变换。下面是程序编写:
void IIC_Stop(void)
{
SDA_OUT(); /*设置C12为输出模式*/
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;/*当SCL为高时,SDA由高到低的跳变*/ /*如出现电平不稳,也可在本句上方+一个延时*/
delay_us(4);
}
4、应答信号
根据我们此项目来说,我们是使用单片机读取AT24C02里的数据。故此处发送器为AT24C02,接收器为单片机。
发送器(AT24C02)每发送一个字节, 就在时钟脉冲第9个期间释放数据线,由接收器(单片机)反馈一个应答信号。
当应答信号为低电平时, 表示接收器已经成功地接收了该字节,规定为应答位(ACK)
当应答信号为高电平时, 表示接收器接收该字节失败。规定为非应答位(NACK)
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低, 并且确保在该时钟的高电平期间为稳定的低电平。
同时我们还需要对应答信号进行判断,所以我们此处需要编写三个程序:1、产生应答信号;2、产生非应答信号;3、检测应答信号,其中产生应答信号和产生非应答信号是接收器(单片机)使用的,检测应答信号为发送器(AT24C02)使用的。
(1)产生应答信号
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
(2)产生非应答信号
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
(3)检测应答信号
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; /*数据传送失败。检测为应答信号*/
}
5、数据传输、数据的有效性
(1)数据的有效性
I2C总线进行数据传送时, 时钟信号为高电平期间, 数据线上的数据必须保持稳定, 只有在时钟线上的信号为低电平期间, 数据线上的高电平或低电平状态才允许变化。
即: 数据在SCL的上升沿到来之前就需准备好。 并在在下降沿到来之前必须稳定。(如图示,SCL周期小于SDA周期,且被包含在内)
(2)数据传输
数据位的传输采用边沿触发。
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(同步控制), 即在SCL串行时钟的配合下, 在SDA上逐位地串行传送每一位数据 。
写数据
//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;/*需要发送的数据经过和0x80的与运算之后,右移7位*/
txd<<=1;/*左移7位*/
delay_us(2); /*对延时是必须的*/
IIC_SCL=1;
delay_us(2); /*对延时是必须的*/
IIC_SCL=0;
delay_us(2); /*对延时是必须的*/
}
}
IIC_SDA=(txd&0x80)>>7; txd<<=1;
这两句有疑问的同学可以看我下面推导:
读数据
//读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;
}
参考文献:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f103_mini.html