基于STM32H723的硬件IIC+DMA,实现对EEPROM的写入及读取操作,建议配合数据手册区查看代码帮助理解,使用DMA时,注意把DMA设置的缓存地址设置在0x24000000以后的区域,DMA才能够访问。
/**************************************************************************
*EEPROM的IIC驱动初始化,带DMA
*PF0----------------------IIC2_SDA_EEPROM
*PF1----------------------IIC2_SCL_EEPROM
*PF2----------------------WP_EEPROM
*
*
**************************************************************************/
uint8_t IIC_And_DMA_Driver_With_EEPROM_Init(void)
{
DMA_InitTypeDef DMA_InitStruct;
RCC->AHB4ENR |= 1 << 5; //使能GPIOF时钟
RCC->APB1LENR |= 1 << 22; //使能I2C2外设时钟
/*GPIO配置*/
GPIO_Set(GPIOF,1 << 0,GPIO_MODE_AF,GPIO_OTYPE_OD,GPIO_SPEED_LOW,GPIO_PUPD_PU); //PF0
GPIO_Set(GPIOF,1 << 1,GPIO_MODE_AF,GPIO_OTYPE_OD,GPIO_SPEED_LOW,GPIO_PUPD_PU); //PF1
GPIO_Set(GPIOF,1 << 2,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_LOW,GPIO_PUPD_PU);//PF2
GPIO_AF_Set(GPIOF,0,4);
GPIO_AF_Set(GPIOF,1,4);
GPIO_Pin_Set(GPIOF,PIN2,0);
//I2C 时钟来源默认100M 来自PCLK1
RCC->APB1LRSTR |= 1 << 22;
RCC->APB1LRSTR &= ~(1 << 22);
I2C2->CR1 |= 0 << 0;
I2C2->TIMINGR = 0x20601138 & 0xF0FFFFFF;
//此处将IIC的时钟频率设置为400K,如果要设置为100K,则推荐0x20601138,具体配置方法可查数据手册
I2C2->CR2 |= ((0xA0 >> 1) & 0x7F) << 1;//这里设置的IIC从机的地址
I2C2->CR2 |= 1 << 10;
I2C2->OAR1 = ((0 << 15) | (0)); //失能自身地址1 自身地址设为0
I2C2->CR2 |= (1 << 15); //使能NACK,当作为从机时,需要失能NACK
I2C2->CR2 &= ~(1 << 25); //失能AUTOEN
I2C2->OAR2 = ((0 << 15) | (0) | (0 << 8));//失能自身地址2 自身地址设为0 无屏蔽位
I2C2->CR1 = ((0 << 19) | (0 << 17)); //失能广播呼叫功能,时钟延长
I2C2->CR1 |= 1 << 15; //使能DMA接收请求
I2C2->CR1 |= 1 << 14; //使能DMA发送请求
I2C2->CR1 |= 1 << 0;
/*以上是IIC的寄存器配置,400K,以下则是DMA的配置,这里我用了自己仿ST标准库实现DMA配置*/
DMA_InitStruct.u32DMA_NumberData = 0;
DMA_InitStruct.u32DMA_Memory1BaseAddr=(uint32_t)u8IIC_DMA_Transmit_For_EEPROM_Buffer;
//DMA存储器地址
DMA_InitStruct.u32DMA_PeripheralBaseAddr = (uint32_t)&I2C2->TXDR;//外设地址
DMA_InitStruct.u8DMA_Channel = 36;//触发方式,需要查数据手册的DMA映射表
DMA_InitStruct.u8DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStruct.u8DMA_DoubleBufferMode = DMA_DoubleBuffer_Disable;
DMA_InitStruct.u8DMA_MemoryBurstTransfer = DMA_MemoryBurst_Single;
DMA_InitStruct.u8DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStruct.u8DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.u8DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.u8DMA_PeripheralBurstTransfer = DMA_PeripheralBurst_Single;
DMA_InitStruct.u8DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.u8DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.u8DMA_Priority = DMA_Priority_High;
DMA_Driver_Init(DMA2_Stream6,&DMA_InitStruct);
DMA_InitStruct.u32DMA_Memory1BaseAddr= (uint32_t)u8IIC_DMA_Receive_For_EEPROM_Buffer;
//DMA存储器地址
DMA_InitStruct.u32DMA_PeripheralBaseAddr = (uint32_t)&I2C2->RXDR;//外设地址
DMA_InitStruct.u8DMA_Channel = 35;
DMA_InitStruct.u8DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_Driver_Init(DMA2_Stream7,&DMA_InitStruct);
return 1;
}
以上便是IIC2主机+DMA的寄存器配置实现,接下来是IIC的主机发送代码实现
/*******************************************************************************
** 函数名称:IIC_DMA_Transmit_For_EEPROM_Driver
** 函数作用:IIC向EEPROM发送数据驱动
** 输入参数:u32EEPROM_Address------------访问的EEPROM地址
** u8Length---------------------写入EEPROM的数据长度
** pBuffer----------------------写入EEPROM的数据存储指针
** 输出参数:0------------------失败
** 1------------------成功
** 使用样例:无
** 函数备注:无
*******************************************************************************/
uint8_t IIC_DMA_Transmit_For_EEPROM_Driver(uint32_t u32EEPROM_Address,uint8_t u8Length,uint8_t* pBuffer)
{
uint16_t t_u16WaitTime = 0;
if(u8Length > EEPROM_TRANSMIT_BUFFER_MAX_SIZE)
{
return 0;//当发送的数据长度大于发送DMA缓存空间时,直接返回失败
}
I2C2->ICR |= 1 << 4; //清楚否应答标志
I2C2->ICR |= 1 << 5; //清除停止位产生标志
I2C2->CR2 &= ~(1 << 10); //EEPROM写指令
I2C2->CR2 &= ~(0xFF << 16); //清除Nbytes
I2C2->CR2 |= 2 << 16; //2个字节待传输,发送要写入的EEPROM地址
I2C2->CR2 |= 1 << 24; //重载模式
I2C2->CR2 |= 1 << 25; //自动发送停止位
I2C2->CR2 |= 1 << 13; //产生起始位+地址
while((I2C2->ISR & 1 << 1) != (1 << 1))//等待发送完成
{
/*在这里实现超时判断,有用操作系统的,可以用操作系统的延时函数*/
}
I2C2->TXDR = (uint8_t)(u32EEPROM_Address >> 8);
t_u16WaitTime = 0;
while((I2C2->ISR & 1 << 1) != (1 << 1))//等待发送完成
{
/*在这里实现超时判断,有用操作系统的,可以用操作系统的延时函数*/
}
I2C2->TXDR = (uint8_t)u32EEPROM_Address;
t_u16WaitTime = 0;
while((I2C2->ISR & 1 << 7) != (1 << 7))//等待发送完成
{
/*在这里实现超时判断,有用操作系统的,可以用操作系统的延时函数*/
}
I2C2->CR2 &= ~(0xFF << 16); //清除Nbytes
I2C2->CR2 |= u8Length << 16; //2个字节待传输
I2C2->CR2 &= ~(1 << 24); //重载模式
SCB_CleanDCache();
DMA2_Stream6->CR &= ~(1 << 0);
t_u16WaitTime = 0;
while(DMA2_Stream6->CR&0X01)//等待DMA可被配置
{
/*在这里实现超时判断,有用操作系统的,可以用操作系统的延时函数*/
}
DMA2->HIFCR |= 0x3D << 16; //清空该Stream上的所有中断标志
memcpy(u8IIC_DMA_Transmit_For_EEPROM_Buffer,pBuffer,u8Length);
DMA2_Stream6->NDTR = u8Length; //发送数量为0
DMA2_Stream6->CR |= 1 << 0; //复位CR寄存器
t_u16WaitTime = 0;
while(DMA2_Stream6->NDTR != 0)//等待DMA发送完成
{
/*在这里实现超时判断,有用操作系统的,可以用操作系统的延时函数*/
}
t_u16WaitTime = 0;
I2C2->ICR |= 1 << 5; //清除停止位产生标志
DMA2->HIFCR |= 0x3D << 16;
return 1;
}
以上是向EEPROM写入数据的驱动,接下来则是实现从EEPROM读取数据的接收驱动
/*******************************************************************************
** 函数名称:IIC_DMA_Receive_For_EEPROM_Driver
** 函数作用:IIC从EEPROM接收数据驱动
** 输入参数:u32EEPROM_Address------------访问的EEPROM地址
** u8Length---------------------写入EEPROM的数据长度
** pBuffer----------------------写入EEPROM的数据存储指针
** 输出参数:0------------------失败
** 1------------------成功
** 使用样例:无
** 函数备注:无
*******************************************************************************/
uint8_t IIC_DMA_Receive_For_EEPROM_Driver(uint32_t u32EEPROM_Address,uint8_t u8Length,uint8_t * pBuffer)
{
static uint8_t * pAllocateMemory;
uint16_t t_u16WaitTime = 0;
I2C2->ICR |= 1 << 4; //清楚否应答标志
I2C2->ICR |= 1 << 5; //清除停止位产生标志
I2C2->CR2 &= ~(1 << 10); //写
I2C2->CR2 &= ~(0xFF << 16); //2个字节待传输
I2C2->CR2 |=2 << 16; //2个字节待传输
I2C2->CR2 &= ~(1 << 24); //重载模式
I2C2->CR2 &= ~(1 << 25); //不发送停止位,延迟SCL
I2C2->CR2 |= 1 << 13; //产生起始位+地址
t_u16WaitTime = 0;
while((I2C2->ISR & 1 << 1) != (1 << 1))
{
//等待IIC发送完成,这里没有用DMA发送,这里需自己实现超时退出
}
I2C2->TXDR = (uint8_t)(u32EEPROM_Address >> 8);;
t_u16WaitTime = 0;
while((I2C2->ISR & 1 << 1) != (1 << 1))
{
//等待IIC发送完成,这里没有用DMA发送,这里需自己实现超时退出
}
I2C2->TXDR = (uint8_t)u32EEPROM_Address;
t_u16WaitTime = 0;
while((I2C2->ISR & 1 << 6) != (1 << 6))
{
//等待IIC发送完成,这里没有用DMA发送,这里需自己实现超时退出
}
pAllocateMemory = malloc(u8Length);//动态分配DMA接收缓存
memset(pAllocateMemory,0,u8Length);
DMA2_Stream7->CR &= ~(1 << 0);
t_u16WaitTime = 0;
while(DMA2_Stream7->CR&0X01)
{
//等待DMA可被配置,这里没有用DMA发送,这里需自己实现超时退出
}
DMA2->HIFCR |= 0x3D << 22;
DMA2_Stream7->M0AR = (uint32_t)pAllocateMemory;
DMA2_Stream7->NDTR = u8Length;
DMA2_Stream7->CR |= 1 << 0;
I2C2->CR2 |= (1 << 10); //EEPROM读指令
I2C2->CR2 &= ~(0xFF << 16); //清空NBYTES位
I2C2->CR2 |= u8Length << 16; //127个字节待读取
I2C2->CR2 |= 1 << 13; //产生起始位+地址
I2C2->CR2 |= (1 << 25); //AutoEnd
t_u16WaitTime = 0;
while(DMA2_Stream7->NDTR != 0)
{
//等待DMA接收完成
}
SCB_CleanInvalidateDCache();
memcpy(pBuffer,pAllocateMemory,u8Length);
free(pAllocateMemory);
DMA2->HIFCR |= 0x3D << 22;
return 1;
}
由于向EEPROM写入数据时,当发送停止位后,EEPROM才真正开始将缓存里的数据写入EEPROM的真实地址,所以在这里加一个查询EEPROM是否完成真正从EEPROM写入数据的查询判断
uint8_t IIC_Wait_EEPROM_Write_Into_Memory(void)
{
uint16_t t_u16WaitTime = 0;
static uint8_t u8OperateStep = 0;
static uint8_t u8Counters = 0;
while(1)
{
switch(u8OperateStep)
{
case 0:
{
I2C2->ICR |= 1 << 4;
I2C2->ICR |= 1 << 5; //清除停止位产生标志
I2C2->CR2 &= ~(1 << 10); //EEPROM写指令
I2C2->CR2 &= ~(0xFF << 16); //0个字节待传输,发送要写入的EEPROM地址
I2C2->CR2 &= ~(1 << 24); //重载模式
I2C2->CR2 &= ~(1 << 25); //不生成停止位
I2C2->CR2 |= 1 << 13; //产生起始位+地址
u8OperateStep = 1;
break;
}
case 1:
{
if((I2C2->ISR & (1 << 4)) != (1 << 4))//接收到应答,允许继续向EEPROM操作
{
u8Counters = 0;
u8OperateStep = 0;
return 1;
}
else
{
if(++u8Counters > 60)
{
u8Counters = 0;
u8OperateStep = 0;
return 0;
}
else
{
I2C2->ICR |= 1 << 4;
I2C2->ICR |= 1 << 5; //清除停止位产生标志
I2C2->CR2 &= ~(1 << 10); //EEPROM写指令
I2C2->CR2 &= ~(0xFF << 16);//0个字节待传输,发送要写入的EEPROM地址
I2C2->CR2 &= ~(1 << 24); //重载模式
I2C2->CR2 &= ~(1 << 25); //不生成停止位
I2C2->CR2 |= 1 << 13; //产生起始位+地址
}
}
break;
}
default:
{
break;
}
}
//在这里可以增加操作系统的延时函数
}
}
以上便是STM32H7使用硬件IIC实现对EEPROM的读写操作,并使用查询法查询EEPRO是否完成真正的数据写入