Partie II:
Salutations!
Aujourd'hui, on va commencer à dresser un plan, et choisir des conventions. Ça veut dire qu'on va définir les interfaces qui nous permettront d'utiliser les composants de la T-Watch depuis le code
de manière unifiée et efficace.
Si je fait ça maintenant, c'est parce qu'avant de faire n'importe quoi, il faut absolument se faire un chemin. Si on ne sait pas ou on va, on à tendance à tourner en rond et c'est méga chiant. Du
coup, pour que vous me suiviez, j'ai fait un schéma:
l'idée derrière tout ça c'est de rendre les données accessibles, le tout de manière homogène. On abstrait la gestion des périphériques, et on donne accès à des fonctions spécifiques via une structure organisée.
En faisant comme ça, on s'offre la possibilité de modifier les structures indépendamment des autres. Par exemple, si on décide de changer la partie qui gère l'affichage, on aura qu'a brancher la
structure sur un autre driver, et tout le code qui utilisait déjà l'affichage n'y verra que du feu.
Pour mettre en place le plan, on va utiliser les types, et les structures. On va donc commencer par le haut du schéma, et coder les fonctions utiles à chaque
composant. Une fois que c'est fait, on les intègre à une structure qui va nous permettre d'accéder à ces fonctions via l'application finale.
Driver
Dans l'exemple du AXP202, le "driver" à proprement parler, ça sera les fonctions spécifiques au composant, et les fonctions read/write. Les fonctions spécifiques dont je parle, ce sont celles décrite dans la documentation du composant. On aura pas besoin de tout coder, parce qu'on aura pas forcément besoin de toutes les fonctions sur tous les composants. On essaye de faire quelque-chose de léger, et extensible, alors on va se focaliser sur l'essentiel.
Objet
L'objet, toujours pour notre AXP202, sera une structure permettant de donner accès à chacune des fonctions du driver. En C, ça ressemblera à ça:
typedef struct {
// variables utiles au driver
esp_err_t init;
esp_err_t status;
// Fonctions génériques
esp_err_t (*writeCb)(uint8_t reg, uint8_t nbytes, uint8_t *data);
esp_err_t (*readCb)(uint8_t reg, uint8_t nbytes, uint8_t *data);
// Fonctions spécifiques
esp_err_t (*setPowerOutPut) (uint8_t ch, bool en);
esp_err_t (*getChargeControlCur)( uint8_t *charge);
esp_err_t (*setChargeControlCur)( uint16_t ma);
} power_t
Ça c'est juste un exemple. On verra comment mettre ça en forme dans la partie "Conventions" qui suit.
Contexte
// Exemple de contexte
typedef struct {
esp_err_t init;
esp_err_t status;
display_t *Display;
power_t *Power;
/*
[...]
*/
} twatch_t;
Si on fait tout ça, c'est pour rendre les données et E/S accessibles. Si vous êtes entrain de vous dire qu'on pourrait parfaitement se passer du contexte, vous avez en partie raison, mais, dans ce cas précis, ça va nous intéresser parce que les applications qu'on va vouloir développer sur la T-Watch vont nécessiter une certaine organisation. Il est bien plus facile de maîtriser son code quand on peut contrôler les données, et quand elles sont bien organisées.
Convention
Pour ma part, pour le nommage, j'aime bien utiliser le CamelCase, et le snake_case conjointement, mais, ça on s'en fou un peu, c'est de la rhétorique. Ce qui va surtout falloir décider, c'est comment les nommer fonctionnellement. Comme je suis entrain de faire un truc spécifique à la T-Watch, je vais nommer mon contexte
TWatch
et,
chacun de ses périphérique aura un nom évocateur. Par exemple, l'écran et tout ce qui s'y rapporte sera Display
.
// Exemple de contexte
typedef struct {
esp_err_t init;
esp_err_t status;
display_t *Display;
power_t *Power;
acc_t *Acc;
clock_t *Clock;
vibrator_t *Vibrator;
speaker_t *Speaker;
ir_t *IR;
} twatch_t;
1. Créer une structure statique.
2. Créer une structure dynamique.
C'est très avantageux parce qu'on aura pas à allouer quoi que ce soit. En fait, on aura une structure vide, qu'on remplira petit à petit avec des objets qu'on allouera uniquement à l'initialisation. On pourra très bien désallouer l'écran, pour réserver la mémoire à une utilisation ponctuelle, puis le ré-allouer dans la foulée.
twatch_t TWatch = {null};
power_t *powerInit( void ){
power_t *object = heap_caps_malloc(sizeof(power_t), MALLOC_CAP_8BIT);
return object;
}
power_t *power = powerInit();
...
TWatch.Power = power;
...
free(TWatch.power);
Ça m'a l'aire pas mal pour le moment. Si vous avez des interrogations, des critiques, ou des coquilles à remonter, n'hésitez pas à me pinger sur twitter!
La prochaine fois, je vous en dirai plus, et on commencera à faire les choses correctement. Tout ce qui nous reste à faire, c'est lire plus de doc, lire et écrire plus de doc, et tout ranger.
La suite au prochain article
HppHckng!