STM32 HAL库教程:I2C串行接口配置
电气特性
I2C(Inter-Integrated Circuit)是一种由飞利浦公司(现恩智浦半导体)开发的串行通信协议,用于连接低速外围设备。I2C总线只需要两根线(SDA:串行数据线,SCL:串行时钟线)就可以实现多个设备之间的数据交换。以下是I2C的主要电气特性:
-
两线接口:
- SDA(Serial Data Line):用于传输数据。(数据线)
- SCL(Serial Clock Line):用于同步数据传输。(时钟线)
-
多主从结构:
- I2C总线支持多个主设备和一个或多个从设备。在任何时刻,只有一个主设备控制总线。(不能同时存在多个主设备)
-
地址编码:
- 每个从设备都有唯一的7位或10位地址,主设备通过地址来选择要通信的从设备。
-
时钟同步:
- I2C总线上的所有设备都同步于SCL线上的时钟信号。时钟线由主设备控制。
-
数据传输速率:
- 标准模式下,时钟频率可达100 kHz。
- 快速模式下,时钟频率可达400 kHz。
- 高速模式下,时钟频率可达3.4 MHz。
-
起始和停止条件:
- 数据传输开始于起始条件,结束于停止条件。起始条件是SCL为高电平时,SDA由高电平向低电平跳变;停止条件是SCL为高电平时,SDA由低电平向高电平跳变。
-
数据有效性:
- 数据在SCL为高电平时保持稳定,在SCL为低电平时改变。
-
位的表示:
- I2C使用8位数据传输,每次传输可以是一个字节(8位),也可以是多个字节。
-
确认(ACK/NACK):
- 每个字节传输后,接收方会通过驱动SDA线至低电平来发送一个确认位(ACK),或者通过不驱动SDA线(保持高电平)来发送一个非确认位(NACK)。
协议
I²C写操作的详细步骤:
-
起始条件:主设备通过将SDA线从高电平拉到低电平,同时保持SCL为高电平,然后释放SCL,使其变为低电平,从而产生起始条件。总线在起始条件后处于忙碌状态。
-
发送从设备地址:主设备发送从设备的7位或10位地址,后面跟着一个写操作位(即最低位为0)。所有从设备都会接收这个地址,但只有地址匹配的从设备会响应。
-
从设备响应:地址匹配的从设备会发送一个确认(ACK)信号,即在第9个时钟周期时,从设备将SDA线拉低。
-
发送数据:主设备开始发送数据字节,每个字节后面都跟着一个时钟周期,用于从设备发送ACK信号。主设备可以发送多个字节,直到发送完所有需要的数据。
-
停止条件:当主设备发送完所有数据后,它会发出停止条件,即将SDA线从低电平拉到高电平,同时保持SCL为高电平,然后释放SDA线。
-
从设备处理数据:从设备在接收到停止条件后,会处理这些数据,例如存储到内部寄存器或EEPROM中。
以下是I²C写过程的时序图表示:
起始条件 -> [设备地址 + W] -> ACK -> [数据1] -> ACK -> [数据2] -> ACK -> ... -> [数据N] -> ACK -> 停止条件
->
表示时间流动,[]
表示数据字节,R
表示读操作位,ACK
表示主设备发送的确认信号,NACK
表示主设备发送的否定确认信号。
I²C读操作的详细步骤:
-
起始条件:主设备通过将SDA线从高电平拉到低电平,同时保持SCL为高电平,然后释放SCL,使其变为低电平,从而产生起始条件。总线在起始条件后处于忙碌状态。
-
发送从设备地址:主设备发送从设备的7位或10位地址,后面跟着一个读操作位(即最低位为1)。所有从设备都会接收这个地址,但只有地址匹配的从设备会响应。
-
从设备响应:地址匹配的从设备会发送一个确认(ACK)信号,即在第9个时钟周期时,从设备将SDA线拉低。
-
读取数据:从设备开始发送数据字节,主设备在每个字节后面通过发送ACK信号来请求更多的数据,或者通过发送NACK信号来结束读取过程。
-
停止条件:当主设备完成数据读取后,它会发出停止条件,即将SDA线从低电平拉到高电平,同时保持SCL为高电平,然后释放SDA线。
-
主设备处理数据:主设备接收到数据后,可以根据需要进行处理或存储。
以下是I²C读过程的时序图表示:
起始条件 -> [设备地址 + R] -> ACK -> [数据1] -> ACK -> [数据2] -> ACK -> ... -> [数据N] -> NACK -> 停止条件
->
表示时间流动,[]
表示数据字节,R
表示读操作位,ACK
表示主设备发送的确认信号,NACK
表示主设备发送的否定确认信号。
I 2 C的功能框图
STM32CudeMX
Master features 主模式特性
Slave features 从模式特性
-
Byte:
- 一个字节(Byte)通常包含8位(bit),这是计算机中最小的可寻址的存储单元。
- 字节是大多数计算机体系结构中的基本数据单位,用于表示字符、数字和其他数据类型。
- 在不同的上下文中,字节可以表示不同的含义,例如在数据通信中,它通常指的是传输的一个数据单元。
-
Half Word:
- 半字(Half Word)通常包含16位(bit),即2个字节。
- 在16位或更宽的处理器中,半字可能是处理器可以同时处理的数据单位之一。
- 在一些编程语言和操作系统中,半字用于指定数据类型的大小,例如短整型(short)在某些体系结构上可能是16位的。
-
Word:
- 一个字(Word)的大小取决于具体的处理器架构,但通常是16位、32位或64位。
- 在32位处理器中,一个字通常是32位,即4个字节。
- 在64位处理器中,一个字可能是64位,即8个字节。
- 字是许多处理器的主要数据单位,用于表示整数、指针和内存地址。
有哪些函数
-
初始化和去初始化:
HAL_I2C_Init()
:初始化I2C外设。HAL_I2C_DeInit()
:去初始化I2C外设。-
配置:
HAL_I2C_Config()
:配置I2C的一些参数,如时钟速度、地址等。-
数据传输:
HAL_I2C_Master_Transmit()
:作为主设备发送数据到从设备。HAL_I2C_Master_Receive()
:作为主设备从从设备接收数据。HAL_I2C_Slave_Transmit()
:作为从设备发送数据到主设备。HAL_I2C_Slave_Receive()
:作为从设备从主设备接收数据。-
状态和错误处理:
HAL_I2C_GetState()
:获取I2C外设的当前状态。HAL_I2C_GetError()
:获取I2C的错误代码。-
中断处理:
HAL_I2C_IRQHandler()
:I2C中断处理函数。HAL_I2C_MasterTxCpltCallback()
:主设备发送完成回调函数。HAL_I2C_MasterRxCpltCallback()
:主设备接收完成回调函数。HAL_I2C_SlaveTxCpltCallback()
:从设备发送完成回调函数。HAL_I2C_SlaveRxCpltCallback()
:从设备接收完成回调函数。-
其他功能:
HAL_I2C_IsDeviceReady()
:检查指定地址的从设备是否就绪。HAL_I2C_Mem_Write()
:向从设备的内存写入数据。HAL_I2C_Mem_Read()
:从从设备的内存读取数据。
代码编写
检查指定地址的从设备是否就绪。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
char I2c_Data[9]={"AA"};
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(HAL_I2C_IsDeviceReady(&hi2c1,0x78,2,500)==HAL_OK){
HAL_UART_Transmit(&huart1,(uint8_t*)I2c_Data,9,1000);
}else{
char a[]={"null"};
HAL_UART_Transmit(&huart1,(uint8_t*)a,5,1000);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
注意
在I2C(Inter-Integrated Circuit)总线协议中,通常有一个主机(master)和多个从机(slave)。按照I2C协议的设计,所有的数据传输都是在主机控制下进行的。也就是说,在标准的I2C通信中,从机与从机之间不能直接进行通信,所有的数据传输都需要通过主机来控制。
作者:快秃头的码农