STM32G0 的IIC 接口做为slave从机应用程序示例

最近在研究STM32的IIC接口,严格来说,是STM32做为IIC或者I2C通信的从机。STM32做主机是很简单,意法半导体的HAL库中也提供了很多很方便使用的函数。

但是STM32做IIC从机,这个之前确实用的少。但研究了一下,发现用法不是很难,现记录如下。

一、通用配置

本次使用的 STM32G030F6 单片机,开启SWD调试接口,时钟使用芯片内部的RC时钟,倍频后64MHZ。

二、IIC接口配置

STM32G030F6有两个IIC接口,这里使用的是I2C1接口,STM32CUBEMX中的配置如下。

2.1 I2C接口参数

相关参数如下图所示:

主要看3个地方:

  • 1,速度模式,我选了标准模式,然后速率选的是100KHZ。
  • 2、上升时间和下降时间,这里写的是100ns,应该是默认值。
  • 3、从设备主地址,范围是0~127,一定要和主机的地址一致。

2.2 IO上拉电阻配置

通常来说,设计PCB的时候,会在板子上给IIC从设备加上上拉电阻。但随着单片机的配置升高,有的型号的IIC接口自带上拉电阻。所以,具体情况根据电路实际情况判断。我这里开启了上拉电阻:

原因很简单,调试过程中,我是让一个STM32F103C8T6的最小系统板做为IIC的主机,和从机通信。而这个最小系统板上的IIC接口没有外接上拉电阻,IIC模式下也没有上拉电阻(F103系列和G0系列的IIC的硬件差别),如下图:

所以这种情况下,我必须要开启STM32G030的上拉电阻。

2.3 开启中断

因为是从机,所以要开启IIC的中断:

还有这里:

确保数据收发及时。

三、驱动程序

接下来是IIC从机相关的程序,HAL库中准备了各种现成的函数。

3.1 开启中断

首先是开启侦听中断,在初始化部分:

HAL_I2C_EnableListen_IT(&hi2c1); 

这个函数的功能主要有两个:

  • 1、使能侦听模式,I2C 设备处于侦听模式时,它会持续监听 I2C 总线上的信号。对于从机而言,这意味着它时刻准备响应主机发送的地址和数据。
  • 2、开启中断功能,开启中断后,当 I2C 总线上发生特定事件(如地址匹配、数据接收完成等)时,硬件会触发中断。在中断服务程序中,可以对这些事件进行相应处理。

3.2 侦听完成回调函数

IIC做为从机时,中断相应的回调函数较多。首先是侦听完成回调函数,即一次完整的IIC通信完成之后,会进入这个中断回调函数。

// 侦听完成回调函数
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c)
{
  // 完成一次通信,清除状态
  first_byte_state = 1;
  offset = 0;
  re_flag = 1;
  addr_change_num = addr_change_num + get_ring_length();
  HAL_I2C_EnableListen_IT(&hi2c1); // slave is ready again
}

first_byte_state定义时默认为1,用来判断是否是第一个收到的值,即寄存器地址。

Offset用来表示当前寄存器的地址。

其它的操作是我解析数据用的,不用过多关心。

3.3 地址匹配回调函数

即从机的硬件地址和主机发送的硬件地址一致的时候,会进入这个中断:

// I2C设备地址回调函数

// I2C设备地址回调函数
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
  if(TransferDirection == I2C_DIRECTION_TRANSMIT) 
  {// 主机发送,从机接收
    if(first_byte_state) 
    {// 准备接收第1个字节数据
      HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, &offset, 1, I2C_NEXT_FRAME);  // 每次第1个数据均为偏移地址
    } 
  } 
  else 
  {// 主机接收,从机发送
    HAL_I2C_Slave_Seq_Transmit_IT(&hi2c1, &ram[offset], 1, I2C_NEXT_FRAME);  // 打开中断并把ram[]里面对应的数据发送给主机
  }
}

中断中会判断数据传输方向,是写入还是读出,并以此执行后面的操作。

3.4 数据接收完成回调函数

字面意思:接收时,数据收完会进入这个中断回调函数。先看函数:

// I2C数据接收回调函数
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  if(first_byte_state) 
  {// 收到的第1个字节数据(偏移地址)
    first_byte_state = 0;
  } 
  else 
  {// 收到的第N个字节数据
    offset++;  // 每收到一个数据,偏移+1
  }
  // 打开I2C中断接收,下一个收到的数据将存放到ram[offset]
  //HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 接收数据存到ram[]里面对应的位置
	HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, &ram[offset], 1, I2C_NEXT_FRAME);  // 接收数据存到ram[]里面对应的位置
}

程序中有这么一句:

HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, &ram[offset], 1, I2C_NEXT_FRAME); 

就是说,收到一个数据时,会进入该回调函数。

当然,也可以改成这样(被我注释掉的那句):

HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, &ram[offset], sizeof(ram), I2C_NEXT_FRAME); 

ram是一个128元素的数组,即收到128个数据才会进入该回调函数。

具体数量根据需求调整。

3.5 发送完成回调函数

即从机发送完指定的数据个数后,进入该回调函数:

// I2C数据发送回调函数
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  offset++;  // 每发送一个数据,偏移+1
  HAL_I2C_Slave_Seq_Transmit_IT(&hi2c1, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 打开中断并把ram[]里面对应的数据发送给主机
}

当主机要从从机中读取某些数据时,会用到这个回调函数。

3.6 故障回调函数

当I2C总线,无论主机还是从机,通信过程中发送故障的时候,会调用该函数。在这个函数中,可以做一些处理,例如记录故障信息,重启相应的总线接口。

void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
    // 检查是哪个 I2C 外设发生了错误
    if (hi2c == &hi2c1)
    {
        // 记录错误信息
        // 这里可以使用串口打印错误信息
        printf("I2C1 communication error! Error code: %d\n", hi2c->ErrorCode);

        // 重置 I2C 外设
        HAL_I2C_DeInit(hi2c);
        HAL_I2C_Init(hi2c);
    }
    else if (hi2c == &hi2c2)
    {
        // 处理 I2C2 的错误
        printf("I2C2 communication error! Error code: %d\n", hi2c->ErrorCode);
        HAL_I2C_DeInit(hi2c);
        HAL_I2C_Init(hi2c);
    }
}

四、注意事项

注意事项主要有两个,一个是上拉电阻的事情,前面已经提过了。另一个是硬件接线的问题,如果主机和从机在两个不同的板子上,接线的时候尽量要短,否则信号容易失真。

主要事项就这样,上电工作的图片或记录就不发了。程序肯定能用,实在有问题的可以留言,我再分享源码。

我是单片机爱好者-MCU起航,打完收工!

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据