1、简介
之所以叫“I2C硬件控制方式”是与“软件控制方式”相对。I2C软件控制,就是写程序直接操作两个GPIO引脚,分别作为时钟线SCL和数据线SDA,按照I2C协议的时序要求,操作GPIO输入、输出、高电平、低电平。
听着就很复杂,好在STM32中有I2C的硬件实现,即通过简单的操作寄存器即可实现收发数据。
2、手册
2.1 寄存器功能框图
2.2 I2C引脚
STM32F407ZGT6中有3个I2C总线,对应的引脚如下图所示。
其中I2C1默认引脚是PB6、PB7,可以重映射到PB8、PB9上。
2.3 寄存器
寄存器地址:
3、代码详解
I2C基本编程步骤:初始化时钟、配置引脚、起始信号、读、写、终止信号
3.1 I2C初始化
3.1.1 初始化时钟
I2C在APB1总线上,并且I2C1的引脚位PB6、PB7,因此使能使能GPIOB时钟,以及I2C的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
3.1.2 配置引脚位开漏输出
static void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
#a)使能与 I2C 有关的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
#b)配置SCL和SDA引脚位开漏输出GPIO_Mode_AF_OD
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
3.1.3 设置I2C工作模式
设置工作模式、占空比、地址7位或10位、通信速率等
工作模式有三种:I2C、SMBusDevice、SMBusHost。
SMBus (System Management Bus,系统管理总线)和I2C类似,但是在时序特性上有一些差异:
首先,SMBus需要一定数据保持时间,而 I2C总线则是从内部延长数据保持时间。
SMBus具有超时功能,因此当SCL太低而超过35 ms时,从器件将复位正在进行的通信。相反,I2C采用硬件复位。
SMBus具有一种警报响应地址(ARA),因此当从器件产生一个中断时,
它不会马上清除中断,而是一直保持到其收到一个由主器件发送的含有其地址的ARA为止。
SMBus只工作在从10kHz到最高100kHz。最低工作频率10kHz是由SMBus超时功能决定的。
static void I2C_Mode_Configu(void)
{
I2C_InitTypeDef I2C_InitStructure;
#a)设置位I2C模式
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
#b)设置高低电平占空比,这个不用纠结,可以随意设置,一般设备兼容性都没问题
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
#c)设置自己的地址为7位
I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;
#d)默认使能ACK,当需要发送NACK时,再重新设置
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
#e)设置I2C的寻址地址为7位
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
#f)设置时钟为:400000
I2C_InitStructure.I2C_ClockSpeed = 400000;
#g)初始化I2C1:(APB1PERIPH_BASE + 0x5400)
I2C_Init(I2C1, &I2C_InitStructure);
#h)使能 I2C1
I2C_Cmd(I2C1, ENABLE);
}
3.2 写操作
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
{
#a)发送起始信号“S”
I2C_GenerateSTART(I2C1, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#b)等待起始信号发送成功:测试 EV5 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
}
I2CTimeout = I2CT_FLAG_TIMEOUT;
#c)发送从地址
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
#d)等待从地址发送成功:测试 EV6 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
}
#e)发送需要写入EEPROM的地址(本质也是数据)
I2C_SendData(I2C1, WriteAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#f)等待数据发送成功:测试 EV8 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
}
#g)发送需要写入的数据
I2C_SendData(I2C1, *pBuffer);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#h)等待数据发送成功:测试 EV8 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
#i)发送停止信号“P”
I2C_GenerateSTOP(I2C1, ENABLE);
return 1;
}
3.3 读操作
uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
#a)等待是否可以读
//*((u8 *)0x4001080c) |=0x80;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
}
#b)发送起始信号“S”
I2C_GenerateSTART(I2C1, ENABLE);
//*((u8 *)0x4001080c) &=~0x80;
I2CTimeout = I2CT_FLAG_TIMEOUT;
#c)等待起始信号发送成功:测试 EV5 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
#d)发送从地址
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#e)等待从地址发送成功:测试 EV6 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
}
#f)EV6后执行EV6_1:清除EV6 (再次设置PE位)
I2C_Cmd(I2C1, ENABLE);
#g)发送需要读取的地址
I2C_SendData(I2C1, ReadAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#h)等待数据发送成功:测试 EV8 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);
}
#i)复合命令:再次发送起始信号“S”
I2C_GenerateSTART(I2C1, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#j)等待起始信号发送成功:测试 EV5 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);
}
#k)发送从地址
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);
I2CTimeout = I2CT_FLAG_TIMEOUT;
#l)等待从地址发送成功:测试 EV6 即可
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);
}
#m)循环读
while(NumByteToRead)
{
if(NumByteToRead == 1)
{
#n)读取完毕,发送NACK
I2C_AcknowledgeConfig(I2C1, DISABLE);
#o)发送停止信号“P”
I2C_GenerateSTOP(I2C1, ENABLE);
}
#p)每次读取一个字节前,先测试 EV7
I2CTimeout = I2CT_LONG_TIMEOUT;
while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
{
#q)读取一字节
*pBuffer = I2C_ReceiveData(I2C1);
pBuffer++;
NumByteToRead--;
}
}
#r)为下一次读取做准备,即将ACK设置为1
I2C_AcknowledgeConfig(I2C1, ENABLE);
return 1;
}
3.4 待机状态
向EEPROM写入数据后,调用这个函数等待EEPROM 内部擦写完毕。文章来源:https://www.toymoban.com/news/detail-400824.html
这个函数主要实现是向EEPROM 发送它设备地址,检测EEPROM 的响应,若EEPROM 接收到地址后返回应答信号,则表示EEPROM 已经准备好,可以开始下一次通讯。函数中检测响应是通过读取STM32 的SR1 寄存器的ADDR 位及AF 位来实现的,当I2C 设备响应了地址的时候,ADDR 会置1,若应答失败,AF 位会置1。文章来源地址https://www.toymoban.com/news/detail-400824.html
void I2C_EE_WaitEepromStandbyState(void)
{
vu16 SR1_Tmp = 0;
do
{
#a)发送起始信号“S”
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
#b)读取I2C1 SR1 寄存器
SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);
#c)发送从地址
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);
}while(!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));
#d)清除AF信号
I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);
#e)发送停止信号“P”
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
到了这里,关于【STM32】入门(七):I2C硬件控制方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!