Partie I:
Aujourd'hui on parle du driver AXP202, le gestionnaire d'alimentation intégré à la TWatch2020. On va commencer à coder un driver et une interface pour lui, et lire un peu de doc/code.
Présentation et analyse
Alors; l'AXP202 est un gestionnaire d'alimentation. C'est un composant qui est là pour gérer la charge de la batterie, les sources de courant, et
l'alimentation de certaines parties de la montre. Sa documentation est disponible ici.
Je n'ai pas remarqué tout de suite qu'il était la pièce maitresse de la TWatch, mais, bien vite, j'ai vu que l'écran lui-même, avait son rétro-éclairage conditionné par ce composant. Pour tout vous
dire, c'est la première fois que je touche à ce truc. En lisant la doc, je me suis vite rendu compte que c'était un composant très complet, et, plutôt complexe, qui permet d'abstaire et de gérer
absolument tout ce qui touche à l'alim...
Il est controlé via i2c et sa documentation nous en dit plus. Notament, Cinq LDO (des
régulateurs de tension linéaires), deux alimentations DC/DC Buck, et tout un arsenal d'interfaçage et de gestion. Je ne vais pas vous refaire la
doc, mais, je vous ai sélectionné deux morceaux:
Notre driver d'AXP202
Je ne vous ai pas parlé des conventions, ni même du détail de quoi que ce soit, mais, on peut déjà commencer à dégrossir le boulot en regardant du côté de l'I2C. L'AXP202 est entièrement contrôlé via ce protocole, et, si on veut pouvoir le configurer, il nous faut maîtriser la communication entre notre ESP32 et l'AXP202.
Pour utiliser l'I2C, on va se baser sur les recommandations du fondeur (espressif) puis-ce qu'on utilise ses outil. Dans mon cas, j'ai potassé le guide d'Espressif, et les exemples. J'ai aussi fait quelque recherches pour avoir plus de code à lire.
Bon, en vrai, j'ai déjà pas mal utilisé l'I2C avec l'ESP-IDF, donc j'y suis allé un peu au feeling, en gardant toujours un oeil sur le code source "officiel" de la montre. (je vous préviens, c'est de l'arduino pourri du cul)
Grosso-modo, on va devoir lui envoyer des octets, et en recevoir. Le but de tout ça, c'est charger et lire des registres qui correspondent aux configurations.
Lire et écrire
Pour pouvoir lire et écrire les registres de ce AXP202, j'ai commencé par écrire trois fonctions qui vont servir à gérer la communication:
void i2c_master_init()
{
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = AXP202_SDA_PIN,
.scl_io_num = AXP202_SCL_PIN,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400000
};
i2c_param_config(AXP202_I2C_NUM, &i2c_config);
i2c_driver_install(AXP202_I2C_NUM, I2C_MODE_MASTER, 0, 0, 0);
}
void axp202_i2c_read(uint8_t reg, uint8_t nbytes, uint8_t *data)
{
esp_err_t espErr;
i2c_cmd_handle_t cmd;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (AXP202_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (AXP202_I2C_ADDRESS << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, data , I2C_MASTER_NACK);
i2c_master_stop(cmd);
espErr = i2c_master_cmd_begin(AXP202_I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
assert(espErr==ESP_OK);
i2c_cmd_link_delete(cmd);
}
void axp202_i2c_write(uint8_t reg, uint8_t nbytes, uint8_t *data)
{
esp_err_t espErr;
i2c_cmd_handle_t cmd;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (AXP202_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg, true);
i2c_master_write_byte(cmd, *data, true);
i2c_master_stop(cmd);
espErr = i2c_master_cmd_begin(AXP202_I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
assert(espErr==ESP_OK);
i2c_cmd_link_delete(cmd);
}
Je me suis largement basé sur le code fourni. Je me suis contenté de le porter en intégrant les fonctions axp202_i2c_read et axp202_i2c_write.
Ces trois fonctions vont évoluer au fil du développement, mais, leur fonction restera la même: abstraire la communication avec l'AXP202, et de fournir une base d'interface
- Initialiser un port I2C
- Lire un registre (uint8_t)
- Écrire dans un registre (uint8_t)
Contrôler les registres
Maintenant qu'on a abstrait la communication avec le composant, on va pouvoir commencer à abstraire ses fonctions. Quelles fonctions? Celle de la doc, mais, aussi
et surtout celles qu'on a repéré dans le code officiel.
En effet, on a pas le schéma de la TWatch, mais, par contre, on a tout le code fourni qui va nous permettre d'en savoir bien plus.
Dans le fichier "TTGO_TWatch_Library/src/TTGO.h" du code officiel, on peut jeter un oeil aux fonctions appelées pour l'initialisation des composants. En particulier, dans les fonctions "powerOff" et "initTFT", on trouve quelques indices:
// File: TTGO_TWatch_Library/src/TTGO.h
void powerOff()
{
#ifndef LILYGO_WATCH_2020_V1
power->setPowerOutPut(AXP202_EXTEN, false);
power->setPowerOutPut(AXP202_LDO4, false);
power->setPowerOutPut(AXP202_DCDC2, false);
power->setPowerOutPut(AXP202_LDO3, false);
power->setPowerOutPut(AXP202_LDO2, false);
#else
power->setPowerOutPut(AXP202_EXTEN, false);
power->setPowerOutPut(AXP202_LDO4, false);
power->setPowerOutPut(AXP202_DCDC2, false);
power->setPowerOutPut(AXP202_LDO3, false);
// power->setPowerOutPut(AXP202_LDO2, false);
#endif /*LILYGO_WATCH_2020_V1*/
}
...
void initTFT()
{
#ifdef LILYGO_WATCH_HAS_DISPLAY
#ifdef LILYGO_WATCH_2020_V1
//In the 2020V1 version, the ST7789 chip power supply
//is shared with the backlight, so LDO2 cannot be turned off
power->setPowerOutPut(AXP202_LDO2, AXP202_ON);
#endif
...
Bon, déjà on peut voir que les gens qui font de l'arduino ne respectent rien, et mettent du code Cpp dans des fichiers .h, BREF
Mais au delà de ça, on apprend que l'écran (TFT) est alimenté via l'un des régulateurs linéaires de l'AXP. En gros, tant qu'on a pas géré ce régulateur, l'écran ne fonctionnera pas
(puis-ce qu'il ne sera pas alimenté). On se rend compte que la fonction setPowerOutPut est importante, et plutot générique, et ça, c'est un indice qui nous dit qu'on va devoir la
porter.
C'est parfait pour notre exemple, on va pouvoir coder une fonction setPowerOutPut qui nous servira d'ersatz C. Voilà ce que j'ai fait pour ma part:
void axp202_setPowerOutPut (uint8_t ch, bool en)
{
uint8_t data;
uint8_t val = 0;
axp202_i2c_read(AXP202_LDO234_DC23_CTL, 1, &data);
if (en) {
data |= (1 << ch);
} else {
data &= (~(1 << ch));
}
axp202_i2c_write(AXP202_LDO234_DC23_CTL, 1, &data);
vTaskDelay(800/portTICK_PERIOD_MS);
axp202_i2c_read(AXP202_LDO234_DC23_CTL, 1, &val);
if (data == val) {
// Debug nul à virer une fois dégrossi
ESP_LOGI(tag, " Set power output: %d %d %d ", ch, val, data);
}
}
Je me suis largement basé sur le code fourni. Je me suis contenté de le porter en intégrant les fonctions axp202_i2c_read et axp202_i2c_write
Il est temps de laisser reposer le code, et de commencer à dresser un plan plus précis concernant l'interface que l'on veut fournir.
La suite au prochain article!
On dressera un plan et une stratégie pour commencer à tout intégrer proprement.
HppHckng!