nPM1300是NRF最新的电源IC,文章比较长,请耐心观看:
对比以前的芯片来看,这次是补全了前面芯片的空缺,而且DCDC LDO 充电这样的设置可以让嵌入式设备的体积再变小一些:
就像一个项目的电源系统,里面有数字的MCU,也有模拟的AEF,以及为了便携的充电芯片,现在一颗芯片就可以全搞定了,而且还多了很多高级功能,比如可调的LDO和BUCK,以及高端产品才能拥有的运输模式。
下面的一个框图就说明了在设计上面的优势,可以直接通过USB接口充电,还拥有最新的Type-C标准,为了充电的上面的安全还加入了NTC功能配合恒流恒压等可以让设备更加的安全,其实配套软件还有更多的功能,可以预测什么的,看别人评测吧。
可以通过外置电阻的设置,在芯片没有被主机控制的情况下就可以供电。最后我觉得是官方给的BUCK做LDO给MCU供电还挺好,剩下的两个LDO做模拟和数字电源。
其实还有一路主机供电的电源,也可以作为扩展电源出现。
1.可以有两个LDO,一个模拟域,一个数字域(但是我没有找到纹波)
2.两个BUCK,有两个模式
1PWM 和滞后模式可根据负载自动切换
1滞后模式可在较低负载电流下提供效率,并且通常以最大 PWM 电流的一半运行。
1BUCK 默认在自动模式下运行。在自动模式下,BUCK 针对低负载电流选择滞后模式,针对高负载电流选择 PWM 模式。
1在 PWM 模式下,BUCK 由于恒定的开关频率和较低的电压纹波而提供清洁的电源,从而与 RF 电路实现最佳共存。
1VSET1和VSET2引脚仅在通电时启用。如果存在 电阻 R (VSETn ,则 BUCK 启用,输出电压由电阻值)定义。如果引脚接地,则 BUCK 禁用。
有两个电阻是控制BUCK输出的
BUCK 的输出电压范围可通过 TWI (IIC) 进行编程。默认输出电压选择位于引脚VSET1和VSET2上,这些引脚使用外部电阻器配置为GND。
BUCK 1和2 的设置电阻
输出电压范围为 1.0 V 至 3.3 V,步长为 100 mV,并在电压配置寄存器BUCK1NORMVOUT和BUCK2NORMVOUT中设置。一旦选择了电压,必须写入寄存器BUCKSWCTRLSEL才能使值生效。
寄存器BUCK1VOUTSTATUS和BUCK2VOUTSTATUS 指示状态或当前电压设置。
这个是编程时候的要点
BUCK组件需要电感和电容
官方的板子推荐的是这颗,但是jlc没有现货,需要在贸泽上面定欧
这个有个重要的参数是要启用BUCK输出的最小VSYS电压,是2.7V,最大的降压电流是0.2A。然后两个电源纹波
强制BUCK1 进入PWM模式
正常模式的输出电压
状态寄存器,BUCK有些寄存器我看不懂是做什么的
可以精确的按照0.1V的步进来调整
充电的电流是可以限制的,USB上面的
VBUS上面的电压信息是可以输出的,所以这里也可以做USB接口信息检测
涓流就是以低速率且恒定方式对电池提供很小的充电电流。
恒流,就是充电电流保持不变,不管输出电压如何变化,锂电池的主要充电方式,电流大于500mA。
电池维护功能
充电电流限值以 2 mA 为步长设置在 32 mA 至 800 mA 之间。充电电流 I (CHG)由 TWI 配置,默认值为 32 mA。
支持NTC测温
可以通过NTC 电流的配合来适应温度智能充电
电池的充电终止电压也是可以设置的,常温下
涓流充电设置,默认是2.9V
充电状态,这些都是可以通过查询来读出的
官方的板子真的很漂亮,hhhh,原理图也是赏心悦目
LED-DRV 由三个相同的低侧 LED 驱动器组成,位于引脚 LED0、LED1和LED2上。引脚配置彼此独立。
可以在寄存器中配置引脚以实现以下目的:
1充电指示
1充电错误指示
1RGB LED(需要全部三个引脚)
1通用开漏输出
开漏模式一般应用在I2C、SMBUS通讯等需要“线与”功能的总线电路中,除此之外,还用在电平不匹配的场合,如需要输出5伏的高电平,就可以在外部接一个上拉电阻,上拉电源为5伏,并且把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5伏的电平。 |
---|
所以:在开漏模式下运行的每个 LED 引脚都需要一个外部上拉电阻。
GPIO 由VDDIO引脚供电。
GPIO作为输入的时候,一定有去抖动,这里直接内置了,20ms,实在是贴心,给出了一个寄存器来控制
给了一个定时器,但是是一带而过。
但是我研究后,不知道这个东西咋用:
当设备处于休眠模式时,唤醒定时器会以可编程间隔唤醒系统。当系统处于运输或休眠模式时,请勿使用看门狗定时器或通用定时器。
运输和休眠模式将电池与系统隔离,并最大限度地减少静态电流。
休眠模式与运输模式相同,不同之处在于,在休眠模式下,计时器正在运行并作为额外的唤醒源。
这个运输模式,现在在产品上面还挺普遍的,比如无人机,电脑,电动牙刷什么的。
Ship功能是在产品出厂之后的长期保管中,尽可能减少电池余量的功能。Ship功能也称为运输模式等其它名称。
没有搭载Ship功能的电子设备,出厂后仍处于电池发生消耗电流的状态。在这种状态下,电子设备在长期保存中电池余量逐渐减少,导致的电池因深度过放电导致的损坏。
可以设想当产品到达最终客户时,可能会出现电池电量太低以至产品不能立即启动,或者目标电池驱动时间不足的情况。
为了抑制出厂后长期保存状态下电池发生的电流消耗,电子设备需要具备Ship功能。
就是一种
有三种方式退出这个模式,按键,当你摁对应的按钮够了时间就退出,还有VSYS电压唤醒,以及定时器自动唤醒
真不好翻译,应该就是测量一个按键上面的时间。
SHPHLD引脚除了用于退出航行和休眠模式外,还是一个复位控制。
芯片操作其实简单的,就是IIC:
TWI 由 VDDIO 供电。建议将VDDIO连接到 BUCK 输出、VOUT1或VOUT2。除运输和休眠模式外,芯片的所有操作模式下都必须有 VDDIO。
也就是推荐连接到BUCK上面
设计的时候,这个VDDIO
夸夸大厂的文档,IIC协议写的简洁明了,开始信号 地址 读写 回应 寄存器地址 8位数据 回复 不要啦
对应的读取
看一个官方给的demo,里面要点众多,幸亏我用了一次。这里分析一下:Type-C的CC感应线,还有USB -上面的保护二极管:
注意这里的USB需要差分走线,电池输入部分使用了NTC测温,重启的引脚被引出,LED也是被引出,BUCK的输出电压被外置的电阻指定,运输引脚被引出,IIC控制引出,还有中断和重启,就是软件自动控制,不需要手动,VDDIO是1.8V,记住我上面写的东西,这个地方是芯片的供电,推荐是BUCK1上面,LDO的输出有两个滤波电容。
这里就有个有趣的事情,我们都知道MCU需要一个LDO,但是我用了PMIC,PMIC是数字器件,必须主机启动后使用IIC控制,这里就有一些鸡生蛋蛋生鸡的问题,所以需要PMIC在MCU启动前就给MCU供电。
SO:BUCK1 自动启动并为 nRF5x 主机 SoC 提供 1.8 V 电压。
BUCK1 是系统的 I/O 电压。
BUCK2 自动启动,提供 3 V 输出电压,用于其他应用功能。
这里也学了新东西,BUCK可以直接给MCU供电,PMIC是可以的。
在以上VBUS是USB的电压:
PMIC上面还天然的设计了一个VBUSOUT的引脚,时候应用USB外设的器件
我在PCB设计之前,喜欢研究芯片的引脚,因为布过线的都知道,一个良好的布局多重要,芯片的左边是BUCK,下面是IIC,左面是LDO,上面是USB部分,大概就是这样的。
官方给了三个配置
注意的地方就是电源这些,IIC的上拉:
我这里给出自己的设计图,LDO的输出我留了俩路,可以给模拟和数字器件分别供电,中间给出了BUCK的设置电压用的电阻表。
这里学习官方,上拉和设置使用了最小的封装
在简单的配置里面,最小封装是用在了滤波和上下拉。
最简单同理
布局要求是:
为了获得最佳性能,建议使用至少两层的 PCB,其中包括接地层。(这里都是4层的,因为性能是一部分,布线我实在是器件多了2层布不出来,一个完整层被打的七零八落的,哭死)
BUCK 电源电压应使用尽可能靠近电源引脚的高性能电容器进行去耦。
应避免在 PCB 上采用较长的电源线。所有设备接地、VDD 连接和 VDD 旁路电容都必须尽可能靠近设备连接。
官方的PCB里面给的布局,其实就是真实PCB的一个改版,主要是去耦电容更加的紧凑,主要是左边BUCK的电容和电感。
就是布这里的时候要求足够的近,一个小环路
这里是可以从电容中间之间穿线出去
在PCB里面更加的清晰,对称布局
看我画住的地方
这个布局就很好,真实布局
我自己的布局就不好,过多的孔打散了地,以及追求对称失去了性能,需要整改
比如LDO的偏置电容,一般是要求直接压在上面的
现在在看一次这个布局,就很清晰了
在软件开发上面,也是齐全的配置,首先使用官方最大支持力度的zephyr,demo的代码也是基于此:
可以GitHub上面搜索这个库
这次设计给到了:提供了一个 shell 接口来支持 nPM1300 PMIC 的功能,我更改为串口控制版:
官方指令
实现的独有函数,还有一个回调,但是耦合很强,就没有分析了,就看单函数
分析一个吧,是读取多个寄存器的函数,说实话套的很深:
因为寄存器是基地址 偏移,前者是高八位,后是第八位,其实这个是说明了要读取的寄存器位置
这个内联函数封装了I2C通信,通过 i2c_write_read 函数执行具体的读写操作。参数包括I2C设备规范、写缓冲区、写字节数、读缓冲区和读字节数。
然后内联函数才又执行了一个函数
函数通过I2C接口执行写和读操作。它构建了两个I2C消息,一个用于写操作,另一个用于读操作。然后调用 i2c_transfer 函数执行这两个消息。
1msg[0]: 写操作,包含要写入的数据。
1msg[1]: 读操作,包含要读取的数据。
1I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP 标志表示在读操作之前发送重启信号,读操作之后发送停止信号。
mfd_npm1300_reg_read_burst 函数被调用时,会构建一个包含寄存器基地址和偏移地址的缓冲区 buff。
然后调用 i2c_write_read_dt 函数,该函数进一步调用 i2c_write_read 函数。
i2c_write_read 函数创建两个I2C消息,一个用于写入寄存器地址,另一个用于读取数据。
最终,i2c_transfer 函数执行I2C传输,写入地址后读取数据,并将数据存储在 data 缓冲区中。
我这里给出使用串口控制PMIC的代码:
C #include#include#include#include#include#include#include#include #define UART_DEVICE_NODE DT_LABEL(DT_CHOSEN(zephyr_console)) #define BUFFER_SIZE 64 static const struct device *uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE); static const struct device *regulators = DEVICE_DT_GET(DT_NODELABEL(npm1300_ek_regulators)); static const struct device *charger = DEVICE_DT_GET(DT_NODELABEL(npm1300_ek_charger)); static char rx_buf[BUFFER_SIZE]; static volatile int rx_buf_pos; void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data) { switch (evt->type) { case UART_RX_RDY: for (int i = 0; i < evt->data.rx.len; i ) { rx_buf[rx_buf_pos ] = evt->data.rx.buf[i]; if (rx_buf_pos >= BUFFER_SIZE) { rx_buf_pos = 0; } } break; case UART_RX_DISABLED: uart_rx_enable(dev, rx_buf, sizeof(rx_buf), 50); break; default: break; } } void configure_uart(void) { uart_callback_set(uart_dev, uart_cb, NULL); uart_rx_enable(uart_dev, rx_buf, sizeof(rx_buf), 50); } void process_command(const char *cmd) { if (strncmp(cmd, "GET_STATUS", 10) == 0) { struct sensor_value volt, current, temp, status, error; sensor_sample_fetch(charger); sensor_channel_get(charger, SENSOR_CHAN_GAUGE_VOLTAGE, &volt); sensor_channel_get(charger, SENSOR_CHAN_GAUGE_AVG_CURRENT, ¤t); sensor_channel_get(charger, SENSOR_CHAN_GAUGE_TEMP, &temp); sensor_channel_get(charger, SENSOR_CHAN_NPM1300_CHARGER_STATUS, &status); sensor_channel_get(charger, SENSOR_CHAN_NPM1300_CHARGER_ERROR, &error); printk("V: %d.d I: %d.d T: %d.d Status: %d Error: %dn", volt.val1, volt.val2, current.val1, current.val2, temp.val1, temp.val2, status.val1, error.val1); } else if (strncmp(cmd, "SET_DVS ", 8) == 0) { int state = cmd[8] - '0'; if (state >= 0 && state <= 3) { regulator_parent_dvs_state_set(regulators, state); printk("Set DVS state to %dn", state); } else { printk("Invalid DVS staten"); } } else { printk("Unknown commandn"); } } void main(void) { if (!device_is_ready(uart_dev)) { printk("UART device not readyn"); return; } if (!device_is_ready(regulators)) { printk("Regulator device not readyn"); return; } if (!device_is_ready(charger)) { printk("Charger device not readyn"); return; } configure_uart(); printk("UART initializedn"); while (1) { if (rx_buf_pos > 0) { rx_buf[rx_buf_pos] = ' |
---|