赞
踩
欢迎来到我的博客。今天我想向大家介绍一下STM32软件I2C功能。
首先,让我们来了解一下I2C(Inter-Integrated Circuit)总线。I2C是一种串行通信总线,最初由Philips公司开发。它允许多个设备使用同一条总线进行通信,并且每个设备都有唯一的地址。I2C通常用于连接微控制器、传感器和其他外设。
在STM32中,I2C总线被实现为硬件和软件两种方式。硬件I2C功能可以直接使用STM32芯片上的I2C外设,而软件I2C需要通过编程实现。由于某些应用场景不适宜使用硬件I2C功能,所以软件I2C在STM32中也变得非常重要。
STM32软件I2C功能与硬件I2C功能类似,它们之间的主要区别在于数据传输的过程。软件I2C需要使用GPIO口模拟I2C通信过程,因此实现起来相对复杂。但是软件I2C具有很高的灵活性,可以根据需要进行修改和扩展。
在STM32中,软件I2C驱动程序通常由以下几个部分组成:
初始化:这一步包括配置GPIO口、设置时序等操作,以确保I2C通信正常进行。
启动:启动信号是I2C总线上的一个信号,用于指示传输开始。为了在软件I2C中实现“启动”信号,我们需要将SDA(数据线)从高电平拉到低电平,然后将SCL(时钟线)从高电平拉到低电平。
停止:停止信号用于指示传输结束。在软件I2C中,我们需要将SCL从低电平拉到高电平,然后将SDA从低电平拉到高电平。
数据传输:数据传输通过向SDA写入位来完成。在传输数据之前,我们需要向SCL写入一个脉冲来获取ACK(应答)信号,以确保数据已被正确接收。
虽然软件I2C比硬件I2C更加复杂,但它具有很高的灵活性和可扩展性。此外,在某些情况下,软件I2C可以提供更好的性能和功耗优化。
下面上代码。根据野火例程修改而来,已验证。
/** ****************************************************************************** * @file bsp_i2c_ee.c * @version V1.0 * @date 2023-4-12 * @brief 用gpio模拟i2c总线, 适用于STM32系列CPU。该模块不包括应用层命令帧,仅包括I2C总线基本操作函数。 ****************************************************************************** #include "bsp_i2c_gpio.h" #include "stm32f4xx.h" #include <stdio.h> /* ********************************************************************************************************* * 函 数 名: i2c_Delay * 功能说明: I2C总线位延迟,最快400KHz * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ static void i2c_Delay(void) { uint8_t i; /* 下面的时间是通过逻辑分析仪测试得到的。 工作条件:CPU主频72MHz ,MDK编译环境,1级优化 循环次数为10时,SCL频率 = 205KHz 循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us 循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us */ for (i = 0; i < 10; i++) ; } /* ********************************************************************************************************* * 函 数 名: i2c_Start * 功能说明: CPU发起I2C总线启动信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_Start(void) { /* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */ BSP_I2C_SDA_1(); BSP_I2C_SCL_1(); i2c_Delay(); BSP_I2C_SDA_0(); i2c_Delay(); BSP_I2C_SCL_0(); i2c_Delay(); } /* ********************************************************************************************************* * 函 数 名: i2c_Stop * 功能说明: CPU发起I2C总线停止信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_Stop(void) { /* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */ BSP_I2C_SDA_0(); BSP_I2C_SCL_1(); i2c_Delay(); BSP_I2C_SDA_1(); } /* ********************************************************************************************************* * 函 数 名: i2c_SendByte * 功能说明: CPU向I2C总线设备发送8bit数据 * 形 参:_ucByte : 等待发送的字节 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_SendByte(uint8_t _ucByte) { uint8_t i; /* 先发送字节的高位bit7 */ for (i = 0; i < 8; i++) { if (_ucByte & 0x80) { BSP_I2C_SDA_1(); } else { BSP_I2C_SDA_0(); } i2c_Delay(); BSP_I2C_SCL_1(); i2c_Delay(); BSP_I2C_SCL_0(); if (i == 7) { BSP_I2C_SDA_1(); // 释放总线 } _ucByte <<= 1; /* 左移一个bit */ i2c_Delay(); } } /* ********************************************************************************************************* * 函 数 名: i2c_ReadByte * 功能说明: CPU从I2C总线设备读取8bit数据 * 形 参:无 * 返 回 值: 读到的数据 ********************************************************************************************************* */ uint8_t i2c_ReadByte(void) { uint8_t i; uint8_t value; /* 读到第1个bit为数据的bit7 */ value = 0; for (i = 0; i < 8; i++) { value <<= 1; BSP_I2C_SCL_1(); i2c_Delay(); if (BSP_I2C_SDA_READ()) { value++; } BSP_I2C_SCL_0(); i2c_Delay(); } return value; } /* ********************************************************************************************************* * 函 数 名: i2c_WaitAck * 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号 * 形 参:无 * 返 回 值: 返回0表示正确应答,1表示无器件响应 ********************************************************************************************************* */ uint8_t i2c_WaitAck(void) { uint8_t re; BSP_I2C_SDA_1(); /* CPU释放SDA总线 */ i2c_Delay(); BSP_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */ i2c_Delay(); if (BSP_I2C_SDA_READ()) /* CPU读取SDA口线状态 */ { re = 1; } else { re = 0; } BSP_I2C_SCL_0(); i2c_Delay(); return re; } /* ********************************************************************************************************* * 函 数 名: i2c_Ack * 功能说明: CPU产生一个ACK信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_Ack(void) { BSP_I2C_SDA_0(); /* CPU驱动SDA = 0 */ i2c_Delay(); BSP_I2C_SCL_1(); /* CPU产生1个时钟 */ i2c_Delay(); BSP_I2C_SCL_0(); i2c_Delay(); BSP_I2C_SDA_1(); /* CPU释放SDA总线 */ } /* ********************************************************************************************************* * 函 数 名: i2c_NAck * 功能说明: CPU产生1个NACK信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_NAck(void) { BSP_I2C_SDA_1(); /* CPU驱动SDA = 1 */ i2c_Delay(); BSP_I2C_SCL_1(); /* CPU产生1个时钟 */ i2c_Delay(); BSP_I2C_SCL_0(); i2c_Delay(); } /* ********************************************************************************************************* * 函 数 名: i2c_CfgGpio * 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_CfgGpio(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; I2Cx_SCL_GPIO_CLK_ENABLE(); I2Cx_SDA_GPIO_CLK_ENABLE(); /**I2C2 GPIO Configuration PB10 ------> I2C2_SCL PB9 ------> I2C2_SDA */ GPIO_InitStruct.Pin = BSP_I2C_SCL_PIN | BSP_I2C_SDA_PIN; ; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(BSP_GPIO_PORT_I2C, &GPIO_InitStruct); /* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */ i2c_Stop(); }
#ifndef _BSP_I2C_GPIO_H #define _BSP_I2C_GPIO_H #include <inttypes.h> #define BSP_I2C_WR 0 /* 写控制bit */ #define BSP_I2C_RD 1 /* 读控制bit */ /* 定义I2C总线连接的GPIO端口时钟控制 */ #define I2Cx_SDA_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define I2Cx_SCL_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() /* 定义I2C总线连接的GPIO端口, 用户只需要修改下面3行代码即可任意改变SCL和SDA的引脚 */ #define BSP_GPIO_PORT_I2C GPIOB /* GPIO端口 */ #define BSP_I2C_SCL_PIN GPIO_PIN_8 /* 连接到SCL时钟线的GPIO */ #define BSP_I2C_SDA_PIN GPIO_PIN_9 /* 连接到SDA数据线的GPIO */ /* 定义读写SCL和SDA的宏,已增加代码的可移植性和可阅读性 */ #if 0 /* 条件编译: 1 选择GPIO的库函数实现IO读写 */ #define BSP_I2C_SCL_1() digitalH(BSP_GPIO_PORT_I2C, BSP_I2C_SCL_PIN) /* SCL = 1 */ #define BSP_I2C_SCL_0() digitalL(BSP_GPIO_PORT_I2C, BSP_I2C_SCL_PIN) /* SCL = 0 */ #define BSP_I2C_SDA_1() digitalH(BSP_GPIO_PORT_I2C, BSP_I2C_SDA_PIN) /* SDA = 1 */ #define BSP_I2C_SDA_0() digitalL(BSP_GPIO_PORT_I2C, BSP_I2C_SDA_PIN) /* SDA = 0 */ //#define BSP_I2C_SDA_READ() GPIO_ReadInputDataBit(BSP_GPIO_PORT_I2C, BSP_I2C_SDA_PIN) /* 读SDA口线状态 */ #define BSP_I2C_SDA_READ() ((BSP_GPIO_PORT_I2C->IDR & BSP_I2C_SDA_PIN) != 0) /* 读SDA口线状态 */ #else /* 这个分支选择直接寄存器操作实现IO读写 */ /* 注意:如下写法,在IAR最高级别优化时,会被编译器错误优化 */ #define BSP_I2C_SCL_1() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SCL_PIN /* SCL = 1 */ #define BSP_I2C_SCL_0() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SCL_PIN << 16U /* SCL = 0 */ #define BSP_I2C_SDA_1() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SDA_PIN /* SDA = 1 */ #define BSP_I2C_SDA_0() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SDA_PIN << 16U /* SDA = 0 */ #define BSP_I2C_SDA_READ() ((BSP_GPIO_PORT_I2C->IDR & BSP_I2C_SDA_PIN) != 0) /* 读SDA口线状态 */ #endif /* 直接操作寄存器的方法控制IO */ #define digitalH(p, i) \ { \ p->BSRR = i; \ } // 设置为高电平 #define digitalL(p, i) \ { \ p->BSRR = (uint32_t)i << 16; \ } // 输出低电平 void i2c_CfgGpio(void); void i2c_Start(void); void i2c_Stop(void); void i2c_SendByte(uint8_t _ucByte); uint8_t i2c_ReadByte(void); uint8_t i2c_WaitAck(void); void i2c_Ack(void); void i2c_NAck(void); #endif
最后,不要忘记在主程序中调用 i2c_CfgGpio(); 完成用于模拟I2C的GPIO初始化。
这里我使用的是lis2dw12加速度传感器,在数据手册中给出了I2C通信时序如下。

Master:主机
Slave:从机
ST:起始信号 START signal
SAD:从机地址 Slave Address
SAK:从机应答 slave acknowledge
DATA :8位的数据内容
SP:停止信号 STOP signal
NMAK :非主机应答 No Master Acknowledge
SUB:8位的子地址 8-bit sub-address
W :读操作
R:写操作
软件模拟这个流程就能实现通讯,对照时序图和程序的每一个步骤阅读,方便理解。
使用前记得包含头文件
#include "bsp_i2c_gpio.h"

/* * @brief Read generic device register (platform dependent) * * @param handle customizable argument. In this examples is used in * order to select the correct sensor bus handler. * @param reg register to read * @param bufp pointer to buffer that store the data read * @param len number of consecutive register to read * */ static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len) { uint16_t i; /* 第1步:发起I2C总线启动信号 */ i2c_Start(); /* 第2步:发送控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ i2c_SendByte(BSP_I2C_ADD | BSP_I2C_WR); /* 写指令 */ /* 第3步:等待ACK */ if (i2c_WaitAck() != 0) { goto cmd_fail; } /* 第4步: 发送SUB */ i2c_SendByte(reg); /* 第5步: 等待ACK */ if (i2c_WaitAck() != 0) { goto cmd_fail; } /* 第6步: 发送SR (repeated START) */ i2c_Start(); /* 第7步: 发送控制字节 */ i2c_SendByte(BSP_I2C_ADD | BSP_I2C_RD); /* 读指令 */ /* 第8步: 发送ACK */ if (i2c_WaitAck() != 0) { goto cmd_fail; } /* 第9步: 循环读取数据 */ for (i = 0; i < len; i++) { bufp[i] = i2c_ReadByte(); /* 读1个字节 */ /* 每读完1个字节后,需要主机发送ACK,最后一个字节发送NACK */ if (i != len - 1) { i2c_Ack(); /* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */ } else { i2c_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ } } /* 第10步:发送停止信号 */ i2c_Stop(); return 0; cmd_fail: i2c_Stop(); return 1; }

/* * @brief Write generic device register (platform dependent) * * @param handle customizable argument. In this examples is used in * order to select the correct sensor bus handler. * @param reg register to write * @param bufp pointer to buffer that store the data read * @param len number of consecutive register to read * */ static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t len) { uint16_t i; /* 第0步: 发送停止信号 */ i2c_Stop(); /* 第1步: 发起I2C总线启动信号 */ i2c_Start(); /* 第2步: 发送控制字节 */ i2c_SendByte(BSP_I2C_ADD | BSP_I2C_WR); /* 第3步: 等待ACK */ if (i2c_WaitAck() != 0) { goto cmd_fail; } /* 第4步: 发送SUB */ i2c_SendByte(reg); /* 第5步: 等待ACK */ if (i2c_WaitAck() != 0) { goto cmd_fail; } /* 第6步: 循环发送DATA */ for (i = 0; i < len; i++) { i2c_SendByte(bufp[i]); /* 发一个数据 */ i2c_Ack(); /*发完一个数据后等待ACK*/ } /* 第7步: 发送停止信号 */ i2c_Stop(); return 0; cmd_fail: i2c_Stop(); return 1; }
总而言之,STM32软件I2C是一种非常重要的通信方式,尤其适用于那些不适合使用硬件I2C的应用场景。希望本文对你了解STM32软件I2C功能有所帮助,如果有不理解的地方欢迎私信留言。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。