一、应用场景
??????? I2C在单片机开发中应用特别广泛,比如EEPROM中AT24CXX芯片使用的就是I2C总线。在STM32中,HAL库提供了I2C底层驱动,但小编在使用中用不了,不知是哪的问题。所以还是自己写底层时序吧,这种引脚使用上也更加的灵活。
二、原理图
(1)这里以AT24C128芯片和STM32F103RET6为例。
(2)EEPROM部分原理图
上图中A0、A1和A2为地址引脚;
WP为写保护引脚;
EEPROM_SCL为时钟引脚;
EEPROM_SDA为数据引脚。
?(3)STM32F103RET6部分电路图
?WP引脚接PB5引脚;EEPROM_SCL接PB6引脚;EEPROM_SDA接PB7引脚。
三、STM32CubeMX配置
1、时钟配置。略
2、使用定时器完成延时uS
一、STM32用HAL库实现uS级延时_朱嘉鼎的博客-CSDN博客
四、KeilMDK中代码的编写
1、新建drv_i2c.c文件和drv_i2c.h文件。
2、drv_i2c.h代码
#ifndef __DRV_I2C_H__
#define __DRV_I2C_H__
/*头文件包含*/
#include "tim.h"
#include "drv_gpio.h"
/*置位与清零SCL管脚*/
#define __AT24CXX_I2C_SCL_H() HAL_GPIO_WritePin(EP_SCL_GPIO_Port,EP_SCL_Pin,GPIO_PIN_SET)
#define __AT24CXX_I2C_SCL_L() HAL_GPIO_WritePin(EP_SCL_GPIO_Port,EP_SCL_Pin,GPIO_PIN_RESET)
/*置位与清零SDA管脚*/
#define __AT24CXX_I2C_SDA_H() HAL_GPIO_WritePin(EP_SDA_GPIO_Port,EP_SDA_Pin,GPIO_PIN_SET)
#define __AT24CXX_I2C_SDA_L() HAL_GPIO_WritePin(EP_SDA_GPIO_Port,EP_SDA_Pin,GPIO_PIN_RESET)
/*读取SDA管脚状态*/
#define __AT24CXX_READ_I2C_SDA() HAL_GPIO_ReadPin(EP_SDA_GPIO_Port,EP_SDA_Pin)
/*设置SDA管脚为输出模式*/
#define __AT24CXX_I2C_SDA_OUT() SDA_OutputMode()
/*设置SDA管脚为输入模式*/
#define __AT24CXX_I2C_SDA_INPUT() SDA_InputMode()
/*I2C通信时的应答信号*/
typedef enum
{
ACK = GPIO_PIN_RESET,
NACK = GPIO_PIN_SET
}I2C_ACK_TypeDef;
typedef enum
{
DEVICE_AT24CXX = 0x00U
}I2C_DeviceTypeDef;
/*函数声明*/
void I2C_Delay_us(uint16_t t);
void I2C_Start(I2C_DeviceTypeDef Device); /*I2C开始*/
void I2C_Stop(I2C_DeviceTypeDef Device); /*I2C停止*/
I2C_ACK_TypeDef I2C_Write_Byte(I2C_DeviceTypeDef Device, uint8_t Byte); /*I2C写一个字节*/
uint8_t I2C_Read_Byte(I2C_DeviceTypeDef Device, I2C_ACK_TypeDef AckValue); /*I2C读一个字节*/
#endif
3、drv_i2c.c代码
#include "drv_i2c.h"
/*
*功能:定时器I2C延时
*参数:延时的us数,us 范围-0~65535us
*注意:使用定时器2产生延时
*返回值:无
*/
void I2C_Delay_us(uint16_t t)
{
uint16_t counter = 0;
__HAL_TIM_SET_AUTORELOAD(&htim2, t); // 设置定时器自动加载值,到该值后重新计数
__HAL_TIM_SET_COUNTER(&htim2, 0); // 设置定时器初始值
HAL_TIM_Base_Start(&htim2); // 启动定时器
while(counter != t) // 直到定时器计数从 0 计数到 us 结束循环,刚好 us
{
counter = __HAL_TIM_GET_COUNTER(&htim2); // 获取定时器当前计数
}
HAL_TIM_Base_Stop(&htim2); // 停止定时器
}
/*
*功能:设置SDA管脚为输出模式
*参数:无
*返回值:无
*/
void SDA_OutputMode(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = EP_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /*输出模式*/
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/*
*功能:设置SDA管脚为输入模式
*参数:无
*返回值:无
*/
void SDA_InputMode(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = EP_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /*输入模式*/
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/*
*功能:SDA和SCL引脚电平初始化
*参数:无
*返回值:无
*测试:OK
*/
static void AT24CXX_I2C_Init(void)
{
__AT24CXX_I2C_SCL_H();
__AT24CXX_I2C_SDA_H();
}
/*
*功能:I2C总线起始信号时序
*参数:无
*返回值:无
*测试:OK
*/
static void AT24CXX_I2C_Start(void)
{
__AT24CXX_I2C_SDA_OUT(); /*SDA配置为输出模式*/
/*SCL为高电平,SDA的下降沿为I2C起始信号*/
__AT24CXX_I2C_SDA_H();
__AT24CXX_I2C_SCL_H();
I2C_Delay_us(1);
__AT24CXX_I2C_SDA_L();
I2C_Delay_us(10);
__AT24CXX_I2C_SCL_L();
I2C_Delay_us(1);
}
/*
*功能:I2C总线停止信号时序
*参数:无
*返回值:无
*测试:OK
*/
static void AT24CXX_I2C_Stop(void)
{
__AT24CXX_I2C_SDA_OUT();
/*SCL为高电平,SDA的上升沿为I2C停止信号*/
__AT24CXX_I2C_SDA_L();
__AT24CXX_I2C_SCL_H();
I2C_Delay_us(1);
I2C_Delay_us(10);
__AT24CXX_I2C_SDA_H();
}
/*
*功能:I2C总线写字节时序
*参数:Byte:写入的内容
*返回值:I2C_ACK_TypeDef:应答信号是高电平还是低电平
*测试:OK
*/
static I2C_ACK_TypeDef AT24CXX_I2C_Write_Byte(uint8_t Byte)
{
I2C_ACK_TypeDef ack_value;
__AT24CXX_I2C_SDA_OUT(); /*SDA配置为输出模式*/
/*SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据*/
for (uint8_t i = 0; i < 8; i++)
{
/*SCL清零,主机SDA准备数据*/
__AT24CXX_I2C_SCL_L();
I2C_Delay_us(1);
if((Byte & (1 << 7)) == (1 << 7))
{
__AT24CXX_I2C_SDA_H();
}
else
{
__AT24CXX_I2C_SDA_L();
}
I2C_Delay_us(1);
/*SCL置高,传输数据*/
__AT24CXX_I2C_SCL_H();
I2C_Delay_us(10);
/*准备发送下一比特位*/
Byte <<= 1;
}
__AT24CXX_I2C_SCL_L();
__AT24CXX_I2C_SDA_INPUT();
/*释放SDA,等待从机应答*/
__AT24CXX_I2C_SDA_H();
I2C_Delay_us(1);
__AT24CXX_I2C_SCL_H();
I2C_Delay_us(10);
ack_value = (I2C_ACK_TypeDef)__AT24CXX_READ_I2C_SDA();
__AT24CXX_I2C_SCL_L();
I2C_Delay_us(1);
/*返回从机的应答信号*/
return ack_value;
}
/*
*功能:I2C总线读字节时序
*参数:
*返回值:读取到的数据
*测试:OK
*/
static uint8_t AT24CXX_I2C_Read_Byte(I2C_ACK_TypeDef AckValue)
{
uint8_t byte = 0;
__AT24CXX_I2C_SDA_INPUT();
/*SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据*/
for (uint8_t i = 0; i < 8; i++)
{
/*准备接收下一比特位*/
byte <<= 1;
/*SCL清零,从机SDA准备数据*/
__AT24CXX_I2C_SCL_L();
I2C_Delay_us(10);
/*SCL置高,获取数据*/
__AT24CXX_I2C_SCL_H();
I2C_Delay_us(10);
byte |= __AT24CXX_READ_I2C_SDA();
}
/*SCL清零,主机准备应答信号*/
__AT24CXX_I2C_SCL_L();
I2C_Delay_us(1);
__AT24CXX_I2C_SDA_OUT();
/*主机发送应答信号*/
if(AckValue == ACK)
{
__AT24CXX_I2C_SDA_L();
}
else
{
__AT24CXX_I2C_SDA_H();
}
I2C_Delay_us(1);
__AT24CXX_I2C_SCL_H();
I2C_Delay_us(10);
/*SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号*/
__AT24CXX_I2C_SCL_L();
__AT24CXX_I2C_SDA_H();
I2C_Delay_us(1);
/*返回数据*/
return byte;
}
/*
*功能:I2C初始化
*测试:OK
*/
void I2C_Init(I2C_DeviceTypeDef Device)
{
switch(Device)
{
case DEVICE_AT24CXX: AT24CXX_I2C_Init(); break;
default : break;
}
}
/*
*功能:I2C开始信号,针对不同型号设备的封装
*测试:OK
*/
void I2C_Start(I2C_DeviceTypeDef Device)
{
switch(Device)
{
case DEVICE_AT24CXX: AT24CXX_I2C_Start(); break;
default : break;
}
}
/*
*功能:I2C停止信号,针对不同型号设备的封装
*测试:OK
*/
void I2C_Stop(I2C_DeviceTypeDef Device)
{
switch(Device)
{
case DEVICE_AT24CXX: AT24CXX_I2C_Stop(); break;
default : break;
}
}
/*
*功能:I2C写一个字节
*参数:Device:写入的设备
* Byte: 需要写入的内容
*返回值:应答信号,正确应答信号是将SDA拉低
*/
I2C_ACK_TypeDef I2C_Write_Byte(I2C_DeviceTypeDef Device, uint8_t Byte)
{
switch(Device)
{
case DEVICE_AT24CXX: return AT24CXX_I2C_Write_Byte(Byte); break;
default : return NACK; break;
}
}
/*
*功能:I2C读一个字节
*参数:Device:读出的设备
* AckValue: 主机发送的应答信号
*返回值:读到的内容
*测试:OK
*/
uint8_t I2C_Read_Byte(I2C_DeviceTypeDef Device, I2C_ACK_TypeDef AckValue)
{
switch(Device)
{
case DEVICE_AT24CXX: return AT24CXX_I2C_Read_Byte(AckValue);
default : return 0;
}
}
3、mai.c文件中测试代码
(1)包含头文件
#include "drv_uart.h"
#include "drv_at24cxx.h"
(2)main函数中while中代码
/*Test*/
printf("Hello World! \r\n");
HAL_Delay(500);
__EEPROM_UNLOCK();
AT24CXX_WriteOneByte(0X00, 88);
__EEPROM_LOCK();
HAL_Delay(500);
printf("AT24CXX Test: %d\r\n", AT24CXX_ReadOneByte(0X00));
(3)下载程序后,串口打印信息如下
---------------------------------------------------------------------------------------------------------------------------------如有不足,请忽略。
|