|
背景 对于每个MCU系列,STMicroelectronics都提供了一个嵌入式固件包,其中包括硬件抽象层(HAL)驱动。该驱动提供了一组高级API,旨在以高度可移植的方式抽象出MCU及其外设的复杂性。I2C接口就是其中之一。有关HAL I2C驱动入门的初学者指南,请参阅Shawn Hymel的教程。如需快速了解I2C主设备可用的通信功能,请继续阅读。 HAL驱动支持三种编程模型用于其数据处理功能:轮询、中断和DMA。轮询函数以阻塞模式运行,这意味着这些函数在操作完成之前不会返回。为了防止应用程序挂起,用户必须提供合适的超时值。中断和DMA函数以非阻塞模式运行,这意味着这些函数在操作启动后返回,允许应用程序继续执行,而操作在后台继续进行。然而,必须配置并启用回调函数以处理操作完成后引发的信号。 在阻塞模式下作为I2C主设备时,有四个API函数可用于与从设备通信:
对于非阻塞功能,中断和DMA模式有等效的函数。然而,由于轮询函数是三者中最直接的,因此本参考指南将使用它们。 发送数据从主设备向从设备发送数据在大多数情况下并不复杂。可以使用HAL_I2C_Master_Transmit() 或HAL_I2C_Mem_Write() 函数。选择哪一个取决于消息结构或仅仅是个人偏好。 HAL_I2C_Master_Transmit()此API函数的函数原型如下所示。第一个参数是一个配置结构,其创建在入门教程中有详细说明。第二个参数是从设备的地址(必须左移一位)。第三个和第四个参数分别是指向数据缓冲区的指针和应从缓冲区发送到从设备的数据量。最后一个参数是以毫秒为单位的超时时间。请注意,用户可以提供 HAL_MAX_DELAY作为参数以禁用超时并无限阻塞,直到函数返回。 HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);调用此函数生成的 I2C序列如下所示。阴影区域表示信号由从设备驱动的位置。在这种情况下,从设备仅确认其自身地址(加上写位)以及随后的任何数据字节。请注意,发送的数据字节数由提供给函数调用的Size参数决定。
作为此函数使用的示例,请考虑以下代码,其中缓冲区的内容被发送到地址为0x40的从设备。I2C传输由逻辑分析仪捕获,生成的波形也如下所示。请注意,起始条件存在,但在解码协议图形中没有标签的空间。 uint8_t dataBuffer[10] = {0x03, 0x01};HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);
HAL_I2C_Mem_Write() 此函数适用于主设备希望写入从设备上特定内存位置的常见场景。例如,大多数I2C传感器包含用于更改设置和启动测量的配置和命令寄存器。此函数的原型如下。 HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);显然,它包含与HAL_I2C_Master_Transmit() 函数相同的所有参数,以及两个额外的参数。第一个参数MemAddress是从设备中缓冲区内容将写入的起始内存地址。第二个参数MemAddSize是内部内存地址的大小(I2C_MEMADD_SIZE_8BIT 或I2C_MEMADD_SIZE_16BIT)。I2C序列现在将如下所示。
它与HAL_I2C_Master_Transmit()生成的序列相同,只是MemAddress 参数在从设备地址之后和来自数据缓冲区的第一个字节之前发送。以下示例使用HAL_I2C_Mem_Write()函数将值0x01写入从设备上位于内存地址0x03的寄存器。请注意,逻辑分析仪捕获的I 2C操作与上面HAL_I2C_Master_Transmit() 函数的示例完全相同。 uint8_t dataBuffer[10] = {0x01};HAL_I2C_Mem_Write(&hi2c1, (0x40 << 1), 0x03, I2C_MEMADD_SIZE_8BIT, dataBuffer, 1, HAL_MAX_DELAY);
接收数据 与从主设备发送数据到从设备不同,用于从从设备接收数据的两个函数不可互换。一个函数仅接收数据,而另一个函数首先指定从何处接收数据。 HAL_I2C_Master_Receive()此API函数用于简单地从从设备请求数据。请注意,以下原型中的参数与HAL_I2C_Master_Transmit()的参数相同。然而,在这种情况下,数据缓冲区用于存储传入数据,Size 参数指定在发送Nack之前接收多少字节的数据。 HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);描述此函数操作的序列图如下所示。请注意,它与上面讨论的任何一个数据传输函数都有根本的不同。首先,地址字节中的方向位被设置为读取而不是写入。其次,在从设备确认其自身地址后,它开始向主设备发送数据字节(请注意,阴影字段表示从设备正在驱动信号)。现在,主设备负责确认它接收到的每个字节,直到它不再希望接收它们。它通过发送Nack后跟停止条件来告诉从设备停止发送数据。
下面的代码示例显示了主设备从地址为0x40的从设备请求三个字节的数据。通过观察逻辑分析仪的捕获,我们可以看到,操作完成后,数据缓冲区将包含值{0x00, 0x68, 0xF0}。 HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1), dataBuffer, 3, HAL_MAX_DELAY);
HAL_I2C_Mem_Read() 此API函数用于从特定内存地址的从设备请求数据。再次考虑一个 I2C传感器,其中测量值存储在从设备的特定寄存器中,主设备必须从该寄存器读取数据。如下所示,函数原型包含与HAL_I2C_Mem_Write() 函数相同的参数。然而,与上述情况一样,数据缓冲区将用于存储传入的数据,而不是作为数据源。 HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);此函数的独特之处在于,它首先执行I2C传输操作,以告诉从设备从哪个内存地址获取数据。随后是重复的起始条件以开始接收操作。完整的 I2C序列如下所示。请注意,HAL_I2C_Mem_Read() 是唯一能够在阻塞模式下生成重复起始条件的函数。 如果需要重复起始条件,仅调用HAL_I2C_Master_Transmit()后立即调用 HAL_I2C_Master_Receive()是不够的。
以下代码示例从地址为0x40的从设备的内存地址0x01开始接收两个字节的数据。从逻辑分析仪的捕获中注意到,操作完成后,缓冲区将包含{0x68, 0x90}。 HAL_I2C_Mem_Read(&hi2c1, (0x40 << 1), 0x01, I2C_MEMADD_SIZE_8BIT, dataBuffer, 2, HAL_MAX_DELAY);/* This is NOT guaranteed to work as a substitute for the above! */// dataBuffer[0] = 0x01;// HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 1, HAL_MAX_DELAY);// HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);
总结 STMicroelectronics提供的HAL I2C驱动程序允许主设备以阻塞模式或非阻塞模式与从设备通信(阻塞模式是两者中较简单的)。要在阻塞模式下向从设备发送数据,可以使用HAL_I2C_Master_Transmit() 函数或HAL_I2C_Mem_Write()函数。两者可以互换使用。唯一的区别是,HAL_I2C_Mem_Write()明确指定了从设备上的内存地址作为参数。用户应根据其应用程序决定哪种方式最合适。要从从设备接收数据,可以使用HAL_I2C_Master_Receive()函数或HAL_I2C_Mem_Read()函数。然而,这两个函数不能互换使用。HAL_I2C_Master_Receive() 只是从从设备读取数据,而 HAL_I2C_Mem_Read() 首先向从设备发送一个内存地址,然后发出一个重复的起始条件,接着进行读取操作以获取位于该内存地址的数据。选择使用哪个函数取决于从设备期望的 I2C序列。 LDW0912025 年12 月 2 日 01:152 HAL_I2C_Master_Transmit() vs HAL_I2C_Mem_Write() // 简单发送 - 设备地址+数据 HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); // 带内存地址的发送 - 设备地址+内存地址+数据 HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 直接读取 - 设备地址后立即读取数据 HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); // 带地址的读取 - 设备地址→发送内存地址→重复起始→读取数据 HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); |
【评论有奖】STM32CubeIDE 2.0版本要来了
F429I-DISC1体验报告(2) 按钮和弹窗GUI的简单交互设计丨国庆开发板测评活动
架构更新!STM32CubeIDE 2.0.0重磅发布,STM32CubeMX成独立工具(文末有奖)
在旧版本STM32CubeIDE内快速更新至STM32CubeIDE2.0
经验分享 | 基于STM32CubeIDE的指定存储话题
实战经验 | 使用STM32CubeIDE开发上位机工具
【教程】STM32CubeIDE for Visual Studio Code 安装
解锁STM32开发新体验,STM32CubeIDE for VS Code全解析(附实操视频)
速看!STM32CubeIDE for Visual Studio Code预发布版全面整合至正式版,完成重大升级
速看!STM32CubeIDE for Visual Studio Code预发布版全面整合至正式版,完成重大升级
微信公众号
手机版