引言
本文仅提供方案的关键点,有人需要的话再写详细的操作指南和源代码。
本人近日在Robomaster中需要做一个飞镖内部的小型的供电模块,由一个电池驱动,通过有线或无线方式充电,由ESP32C3提供远程开关功能,供飞镖头运作。
方案中采用ESP32C3挂载I2C总线,总线挂载IP5306_I2C和NU1680。IP5306用于锂电池线性充电、5V Boost和开关使能,NU1680用于无线充电RX。
IP5306使用手记
由于IP5306这个芯片是出了名的有各种bug的芯片,故提供我队的驱动思路。
IP5306白片存在两大问题:
- 仅支持按键操作,单击开启Boost,双击关闭Boost;
- 开启Boost后在轻载条件下会不定时关断。
- 关闭Boost后会有2V左右的浮动电压。
所以我们使用了IP5306_I2C版本,这个版本的IP5306可以通过I2C寄存器进行开关/常开/屏蔽按键操作,通过一些内部寄存器还可以获得电池电量(0-25-50-75-100)的信息,大大便利了单片机进行远程开关的操作。
长期调试后我们这样实现该芯片的开关控制:
- 系统上电(电池上电/5V上电)后通过KEY引脚发送一个单脉冲,强制启动5V Boost输出,否则IP5306不会自动识别负载,所有I2C寄存器都处于浮空状态,读取出来的数据可以说是随机的。
- 上电初始化I2C寄存器完成之后就可以直接操作0x00(Sys_Ctrl0)寄存器的Boost_Enable位进行开断使能操作,Key引脚只需要上拉,不需要任何操作。
- 通过读取IP5306的INT引脚判断是否成功开断。INT为高时认为BOOST已开启,为低时认为BOOST已关断。
关键驱动代码片段:
// GPIO for charging status
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 1;
io_conf.pin_bit_mask = (1ULL << POWER_KEY);
gpio_config(&io_conf);
// Set default level
gpio_set_level(POWER_KEY, 1);
// INT_5306
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << INT_5306);
gpio_config(&io_conf);
// IP5306
ESP_LOGI(TAG, "Powering on");
gpio_set_level(POWER_KEY, 1);
gpio_set_level(POWER_KEY, 0);
// wait for 50ms
vTaskDelay(50);
gpio_set_level(POWER_KEY, 1);
vTaskDelay(2000);
gpio_reset_pin(POWER_KEY);
IP5306_setup();
其他驱动代码参考:
// ================ Power IC IP5306 ===================
#define IP5306_ADDR (0x75)
#define IP5306_REG_SYS_CTL0 (0x00)
#define IP5306_REG_SYS_CTL1 (0x01)
#define IP5306_REG_SYS_CTL2 (0x02)
#define IP5306_REG_Charger_CTL0 (0x20)
#define IP5306_REG_Charger_CTL1 (0x21)
#define IP5306_REG_Charger_CTL2 (0x22)
#define IP5306_REG_Charger_CTL3 (0x23)
#define IP5306_REG_CHG_DIG_CTL0 (0x24)
#define IP5306_REG_READ0 (0x70)
#define IP5306_REG_READ1 (0x71)
#define IP5306_REG_READ2 (0x72)
#define IP5306_REG_READ3 (0x77)
// REG_CTL0
#define BOOST_ENABLE_BIT (0x20)
#define CHARGE_OUT_BIT (0x10)
#define BOOT_ON_LOAD_BIT (0x04)
#define BOOST_OUT_BIT (0x02)
#define BOOST_BUTTON_EN_BIT (0x01)
//- REG_CTL1
#define BOOST_SET_BIT (0x80)
#define WLED_SET_BIT (0x40)
#define SHORT_BOOST_BIT (0x20)
#define VIN_ENABLE_BIT (0x04)
#define LOWPOWER_SHUTDOWN_BIT (0x01)
//- REG_CTL2
#define SHUTDOWNTIME_MASK (0x0c)
#define SHUTDOWNTIME_64S (0x0c)
#define SHUTDOWNTIME_32S (0x04)
#define SHUTDOWNTIME_16S (0x08)
#define SHUTDOWNTIME_8S (0x00)
//- REG_READ0
#define CHARGE_ENABLE_BIT (0x08)
//- REG_READ1
#define CHARGE_FULL_BIT (0x08)
//- REG_READ2
#define LIGHT_LOAD_BIT (0x20)
#define LOWPOWER_SHUTDOWN_BIT (0x01)
//- CHG
#define CURRENT_100MA (0x01 << 0)
#define CURRENT_200MA (0x01 << 1)
#define CURRENT_400MA (0x01 << 2)
#define CURRENT_800MA (0x01 << 3)
#define CURRENT_1600MA (0x01 << 4)
#define BAT_4_2V (0x00)
#define BAT_4_3V (0x01)
#define BAT_4_3_5V (0x02)
#define BAT_4_4V (0x03)
#define CHG_CC_BIT (0x20)
#define LIGHT_LOAD_BIT (0x20)
// Add voltage marcros
#define ADD_VOLTAGE_0MV (0x00)
#define ADD_VOLTAGE_14MV (0x01)
#define ADD_VOLTAGE_28MV (0x02)
#define ADD_VOLTAGE_42MV (0x03)
/**
* @brief Read a byte from a IP5306 register
*/
static esp_err_t ip5306_register_read(uint8_t reg_addr, uint8_t *data)
{
i2c_master_write_read_device(I2C_MASTER_NUM, IP5306_ADDR, ®_addr, 1, data, 1, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "Read from register 0x%02x: 0x%02x", reg_addr, *data);
return ESP_OK;
}
/**
* @brief Write a byte to a IP5306 register
*/
static esp_err_t ip5306_ADDR_register_write_byte(uint8_t reg_addr, uint8_t data)
{
uint8_t write_buf[2] = {reg_addr, data};
return i2c_master_write_to_device(I2C_MASTER_NUM, IP5306_ADDR, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}
int8_t getBatteryLevel()
{
uint8_t data;
ip5306_register_read(IP5306_REG_READ3, &data);
switch (data & 0xf0)
{
case 0x00:
return 100;
case 0x80:
return 75;
case 0xC0:
return 50;
case 0xE0:
return 25;
default:
return 0;
}
}
esp_err_t setBatteryVoltage(uint8_t voltage_marco, uint8_t add_voltage_marco)
{
uint8_t data;
ip5306_register_read(IP5306_REG_Charger_CTL2, &data);
ESP_LOGI(TAG, "1BatteryVoltage Read from register 0x%02x: 0x%02x", IP5306_REG_Charger_CTL2, data);
data = (data & 0xf0) | (voltage_marco) | (add_voltage_marco);
ip5306_ADDR_register_write_byte(IP5306_REG_Charger_CTL2, data);
return ESP_OK;
}
esp_err_t setVinMaxCurrent(uint8_t current_marco)
{
uint8_t data;
ip5306_register_read(IP5306_REG_CHG_DIG_CTL0, &data);
ip5306_ADDR_register_write_byte(IP5306_REG_CHG_DIG_CTL0, (data & 0xe0) | current_marco);
return ESP_OK;
}
esp_err_t IP5306_setup()
{
uint8_t data;
ip5306_register_read(IP5306_REG_SYS_CTL0, &data);
ip5306_ADDR_register_write_byte(IP5306_REG_SYS_CTL0, (data & 0x50) | BOOST_ENABLE_BIT);
ip5306_register_read(IP5306_REG_SYS_CTL1, &data);
ip5306_ADDR_register_write_byte(IP5306_REG_SYS_CTL1, (data & 0x1D));
setBatteryVoltage(BAT_4_2V, ADD_VOLTAGE_28MV);
setVinMaxCurrent(CURRENT_800MA | CURRENT_200MA);
return ESP_OK;
}
void switch_power(bool on)
{
ESP_LOGI(TAG, "Switching power %s", on ? "on" : "off");
uint8_t data;
if (on)
{
ip5306_register_read(IP5306_REG_SYS_CTL0, &data);
ip5306_ADDR_register_write_byte(IP5306_REG_SYS_CTL0, (data & 0xD0) | BOOST_ENABLE_BIT);
}
else
{
ip5306_register_read(IP5306_REG_SYS_CTL0, &data);
ip5306_ADDR_register_write_byte(IP5306_REG_SYS_CTL0, (data & 0xD0));
}
}
NU1680使用手记
使用NU1680需要注意的点有散热、EN使能脚、谐振电容选取和线圈选取。
散热条件不好的板子可以通过VOUT端的电容值修改系统功率。5V1A会比较容易发热,5V0.7A较理想。
EN使能脚不使用可以悬空或接地。如果需要电源选择电路,绘制电路的时候注意MOS管体二极管的方向、GDS三极的电压,如果MOS管偏置不当,可能会导致EN脚异常拉高,导致无法供电。
我们在使用NU1680时遇到的最大问题就是线圈的选型。
经询问代理商和我们自己进行的测试,我们发现NU1680可以用10-16uH的线圈,如果遇到无法供电的情况,可能是TX线圈和RX线圈的耦合系数过低,导致TX线圈不能足功率发射。可以通过更换TX线圈、添加隔磁片、提高线圈中心重合度来保证供电。
为提高系统效率,最好自行计算谐振电容。计算选取的频率是110kHz,电容的封装可以按推荐选择0402,也可以自行选择0603等封装。
选取线圈最好使用品质因数高、内阻小的线圈,以进一步提高输电效率,减少发热。