Autres Realisation d'un system de filetage par µC

simon74
Compagnon
3 Mai 2016
963
Savoie
  • Auteur de la discussion
  • #1
J'avait commencé cela en MP avec @vibram, mais je pense que ça peut être intéressant pour un public plus large, donc je le publie directement en fil séparé.

Je m'excuse d'avance pour des fautes d'orthographe et surtout de grammaire, ainsi que des éventuels anglicismes. Je suis ce que je suis.

On va parler du programmation d'un système de filetage pour tours pas équipées de vis mère, utilisant les uC STM32, particulièrement les STM32F103 trouvable sous le nom de "Blue Pill", "Maple Mini" ect. Je ne parlerai pas de Arduino, même si, d'un façon non-officiel, y a du
support pour ces cartes sur Arduino (gogol pour STM32Duino). Les exemples en C sera centre atour le HAL de ST Microsystems et non pas
autour de Arduino - par contre, ça sera probablement possible de les convertir en libraire Arduino. On risque même d'avoir des exemple en Forth ou Scheme.

Ceci ne sera pas un truc "clefs en main", ce n'est pas mon truc. Ca resterai plus un explication de façon d'approcher un problème. "Cut and paste" n'est pas mon truc non plus, je déconseille fortement de le faire. Franchement, même le code ne sera pas un but, (ce qui fait que le "réalisation" dans le titre de ce fil soit peut-être exagéré) c'est plus un explication des décisions que je prendrai en développant un système de ce type.

En "conceptuel" de l'informatique, y en a pas une seule façon de faire les choses correctement, mais plein de façons de faire incorrectement. Ceci sera un explication de ce que je considère être un des façons de faire, en espérant que ça sera aussi "correcte".

Pour suivre, il en faut quelques trucs. Déjà, il en faut RM0008 de chez ST, la manuel de référence de ces processeurs. Mon copie est version 13, de 2011, autres versions peut être different en nombre de page, ect. A cote de ça, il sera probablement bien d'en avoir le code du HAL, comme référence (par exemple, ici https://github.com/rpavlik/xpacks-stm32f1-hal)

Si le manque d'Arduino gêne (et j'espère que ça ne sera pas le cas), je laisse des modos trancher. Je ne veut surtout pas créer de polémique, et je demanderai même d'avoir des messages de ce type consigné a la bac a copeaux.
 
Dernière édition:
simon74
Compagnon
3 Mai 2016
963
Savoie
  • Auteur de la discussion
  • #3
Descriptif Generale

On va parler d'une système avec un codeur sur un broche tournant, un moteur pas a pas qui tournerai une vis mère (qui pourrait être une vis mère séparé, le vis d'un chariot, ou autre), quelques boutons ou autre inputs, et éventuellement un écran LCD.

Le but sera de faire tourner la vis mère a la bonne vitesse par rapport a vitesse de la broche pour que un outil pousse par ce vis dessine un pas de vis sur la pièce tenu sur / dans la broche. On doit avoir la possibilité de définir le pas de ce vis, ainsi que sa longueur.

En termes de matériel, mon ami @vibram est équipé de:
  • Carte embarque stm32f103 "blue pill"
  • Un codeur qui génère du quadrature.
  • Moteur PaP avec driver avec deux entrées - "direction" et "step"
  • Quelques boutons
  • Un écran LCD, qui sera géré par un libraire existant.
Pour ma réalisation perso, j'ai plus ou moins la même, mais mes drivers PaP sont a 4 entrées (les H-bridge de base), et nécessite la génération des signaux de commande "low level" des moteurs, mais la problème est plus ou moins la même. On discutera des deux.
 
simon74
Compagnon
3 Mai 2016
963
Savoie
  • Auteur de la discussion
  • #6
Quelques Concepts de Base

(ou, «la ou ça risque de froisser quelques uns»)

Il ne faut pas oublier qu'on vas créer une système de contrôle "real time"; une fois attelée sur un tour de type schaublin les erreurs peut entrainer les dégâts très lourds, dangereux pour l'opérateur, et très cher a réparer. Ce n'est pas notre bon vieux Microsoft Windows, "turn it off and turn it on
again". On n'as pas le droit de louper des signaux de commande, ni de se tromper dans les signaux de commande. Notre programme doit être
solide, correct. Parfait n'existe pas, mais faut s'y approcher.

En programmation, peu importe la langue utilise ou la plate-forme ciblée, on produit en moyen 1 erreur tous les 10 lignes de code. Nous,
les programmeurs, va te dire que non, mais on sur-estime quotidiennement nos capacités - en réalité on est tous touche par ce "règle". Donc 10x moins de code écrit, c'est 10x moins d'erreurs a corriger.

Réduire les lignes de code, réduit aussi (en général) le temps d'exécution de ce code. Surtout dans les parties de code critique a l'interception et action sur les signaux de commande, la vitesse d'exécution c'est d'un importance capital.

Moins de code, c'est aussi un program moins "lourd" en taille, important sur un microcontroleur avec des capacités assez restreints.

Ca a été dit ailleurs, que il ne faut pas tripoter avec des valeurs de type "float". Mais la raison cité était, pour moi, complètement fausse. On n'est plus a l'épopée des processeurs 8-bit tel que les 8051 (perso, j'ai commence sur les 4040 et 6502 dans les années '80) ou les floats étaient énormément difficile et impensable, voir impossible, a utiliser. Maintenant ils existants, "bonne marche", des uCs avec unité FP, séparé du processeur proprement dit, pour lesquels les floats sont plus vite calcule que les integers. Ce n'est pas le cas pour les STM32F1xx, mais les STM32F4xx... La problème avec les floats en ça cas particulier est l'exactitude. Y a beaucoup de matière traitent ce sujet, mais suffit de dire que les valeurs float ne sont rarement exactes, et les comparaisons directes de style
Code:
0.1f == 1f / 10f
produise souvent les résultats peu attendus, c'est a dire false.

Pour éviter tout cela, les mathématiques on va utiliser tournent autour des rapports - vitesse broche vs vitesse vis mère, ect, et peut (pour ne pas dire
"doit") être représenté d'un façon exacte comme le rapport entre deux nombres intégrales. Faut comprendre que cela est dictée par les nécessités et spécificités de l'application et pas par raison de performance. Y en a des applications, même en embarqué, ou l'utilisation des floats sera tout a fait justifié.
 
simon74
Compagnon
3 Mai 2016
963
Savoie
  • Auteur de la discussion
  • #9
Quelques Contraintes Physiques

Ce système as, quand même, une partie physique, et les contraintes de ce partie "hardware" ont des implications pour la partie "software".

Ca sera facile a considérer que les longueurs de filetage a faire peut être traite uniquement par le program. Ca sera, selon moi, un erreur assez grave. Tout erreur de code, tout signal manque, tout impulsion loupé par le moteur PaP, peut entrainer un filetage "bourré", ce qui peut entrainer un "crash" de l'outil, et des dégâts matériels. Pour moi, les butées doit être physique, ce qui va dire butées de début et fin de filetages, ainsi que (éventuellement) fin de course de vis mère.

J'ai mentionné les "pas manquées". Les moteurs PaP ne sont pas, malgré leurs apparences, garanti de faire un pas chaque fois ils recevants l'impulsion correct. Ils ont des vitesses maximales qui ne peut être dépassé, et bien sur ils ont de l'inertie, ce qui implique des courbes d'accélération et décélération - on doit suivre ces courbes pour éviter les "pas manquées". Ils ont aussi un couple limité, qui peut occasionner le manque d'un, plusieurs, ou même tous les commandes envoyées. Si on veut traiter les PaP comme des accessoires "fire and forget", sans avoir besoin d'un codeur supplémentaire sur la vis mère, il faut bien gérer notre tir, et bien choisir notre matériel. On en parlera plus quand on traite la génération des signaux de pas.

Le course des chariots Schaublin (ou autres de ce type) étant assez limite, il sera bien de pouvoir faire un repérage de pas "manuel" en cas de filetage longue nécessitant le déplacement de chariot. Mais cela implique une autre problème, celle du commander la sorti d'outil de la pièce en milieu de filetage, donc je vais, pour l'instant, consigner cette idée a la poubelle.
 
simon74
Compagnon
3 Mai 2016
963
Savoie
C'est le même cahier des charges que celui de ma petite bobineuse
Tout a fait - ce style de conception n'est pas limité aux filetages, et les contraintes sont les memes; j'espere rester assez generale pour que ca soit interessant pour tout le monde.
Je pense avoir trouvé un moyen avec mon code existant de retomber dans le pas
C'est tres bien, cette probleme n'est pas anodyne.
j'ai hâte de voir une manière plus propre qui est la tienne
Comme j'ai dit, y en a pas une seule facon "correcte" de faire :)
 
simon74
Compagnon
3 Mai 2016
963
Savoie
Un dessin de système plus concret.

Arrivé a ce point, on peut définir plus concretement le structure du logiciel, au moins sa partie "controle".

On as, comme entrees :

  • Une commande «Go!» qui va demarrer l'application
  • Plusiers commandes «Stop!» (bouton d'arrêt, butées fin de courses) qui vont entrainer l'arret anticipé de l'application
  • Un (ou des) butée(s) de fin de filetage
  • Un codeur de broche
  • Eventuellement un codeur de vis mère
Les parametres de filetage, on considerera comme fixe, predefini. Je ne traiterai pas le "user interface".

Comme sorties :
  • Les signaux de moteur PaP
  • Eventuellement un ou des signal marche / arret / direction de la broche.
Et des parametres :
  • Nombre de pas par tour donné par le (ou les) codeur(s)
  • Pas de vis mère
  • Nombre de pas par tour de moteur PaP
  • Pas de vis ciblé

<Petit deviation vers la monde des matheux>

Les pas de vis mère et vis ciblé seront des rapports en termes de mm. Cela pour pouvoir rester generale, et pour eviter l'utilisation des floats qui sont inexactes. Quelques exemples:

4mm -> 4 : 1
Pas Schaublin -> 5 : 3
20 TPI -> 127 : 100

Donc, en C on peut utiliser un struct de ce type
C:
typedef struct {
  int32_t numerator;
  int32_t denominator;
} rational_t;
On as aussi quelques operations a definir sur les rapports, qu'on s'on souviens de nos jours ecoliers. Addition, soustraction, multiplication, division sont simples, et ne necessite pas que les valeurs soit canonique. Avant comparaison, par contre, faut le faire, en utilisant la methode de greatest common divisor. Code pour tous cela peut etre trouvé, par exemple, sur rosetta code. En termes de performance, ces fonctionnalités peut etre mises "inline" - franchement on va pas utiliser beaucoup.

<fin de deviation>

Ignorant le fonction de retomber dans les pas, l'application est assez, voir "tres" simple. On suit le vitesse de la broche instant par instant et on changes la vitesse de la vis mere en fonction de cela. En pseudocode, ca donne quelque chose comme ca:

Code:
static const uint32_t coder_resolution = 1000;
static const uint32_t motor_resolution = 72;
static const rational_t leadscrew_pitch = {4, 1};
static const rational_t desired_pitch = {127, 100};

// Variables controlés "ailleurs"
volatile extern uint32_t codeur_valeur;
volatile extern uint32_t codeur_us;
volatile extern bool_t threading;

// Et celle qui nous interesse
uint32_t motor_us; 

while (true) {
  if (threading) {
    rational_t rapport = {codeur_resolution, moteur_resolution};
    // Math en mode rational, bien sur
    rapport *= leadscrew_pitch;
    rapport /= desired_pitch;
    rapport.numerator *= codeur_us;
    
    // ceci va changer "magiquement" la vitesse de moteur PaP
    motor_us = rapport.numerator / rapport.denominator;
  } else {
    sleep();
  }
}
 
simon74
Compagnon
3 Mai 2016
963
Savoie
Les codeurs en STM32

Bon, assez d'amuse-gueules, on arrive vers le plat. Le codeur est un truc important. Si on loupe le codeur, on loupe le tout, on ne sais pas ou on est. Ya deux types de codeur - les codeurs relatif, qui génère deux signaux en quadrature donnant les pas et leurs directions, et les codeurs absolus qui donne la position exacte du codeur. On parlera des codeurs relatif ici.

Comment gérer un codeur, donc?

  1. Lire ses signaux directement, en "user code". Cela ne laisse pas trop de temps pour faire autre choses, car on peut louper des pas.
  2. Avec un timer qui génère un interrupt assez rapide, on peut scruter les deux entrées et réagir aux changements. Comme option 1, mais avec moins de chance de louper des changements (au moins si ils ne sont pas plus rapides que les interrupts, mais on peut scheduler celles-ci pour éviter). Par contre, on est sujet aux artefacts du "Nyquist sampling frequency".
  3. On peut mettre des interrupts sur les changements des deux entrées, et avec un peu de logique, on suit les impulsions du codeur. Ceci est la méthode la plus utilisé sur les µCs en générale.
  4. Sur les STM32 (et, de mémoire, sur les STM8) y a un mode "codeur" sur quelques uns des timers. Ceci est vraiment intéressant, car la totalité du logique se passe en dehors de, et indépendant de, la code qui se passe sur le processeur. Y a presque rien a faire, et c'est ça qu'on va faire. Moins de code, fainéantise comme philosophie.
En se repérant a section 15.3.12 de RM0008, on se découvrira comment ça marche. On definira un niveau de filtrage pour eviter les fausses lectures (debouncing), et le timer va compter, sous la controle de codeur, de 0 a n, par repetition (ou n est le nombre de pulsations/tour generé par le codeur, et que on va donner au timer comme "auto reload value").

On peut utiliser le mode DMA du timer pour copier ses valeurs internes vers nos variables chaque fois que ca change, et / ou utiliser des interrupts capture/compare pour faire des choses au passage de zero. On peut utiliser notre compteur comme "gachette" pour demarrer un autre timer, et encore d'autres choses. Le tout avec un minimum absolu de code executant sur le processeur meme.

Toujours ignorant le "retomber dans les pas", on s'en fout bien assez du positionnement exacte de la broche. Ce qu'on veut, c'est sa vitesse, combien de µS entre chaque changement de position, et ca, on n'as pas directement. Pour l'avoir, en mode "naïf", on pourrait utiliser le mode codeur, avec un interrupt sur chaque changement. Dans l'interrupt, on prends le temps exacte, et on calcul notre vitesse. Admettons un vitesse maximale de broche en filetage de 240 t/m, ca fait 4 t/s, ce qui fait (avec le codeur de vibram) 2000 interrupts/sec si on interrupt sur chaque pulsation, ou 1000 interrupts/sec si on interrupt sur une seule signal. En effet, on a utilisé le mode codeur pour eviter de faire un interrupt sur chaque changement des signaux, mais on va faire un interrupt a chaque changement des signaux quand meme.

Dans RM0008, meme, il est dit

The timer, when configured in Encoder Interface mode provides information on the sensor’s current position. You can obtain dynamic information (speed, acceleration, deceleration) by measuring the period between two encoder events using a second timer configured in capture mode. The output of the encoder which indicates the mechanical zero can be used for this purpose. Depending on the time between two events, the counter can also be read at regular times. You can do this by latching the counter value into a third input capture register if available (then the capture signal must be periodic and can be generated by another timer). when available, it is also possible to read its value through a DMA request generated by a Real-Time clock.
Admettons qu'on va utiliser TIM2 (timer 2) en mode codeur. Pour avoir le valeur de codeur en temps reel pour notre application, on va le configurer pour faire un transfert DMA de sa compteur vers nos variables (voila le pourquoi du volatile dans le pseudocode dessus). A ce point la, on a le valeur de codeur, sans aucun code mise apart sa configuration.

On va aussi configurer sa compteur comme signal de reset pour un autre compteur, par exemple TIM3, qui fera un DMA de son compteur sur le trigger, encore, vers nos variables. On peut jouer sur le prescaler de ce compteur pour compter le temps pour un ou plusiers "ticks" de codeur. Encore, on fini avec les valeurs qu'on veut, sans faire executer du code sur le processeur.

Il exist une autre probleme. Qu'est-ce-que va passer quand le codeur s'arrete subitement? On n'aura plus de updates, donc notre PaP va continuer a avancer a sa vitesse actuel, broche a l'arret, jusqu'au butee, ruinant notre piece. Mince alors. Il faut un watchdog, quelque chose qui arret le PaP en cas d'arret de broche. Et encore, on as des outils pour ca. Sur notre timer "esclave", on configurera un interrupt sur un canal capture/compare; si le compteur arrive vers un valeur predefini, on aura un interrupt qui peut arreter le tout.

Donc, on aura les valeurs qui nous interesse, automatiquement et sans avoir besoin d'executer du code, et un petit bout de code qui sera execute en cas d'arret, ce qui doit arriver assez peu souvent.

Prochaine etape, du code pour faire en sort que ca se passe comme ca.
 
La dernière réponse à ce sujet date de plus de 6 mois

Dernières discussions

Haut