文章内容根据野火学习教程进行整理,仅仅是学习记录。
开发板: 野火STM32F429-挑战者V2
官方固件库版本: STM32F4xx_DSP_StdPeriph_Lib_V1.8.0
这里以与EEPROM进行通讯的代码作为例子。
从电路图上主要是要知道3点关键信息
从电路图上可以看出:
这个一般硬件工程师都会在电路图上标出来吧。
另外如果只标出了使用的GPIO引脚,也可以通过 《STM32F4xx中文数据手册》 查找相关GPIO的复用得知所挂的I2C总线。
查看EPPROM的参考手册可以得知设备地址是由1010 A2A1A0一共位组合而成,而从电路图可以得知A2=0、A1=0、A0=0,所以最终的I2C地址位101 0000(0x50)。
其实在I2C总线上只要设备地址是唯一的就可以了。
由于是使用I2C对EEPROM进行读写,所以编码主要是分为I2C配置和对EEPROM读写两个部分。
根据电路连接情况就可以知道GPIO引脚以及哪个I2C总线啦。
#define I2C1_OwnAddress 0X0A /* STM32的I2C1设备自身地址,自定义,与其他I2C设备地址不同即可 */
#define I2C1_Speed 400000 /* I2C1设备速率400KHz */
#define I2C1_GPIO_PORT GPIOB /* I2C1所接GPIO的端口 */
#define I2C1_SCL_PIN GPIO_Pin_6 /* I2C1的SCL所接的GPIO引脚 */
#define I2C1_SDA_PIN GPIO_Pin_7 /* I2C1的SDA所接的GPIO引脚 */
#define I2C1_SCL_SOURCE GPIO_PinSource6 /* I2C1的SCL所接的GPIO引脚序号 */
#define I2C1_SDA_SOURCE GPIO_PinSource7 /* I2C1的SDA所接的GPIO引脚序号 */
#define I2C1_GPIO_AF GPIO_AF_I2C1 /* I2C1的SDA、SCL的GPIO引脚复用功能 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /* 初始化GPIO端口时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); /* 初始化I2C1的外围时钟 */
不管是使用GPIO还是I2C,相应的总线时钟都是要使能的。
/**************************************************************************************************
** 函数名称: gpio_cfg
** 功能描述: I2C的GPIO配置
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
static void gpio_cfg(void)
{
GPIO_InitTypeDef GPIO_def;
GPIO_def.GPIO_Pin = I2C1_SCL_PIN | I2C1_SDA_PIN; /* 要配置的GPIO的PIN脚 */
GPIO_def.GPIO_Mode = GPIO_Mode_AF; /* 要配置的GPIO模式(复用) */
GPIO_def.GPIO_Speed = GPIO_Speed_50MHz; /* 要配置的GPIO速率(50MHz) */
GPIO_def.GPIO_OType = GPIO_OType_OD; /* 输出类型(开漏) */
GPIO_def.GPIO_PuPd = GPIO_PuPd_UP; /* 引脚默认状态(上拉) */
GPIO_Init(I2C1_GPIO_PORT, &GPIO_def);
GPIO_PinAFConfig(I2C1_GPIO_PORT, I2C1_SCL_SOURCE, I2C1_GPIO_AF); /* 配置复用类型 */
GPIO_PinAFConfig(I2C1_GPIO_PORT, I2C1_SDA_SOURCE, I2C1_GPIO_AF); /* 配置复用类型 */
}
/**************************************************************************************************
** 函数名称: i2c_cfg
** 功能描述: I2C配置
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
static void i2c_cfg(void)
{
I2C_InitTypeDef I2C_def;
I2C_def.I2C_Mode = I2C_Mode_I2C; /* I2C模式选择 */
I2C_def.I2C_DutyCycle = I2C_DutyCycle_2; /* 低电平时间:高电平时间 = 2:1 */
I2C_def.I2C_OwnAddress1 = I2C1_OwnAddress; /* I2C设备地址 */
I2C_def.I2C_Ack = I2C_Ack_Enable; /* 使能应答 */
I2C_def.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /* I2C的寻址模式 */
I2C_def.I2C_ClockSpeed = I2C1_Speed; /* 通信速率 */
I2C_Init(I2C1, &I2C_def); /* I2C初始化 */
I2C_Cmd(I2C1, ENABLE); /* 使能I2C */
I2C_AcknowledgeConfig(I2C1, ENABLE); /* 使能指定I2C总线的应答功能 */
}
#define EE_I2C I2C1 /* 所挂载的I2C总线 */
#define EE_I2C_ADDR 0XA0 /* EEPROM的I2C设备地址 1010000(0x50), (0x50 << 1)*/
#define EE_SHORT_TIMEOUT 0x1000 /* I2C检测短等待超时时间 */
#define EE_LONG_TIMEOUT 0xA000 /* I2C检测长等待超时时间 */
#define EE_WAIT_TIMES 300 /* I2C检测设备状态最大次数 */
#define TEST_ADDR 0x30 /* 要写入数据的EEPROM地址 */
#define TEST_DATA 0x55 /* 要写入的数据 */
在I2C通讯中根据协议会需要检测EV5、EV6、EV7、EV8、EV9等事件。
我自己把检测I2C事件函数封装了一下,加入了超时跳出的机制,方便调用。
/**************************************************************************************************
** 函数名称: i2c_check_event
** 功能描述: 检测I2C设备的事件
** 输入参数: 无
** 输出参数: 无
** 返回参数: 检测成功返回0,失败返回-1
**************************************************************************************************/
static s32 i2c_check_event(u32 i2c_event)
{
__IO u32 timeout;
timeout = EE_SHORT_TIMEOUT;
while(SUCCESS != I2C_CheckEvent(EE_I2C, i2c_event)) {
timeout--;
if (timeout == 0) {
return -1;
}
}
return 0;
}
在I2C通讯中会有需要检测寄存器标志位状态的时候。
我把检测标志位的函数封装了一下,加入了期望值匹配状态以及超时机制,也是为了方便使用。
/**************************************************************************************************
** 函数名称: i2c_check_flag
** 功能描述: 检测I2C设备的标志
** 输入参数: i2c_flag:要检测的标志
** expect:期望的状态,RESET或者SET
** 输出参数: 无
** 返回参数: 匹配返回0,不匹配返回-1
**************************************************************************************************/
static s32 i2c_check_flag(u32 i2c_flag, FlagStatus expect)
{
__IO u32 timeout;
timeout = EE_LONG_TIMEOUT;
while(I2C_GetFlagStatus(EE_I2C, i2c_flag) != expect) {
timeout--;
if (timeout == 0) {
return -1;
}
}
return 0;
}
在I2C通讯过程当中,如果从设备内部正在紧张地处理着一些事情,比如果读写啥的是没有空理会I2C总线上主机设备发送的命令的。所以要保证I2C从设备能够正常的接收到命令有时候就需要去探寻一下从设备是不是有空了。
我把这个探寻机制封装了一下。
大致流程如下:
/**************************************************************************************************
** 函数名称: i2c_wait_standbystate
** 功能描述: 检测指定的I2C设备处于待命状态
** 输入参数: addr:要检测的I2C设备的地址
** 输出参数: 无
** 返回参数: 等待成功返回0,失败返回-1
**************************************************************************************************/
static s32 i2c_wait_standbystate(u8 addr)
{
__IO u16 tmpSR1; /* SR1寄存器的值 */
__IO u32 timeout; /* 超时计数器 */
__IO u32 trials; /* 等待次数计数器 */
timeout = 0;
if (0 != i2c_check_flag(I2C_FLAG_BUSY, RESET)) {
printf("%s, i2c is busy !\r\n", __FUNCTION__);
return -1;
}
while ((timeout++) < EE_LONG_TIMEOUT ) {
I2C_GenerateSTART(EE_I2C, ENABLE); /* 发送开始命令 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_MODE_SELECT)) { /* EV5 */
printf("%s, i2c start error !\r\n", __FUNCTION__);
return -1;
}
I2C_Send7bitAddress(EE_I2C, addr, I2C_Direction_Transmitter); /* 配置I2C从设备的地址 */
trials = 0;
do {
trials++;
tmpSR1 = I2C_ReadRegister(EE_I2C, I2C_Register_SR1); /* 获取当前SR1寄存器的值 */
} while (((tmpSR1 & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0) && (trials < EE_WAIT_TIMES));
if (tmpSR1 & I2C_SR1_ADDR) { /* 检查ADDR标志位是否被设置 */
I2C_ReadRegister(EE_I2C, I2C_Register_SR2); /* 清除ADDR标志,先读SR1再读SR2可以清除ADDR标志 */
I2C_GenerateSTOP(EE_I2C, ENABLE);
return 0;
}
if (tmpSR1 & I2C_SR1_AF) { /* 检查AF标志位是否被设置 */
I2C_ClearFlag(EE_I2C, I2C_FLAG_AF); /* 清除AF标志位 */
}
}
return -1;
}
大致流程如下:
/**************************************************************************************************
** 函数名称: i2c_ee_writebyte
** 功能描述: 写一个字节到I2C的EEPROM中
** 输入参数: dev_addr:I2C设备地址
** addr:要写入数据的地址
** data:要写入的数据
** 输出参数: 无
** 返回参数: 写入成功返回0,失败返回-1
**************************************************************************************************/
s32 i2c_ee_writebyte(u8 dev_addr, u8 addr, u8 data)
{
if (0 != i2c_check_flag(I2C_FLAG_BUSY, RESET)) {
printf("%s, i2c is busy !\r\n", __FUNCTION__);
return -1;
}
I2C_GenerateSTART(EE_I2C, ENABLE); /* 发送开始命令 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_MODE_SELECT)) { /* EV5 */
printf("%s, i2c start error !\r\n", __FUNCTION__);
return -1;
}
I2C_Send7bitAddress(EE_I2C, dev_addr, I2C_Direction_Transmitter); /* 配置EEPROM设备的地址,写方向 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { /* EV6 */
printf("%s, i2c send 7bit address error, write !\r\n", __FUNCTION__);
return -1;
}
I2C_SendData(EE_I2C, addr); /* 发送要写入的地址 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { /* EV8 */
printf("%s, i2c send a byte error !\r\n", __FUNCTION__);
return -1;
}
I2C_SendData(EE_I2C, data); /* 发送要写入的数据 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { /* EV8 */
printf("%s, i2c send a byte error !\r\n", __FUNCTION__);
return -1;
}
I2C_GenerateSTOP(EE_I2C, ENABLE); /* 发送停止位 */
return 0;
}
大致流程如下:
/**************************************************************************************************
** 函数名称: i2c_ee_readbyte
** 功能描述: 读一个字节
** 输入参数: dev_addr:I2C设备地址
** addr:要读取数据的地址
** 输出参数: 无
** 返回参数: 读取到的字节,默认为0
**************************************************************************************************/
u8 i2c_ee_readbyte(u8 dev_addr, u8 addr)
{
u8 value;
if (0 != i2c_check_flag(I2C_FLAG_BUSY, RESET)) {
printf("%s, i2c is busy !\r\n", __FUNCTION__);
return 0;
}
I2C_GenerateSTART(EE_I2C, ENABLE); /* 发送开始命令 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_MODE_SELECT)) { /* EV5 */
printf("%s, i2c start error !\r\n", __FUNCTION__);
return 0;
}
I2C_Send7bitAddress(EE_I2C, dev_addr, I2C_Direction_Transmitter); /* 配置EEPROM设备的地址,写方向 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { /* EV6 */
printf("%s, i2c send 7bit address error, write !\r\n", __FUNCTION__);
return 0;
}
I2C_SendData(EE_I2C, addr); /* 发送要读取的地址 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { /* EV8 */
printf("%s, i2c send a byte error !\r\n", __FUNCTION__);
return 0;
}
I2C_GenerateSTART(EE_I2C, ENABLE); /* 发送开始命令 */
if (0 != i2c_check_event(I2C_EVENT_MASTER_MODE_SELECT)) { /* EV5 */
printf("%s, i2c start error !\r\n", __FUNCTION__);
return 0;
}
I2C_Send7bitAddress(EE_I2C, dev_addr, I2C_Direction_Receiver);
if (0 != i2c_check_event(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { /* EV6 */
printf("%s, i2c send 7bit address error, read !\r\n", __FUNCTION__);
return 0;
}
if (0 != i2c_check_event(I2C_EVENT_MASTER_BYTE_RECEIVED)) { /* EV7 */
printf("%s, i2c read data error !\r\n", __FUNCTION__);
return 0;
}
value = I2C_ReceiveData(EE_I2C); /* 读取数据 */
I2C_AcknowledgeConfig(EE_I2C, DISABLE); /* 非应答 */
I2C_GenerateSTOP(EE_I2C, ENABLE); /* 发送停止位 */
return value;
}
大致流程如下:
/**************************************************************************************************
** 函数名称: bsp_eeprom_init
** 功能描述: EEPROM初始化
** 输入参数: 无
** 输出参数: 无
** 返回参数: 无
**************************************************************************************************/
void bsp_eeprom_init(void)
{
u32 value;
i2c_ee_writebyte(EE_I2C_ADDR, TEST_ADDR, TEST_DATA);
i2c_wait_standbystate(EE_I2C_ADDR);
value = i2c_ee_readbyte(EE_I2C_ADDR, TEST_ADDR);
printf("value = %x\r\n", value);
}