📢声明:参考的是江科大的代码,图片截于江科大PPT。
一、I2C硬件电路:
二、I2C通信的时序。
1、起始条件:SCL高电平期间,SDA从高电平切换到低电平。
2、终止条件:SCL高电平期间,SDA从低电平切换到高电平。
3、发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
4、接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
5、发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
6、接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
三、软件I2C程序编写步骤:
1、在Keil中新建MyI2C.c和MyI2C.h文件。
2、编写引脚配置层函数。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#define MyI2C_GPIO_RCC RCC_APB2Periph_GPIOB
#define MyI2C_GPIO GPIOB
#define MyI2C_SCL GPIO_Pin_3
#define MyI2C_SDA GPIO_Pin_4
/**
* 函数功能:I2C时钟线写
* 入口参数:BitVal:控制引脚0或1
* 返 回 值:无
*/
void MyI2C_W_SCL(BitAction BitVal)
{
GPIO_WriteBit(MyI2C_GPIO, MyI2C_SCL, BitVal);
Delay_us(10);
}
/**
* 函数功能:I2C数据线写
* 入口参数:BitVal:控制引脚0或1
* 返 回 值:无
*/
void MyI2C_W_SDA(BitAction BitVal)
{
GPIO_WriteBit(MyI2C_GPIO, MyI2C_SDA, BitVal);
Delay_us(10);
}
/**
* 函数功能:I2C数据线读
* 入口参数:无
* 返 回 值:读取数据线引脚的状态
*/
uint8_t MyI2C_R_SDA(void)
{
uint8_t i;
i = GPIO_ReadInputDataBit(MyI2C_GPIO, MyI2C_SDA);
Delay_us(10);
return i;
}
3、编写I2C初始化函数。
/**
*** 函数功能:软件I2C初始化
*** 入口参数:无
*** 返 回 值:无
***/
void MyI2c_Init(void)
{
/*开启GPIOB时钟*/
RCC_APB2PeriphClockCmd(MyI2C_GPIO_RCC, ENABLE);
/*开启AFIO时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/*JTAG/SWD复用功能重映射*/
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //因为我这里使用了GPIOB_Pin_3和Pin_4
//所以才需要调用这个库函数
/*初始化引脚*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出模式
GPIO_InitStructure.GPIO_Pin = MyI2C_SCL | MyI2C_SDA; //GPIOB_Pin_3 | GPIOB_Pin_4
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MyI2C_GPIO, &GPIO_InitStructure);
MyI2C_W_SCL(1); //时钟线默认高电平
MyI2C_W_SDA(1); //数据线默认高电平
}
4、编写I2C起始条件函数。
/**
* 函数功能:I2C起始
* 入口参数:无
* 返 回 值:无
*/
void MyI2C_Start(void)
{
MyI2C_W_SDA(1); //释放数据线
MyI2C_W_SCL(1); //释放时钟线
MyI2C_W_SDA(0); //下拉数据线
MyI2C_W_SCL(0); //下拉时钟线
}
5、编写I2C终止条件函数。
/**
* 函数功能:I2C终止
* 入口参数:无
* 返 回 值:无
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0); //下拉数据线
MyI2C_W_SCL(1); //释放时钟线
MyI2C_W_SDA(1); //释放数据线
}
6、编写I2C发送1个字节函数。
/**
* 函数功能:I2C发送一个字节
* 入口参数:Byte: 要发送的一个字节数据
* 返 回 值:无
*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++) //循环发送8次,完成一个字节的发送
{
MyI2C_W_SDA(Byte & (0x80 >> i));//从高位到地位一次发送
MyI2C_W_SCL(1); //释放时钟线
MyI2C_W_SCL(0); //下拉时钟线
}
}
6、编写I2C接收1个字节函数。
/**
* 函数功能:I2C接收一个字节
* 入口参数:无
* 返 回 值:接收到的字节数据
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i;
uint8_t Byte = 0x00; //定义接收的数据
MyI2C_W_SDA(1); //释放数据线
for (i = 0; i < 8; i++) //重复接收8次, 完成接收一个字节
{
MyI2C_W_SCL(1); //释放时钟线
if (MyI2C_R_SDA() == 1) //读取数据线的状态,1就保存数据, 0就不作处理
{
Byte |= (0x80 >> i);
}
MyI2C_W_SCL(0); //下拉时钟线
}
return Byte; //返回接收到的数据
}
7、编写I2C发送应答位函数。(0表示应答,1表示非应答)
/**
* 函数功能:I2C发送应答
* 入口参数:AckBit:要发送的应答。0表示应答,1表示非应答。
* 返 回 值:无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1); //释放时钟线
MyI2C_W_SCL(0); //下拉时钟线
}
8、编写I2C接收应答位函数。(0表示应答,1表示非应答)
/**
* 函数功能:I2C接收应答位
* 入口参数:无
* 返 回 值:接收到的应答位。0表示应答,1表示非应答。
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit; //定义应答位变量
MyI2C_W_SDA(1); //释放数据线
MyI2C_W_SCL(1); //释放时钟线
AckBit = MyI2C_R_SDA(); //接收应答
MyI2C_W_SCL(0); //下拉时钟线
return AckBit; //返回应答
}
9、MyI2C.h文件内容。
#ifndef __MYI2C_H
#define __MYI2C_H
void MyI2c_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif