IIC(Inter-Integrated Circuit)集成电路总线,其实是IICBus简称,是一种总线结构。
它是Philips公式推出的一种基于两线的芯片间串行通信总线,使用多主从架构。
I2C总线有多种用途,包括CRC码的生成和校验、MBus(SystemManagement Bus)、PMBus(Power Management Bus )。
IIC特点:
使用I2C总线设计计算机系统十分方便灵活,体积也小,因而在各类实际应用中得到广泛应用。
空闲状态
I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。
数据传送
■ 一帧共9bit:8bit主机数据 + 1bit从机应答
■ 数据传送时先传送最高位
■ 数据的有效性
SCL为高电平的时,SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。
当SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。
■ 传输的第一个数据 为8bit的寻址字节,寻址字节后面发送的是传输的数据。
寻址字节:D7~D1位组成从机的地址。D0位是读写位,为“0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。被寻的从机会响应一个有效应答信号。
最后是主机与从机之间传输数据(8bit),数据接收方接到数据会响应一个有效应答信号ACK 。
■ 应答信号ACK
有效应答信号ACK 为 低电平0 。
终止信号。
在数据传输完成后,总是由主控器发出停止信号。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。终止信号产生后,总线处于空闲的状态。
灰色部分是主机向从机传送的数据;白色部分是从机向主机传送的数据。
主机指定位置读取从机数据的过程:如下例子
简述通过I2C接口读取设备X的寄存器Y的值的过程
发送起始信号——发送设备X地址+读写位0——读取ACK——发送寄存器地址Y——读取ACK——重复起始信号——发送设备X地址+读写位1——读取ACK——读取数据——发送NACK——发送停止信号
简述通过I2C接口读取设备X的寄存器Y的值的过程
发送起始信号——发送设备X地址+读写位0——读取ACK——发送寄存器地址Y——读取ACK——重复起始信号——发送设备X地址+读写位1——读取ACK——读取数据——发送NACK——发送停止信号
时钟控制逻辑
SCL线的时钟信号,由I2C接口根据时钟控制寄存器(CCR)控制,控制的参数主要为时钟频率。
■ 可选择I2C通讯的“标准/快速”模式,这两个模式分别I2C对应100/400Kbit/s的通讯速率。
■ 在快速模式下可选择SCL时钟的占空比,可选Tlow/Thigh=2 或 Tlow/Thigh=16/9模式。
■ CCR寄存器中12位的配置因子CCR,它与12C外设的输入时钟源共同作用,产生SCL时钟。STM32的I2C外设输入时钟源为PCLK1。
数据控制逻辑
I2C的SDA信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源是数据寄存器(DR)、目标时是目标地址寄存器(OAR) PEC寄存器以及SDA数据线。
当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过SDA信号线发送出去;
当从外部接收数据的时候,数据移位寄存器把SDA信号线采样到的数据一位一位地存储到“数据寄存器”中。
整体控制逻辑
整体控制逻辑负责协调整个I2C外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变。
在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR1和SR2)”,只要读取这些寄存器相关的寄存器位,就可以了解I2C的工作状态。常使用到SR2中的BUSY标志位来判断是由正在传输数据。
使用12C外设通讯时,在通讯的不同阶段它会对“状态寄存器(SR1及SR2)”的不同数据位写入参数,通过读取这些寄存器标志来了解通讯状态。
控制产生起始信号(S),当发生起始信号START后,它产生事件“EV5”,并会对SR1寄存器的“SB”位置1,表示起始信号已经发送;
发送设备地址(从地址通过内部移位寄存器被送到SDA线上)并等待应答信号,若有从机应答,则产生事件“EV6”及“EV8”,这时SR1寄存器的“ADDR”位及“TXE”位被置1。ADDR为1表示地址已经发送,TXE为1表示数据寄存器为空;
往I2C的“数据寄存器DR”写入要发送的数据,这时TXE位会被重置0,表示数据寄存器非空,I2C外设通过SDA信号线一位位把数据发送出去后, 又会产生“EV8”事件,即TXE位被置1,重复这个过程,可以发送多个字节数据;
发送数据完成后,控制I2C设备产生一一个停止信号 ( P ),这个时候会产生EV2 事件,SR1的TXE位及BTF位都被置1,表示通讯结束。
IIC初始化结构体
typedef struct
{
uint32_t I2C_ClockSpeed; /*设置SCL时钟频率,此值要低于40 000 */
uint16_t I2C_Mode; /*!< 指定工作模式,可选IIC模式及SMBUS模式 */
uint16_t I2C_DutyCycle; /*!< 指定时钟占空比,可选low/high = 2:1 及 16:9模式 */
uint16_t I2C_OwnAddress1; /*!< 指定自身的IIC设备地址,参数可以是 7-bit 或者 10-bit */
uint16_t I2C_Ack; /*!< 使能或关闭响应(一般都有使能) */
uint16_t I2C_AcknowledgedAddress; /*!<指定地址的长度,可为 7-bit 及 10-bit */
}I2C_InitTypeDef;
产生起始、停止信号函数
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
/* Generate a START condition */
I2Cx->CR1 |= CR1_START_Set;
}
else
{
/* Disable the START condition generation */
I2Cx->CR1 &= CR1_START_Reset;
}
}
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
/* Generate a STOP condition */
I2Cx->CR1 |= CR1_STOP_Set;
}
else
{
/* Disable the STOP condition generation */
I2Cx->CR1 &= CR1_STOP_Reset;
}
}
同 I2C_GenerateSTART();
读取标志位函数
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
{
//...
}
- This parameter can be one of the following values:
- @arg I2C_FLAG_DUALF: Dual flag (Slave mode)
- @arg I2C_FLAG_SMBHOST: SMBus host header (Slave mode)
- @arg I2C_FLAG_SMBDEFAULT: SMBus default header (Slave mode)
- @arg I2C_FLAG_GENCALL: General call header flag (Slave mode)
- @arg I2C_FLAG_TRA: Transmitter/Receiver flag
- @arg I2C_FLAG_BUSY: Bus busy flag
- @arg I2C_FLAG_MSL: Master/Slave flag
- @arg I2C_FLAG_SMBALERT: SMBus Alert flag
- @arg I2C_FLAG_TIMEOUT: Timeout or Tlow error flag
- @arg I2C_FLAG_PECERR: PEC error in reception flag
- @arg I2C_FLAG_OVR: Overrun/Underrun flag (Slave mode)
- @arg I2C_FLAG_AF: Acknowledge failure flag
- @arg I2C_FLAG_ARLO: Arbitration lost flag (Master mode)
- @arg I2C_FLAG_BERR: Bus error flag
- @arg I2C_FLAG_TXE: Data register empty flag (Transmitter)
- @arg I2C_FLAG_RXNE: Data register not empty (Receiver) flag
- @arg I2C_FLAG_STOPF: Stop detection flag (Slave mode)
- @arg I2C_FLAG_ADD10: 10-bit header sent flag (Master mode)
- @arg I2C_FLAG_BTF: Byte transfer finished flag
- @arg I2C_FLAG_ADDR: Address sent flag (Master mode) "ADSL"
- Address matched flag (Slave mode)"ENDA"
- @arg I2C_FLAG_SB: Start bit flag (Master mode)
发送地址函数
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_I2C_DIRECTION(I2C_Direction));
/* Test on the direction to set/reset the read/write bit */
if (I2C_Direction != I2C_Direction_Transmitter)
{
/* Set the address bit0 for read */
Address |= OAR1_ADD0_Set;
}
else
{
/* Reset the address bit0 for write */
Address &= OAR1_ADD0_Reset;
}
/* Send the address */
I2Cx->DR = Address;
}
发送数据函数
/**
* @brief Sends a data byte through the I2Cx peripheral.
* @param I2Cx: where x can be 1 or 2 to select the I2C peripheral.
* @param Data: Byte to be transmitted..
* @retval None
*/
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
/* Write in the DR register the data to be sent */
I2Cx->DR = Data;
}
接收数据函数
/**
* @brief Returns the most recent received data by the I2Cx peripheral.
* @param I2Cx: where x can be 1 or 2 to select the I2C peripheral.
* @retval The value of the received data.
*/
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
/* Return the data in the DR register */
return (uint8_t)I2Cx->DR;
}
读取接收到的数据。
应答信号配置函数
/**
* @brief Enables or disables the specified I2C acknowledge feature.
* @param I2Cx: where x can be 1 or 2 to select the I2C peripheral.
* @param NewState: new state of the I2C Acknowledgement.
* This parameter can be: ENABLE or DISABLE.
* @retval None.
*/
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
/* Enable the acknowledgement */
I2Cx->CR1 |= CR1_ACK_Set;
}
else
{
/* Disable the acknowledgement */
I2Cx->CR1 &= CR1_ACK_Reset;
}
}
为啥使用 EEPROM 做IIC实验呢?便宜且使用IIC协议通讯。
EEPROM-AT24C02具体请看 AT24C02简介
开发板上的EEPROM连接电路图:
iic.h
#ifndef IIC_H
#define IIC_H
#include "stm32f10x.h"
#include "Tool.h"//这个文件实现了位绑定的功能
#include "bsp_systick.h"//这个文件实现systick延时的功能
/****** IIC_SCL时钟端口、引脚定义 ******/
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN GPIO_Pin_6
#define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB
/****** IIC_SDA时钟端口、引脚定义 ******/
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN GPIO_Pin_7
#define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB
/************* IO操作函数 *************/
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
/*********** IIC所有操作函数 ***********/
void IIC_Init(void); //初始化IIC的IO口
void SDA_OUT(void); //配置IIC的SDA为输出模式
void SDA_IN(void); //配置IIC的SDA为输入模式
#endif
iic.c
#include "iic.h"
/*******************************************************************
*函数: IIC_Init
*功能: IIC初始化,配置SCL、SDA引脚为推挽输出
*输入: void
*输出: None
*特殊说明:
*******************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);
GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
IIC_SCL = 1;
IIC_SDA = 1;
}
/*******************************************************************
*函数: SDA_OUT
*功能: 初始化输出IO。配置SDA引脚为推挽输出
*输入: void
*输出: None
*特殊说明:不配置成开漏输出,是为了方便操作
*******************************************************************/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************
*函数: SDA_IN
*功能: 初始化输入IO。配置SDA引脚为上拉输入
*输入: void
*输出: None
*特殊说明:此为软件模拟IIC,使用硬件是要配置成开漏输出
*******************************************************************/
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
}
main.c
#include "stm32f10x.h"
#include "bsp_systick.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "iic.h"
int main(void)
{
u8 txd=0;
u8 readData=0;
u8 i=0;
usart1_Init(9600);
LED_Init();
KEY_Init();
IIC_Init();
printf("开始检测数据!\r\n");
SDA_IN();//SDA设置为输入
if(READ_SDA == 1)//检测一下电路是否接上拉电阻
printf("初始SDA为高电平\r\n");
/********** 起始信号 **********/
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=0;//SDA拉低,作为起始信号
SysTick_Delay_us(5);
IIC_SCL=0;//拉低SCL,准备发送/接受数据
SysTick_Delay_us(5);
/********** 写入设备 **********/
SDA_OUT();
txd=0xa0;//发送的寻找设备的地址
for(i=0;i<8;i++)
{
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0,在改变数据,若初始化时SCL=0,这句可以注释掉
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
IIC_SCL=1;SysTick_Delay_us(2);//根据时序要求,SCL高电平周期要持续2us
IIC_SCL=0;SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
}
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("设备响应成功\n");
IIC_SCL=0;
break;
}
}
/********** 写入内存地址 **********/
SDA_OUT();
txd =255;
for(i=0;i<8;i++)
{
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0,在改变数据
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
IIC_SCL=1;SysTick_Delay_us(2);//根据时序要求,SCL高电平周期要持续2us
IIC_SCL=0;SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
}
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("写入内存地址ack返回0\n");
IIC_SCL=0;
break;
}
}
/********** 写入数据 **********/
SDA_OUT();
txd =0xee;//写入到指定地址的数据
printf("准备写入数据txd:%X\n",txd);
for(i=0;i<8;i++)
{
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0,在改变数据
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
IIC_SCL=1;SysTick_Delay_us(2);//根据时序要求,SCL高电平周期要持续2us
IIC_SCL=0;SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
}
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("写入数据txd,ack返回0\n");
IIC_SCL=0;
break;
}
}
/********** 结束信号 **********/
SDA_OUT();
IIC_SDA=0;//注意一定要SDA=0在前,SCL=1在后,否则写入不能成功,不知原因
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=1;
SysTick_Delay_us(5);
SysTick_Delay_ms(10);//保证数据从缓冲区写入到不丢失区
/********** 起始信号 **********/
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=0;//SDA拉低,作为起始信号
SysTick_Delay_us(5);
IIC_SCL=0;//拉低SCL,准备发送/接受数据
SysTick_Delay_us(5);
/********** 写入设备 **********/
SDA_OUT();
txd=0xa0;//发送的寻找设备的地址
for(i=0;i<8;i++)
{
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0,在改变数据,若初始化时SCL=0,这句可以注释掉
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
IIC_SCL=1;SysTick_Delay_us(2);//根据时序要求,SCL高电平周期要持续2us
IIC_SCL=0;SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
}
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("设备响应成功\n");
IIC_SCL=0;
break;
}
}
/********** 写入内存地址 **********/
SDA_OUT();
txd =255;
for(i=0;i<8;i++)
{
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0,在改变数据
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
IIC_SCL=1;SysTick_Delay_us(2);//根据时序要求,SCL高电平周期要持续2us
IIC_SCL=0;SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
}
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("写入内存地址ack返回0\n");
IIC_SCL=0;
break;
}
}
/********** 起始信号 **********/
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=0;//SDA拉低,作为起始信号
SysTick_Delay_us(5);
IIC_SCL=0;//拉低SCL,准备发送/接受数据
SysTick_Delay_us(5);
/********** 读取设备 **********/
SDA_OUT();
txd=0xa1;
for(i=0;i<8;i++)
{
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0?
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
IIC_SCL=1;SysTick_Delay_us(2);//根据时序要求,SCL高电平周期要持续2us
IIC_SCL=0;SysTick_Delay_us(1);//根据时序要求,SCL低电平周期要持续2us
}
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("设备响应成功\n");
IIC_SCL=0;
break;
}
}
/********** 接受数据 **********/
SDA_IN();//SDA输入
for(i=0;i<8;i++)
{
//拉低SCL,持续一段时间,从机更改数据后,再拉高SCL
IIC_SCL=0;
SysTick_Delay_us(5);//时钟低电平大于4700ns
IIC_SCL=1;
readData<<=1;
if(READ_SDA)
readData++;//最低位置1
SysTick_Delay_us(4);//时钟高电平大于4000ns
}
printf("rec data is :%X\n",readData);
/********** 结束信号 **********/
SDA_OUT();
IIC_SDA=0;
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=1;
SysTick_Delay_us(5);
}
iic.h
#ifndef IIC_H
#define IIC_H
#include "stm32f10x.h"
#include "Tool.h"
#include "bsp_systick.h"
/****** IIC_SCL时钟端口、引脚定义 ******/
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN GPIO_Pin_6
#define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB
/****** IIC_SDA时钟端口、引脚定义 ******/
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN GPIO_Pin_7
#define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB
/************* IO操作函数 *************/
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
/*********** IIC所有操作函数 ***********/
void IIC_Init(void); //初始化IIC的IO口
void SDA_OUT(void); //配置IIC的SDA为输出模式
void SDA_IN(void); //配置IIC的SDA为输入模式
void IIC_Start(void); //
void IIC_Send_Byte(u8 txd);
u8 IIC_Wait_Ack(void);
u8 IIC_Read_Byte(u8 ack);
void IIC_Stop(void);
#endif
iic.c
#include "iic.h"
/*******************************************************************
*函数: IIC_Init
*功能: IIC初始化,配置SCL、SDA引脚为推挽输出
*输入: void
*输出: None
*特殊说明:
*******************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);
GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
IIC_SCL = 1;
IIC_SDA = 1;
}
/*******************************************************************
*函数: SDA_OUT
*功能: 初始化输出IO。配置SDA引脚为推挽输出
*输入: void
*输出: None
*特殊说明:不配置成开漏输出,是为了方便操作
*******************************************************************/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************
*函数: SDA_IN
*功能: 初始化输入IO。配置SDA引脚为上拉输入
*输入: void
*输出: None
*特殊说明:此为软件模拟IIC,使用硬件是要配置成开漏输出
*******************************************************************/
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}
/*******************************************************************
*函数: IIC_Start
*功能: IIC产生起始信号
*输入: void
*输出: None
*特殊说明:
*******************************************************************/
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
SysTick_Delay_us(4);
IIC_SDA=0;
SysTick_Delay_us(4);
IIC_SCL=0;
}
/*******************************************************************
*函数: IIC_Stop
*功能: IIC产生停止信号
*输入: void
*输出: None
*特殊说明:
*******************************************************************/
void IIC_Stop(void)
{
SDA_OUT();
IIC_SDA=0;//注意一定要SDA=0在前,SCL=1在后,否则写入不能成功,不知原因
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=1;
SysTick_Delay_us(5);
}
/*******************************************************************
*函数: IIC_Wait_Ack
*功能: 等待应答信号到来。设置SCL、SDA为高电平,判断SDA是否变为0
*输入: void
*输出: 1, 表示应答失败
0,表示应答成功
*特殊说明:
*******************************************************************/
u8 IIC_Wait_Ack(void)
{
u8 waitTime = 0;
SDA_IN();//SDA设置为输入
IIC_SDA=1;SysTick_Delay_us(1);
IIC_SCL=1;SysTick_Delay_us(1);
while(READ_SDA)
{
waitTime++;
//电平在这段时间中一直为1,则说明应答失败,停止IIC通讯
if(waitTime > 250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
/*******************************************************************
*函数: IIC_Ack
*功能: 产生ACK应答。设置SCL、SDA为低电平,判断SDA是否变为0
*输入: void
*输出: None
*特殊说明:
*******************************************************************/
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;//顺序
SysTick_Delay_us(2);//顺序
IIC_SCL=1;//顺序
SysTick_Delay_us(5);
IIC_SCL=0;
}
/*******************************************************************
*函数: IIC_NAck
*功能: 产生NACK应答。设置SCL、SDA为低电平,判断SDA是否变为0
*输入: void
*输出: None
*特殊说明:
*******************************************************************/
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
SysTick_Delay_us(2);
IIC_SCL=1;
SysTick_Delay_us(2);
IIC_SCL=0;
}
/*******************************************************************
*函数: IIC_Send_Byte
*功能: 发送一字节的数据
*输入: txd : 8bit的数据
*输出: None
*特殊说明:
*******************************************************************/
void IIC_Send_Byte(u8 txd)
{
u8 i;
SDA_OUT();
IIC_SCL=0;//根据通讯有效数据标准,先使SCL=0,在改变数据
SysTick_Delay_us(5);//根据时序要求,SCL低电平周期要持续4.7us
for(i=0;i<8;i++)
{
if((txd&0x80)>0)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
IIC_SCL=1;SysTick_Delay_us(5);//根据时序要求,SCL高电平周期要持续4us
IIC_SCL=0;SysTick_Delay_us(5);//根据时序要求,SCL低电平周期要持续4.7us
}
}
/*******************************************************************
*函数: IIC_Read_Byte
*功能: 读取一字节的数据
*输入: ack: ack为1,发送ACK; ack为0,发送nACK。
*输出: 接收到的数据
*特殊说明:
*******************************************************************/
u8 IIC_Read_Byte(u8 ack)
{
u8 i,receive=0;
SDA_IN();//SDA输入
for(i=0;i<8;i++)
{
IIC_SCL=0;
SysTick_Delay_us(5);//时钟低电平大于4700ns
IIC_SCL=1;
receive<<=1;//先移位,否则在接受最后一位数据时,会把第一个接受的数据移位出去
if(READ_SDA)
receive++;
SysTick_Delay_us(4);//时钟高电平大于4000ns
}
if(!ack)
IIC_NAck();//发送nACK
if(ack)
IIC_Ack();//发送ACK
return receive;
}
main.c
#include "stm32f10x.h"
#include "bsp_systick.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "iic.h"
int main(void)
{
u8 rec=0;
usart1_Init(9600);
LED_Init();
KEY_Init();
IIC_Init();
printf("开始检测数据!\r\n");
/********* 写入数据 *********/
IIC_Start();
IIC_Send_Byte(0xa0);
if(IIC_Wait_Ack() == 0)
printf("设备响应成功\n");
IIC_Send_Byte(0xff);
if(IIC_Wait_Ack() == 0)
printf("写入地址响应成功\n");
IIC_Send_Byte(0x1c);
if(IIC_Wait_Ack() == 0)
printf("写入数据响应成功\n");
IIC_Stop();
/********* 指定内存地址读取数据 *********/
IIC_Start();
IIC_Send_Byte(0xa0);
if(IIC_Wait_Ack() == 0)
printf("设备响应成功\n");
IIC_Send_Byte(0xff);
if(IIC_Wait_Ack() == 0)
printf("写入地址响应成功\n");
IIC_Start();
IIC_Send_Byte(0xa1);
if(IIC_Wait_Ack() == 0)
printf("读取设备响应成功\n");
rec=IIC_Read_Byte(0);
printf("rec data is :%X\n",rec);
}
仿真调试,通过逻辑分析仪查看到的波形图。
最终实验现象可以通过串口助手观察到写入数据和读取数据一致。可以通过改变写入的数据值txd来验证这是否是偶然的成功。经验证不是偶然结果。
/********** 起始信号 **********/
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=0;//SDA拉低,作为起始信号
SysTick_Delay_us(5);
IIC_SCL=0;//拉低SCL,准备发送/接受数据
SysTick_Delay_us(5);
/********** 检查ack **********/
SDA_IN();//SDA设置为输入
IIC_SCL=1;SysTick_Delay_us(1);//上拉SCL,使数据位有效,通知eeprom现在数据有效,别更改数据
IIC_SDA=1;SysTick_Delay_us(1);//上拉SDA
for(i=0;i<250;i++)
{
if(READ_SDA == 0)
{
printf("设备响应成功\n");
IIC_SCL=0;
break;
}
}
/********** 结束信号 **********/
SDA_OUT();
IIC_SDA=0;//注意一定要SDA=0在前,SCL=1在后,否则写入不能成功,不知原因
IIC_SCL=1;
SysTick_Delay_us(5);
IIC_SDA=1;
SysTick_Delay_us(5);