再来一个
————————————————————————————
34STM32-IIC 配置解说
STM32 - I2C简介:
I2C总线接口连接微控制器和串行I2C总线。它提供多主机功能,控制所有I2C总线特定的时序、协议、仲裁和定时。支持标准和快速两种模式,另外STM32的I2C可以使用DMA方式操作。
本文主要以一个实例来介绍STM32-I2C的配置方式和具体在工程中通过调用哪些库函数来实现I2C器件的通信。
实例:写入数据到器件AT24C02并将存入的数据读出
好,我们先来讲讲STM32 I2C模块的端口基本配置,由STM32中文参考手册可以查到在使用I2C时对应的引脚要配置成哪种模式。SCL和SDA引脚都配置成开漏复用输出
本人用的是STM32F103VET6,它有2个I2C接口。I/O口定义为PB6-I2C_SCL,PCB7-I2C1_SDA;PB10-I2C_SCL,PB11-I2C_SDA,由手册可以查出对应的端口。 图文如下:
调用库函数将I2C端口配置好(本文使用的是PB6、PB7端口):
程序代码如下:
/*I2C-IO口配置*/
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //GPIO结构体定义
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能I2C的IO口
/* PB6-I2C1_SCL、PB7-I2C1_SDA*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化结构体配置
}
/* I2C 工作模式配置 */
void I2C_Mode_config(void)
{
/* 使能与 I2C1 有关的时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
/*定义I2C结构体*/
I2C_InitTypeDef I2C_InitStructure;
/*配置为I2C模式*/
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
/*该参数只有在I2C 工作在快速模式(时钟工作频率高于 100KHz)下才有意义。*/
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
/*设置第一个设备自身地址*/
I2C_InitStructure.I2C_OwnAddress1 =0x0A;
/*使能应答*/
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
/*AT24C02地址为7位所以设置7位就行了*/
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /*时钟速率,以HZ为单位的,最高为400khz*/
I2C_InitStructure.I2C_ClockSpeed = 400000;
/* 使能 I2C1 */
I2C_Cmd(I2C1, ENABLE);
/* I2C1 初始化 */
I2C_Init(I2C1, &I2C_InitStructure);
}
好了,STM32 内部的 I2C模块工作模式就这样被设好了,接下来需要完成与外部器件AT24C02(EEPROM)进行通信。将分两部分进行代码解析,第一部分是:对AT24C02进行写操作,第二部分:对AT24C02进行读操作。
第一部分(写):
备注:I2C_PageSize 为宏定义 #define I2C_PageSize 8 ;
/*
* 函数名:I2C_EE_BufferWrite
* 描述 :将缓冲区中的数据写到I2C EEPROM中
* 输入 :-pBuffer 缓冲区指针
* -WriteAddr 接收数据的EEPROM的地址
* -NumByteToWrite 要写入EEPROM的字节数
* 输出 :无
* 返回 :无
* 调用 :外部调用
*/
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
Addr = WriteAddr % I2C_PageSize;//查看输入的地址是不是8的整数倍
count = I2C_PageSize - Addr;//表示距离下一页页首地址的距离(步伐数)
NumOfPage = NumByteToWrite / I2C_PageSize;//算出一共有多少页
NumOfSingle = NumByteToWrite % I2C_PageSize;//算出不够一页的数据的余数
if(Addr == 0) //如果输入的地址是首页地址
{
if(NumOfPage == 0) //如果不足一页数据
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//调用写函数,NumOfSingle不 够一页的余数作为实参
I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作
}
/* If NumByteToWrite > I2C_PageSize */
else //如果数据有一页以上
{
while(NumOfPage--)//用一个while循环,执行页写循环操作,有多少页就写多少次 {
I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize); //调用写函数,将
I2C_PageSize变量作为实 参执行页写
I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作
WriteAddr += I2C_PageSize;//每执行完一次页写对应的地址也需要移8个位 pBuffer += I2C_PageSize;//数据指针移8个位
}
if(NumOfSingle!=0)//如果有不足一页的数据余数则执行
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//调用写函数,NumOfSingle 不够一页的余数作为实参
I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作
}
}
}
else //输入的地址不是首页地址
{
if(NumOfPage== 0) //如果不足一页
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//调用写函数,NumOfSingle不 够一页的余数作为实参 I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作
}
else//如果有一页或一页以上
{
NumByteToWrite -= count;//将地址后续的缺省位置补上数据,数据的多少就是count 的值,NumByteToWrite变量的值就是补上数据之后 还剩下未发送的数量
NumOfPage = NumByteToWrite / I2C_PageSize;//剩余的页数
NumOfSingle = NumByteToWrite % I2C_PageSize;//不足一页的数据数量
if(count != 0)//将地址后续的缺省位置补上数据
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);//调用写函数,以count为实参,将地 址缺省下来的部分地址给填充 上数据
I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作
WriteAddr += count;//加上count后,地址就移位到下一页的首地址
pBuffer += count;//数据指针移count个位
}
while(NumOfPage--)//将剩余的页数数据写入EEPROM
{
I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);//调用写函数,将
I2C_PageSize变量作为实
参执行页写
I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作 WriteAddr += I2C_PageSize;//将地址移8个位
pBuffer += I2C_PageSize; //将数据指针移8个位
}
if(NumOfSingle != 0)//将不足一页的数据写入EEPROM
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//调用写函数,NumOfSingle 不够一页的余数作为实参 I2C_EE_WaitEepromStandbyState();//等待EEPROM器件完成内部操作 }
}
}
}
/*END OF FUNCTION*/
在以上写操作里面我们拿经常被调用的I2C_EE_PageWrite函数还有
I2C_EE_WaitEepromStandbyState函数并结合STM32中文参考手册图文进行对照分析
请读者在读I2C_EE_PageWrite函数时请结合上述时序图和下述代码联系一起看! 注:EEPROM_ADDRESS 为器件的地址,大家按照自己具体器件地址写入即可, 例:#define EEPROM_ADDRESS 0xA0
/*
* 函数名:I2C_EE_PageWrite
* 描述 :在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数 * 不能超过EEPROM页的大小。AT24C02每页有8个字节。
*输入:-pBuffer缓冲区指针;*-WriteAddr接收数据的EEPROM的地;*-NumByteToWrite要写入EEPRO;*输出:无;*返回:无;*调用:外部调用;*/;voidI2C_EE_PageWrite(u8*;I2C_GenerateSTART(I2C1,E;while(!I2C_CheckEvent(I2;I2C_Send7bitA
* 输入 :-pBuffer 缓冲区指针
* -WriteAddr 接收数据的EEPROM的地址
* -NumByteToWrite 要写入EEPROM的字节数
* 输出 :无
* 返回 :无
* 调用 :外部调用
*/
void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
I2C_GenerateSTART(I2C1, ENABLE);//产生起始位
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //清除EV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//发送器件地 址
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //ADDR=1,清除EV6
I2C_SendData(I2C1, WriteAddr); //EEPROM的具体存储地址位置
while(! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//移位寄存器非空,数据寄存器已经空,产生EV8,发送数据到DR既可清除该事件
while(NumByteToWrite--) //利用while循环 发送数据
{
I2C_SendData(I2C1, *pBuffer); //发送数据
pBuffer++; //数据指针移位
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//清除 EV8 }
I2C_GenerateSTOP(I2C1, ENABLE);//产生停止信号
}
/*END OF FUNCTION*/
I2C_EE_WaitEepromStandbyState这个函数,在每调用完写操作函数后都调用这个函数,这个函数是用来检测EEPROM器件是否已经完成内部写的操作,判断器件完成操作后在进行下一步的操作!代码如下:
/*
* 函数名:I2C_EE_WaitEepromStandbyState
* 描述 :Wait for EEPROM Standby state
* 输入 :无
* 输出 :无
* 返回 :无
* 调用 :
*/
void I2C_EE_WaitEepromStandbyState(void)
{
vu16 SR1_Tmp = 0;
do
{
I2C_GenerateSTART(I2C1, ENABLE);//产生起始信号
SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);//读SR1寄存器
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//发送器件 地址清除事 件
}while(!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));//如果接收不到从机的应答(NACK)则说明EEPROM器件还在工作,直到完成操作跳出循环体!
I2C_ClearFlag(I2C1, I2C_FLAG_AF);//清除AF标志位
I2C_GenerateSTOP(I2C1, ENABLE); //产生停止信号
}
第二部分(读):
由以上AT24C02读时序图可以知道:读部分需要产生两次起始信号
另外:主设备在从从设备接收到最后一个字节后发送一个NACK 。接收到NACK 后,从设备释放对SCL和SDA线的控制;主设备就可以发送一个停止/ 重起始条件。
● 为了在收到最后一个字节后产生一个NACK 脉冲,在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)必须清除ACK位。
● 为了产生一个停止/ 重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)设置STOP/START位。
● 只接收一个字节时,刚好在EV6 之后(EV6_1时,清除ADDR 之后)要关闭应答和停止条件的产生位。
请读者将代码和图结合在一起看!
/*
* 函数名:I2C_EE_BufferRead
* 描述 :从EEPROM里面读取一块数据。
* 输入 :-pBuffer 存放从EEPROM读取的数据的缓冲区指针。
* -WriteAddr 接收数据的EEPROM的地址。
* -NumByteToWrite 要从EEPROM读取的字节数。
* 输出 :无
* 返回 :无
* 调用 :外部调用
*/
void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)//需要两个起始信号
{
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); //调用库函数检测I2C器件是否处 于BUSY状态
I2C_GenerateSTART(I2C1, ENABLE);//开启信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//清除EV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//写入器件地址
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除EV6
I2C_SendData(I2C1, ReadAddr); //发送读的地址
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//清除EV8
I2C_GenerateSTART(I2C1, ENABLE);//开启信号
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//清除EV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);//将器件地址传出,主机为读
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));//清除EV6
while(NumByteToRead)
{
if(NumByteToRead == 1)//只剩下最后一个数据时进入if语句
{
I2C_AcknowledgeConfig(I2C1, DISABLE);//最后有一个数据时关闭应答位
I2C_GenerateSTOP(I2C1, ENABLE);//最后一个数据时使能停止位
}
if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)) //读取数据 {
*pBuffer = I2C_ReceiveData(I2C1);//调用库函数将数据取出到pBuffer
pBuffer++; //指针移位
NumByteToRead--;//字节数减1
}
}
I2C_AcknowledgeConfig(I2C1, ENABLE);//将应答位使能回去,等待下次通信 }
STM32-IIC 配置解说到此告一段落!
|