STM32IIC通信详解(硬件实现IIC通信详解II 基于HAL库编程)

本文详细介绍了STM32使用HAL库硬件实现IIC通信的过程,包括通信接口、时钟、数据和控制部分的解析。强调了硬件方式优于软件模拟IIC的效率和封装性,并提供了初始化及收发函数的概述。通过LM75A温度采集为例,展示了实际应用中的操作流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32硬件框架介绍

首先我们来看IIC通信的硬件架构

可以看出,可以分为以上4部分。
第一部分:通信接口
SDA信号和SCL信号由此产生或输入
第二部分:时钟部分
时钟信号由此产生或由此读取
第三部分:数据部分
通信时,数据从缓冲区放入DR寄存器,再由SR寄存器将其一位一位移出到SDA发送。这个过程持续直到数据发送完毕。
第四部分:控制部分
控制部分和初始化密切相关,控制部分设置IIC的工作模式和使能等各种内部配置

我们主要需要理解的部分为1、3部分。内部的时钟和控制由HAL库帮我们管理好了。接下来我们就由此展开讲解如何用硬件方式实现IIC通信,利用IIC接口和STM32中的片内外设,而不是软件模拟的方式来实现。

在这里我也稍微提及一下软件模拟方式实现IIC通信与硬件方式的差别和利弊。软件模拟时,需要优化IO口类型,由于数据经常发生变化,这种类型的数据尽可能的需要放入CPU的寄存器中(__IO关键字),方便使用,这无形降低了CPU寄存器的数量,从而拖慢了运算速度。另外,软件模拟方式,需要CPU实时改变电平,CPU运算负担较大,不合算。再者,使用软件控制电平变化,需要我们仔细理解协议。这个协议是简单的IIC协议,若是比较复杂的协议,我们也要如此模拟产生电平吗?

显然,这体现了一个道理——封装。我们使用协议,一般而言,不需要了解协议内部的具体规定,只要我们用符合规格的设备,就可以实现数据通信。若我们使用协议,还要了解协议内部如何实现,增大了使用协议通信的成本,也浪费了时间。是一种“重复发明轮子”的行为。正如我们使用电子元器件不需要了解内部的物理实现,使用芯片时不需要了解内部电路一样。不同的部件由不同的人管理,这样才是将生产力最大化的方式,也是封装的意义所在。

软件模拟方式的益处在于代码简单明了,干净整洁。但是!笔者十分不推荐这种破坏封装性的编程方式,这种简洁,是牺牲了CPU运算效率换来的。以往,51单片机的内部并没有集成IIC接口,所以需要用软件模拟,前几年,据网上的文献资料表示,STM32的库为了规避IIC的专利,用了较为复杂的方式去控制STM32的IIC接口和协议,所以错误较多。但如今这种错误已经没有了,IIC接口可以正常使用,而且HAL库的封装度极高,在各种配置都较为充裕的STM32中,我十分推荐使用HAL库内部封装的IIC接口。

此举增加的代码的模块性。在移植方面,HAL库抽象出了MSP层,而移植只需要修改MSP层的代码即可(改一下管脚)和软件模拟的移植难度一样低。也降低了CPU的资源占用。长时间模拟电平变化消耗的运算资源还是十分多的。其实笔者也认为,之所以串口通信不是电平模拟,而IIC通信是依赖电平模拟,很大的原因不是硬件实现的方式不行,而是前几年STM32的代码有问题,再早一些的51又不含IIC的接口,所以导致大家基本都使用软件模拟的方式,使用硬件实现的文献也很少,查阅资料也很不方便。但既然现在STM32内部封装好的库已经可以正常使用,我们没有理由占用CPU资源。这也是我要介绍硬件实现的原因。

若读者对软件模拟的方式感兴趣,可以自行查阅网络文献和书籍资料,软件模拟的教程在网络上和书籍中都十分常见,这里不再赘述。即使不用软件模拟,了解算法加深对IIC通信协议的理解,也是很好的行为,所以笔者也推荐去了解一下软件实现IIC通信。

IIC初始化

首先将stm32fxxx_hal_i2c.c,stm32fxxx_hal_i2c_ex.c加入文件中。再在main.h中的stm32fxxx_hal.h中找到stm32fxxx_hal_conf.h,将其中对#define HAL_I2C_MODULE_ENABLED的注释取消。若不知道如何加入文件和清除注释,请参考笔者上一篇文章STM32串口初始化与使用详解(基于HAL库实现)串口初始化部分,此处不再赘述。

初始化使用的函数介绍:

HAL库函数 具体功能
HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c) 初始化函数
HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef *hi2c) 取消初始化函数
__weak void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) 初始化MSP层
__weak void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c) 取消对MSP层的初始化

__weak关键字:

这个关键字告诉编译器,这个定义是弱定义,若在编译时,有一处函数定义不含此关键字,则所有该函数的调用都指向那一个非weak型的函数定义。这个操作叫函数重定向。因为在系统头文件里,MSP层是没有声明的,MSP层是具体对应每一个MCU的物理状态,系统文件不会声明也不能声明,但在单片机做上电初始化时,有一些函数又必须调用,所以系统文件采取了这种用户可以重定向的写法。我们在初始化串口时,需要自己写一个MSPInit,而且不含weak。

这是笔者在上一篇文章介绍__weak关键字时的描述,放在此处供参考用。

HAL_I2C_Init()函数做的事情使将用户传入的I2C句柄初始化配置到具体的I2C设备中,并且调用MSP层初始化。

只要我们初始化了IIC和它的MSP层,这个设备就将变得可用。

初始化:

void I2C1Init(void)
{
   
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)		//按要求配置IIC并初始化
  {
   
    Error_Handler();
  }
}

初始化MSP层:

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
   
  GPIO_InitTypeDef GPIO_InitStruct = {
   0};
  if(hi2c->Instance==I2C1)
  {
   
    __HAL_RCC_GPIOB_CLK_ENABLE();						//初始化对应GPIO时钟
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);				//按上述要求初始化GPIO口
		
    __HAL_RCC_I2C1_CLK_ENABLE();
		
    HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 0);			//分别配置EV和ER的中断优先级且使能中断
    HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
    HAL_NVIC_SetPriority(I2C1_ER_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);	
  }
}

在MAIN函数中调用后,IIC硬件就实现了初始化。

如何实现通信?通信过程是如何实现的?

从缓冲区中将数据放入DR寄存器,再由SR寄存器指挥发送,循环这个过程直到缓冲区内无数据。收数据则同理。
接下来介绍函数(一般而言我们都将MCU做主设备,控制从设备的芯片,所以接下来介绍的收发方式都是主机模式下的,从机模式下同理)

HAL库函数 具体功能
HAL_I2C_Master_Transmit 阻塞模式下的IIC发送函数
HAL_I2C_Master_Receive 阻塞模式下的IIC接收函数
HAL_I2C_Master_Transmit_IT 非阻塞模式下的IIC发送配置函数
HAL_I2C_Master_Receive_IT 非阻塞模式下的IIC接收配置函数
HAL_I2C_Mem_Write 阻塞模式下的IIC寄存器写函数
HAL_I2C_Mem_Read 阻塞模式下的IIC寄存器读函数

不论是读写寄存器还是iic设备,都有轮询、中断和DMA方式,篇幅原因,这里就介绍这些抛砖引玉

阻塞模式下的发送代码如下,笔者将笔者认为比较主要的部分留了下来方便阅读。

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
   
	//…
	//…
  if (hi2c->
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值