Problème de programmation et de compilation.

Bonjour à tous, ça fait longtemps que je ne suis pas passé ici, mais je reviens car je rencontre un soucis de programmation pour mon projet.

Ce n’est peut être pas la meilleure section pour poser la question, mais je préfère demander sur la partie Francophone avant de vouloir tout reformuler en Anglais.

Donc je suis en train de me monter un rhéobus pour mon PC à base d’Arduino DUE.
J’ai déjà une bonne partie du code (fonctionnel), mais j’ai commencé sans connaître le fonctionnement des classes.
Maintenant, je cherche à optimiser le tout, car une grosse partie n’est en fait qu’une série de copier/coller avec des variables qui changent.
Pourquoi ne pas avoir utilisé une fonction générique ? Car je fait appel aux registres PWM et aux timers directement ( REG_PWM_CDTY…, etc… ) et que je ne savais pas vraiment comment faire autrement jusqu’à récemment.

Aujourd’hui ce que je veux faire, c’est écrire une classe générique que j’affecterais à chaque groupe de ventilation, qui possèdent leur propres caractéristiques ( channel PWM, Pin de sortie, etc… ).

Pour faire ça, je me suis inspiré ( sans réellement comprendre ce que je faisais ), de la librairie PWM de Antonio C. Domínguez Brito ( Désolé, pas de lien, mais elle est peut être sur le playground. )

En gros, j’ai un .h contenant ceci:

...
enum class _fan_name_: uint8_t {
	WATERPUMP1,
	WATERPUMP2,
	FRONT,
	TOP,
	CPU,
	HDD
};

template<_fan_name_ FAN> struct fan_capacities {};

#define fan_description(the_name,the_channel,the_pio,the_switch,the_led) \
template<> struct fan_capacities<the_name> \
{ \
	static constexpr const uint8_t channel = the_channel; \
	static constexpr const uint32_t pio_id = the_pio; \
	static constexpr const uint8_t switch_id = the_switch; \
	static constexpr const uint8_t led_id = the_led; \
};

//	       |        Nom	      | PWM Channel | PIO Channel | Switch pos | LED pos
fan_description(_fan_name_::WATERPUMP1,  PWM_CH6    ,   0x40000   ,    0x1	,    0);
fan_description(_fan_name_::WATERPUMP2,  PWM_CH5    ,   0x80000   ,    0x2	,    0);
fan_description(_fan_name_::FRONT     ,  PWM_CH2    ,   0x80      ,    0x4	,    4);
fan_description(_fan_name_::TOP       ,  PWM_CH0    ,   0x8       ,    0x10	,    3);
fan_description(_fan_name_::CPU       ,  PWM_CH3    ,   0x200     ,    0x8	,    1);
fan_description(_fan_name_::HDD       ,  PWM_CH1    ,   0x20      ,    0x20	,    2);

template<_fan_name_ FAN>
class FanCore2 {
private:
	int _minimal_hdw_speed;
	int _manual_speed1;
	int _manual_speed2;
	int _manual_speed3;
	uint8_t _fan_nb_pins;
	bool _on_error;

	void Activate () const;
	void Deactivate () const;
	void Start () const;
	void Stop () const;

public:
	using fan_info = fan_capacities<FAN>;
	enum _fan_type_ {
		UNUSED,
		SIMPLE_FAN,
		COMPUTER_FAN,
		PWM_FAN
	};

	FanCore2 ();

	void Set_Speeds ( int _minimal_, int _speed1_, int _speed2_, int _speed3_ );
	void Set_Pins ( uint8_t _type_ = UNUSED );
	void Set_Error ( bool _error_ = 0 );
	uint8_t Get_Mode () const;
	uint8_t Get_Pin () const;
	uint8_t Get_Pin_int () const;
	bool Get_Error_int () const;
	void Loop ();
};
...

et un .cpp construit de cette façon:

...
template<_fan_name_ FAN>
FanCore2<FAN>::FanCore2 () {}

template<_fan_name_ FAN>
void FanCore2<FAN>::Activate () const{}
...

Le code du .cpp n’est pas réellement important ici donc je n’ai mis que des brides.
Puis dans l’équivalent de mon main, j’ai le #include, et je fais des appels de cette façon:

FanCore2<_fan_name_::WATERPUMP1> Pompe1;		// Gestion de la pompe 1.
FanCore2<_fan_name_::WATERPUMP2> Pompe2;		// Gestion de la pompe 2.
FanCore2<_fan_name_::CPU> toto;				// Essai !

Le tout avant le void setup ().

Le soucis, c’est que le compilateur me gratifie de jolies remarques, et que je ne sais vraiment pas ce que ça veut dire.
En fait, les histoires de templates et struct, c’est vraiment un truc auquel je n’avais pas touché jusqu’à maintenant, et je n’ai fait que copier assez bêtement la structure de la librairie, en la modifiant à ma sauce.
Message du compilo.

/.../Gestion_ventil_due.ino:75: undefined reference to `FanCore2<(_fan_name_)0>::FanCore2()'
/.../Gestion_ventil_due.ino:76: undefined reference to `FanCore2<(_fan_name_)1>::FanCore2()'
/.../Gestion_ventil_due.ino:77: undefined reference to `FanCore2<(_fan_name_)4>::FanCore2()'
sketch/Gestion_ventil_due.ino.cpp.o: In function `setup':
/.../Gestion_ventil_due.ino:135: undefined reference to `FanCore2<(_fan_name_)0>::Set_Pins(unsigned char)'
/.../Gestion_ventil_due.ino:136: undefined reference to `FanCore2<(_fan_name_)0>::Set_Speeds(int, int, int, int)'
/.../Gestion_ventil_due.ino:137: undefined reference to `FanCore2<(_fan_name_)1>::Set_Pins(unsigned char)'
/.../Gestion_ventil_due.ino:138: undefined reference to `FanCore2<(_fan_name_)1>::Set_Speeds(int, int, int, int)'
sketch/Gestion_ventil_due.ino.cpp.o: In function `loop':
/.../Gestion_ventil_due.ino:169: undefined reference to `FanCore2<(_fan_name_)0>::Loop()'
/.../Gestion_ventil_due.ino:170: undefined reference to `FanCore2<(_fan_name_)1>::Loop()'
sketch/external_action.cpp.o: In function `FIRMWARE_to_VARS()':
sketch/external_action.cpp:409: undefined reference to `FanCore2<(_fan_name_)4>::Set_Pins(unsigned char)'
sketch/sensors.cpp.o: In function `TC7_Handler':
sketch/sensors.cpp:407: undefined reference to `FanCore2<(_fan_name_)0>::Set_Error(bool)'
sketch/sensors.cpp:416: undefined reference to `FanCore2<(_fan_name_)0>::Set_Error(bool)'
sketch/sensors.cpp.o: In function `TC8_Handler':
sketch/sensors.cpp:436: undefined reference to `FanCore2<(_fan_name_)1>::Set_Error(bool)'
sketch/sensors.cpp:446: undefined reference to `FanCore2<(_fan_name_)1>::Set_Error(bool)'
sketch/serialdial.cpp.o: In function `SERIAL_debug()':
sketch/serialdial.cpp:470: undefined reference to `FanCore2<(_fan_name_)0>::Get_Error_int() const'
sketch/serialdial.cpp:479: undefined reference to `FanCore2<(_fan_name_)1>::Get_Error_int() const'
collect2: error: ld returned 1 exit status
exit status 1
Erreur de compilation pour la carte Arduino Due (Programming Port)

En gros, ce qui revient, c’est le undefined reference to `FanCore2<(fan_name)0>::, mais comme je n’y comprends rien, c’est pas évident de solutionner le problème.

Petite précision, j’avais commencé en plaçant tout mon code dans le .h, mais comme je dois faire appel à une autre classe déjà instanciée dans le main, je ne peux pas placer de extern maclasse maclasse; dans l’en-tête du .h, d’où la séparation en deux fichiers.

Est-ce que quelqu’un aurait une solution, ou au moins une explication pour mon problème ?

En tout cas, merci d’avoir lu tout ce pavé…

Si je résume: :slight_smile:

Tu veux créer une bibliothèque,
en utilisant des templates
et des structures
en langage objet
pour un DUE
en programmant directement ses registres (PWM et TC)
C'est bien cela ?

Mais voila :

En fait, les histoires de templates et struct, c'est vraiment un truc auquel je n'avais pas touché jusqu'à maintenant, et je n'ai fait que copier assez bêtement la structure de la librairie, en la modifiant à ma sauce.

Quelque chose me dit qu'il faudrait commencer par étapes....à moins que je ne me trompe :o

Dis comme ça, ça ressemble à ton résumé.

Ce que je veux surtout, c’est me débarrasser de code dans le genre…

//BEGIN - Suivi de la température de l'échangeur frontal !
// Théorie : Différence de température entre échangeur et boîtier estimée à 2°C sans ventilation.
// Si la température est supérieure à 2°C, augmentation de la vitesse par pas de 1°C.
/* Exemple:
 * Température échangeur = 30°C, température boîtier = 29°C: 30 - 29 < 2 => Ventilateur éteint.
 * Température échangeur = 31°C, température boîtier = 29°C: 31 - 29 = 2 => Ventilateur vitesse 1 ( Vitesse minimale ).
 * Température échangeur = 33°C, température boîtier = 29°C: 33 - 29 = 4 = 2°C + 2 => Ventilateur vitesse 3 ( 1 + 2 ).
 */
	if ( ( FRONTF.GetMode () == FRONTF.AUTO ) && ( FRONTF.GetPin () != FRONTF.UNUSED ) && ( ( Is_HDW_forced & 0x4 ) == 0x0 ) ) {
		if ( ( DifferenceT ( FRONT_sensor_IN, BOX_sensor_adj ) < _FRONT_DELTAT ) && ( ( micros () - FRONT_speed_down ) > _FRONT_FREQ ) ) {
			// Arrêt total de la ventilation.
			if ( ( DEBUG == 1 )  && ( REG_PWM_CDTY2 != SPEED0 ) && ( Is_calib == 0 ) && ( FRONT_forced == 0 ) ) Serial.println ( INFOMSG_FRONT_AUTOSTOP );
			if ( ( REG_PWM_CDTY2 != SPEED0 ) && ( FRONT_forced == 0 ) ) REG_PWM_CDTYUPD2 = SPEED0;
		}
		else {
			for ( int _FRONT_step = nb_FRONT_step; _FRONT_step >= 0; _FRONT_step -- ) {
				if ( DifferenceT ( FRONT_sensor_OUT, BOX_sensor_adj ) >= FRONT_DELTA_temp [ _FRONT_step ] ) {
					uint8_t _invFRONT_step = map ( _FRONT_step, nb_FRONT_step, 0, 0, nb_FRONT_step );
					if ( ( REG_PWM_CDTY2 > FAN_speed [ _invFRONT_step ] ) && ( FRONT_forced == 0 ) && ( ( micros () - FRONT_speed_down ) > _FRONT_FREQ ) ) {
						// Réduction de la ventilation.
						REG_PWM_CDTYUPD2 = FAN_speed [ _invFRONT_step ];
						FRONT_speed_down = micros ();
					}
					if ( REG_PWM_CDTY2 < FAN_speed [ _invFRONT_step ] ) {
						// Augmentation de la ventilation.
						REG_PWM_CDTYUPD2 = FAN_speed [ _invFRONT_step ];
						FRONT_speed_down = micros ();
					}
					break;
				}
			}
		}
	}
//END

//BEGIN - Suivi de la température de l'échangeur arrière !
// Théorie : Différence de température entre échangeur et boîtier estimée à 2°C sans ventilation.
// Si la température est supérieure à 2°C, augmentation de la vitesse par pas de 1°C.
/* Exemple:
 * Température échangeur = 30°C, température boîtier = 29°C: 30 - 29 < 2 => Ventilateur éteint.
 * Température échangeur = 31°C, température boîtier = 29°C: 31 - 29 = 2 => Ventilateur vitesse 1 ( Vitesse minimale ).
 * Température échangeur = 33°C, température boîtier = 29°C: 33 - 29 = 4 = 2°C + 2 => Ventilateur vitesse 3 ( 1 + 2 ).
 */
	if ( ( CPUF.GetMode () == CPUF.AUTO ) && ( CPUF.GetPin () != CPUF.UNUSED ) && ( ( Is_HDW_forced & 0x8 ) == 0x0 ) ) {
		if ( ( DifferenceT ( CPU_sensor_IN, BOX_sensor_adj ) < _GPU_DELTAT ) && ( GPU_USB_piloted == 0 ) && ( ( micros () - GPU_speed_down ) > _GPU_FREQ ) ) {
			// Arrêt total de la ventilation.
			if ( ( DEBUG == 1 )  && ( REG_PWM_CDTY3 != SPEED0 ) && ( Is_calib == 0 ) && ( GPU_forced == 0 ) ) Serial.println ( INFOMSG_GPU_AUTOSTOP );
			if ( ( REG_PWM_CDTY3 != SPEED0 ) && ( GPU_forced == 0 ) ) REG_PWM_CDTYUPD3 = SPEED0;
		}
		else {
			for ( int _GPU_step = nb_GPU_step; _GPU_step >= 0; _GPU_step -- ) {
				if ( DifferenceT ( CPU_sensor_OUT, BOX_sensor_adj ) >= GPU_DELTA_temp [ _GPU_step ] ) {
					uint8_t _invGPU_step = map ( _GPU_step, nb_GPU_step, 0, 0, nb_GPU_step );
					if ( ( REG_PWM_CDTY3 > FAN_speed [ _invGPU_step ] ) && ( GPU_forced == 0 ) && ( GPU_USB_piloted == 0 ) && ( ( micros () - GPU_speed_down ) > _GPU_FREQ ) ) {
						// Réduction de la ventilation.
						REG_PWM_CDTYUPD3 = FAN_speed [ _invGPU_step ];
						GPU_speed_down = micros ();
					}
					if ( REG_PWM_CDTY3 < FAN_speed [ _invGPU_step ] ) {
						// Augmentation de la ventilation.
						REG_PWM_CDTYUPD3 = FAN_speed [ _invGPU_step ];
						GPU_speed_down = micros ();
						GPU_arduino_piloted = 1;				// L'ARDUINO redevient prioritaire sur l'USB !
						GPU_USB_piloted = 0;
						GPU_speed_down = micros ();
					}
					break;
				}
			}
		}
	}
//END

Et ce n’est qu’une partie, mais comme ça y ressemble fort pour les autres channel PWM.

Les FRONTF.GetMode () == FRONTF.AUTO sont des brides d’un premier essai de classe fonctionnel.
Au départ, c’était surtout des variables globales à la totalité du code. Ensuite, je suis passé sur une première classe, mais pour connaître quel registre utiliser je faisais une utilisation trop importante de switch et de if à mon goût.
Maintenant, ce que je veux, c’est qu’en fonction du nom de classe ( ou un truc simple ), le code sache de suite quel registre PWM utiliser, d’où l’utilisation d’une structure comme je l’avais vu dans cette librairie.
Seulement dans la librairie, l’auteur utilise des namespace. Encore un truc que je ne maîtrise pas, et dont je ne suis pas certain d’avoir besoin ici.

hello
le lien vers le github de Mr Antonio C Dominguez Brito

Il y a ICI un tutoriel très bien fait pour apprendre à écrire du code en langage objet pour un arduino. Et pour savoir manipuler les templates, structures et autres namespace, il y a openclassroom.com.

@Dfgh, merci pour le lien. Je n’ai pas cru utile de le placer dans la discussion.

@Ard_newbie, je viens de survoler ton lien, je le lirai à tête reposé car c’est de l’Anglais et ça me demande un minimum d’effort de compréhension.
Si j’ai bien percuté, c’est pour expliquer comment écrire une classe. Je n’irai pas dire que je suis un pro dans ce domaine, mais je crois avoir déjà pigé le minimum pour en écrire une simple. C’est juste que je n’aime pas en écrire une juste par plaisir de le faire. Je le fais quand je juge cela utile.
Pour Openclassroom.com, j’ai un peu regardé ce week-end, je pense avoir saisi le sens des mots, mais passer d’une application simple comme ils ont à un truc tout imbriqué, c’est une autre histoire.

Pourtant j’ai la certitude de ne pas être loin.

Si je comprends bien les grommellements de mon compilateur, il n’arrive pas à trouver les ‘fonctions’ listées dans la déclaration de la classe.

Si je prends la première erreur, undefined reference to `FanCore2<(fan_name)0>::FanCore2()’ il ne trouve pas la référence au constructeur.
Le 0 ( qui change suivant l’erreur ) prouve qu’il fait bien appel au ENUM class fan_name. Ce qui aurait tendance à me chagriner ici, c’est les parenthèses à <(fan_name)0>. Je ne sais pas pourquoi elles sont ici. Mais c’est peut être normal.

Après, je ne sais pas si j’ai correctement posé ma question.
Je pense que l’utilisation d’une structure est un bon moyen d’arriver à mes fins, mais s’il y as d’autres méthodes plus simples d’utilisation, je suis preneur.

Bon, je me réponds à moi même...

Après avoir fait des recherches, on ne peut pas scinder un template en plusieurs fichiers. Donc j'ai du écrire les fonctions de ma classe directement dans le .h comme je l'avais fait au départ.

J'ai aussi à tout hasard modifié les noms d'instances de mes classes pour qu'ils ne soient plus identiques au nom de classe elle même.

Plus important, je ne pouvais pas faire appel à une classe déjà instanciée depuis le .h. La compilation plantait, ce qui m'avait conduit à vouloir scinder mon template en un .h et un .cpp comme je le faisais pour n'importe quelle classe.
J'ai trouvé un truc tellement bête sur le net que je n'y aurait jamais pensé.

...
class une_classe {
...
...
}

extern une_classe brillant;
...

Il suffit juste de donner une référence externe à l'instanciation de la classe que l'on vient de créer dans le .h contenant la classe.
Chaque appel par le #include ".h" dans un fichier créera le lien.

Donc problème résolu, et merci à ceux qui ont cherché à m'aiguiller.