STM32 HAL I2C(IIC)通信的序列(Seq)传输函数(restart condition)

neozng1@hnu.edu.cn

文章目录

  • STM32 HAL I2C(IIC)通信的序列(Seq)传输函数(restart condition)
    • XferOption的含义
    • HAL源码解析
    • 实验验证

阅读本文需要你对I2C协议有基本的理解,包括收发过程,协议包的定义等。
参考资料和数据手册:

  1. I2C specification
  2. STM32 F4XX Reference Manual
  3. I2C Bus

我们知道,在I2C通信中,如果主机不想释放总线的归属权,希望在此次通信完成后继续给当前从机发送数据,或向另一个新的地址(另一个从机)发送数据,那么可以不发送STOP信号,而是继续占有总线,直接发送一个新的START信号,即I2C协议中所谓的Restart Condition。HAL自然提供了相应的函数支持,他们是一系列以HAL_I2C_Master/Slave_Seq_Transmit/Receive_IT/DMA()命名的函数。

但是HAL库的文档中对这些函数的使用并没有详细的介绍,国内外的网站上也没有相关函数的使用教程和信息,因此笔者在通过资料查阅和实验,以及花费了大量的时间阅读HAL的源码之后,总结出了这一篇参考教程。

本文重点介绍HAL的I2C序列传输主机(master)接口,即HAL库中下列函数的使用,和他们的参数XferOptoin的含义:


HAL库提供的8个用于I2C序列通信的接口

XferOption中的几种可选定义

XferOption的含义

我们按照定义的顺序进行讲解。

I2C_FIRST_AND_LAST_FRAME
I2C_FIRST_FRAME
I2C_NEXT_FRAME
I2C_FIRST_AND_NEXT_FRAME
I2C_LAST_FRAME
I2C_LAST_FRAME_NO_STOP
I2C_OTHER_FRAME
I2C_OTHER_AND_LAST_FRAME
  1. I2C_FIRST_AND_LAST_FRAME

    如果在调用以上函数时传入了该参数,则和普通的I2C传输函数没有什么区别,字如其名,本次传输的是第一次也是最后一次,传输将会在发送或接收了函数参数指定的字节数后结束(主机会发送STOP结束通信)。

  2. I2C_FIRST_FRAME

    该参数在开始第一次传输的时候使用。即如果你需要在一次传输结束之后不释放总线马上开始另一次方向相同且目标从机都相同的传输(此次是transmit下次必须还是transmit,不可以转为receive,且传输的从机必须是同一个),那么就选择这个参数。使用此参数,会在传输结束之后让I2C硬件继续占用总线,直到下一次传输开始。

    传入这个参数后,I2C不会发出RESTART信号,只是传输数据之后保持对总线的占用,因此此次连接的从机会继续对总线保持监测。因此在下一次传输时,通信不能改变方向且必须和同一个从机进行;除非使用I2C_LAST_FRAME_NO_STOPI2C_OTHER_FRAMEI2C_OTHER_AND_LAST_FRAME参数。

  3. I2C_NEXT_FRAME

    字如其名。在之前已经调用过一次传输函数并且传入的参数为I2C_FIRST_FRAME之后,第二次调用同一个传输接口,并希望之后继续占用总线,不要释放(比如有第三次传输或更多次的传输),则使用此参数。注意,传输的方向必须和第一次相同。如第一次使用HAL_I2C_Master_Seq_Receive_IT()并传入了**I2C_FIRST_FRAME,那么本次也应该调用HAL_I2C_Master_Seq_Receive_IT(),同时传入I2C_NEXT_FRAME**参数。

    在完成本次传输之后,下一次的通信不能改变方向且必须和同一个从机进行;除非使用I2C_LAST_FRAME_NO_STOPI2C_OTHER_FRAMEI2C_OTHER_AND_LAST_FRAME参数。

  4. I2C_FIRST_AND_NEXT_FRAME

    该参数是2和3的结合。仅仅是为了代码的复用性和编程更加方便而添加的。在第一次和第二次调用传输接口的时候,都可以传入这个参数。即I2C占有总线之后,连续进行两次方向相同的传输,就可以使用这个参数,同时保持总线不释放,继续占用。

  5. I2C_LAST_FRAME

    在之前已经调用过传输接口函数,即我们已经占有了总线的情况下,希望在本次传输结束后终止,即释放总线发出STOP信号,则传入此参数。注意,传输方向需要和之前相同,地址也需要相同。在本次传输结束之后,主机将会发出STOP信号,释放总线。

  6. I2C_LAST_FRAME_NO_STOP

    注意,这是最关键的一个参数。此参数在已经完成一次传输并且希望在下一次传输中调换方向时使用。很多时候我们需要先向从机传送消息,写入“命令码”,从机会根据命令码准备反馈消息;完成写入后,主机需要立刻启动读取,将数据读回。为了防止其他主机占用总线,我们就需要这个接口。

    在之前一次传输中若调用了参数为I2C_FIRST_FRAMEI2C_NEXT_FRAMEI2C_FIRST_AND_NEXT_FRAME的传输函数,且在此次希望改变传输的方向,则在调用传输函数时传入此参数。

    I2C_LAST_FRAME_NO_STOP为参数的传输函数中,首先会发出restart,然后重新发送从机地址(当然包括读写位),再进行数据写入/读取。并且在此次传输结束之后,总线不会被释放如果希望在下一次传输中再次变换传输方向,可以继续传入此参数,或使用I2C_OTHER_FRAMEI2C_OTHER_AND_LAST_FRAME参数

接下来两个参数都是为切换从机准备的,即使用这两个参数允许此次通信的目标从机(地址)和之前不同。

  1. I2C_OTHER_FRAME

    只需要在之前启用了传输且没有发出STOP(只要没有释放总线),便可以使用此参数开启一次新的传输。并且在这次传输中可以选中其他从机,**即参数中的DevAddress可以不同。**在此次传输结束之后,不会发出STOP信号,之后可以继续传输。

    例如,首先调用HAL_I2C_Master_Seq_Receive_IT(),传入了从机1的地址,XferOptionI2C_FIRST_FRAME,从从机1处接收了10byte的数据;随后,调用HAL_I2C_Master_Seq_Transmit_IT(),传入了从机2的地址,XferOptionI2C_OTHER_FRAME,向从机2发送了5byte的数据,最后再次调用HAL_I2C_Master_Seq_Transmit_IT()XferOptionI2C_LAST_FRAME,向从机2发送了1byte数据之后发出STOP,释放总线。

    整个过程中,I2C总线的情况是这样的:

    1. 主机拉低SDA发送START信号,随后发送从机1的地址,从机1 ack之后主机连续发送10byte的数据(每个byte间从机都会ack应答)
    2. 主机拉低SDA发送RESTART信号,随后发送从机2的地址,从机2 ack后主机连续发送5byte数据
    3. 主机不会发送RESTART,继续向总线上发出1byte数据,从机2 ack后主机发出STOP,释放总线完成整个序列传输。
  2. I2C_OTHER_AND_LAST_FRAME

    此参数和第七个参数类似,只不过在传输结束后会发出STOP信号,结束通信让出总线的归属权。

P.S. : I2C的读写都必须由主机发起,也就是说,如果要在传输的途中改变传输方向,那么主机必须重新发出一包地址信息,因为地址的LSB(最低为)是读写位,只有从机收到地址才知道接下来一个字节是read or write。同时,要发送地址信息,之前必须要由RESTART信号,从机收到START or RESART,才会开始监听接下来一个byte的数据并将其当作地址处理,如果和自己的地址匹配,就接收接下来的信息。
10位地址模式也类似。

P.P.S. : STM32 F7系列就简化了序列通信的复杂度,将所有的参数归结为三种:AUTO_END,SOFT_END以及RELOAD_MODE,分别代表当前传输结束后发送STOP,当前传输结束后啥也不干继续占用总线以及传输前会发送RESTART信号。通过“或”运算( | )来组合三种标志,让用户灵活度大大提高。

HAL源码解析

I2C通信的函数实现其实大同小异,我们以STM32F4 HAL中的HAL_I2C_Master_Seq_Transmit_IT()为例,介绍I2C的Seq transfer序列传输过程。为了有最好的体验,建议将代码复制到VSCode中,提供高亮支持以便观看,或自行打开HAL中的对应文件(stm32f4xx_hal_i2c.c,在CubeMX生成的Driver/STM32F4xx_HAL_Driver/Src文件夹下)。

注意,ST不同的产品线HAL代码的实现不同,这是由于不同系列的硬件支持的特性不同,以及他们之间的兼容性不同。

直接上源码,笔者已经添加了注释并将无用的部分省略(通过//注释以及//…),很详细:

HAL_StatusTypeDef HAL_I2C_Master_Seq_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions)
{// 临时变量申明和赋值,参数检查等// ...if (hi2c->State == HAL_I2C_STATE_READY) // 判断I2C外设是否READY,有没有其他线程占用{/* 如果不是第一次调用接口(不是首次开启传输),要通过I2C的状态寄存器检查I2C硬件是否BUSY */if ((READ_BIT(hi2c->Instance->CR1, I2C_CR1_STOP) == I2C_CR1_STOP) || (XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)){/* Wait until BUSY flag is reset */count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock / 25U / 1000U);do  {//这部分都是超时检查,如果超时会返回HAL_ERROR错误...}while (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); // 轮询,直到I2C_FLAG_BUSY被硬件RESET}// 传输的初始化配置,包括给I2C硬件加锁,配置传输字节数和传输方向等// ... /* 如果传输的方向没有改变,并且XferOption并不是I2C_OTHER_FRAME或I2C_OTHER_AND_LAST_FRAME,那么就不会产生RESTART */if ((Prev_State != I2C_STATE_MASTER_BUSY_TX) || (IS_I2C_TRANSFER_OTHER_OPTIONS_REQUEST(XferOptions) == 1)) // Prev_State当中保存了上一次传输的状态(Tx or Rx){/* Generate Start */SET_BIT(hi2c->Instance->CR1, I2C_CR1_START);}// 解锁I2C硬件,开启中断等// ...return HAL_OK;}else{return HAL_BUSY;}
}

HAL_I2C_Master_Seq_Transmit_IT()实际上是中断和任务前端分离的设计,调用此函数只是设定了一些标志位,随后更多处理会在中断中进行,即HAL_I2C_EV_IRQHandler()函数,这里同样略去不重要的部分:

void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c)
{// 临时变量和参数的初始化与赋值// .../* Master or Memory mode selected */if ((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM)){sr2itflags   = READ_REG(hi2c->Instance->SR2);sr1itflags   = READ_REG(hi2c->Instance->SR1);/* Exit IRQ event until Start Bit detected in case of Other frame requested */if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_SB) == RESET) && (IS_I2C_TRANSFER_OTHER_OPTIONS_REQUEST(CurrentXferOptions) == 1U)){return; // 如果start位还没有产生,直接退出,等到其被置位}/* SB Set ,起始位已经产生----------------------------------------------------------------*/if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_SB) != RESET) && (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_EVT) != RESET)){ // 会通过这个判断说明之前已经产生了RESTART位,在之前的分析中我们知道,只有第一次传输或传入了I2C_OTHER_FRAME的情况下,会发出RESTART信号// 也就是说,如果我们在第一次调用传输接口时传入I2C_FIRST_FRAME,第二次使用I2C_NEXT_FRAME的话,是不会进到这里的,也不会重新发送地址,而是会直接进入下方的/* I2C in mode Transmitter */部分./* Convert OTHER_xxx XferOptions if any */I2C_ConvertOtherXferOptions(hi2c); // 这个宏会把OTHER_FRAME转化成其他参数,方便后续处理,我们不用管I2C_Master_SB(hi2c); // 此函数会根据调用HAL_I2C_Master_Seq_Transmit_IT()时设定的配置,并发送从机地址}/* ADDR Set,从机已经接收到地址发回ACK --------------------------------------------------------------*/else if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_ADDR) != RESET) && (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_EVT) != RESET)){ // 会通过这个判断进入到这里,说明从机已经接收到主机发送的地址,在之前的分析中我们知道,只有FIRST_FRAME或另外两个OTHER_FRAME会发出RESTART并发送从机地址,传入其他参数则不会重新发送地址.I2C_Master_ADDR(hi2c); // 此函数会处理ADDR等相关标志位,并启动数据位的发送}/* I2C in mode Transmitter,说明已经处在数据发送状态下-----------------------------------------------*/else if (I2C_CHECK_FLAG(sr2itflags, I2C_FLAG_TRA) != RESET){  // !!!在使用I2C_NEXT_FRAME,I2C_FIRST_AND_NEXT_FRAME,I2C_LAST_FRAME的时候,进入数据传输阶段都会直接跑到这里,不会进入SB判断和ADDR的判断!!! 也就是说,使用这些参数不会发出RESTART信号,而是继续数据的传输./* 如果启用了DMA传输,那么不能干扰DMA,会跳过这个判断语句.这里我们以IT传输为例,不用关心这句判断,会直接满足条件进入 */if (READ_BIT(hi2c->Instance->CR2, I2C_CR2_DMAEN) != I2C_CR2_DMAEN){// 补充:BTF=Byte Transfer Finished, TXE=Transmit data register Empty/* TXE set and BTF reset ,TXE=1,BTF=0说明发送数据寄存器为空(上一次发送已经完成),新的一次发送尚未开始-----------------------------------------------*/if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_TXE) != RESET) && (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_BUF) != RESET) && (I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_BTF) == RESET)){I2C_MasterTransmit_TXE(hi2c); // 这个函数会判断当前是否已经发送完所有字节,如果发送完,则根据传入的XferOption是否包含LAST决定要不要发送STOP位}/* BTF set -------------------------------------------------------------*/else if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_BTF) != RESET) && (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_EVT) != RESET)){if (CurrentState == HAL_I2C_STATE_BUSY_TX){I2C_MasterTransmit_BTF(hi2c); // 这个函数会判断是否已经发送完所有字节,如果没发送完,则将数据写入DR进行发送;如果发送完,则处理同上.}}}}else{/* I2C in mode Receiver --------------------------------------------------*/// 我们以发送为例,接收就不讲解了,有兴趣的同学可以自行查看源码.当然,需要配合寄存器和功能说明手册}}
}// 补充: I2C的CurrenState(工作状态)有以下几种,含义如注释所示:
typedef enum
{HAL_I2C_STATE_RESET             = 0x00U,   /*!< Peripheral is not yet Initialized         */HAL_I2C_STATE_READY             = 0x20U,   /*!< Peripheral Initialized and ready for use  */HAL_I2C_STATE_BUSY              = 0x24U,   /*!< An internal process is ongoing            */HAL_I2C_STATE_BUSY_TX           = 0x21U,   /*!< Data Transmission process is ongoing      */HAL_I2C_STATE_BUSY_RX           = 0x22U,   /*!< Data Reception process is ongoing         */HAL_I2C_STATE_LISTEN            = 0x28U,   /*!< Address Listen Mode is ongoing            */HAL_I2C_STATE_BUSY_TX_LISTEN    = 0x29U,   /*!< Address Listen Mode and Data Transmissionprocess is ongoing                         */HAL_I2C_STATE_BUSY_RX_LISTEN    = 0x2AU,   /*!< Address Listen Mode and Data Receptionprocess is ongoing                         */HAL_I2C_STATE_ABORT             = 0x60U,   /*!< Abort user request ongoing                */HAL_I2C_STATE_TIMEOUT           = 0xA0U,   /*!< Timeout state                             */HAL_I2C_STATE_ERROR             = 0xE0U    /*!< Error                                     */
} HAL_I2C_StateTypeDef;

实验验证

回学校后补充示波器图像。

STM32 HAL I2C(IIC)通信的序列传输(restart condition)相关推荐

  1. STM32 HAL 硬件IIC+DMA+简单图形库控制OLED

    目录 前言 一.建立工程 二.编写和移植 前期准备 驱动部分修改 三.使用和验证 结论 (2022年1月22日重制)本文主要是移植带简单图形库的程序,如果只是实现DMA控制,建议看[0.96寸 OLE ...

  2. STM32 HAL库IIC驱动

    1. STM32上IIC的一些争议 关于STM32的IIC驱动,网上有很多争论,究竟是使用STM32自带的硬件IIC还是用IO口和软件模拟IIC呢?下面这个图形象展示了这些争论.总结一些:ST为了规避 ...

  3. linux的HAL库函数,STM32 HAL库 IIC 协议库函数

    /* 第1个参数为I2C操作句柄 第2个参数为从机设备地址 第3个参数为从机寄存器地址 第4个参数为从机寄存器地址长度 第5个参数为发送的数据的起始地址 第6个参数为传输数据的大小 第7个参数为操作超 ...

  4. 关于stm32 hal 库 iic 一直是 busy 问题

    最近在用st hal iic 发现 MX_I2C1_Init(); 后 iic 就处于BUSY状态 代码跟踪后发现 具体到 HAL_I2C_MspInit 中 __HAL_RCC_I2C1_CLK_E ...

  5. stm32 hal库 AS5600磁编码器IIC通信读取角度

    stm32 hal库 AS5600磁编码器IIC通信读取角度 下载

  6. STM32的I2C通信

      STM32的两个GPIO引脚,分别用于SCL和SDA,按照I2C规约的时序,像控制LED灯那样控制引脚输出,若是接收数据时则读取SDA线上的电平,那就可以实现I2C通信了,这也是我们在51单片机上 ...

  7. STM32 PAJ7620U2手势识别模块(IIC通信)程序源码详解

    最近在自学设计下基于STM32单片机的项目,想用手势识别模块做一点好玩的,做个手势控制家居设备开关,另外正好借此巩固I²C 通信.因此,我想借这个机会在这里和大家分享一下自己学习STM32单片机时的所 ...

  8. 15 玩转STM32之IIC通信(芯片硬件篇)

    15.1 IIC控制器说明(硬件部分) 在14 玩转STM32之IIC通信(软件模拟篇)我们讲了软件模拟的方式,本章讲解芯片自带的硬件部分(并非所有的芯片都含有IIC,像51系列的是没有的,但是绝大多 ...

  9. 如何快速使用STM32 HAL库和涂鸦Wi-Fi模组进行通信

     简介:本文将教大家如何使用STM32HAL库快速开发和涂鸦WIFI模组通信,接入涂鸦云. 实现功能:通过APP实时监测温湿度数据 程序下载路径:demo程序. demo(定时采集数据)程序. 一.使 ...

最新文章

  1. 谷歌引入自动网络设计,高效解决大规模深度推荐模型的特征嵌入问题
  2. uni-app 使用 web-view 页面之间互相跳转、通信
  3. golang避免SQL注入
  4. turnitin时间
  5. Android开发之旅:HelloWorld项目的目录结构
  6. html分页 css,js+css实现的简单易用兼容好的分页
  7. 【Kafka】kafka AdminClient API
  8. 纯干货:手把手教你用Python做数据可视化(附代码)
  9. python2线程池_python 线程池
  10. 诺基亚S40系统手机使用技巧大全(此乃刘某整理)
  11. 计算机网络实验双绞线制作,实验一 双绞线制作实验报告
  12. 计算机知识竞赛 翻译,英文简历之常见学科竞赛中英文翻译
  13. 调用百度大脑AI开放平台接口实现java+web的图像识别技术
  14. VB制作的“小小莫扎特五线谱助记软件”
  15. 重生之我又是蝌蚪(召唤神龙)源码和无敌版
  16. MySQL增删改查及备份恢复
  17. C语言中如何测量各种变量的长度
  18. 《科学管理原理》读后感
  19. Android基础第四篇
  20. Windows Sockets 函数api (微软官方文档)

热门文章

  1. 解决刷新后回到顶部的问题
  2. PAT甲级 1071 说话方式
  3. 读丘维声之《解析几何》
  4. 万能解压器安卓版_全能压缩软件下载-全能压缩 安卓版v11.5.8-PC6安卓网
  5. iOS TableView 自带可拖动重排功能
  6. 洛谷P3964松鼠聚会
  7. MySQL 服务正在启动 .MySQL 服务无法启动。踩坑(瞎子日记1)
  8. 一共有n个人,给定m对好友关系,好友的好友也算一个朋友圈,问n个人中一共有几个朋友
  9. 33岁的程序员,还在一线编码,该怎么办?
  10. 微型计算机中内存比外存怎样,在同一台计算机中,内存比外存( )。