GD32L233X 硬件I2C读取BQ40Z50的配置


最近用GDL233标准库来对BQ40Z50进行I2C通讯,真的是一堆坑,官方的例程有用但不全,好险有逻辑分析仪,要不然一周都搞不定这玩意。

硬件I2C读取BQZ50的配置

I2C总线协议理解

I2C,即Inter-Integrated Circuit,是一种常用的串行通信协议,用于在器件之间——特别是两个或两个以上不同电路之间建立通信。I2C Primer是最常用的I2C。本文将介绍I2C Primer的基本特性和标准,并重点说明在通信实现过程中如何正确使用该协议。从I2C的基本原理出发,我们将介绍其变体子集——系统管理总线(SMBus)和电源管理总线(PMBus)——的可用性及二者的区别,三种协议各有专门的功能。

I2C Primer

使用一条串行数据(SDA)线、一条串行时钟(SCL)线和一个公共接地来承载所有通信,最大程度地减少连接。硬件最重要的注意是在SDA和SCL上加入上拉电阻。I2C器件通过开集或开漏引脚连接到总线,将线路拉低。当没有数据传输时,I2C总线处于高电平空闲状态;线路被被动拉高。要传输数据,须切换线路,即先拉低再释放(又变为高电平)。数据位在时钟下降沿传输。
开漏输出需要一个上拉电阻才能正确输出高电平。上拉电阻连接在输出引脚和高电平所需的输出电压之间。
对于VCC和VDD (5 V)的典型值,4700 Ω是最常用的上拉电阻值。
作为参考,屏蔽2 AWG双绞线电缆的电容范围为100 pF⁄m至240pF⁄m。因此,I2C链路的最大总线长度约为1米(100 kBaud时)或10米(10 kBaud时)。非屏蔽电缆的电容通常要小得多,但只能用在以其他方式加以屏蔽的外壳内。

特性规格
导线2
最大速度标准模式 = 100kbps 快速模式 = 400kbps 高速模式 = 3.4Mbps 超快速模式 = 5Mbps
同步异步并行串行?同步串行
最大节点数1008 有16个保留节点
帧格式
开始地址可读写ADKData frameACK
单个主器件和多个节点

I2C使用寻址,所以单个主器件可以控制多个节点。使用7位地址可提供128 (27)个唯一地址。使用10位地址很罕见,但可提供1024 (210)个唯一地址。要将多个节点连接到单个主器件,请使用4.7 kΩ上拉电阻连接这些节点,并将SDA和SCL线连接到VCC。

多个主器件和多个节点

多个主器件可以连接到单个节点或多个节点。如果同一系统中有多个主器件,那么当两个主器件争着在同一时间通过SDA线发送或接收数据时,就会出现问题。

为了解决这个问题,每个主器件在传输消息之前,需要检测SDA线是低电平还是高电平。

如果SDA线为低电平,则说明总线由另一个主器件控制,该主器件应等待。如果SDA线为高电平,则它可以安全传输消息。要将多个主器件连接到多个节点,请按照图13所示,使用4.7 kΩ上拉电阻将SDA和SCL线连接到VCC。
仲裁
若干I2C多主器件可以连接到同一I2C总线并同时运行。通过不断监视SDA和SCL有无起始和停止条件,它们可以确定总线是否空闲。如果总线正忙,主器件将延迟挂起的I2C传输,直至停止条件指示总线再次空闲。

时钟同步

也称为时钟延展。

注意:I2C规范没有为时钟延展规定任何超时条件——也就是说,任何器件都可以根据需要保持SCL。

在I2C通信协议中,时钟速度和信号始终由主器件产生。I2C主器件产生的信号提供主器件和节点连接之间的同步。

在某些情况下,节点或子节点不是以全状态工作,在接收主器件生成的时钟之前,需要减慢速度。这是通过一种称为"时钟延展"的机制来实现的。

在时钟延展期间,为了降低总线速度,允许节点压低时钟。而在主器件方面,在其变为高电平状态后,必须回读时钟信号。然后,它必须等待,直至线路达到高电平状态。

虽然时钟延展是一种常见做法,但它对带宽有影响。使用时钟延展时,共享总线的总带宽可能会显著降低。即使使用这种技术,总线性能仍然必须可靠且快速。有必要考虑使用时钟延展的估计影响,尤其是在多个器件共享I2C总线的情况下。

是否需要时钟延展取决于节点器件的功能。这里有两个例子:

  • 处理器件(如微处理器或微控制器)可能需要额外的时间来处理中断,接收和管理数据,以及执行适当的功能。
  • 较简单的器件(如EEPROM)不在内部处理数据,因此不需要时钟延展来执行任何功能。
SMBus

众所周知,SMBus可用于需要对参数进行关键监控的应用。它最常见的应用是计算机主板和嵌入式系统。对于温度、电源电压、风扇监控和⁄或控制集成芯片,它有额外的监控规范。

SMBus是一种2线总线,类似于飞利浦公司于1980年代开发的I2C总线。两个主要信号是时钟(SMBCLK)和数据(SMBDAT)。I2C Primer和SMBus相互兼容,但存在明显差异,例如:

  • SMBus逻辑电平阈值是固定的,与器件的电源电压不成比例。因此,具有不同电源电压的器件可以在同一Primer上运行。例如,一个SMBus可能具有多个由1.8 V、3.3 V和5 V电源供电的器件。
  • 它们都以最高100 kHz的相同速度运行,但I2C Primer有400 kHz和2 MHz两个版本。
  • SMBus规定了最低时钟速度,并限制了时钟在一个事务中可以延展的量。违反超时限制会导致所有SMBus器件复位其I⁄O逻辑以允许总线重启。这种设计增强了总线的鲁棒性。
  • 二者的超时也不同。I2C Primer没有超时,而SMBus有超时——对于10 kHz最低时钟速度,可以考虑35 ms的超时。
  • 分组差错校验(PEC)最初是为SMBus定义的。在每个事务的末尾添加一个分组错误码字节。
  • 其余的一些差异涉及传输类型、警报线、暂停线、关断或上电。

SMBus器件每次收到其自己的地址时,无论在做什么,它都必须应答(ACK),这是一个明确要求,目的是确保主器件可以准确地判断总线上哪些器件处于活动状态。

所有SMBus事务都通过指定的SMBus协议之一执行。

SMBus还有一个可选信号SMBALERT#,节点器件可以使用该信号快速通知主器件或系统主机,它有主器件需要的信息,例如报告故障情况。

SMBus地址有7个二进制位,通常表示为前4位、后3位以及最后一个字母b,例如0001 110b。这些地址占据总线上一个8位字段的高7位。然而,该字段的最低位另有含义,不属于SMBus地址的范围。

PMBus

除了SMBus之外,还有一个变体PMBus,它是一种开放标准电源管理协议。这种灵活且高度通用的标准允许基于模拟和数字技术的器件之间进行通信,并提供真正的互操作性,由此将能降低电源系统设计的复杂性并缩短产品上市时间。

PMBus用于带有电源控制和管理器件的电源的数字管理。它具有支持电源管理要求的命令和结构。这意味着I2C Primer和PMBus在电气要求和命令语义上是兼容和可互操作的。

电源管理的基本参数之一是过压电平监控,PMBus提供了设置和读取该值的命令。PMBus可以附加在I2C Primer和SMBus的已有特性上,充当现有标准(尤其是SMBus)之上的协议层。

I2C 规范仅描述了2线总线的物理层、时序和流控制。I2C规范没有(像SMBus协议那样)描述消息的格式,也没有描述消息的内容。

PMBus规范是一个完整的电源管理协议。它说明了如何将比特和字节从一个器件传送到另一个器件(即传输)。它还描述了一种命令语言,赋予这些比特和字节以意义。

对于冗余系统,一旦电源安装到系统中,最多有三个信号来设置电源的地址位置:地址2、地址1和地址0。对于非冗余系统,电源器件地址位置应为B0h。

硬件

针对基于I2C VDD的电源和驱动(对于VDD = 3.3 V),电源中的器件应与SMBus 2.0高功率规范兼容。该总线应以3.3 V运行。

电源

电源内部的电路应从备用输出获得电源。对于冗余电源,器件应从"逻辑或"器件的系统侧供电。只要系统中的电源或并联冗余电源接通交流电源,PMBus器件就应处于开启状态。

上拉电阻

电源内部的SCL或SDA线上只能使用弱上拉电阻。主要上拉电阻由系统提供,可以连接到3.3 V或5 V。对于系统设计,主要上拉电阻应位于电源外部,并从备用电源轨获取电源。

数据速度

电源中的PMBus器件应以100 kbps SMBus全速运行,并尽可能避免使用时钟延展,因为它会减慢总线速度。

I2C Primer、SMBus和PMBus有何关系?

SMBus最初开发用于协助电池管理系统,使用I2C硬件,但增加了第二级软件,最终允许器件热插拔,而无需重新启动系统。PMBus扩展了SMBus,定义了一组专门用于管理功率转换器的器件命令,暴露了器件的测量电压、电流、温度等属性。一般而言,I2C Primer、SMBus和PMBus器件可以共享总线而不会发生什么大问题。

I2C、SMB、PMB的优势
  • 仅使用两条线
  • 具有ACK⁄NACK位
  • 广为人知的协议
  • 支持多个主器件和多个节点
  • 硬件不如UART复杂
  • 广泛使用的方法
缺点
  • 数据传输速率比SPI慢
  • 数据帧的大小限制为8位
  • 实现所需的硬件比SPI复杂

I2C SMBUS初始化

GPIO 初始化

使能时钟I2C0,GPIOA 然后将PA9和PA10配置成复用模式即可。

I2C时钟配置(有坑)

首先对于I2C的默认时钟采用APB1,这里为16MHZ

rcu_i2c_clock_config(IDX_I2C0, RCU_I2CSRC_CKAPB1);//16M

但之后的SMBUS时钟频率的配置,让我弄了很就才弄明白
GD的标准库的时钟配置不仅需要配置分频,同时还需要配置:
scl_dely : data setup time (SCL延迟时间)
sda_dely : data hold time (数据保持时间)
scl_high_time:SCL高电平时间
scl_low_time: SCL低电平时间
仿照例程进行配置后,发现由于SMBUS的频率要在10Khz-100khz
所以例程的无法对BQ40Z50正常读取。
对时钟进行重新分频得到10us的时钟周期如下:

  i2c_timing_config(I2C0, 0x01, 0x3, 0x0);//(1+1)*(1/16M) = 8M 
  i2c_master_clock_config(I2C0, 0x13, 0x36);//SCL HIGH = (0x13+1+4)*(1/8M) = 3us, SCL LOW = (0x36+1+4)*(1/8M) = 3us period = 10us

才得以对BQ40Z50进行读取。

I2C 总线读写函数(有坑)

I2C 16位写入函数

由于BQ40Z50需要对地址和长度进行同时写入,因此需要对16位的数据进行连续写入,而官方
例程只有单字节写入,加入两字节的数据写入

    /* send a start condition to I2C bus */
    i2c_start_on_bus(I2C0);
    /* clear I2C_TDATA register */
    I2C_STAT(I2C0) |= I2C_STAT_TBE;
    /* wait until the transmit data buffer is empty */
    while(SET != i2c_flag_get(I2C0, I2C_FLAG_TBE)) {};
    // 16bits
    i2c_data_transmit(I2C0, ((addr_in_slave >> 8) & 0xFF));
    while(!i2c_flag_get(I2C0, I2C_FLAG_TBE)) {};
    /* send the internal address: other one byte address */
    i2c_data_transmit(I2C0, (addr_in_slave));

需要注意,要开启自动停止,以及重新配置传输的字节数

    /* configure number of bytes to be transferred */
    i2c_transfer_byte_number_config(I2C0, nbytes + 2);
    /* enable I2C automatic end mode in master mode */
    i2c_automatic_end_enable(I2C0);

I2C 读函数

总所周知,I2C想读就得先写,因此读汉书需要先对地址进行写入,但与写入函数不同的是,我们不需要开启自动停止模式,也就是说在写完地址后我们不需要发送停止位,而自动切换为接受模式

 /* disable I2C automatic end mode in master mode */
 i2c_automatic_end_disable(I2C0);

需要注意的是由于没有自动停止,我们传输完毕后需要等待的标志位是TC而不是TBE否则会卡死在while里

while(!i2c_flag_get(I2C0, I2C_FLAG_TC)) {};

但是!但是!对于传输地址的写入会有停止位,我们需要对TBE标志位进行读取而不是TC

i2c_start_on_bus(I2C0);
 /* wait until the transmit data buffer is empty */
 while(SET != i2c_flag_get(I2C0, I2C_FLAG_TBE)) {};

在接受模式下,我们在重新I2C自动停止和读取的位数进行配置

i2c_transfer_byte_number_config(I2C0, nbytes);
nbytes_reload = nbytes;
i2c_automatic_end_enable(I2C0);

I2C 读取标志位超时问题

对于I2C,为啥很多人说他不好用呢,主要是它超时根本没有一个超时时间,那么在硬件上出现问题,比如把一根线拔了就永久的进入死循环,整机直接跑死,这对于四轴这种东西的话就是直接掉下来了,非常的恐怖。
GD32官方的例程如下:

while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY))
    {}

就一个while跑死去读标志位
更改如下

#define I2C0_READ_TIMEOUT 10  // ms
#define I2C0_WRITE_TIMEOUT 10 // ms

uint8_t i2c0_read_time_out;
uint8_t i2c0_write_time_out;
while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (i2c0_read_time_out++ <= I2C0_READ_TIMEOUT))
    {}
    if(i2c0_read_time_out > I2C0_READ_TIMEOUT)
    {
        TimeOut_UserCallback();
        return 0;
    }
    i2c0_read_time_out = 0;

经过测试就算I2C跑死也不会影响成程序正常运行。

BQ40Z50唤醒测试

理论上通过PRES_EN拉高100ms后初始化I2C就可以,但实际发现,BQZ50唤醒后有一段延时,不能直接对I2C进行open操作,否则会造成死锁,手册上也没有说明唤醒延时。这里进行一个简单的延时测试,通过对电池的插拔来对系统进行复位。

BQ40Z50_ENABLE;
delay_1ms(100);
BQ40Z50_DISABLE;
delay_1ms(2);//delay time prevent i2c deadlocks
延时时间(ms)状态
1ms死锁
2msI2C正常读取数据

对SMBUS协议进行监听分析

通过对上位机bqstudio发送0x09的读取电压指令,得到如下波形
看到线写入0x16读地址,然后写入0x09读指令,在发0x17读指令
得到应答0x4075,电压为16501mV。时钟周期在10us
请添加图片描述

对MCU最后调通的波形进行监听分析,写入0x16读地址,然后写入0x09读指令,在发0x17读指令得到应答0x3F59。
请添加图片描述

对I2C死锁分析

SDA挂死

最常见的情况就是主机在通讯的过程中产生了复位。由于复位动作通常会立刻执行,外设状态机都恢复到默认状态,也就发不出完整的CLK了。那么等到主机复位完成回来后,SCL为高,SDA被从机拉低。主机无法发起START起始条件,不能开始下一次与
从机的通讯,这称为SDA挂死,偶然遇到了一次但忘记截图了。

SCL挂死

SCL挂死(也就是前面所说一直拉低SCL)这种情况在标准I2C从器件上基本不会出现,因为只要芯片还在正常工作buffer总算有准备好的时候,自然就就释放SCL了。往往是使用用户使用MCU作为I2C从机时,程序设计上的问题导致MCU无法读取&填充buffer而导致,重点分析MCU I2C中断服务程序。
可能的情况如下:

  1. 2C中断服务程序被意外屏蔽
  2. 中断服务程序中陷入了一些标志位查询的while(flag != xxx)死循环
  3. I2C功能系统被意外禁止
    通过逻辑分析仪对其进行分析,确实处于SCL挂死状态
    请添加图片描述

看DEBUG里有没有跑死,也确实是某个标志位没有读到,导致进入while循环跑死了。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值