DondoLOG

De Wiki LOGre
Aller à : navigation, rechercher

Projet réalisé par Fma38 et Philippe.

En cours.

Dondolog 20.png Dondolog 21.png Dondolog 22.png Dondolog 23.png

Généralités

Il s'agit d'un double extrudeur. Il en existe certes beaucoup, mais celui-ci présente un certain nombre d'avantages :

  • un seul moteur
  • la tête inutilisée se relève pour ne pas frotter contre l'impression
  • la tête inutilisée est obturée pour ne pas baver sur l'impression

À noter que le DondoLOG a été pour moi (Fma38) l'occasion de m'initier au logiciel Onshape. Il s'agit d'un modeleur 3D utilisant le moteur Parasolid, le même que celui de Solidworks, et qui a la particularité de tourner en ligne, dans un navigateur, ce qui le rend totalement cross-platform (et comme j'utilise linux...). Cerise sur le gâteau, il y a une version gratuite ! Elle impose de rendre ses documents publics, mais dans le cadre de projets sous licence libre, c'est finalement parfait. On a de cette façon accès à une grande bibliothèque d'objets réalisés par d'autres utilisateurs.

Design

Le design d'origine, nommé Dondolo (qui vient de bascule, en italien), a été réalisé par Gianni Franci et publié sur Thingiverse. Une adaptation a peu de temps après été créé par Phil Rebo, le Fabmanager du Fablab du Havre. Ce dernier nous a très aimablement transmis les fichiers Solidworks de sa version, ce qui nous a permis de bien étudier la bête.

Un des aspects intéressants des licences libres est qu'elle permettent la modification des objets, soit pour les adapter à des besoins particuliers, soit pour les améliorer. Bien souvent, les deux. Le Dondolo ne faisant pas exception, ainsi est né le DondoLOG.

Ce qui au départ ne devait être qu'une adaptation pour le nouveau chariot de la LOGresse, sur lequel travaille Marc, s'est vite transformé en une refonte complète de l'engin !

Un des points relevé par Phil est la nécessité d'une grande rigidité, pour que les têtes se repositionnent toujours parfaitement au même endroit. Il compte faire réaliser des pièces en alu par la section technique du Lycée où il travaille. De notre côté, comme nous allons prochainement lancer un nouveau batch de LOGresses, nous allons en profiter pour faire découper des pièces dans de la tôle de 3mm ; le design s'est donc immédiatement adapté dans ce sens, avec un assemblage de pièces en tôle et de pièces imprimées 3D.

Évolutions

Partant du projet initial, la première modification majeur a consisté à déplacer l'axe de rotation des leviers sur le pendule lui-même, le mécanisme de débrayage - un simple pion - étant lui, solidaire de la base. Cela a permis de réduire considérablement le nombre de pièces.

Puis une seconde version majeur a vu le jour, dans laquelle il n'y a plus qu'un seul lever, dont l'axe de rotation est passé au dessus de l'axe de rotation du pendule. Dans cette configuration, tous les efforts vont dans le même sens : maintient en position du pendule sur sa butée de fin course et maintient de la pression du galet sur le fil. Auparavant, l'effort de débrayage s'opposait au maintient du pendule sur se butée.

Le servo actionne directement le levier, lequel entraîne à son tour le pendule. Classe, non ?

Voici une galerie montrant les évolutions du design :


Protos

Pilotage du servo

Explications

Repetier-firmware permet d'envoyer un G-Code lors du changement d'outil (extrudeur). Il suffit donc de lui faire envoyer la position du servo correspondant à chacun des extrudeurs.

Le problème c'est que ce G-Code est compilé en dur dans le firmware ! Or, il est intéressant de pouvoir ajuster la position extrême afin de gérer la pression du galet sur le fil (utile lorsqu'on passe du PLA au Filaflex, par exemple). Recompiler le firmware à chaque fois n'est pas une option !

Une solution purement passive consisterait à intercaler des résistances talon ajustables sur le potar de l'axe du servo. Cela fonctionne, mais les 2 réglages s'influencent l'un-l'autre ; pas pratique :o/

La solution retenue est d'intercaler un µ-contrôleur entre le signal émis par l'électronique de l'imprimante et le servo. L'idée est de faire gérer les positions extrêmes par ce µ-contrôleur, avec possibilité de l'ajuster facilement. Il se trouve que Sparkfun vend un petit circuit qui fait à peu près ça : un contact TOR permet de faire basculer le servo d'une position à l'autre, et 2 petits potars permettent de régler ces positions. Le troisième potar permet de régler la vitesse de déplacement.

Le code ci-dessous permet d'implémenter notre besoin : l'entrée est utilisée pour décoder le pulse servo provenant de l'électronique de l'imprimante, et, en fonction de cette valeur, positionne le servo d'un côté ou de l'autre (il suffit d'envoyer des valeur < ou > à des valeurs pré-définies dans le code). Ici aussi, les potars permettent de régler finement les positions.

De plus, afin de pouvoir charger/décharger facilement le fil, le code gère une position centrale. Cette position est atteinte soit en dé-connectant l'entrée (il suffit d'intercaler un switch sur le signal, ou, dans le cas de Repetier-firmware, d'envoyer un pulse < 500µs, ce qui a pour effet de couper la sortie), soit en envoyant un pulse à environ 1500µs. Cela permet de répondre à tous les cas de figure. Le troisième potar permet de régler finement la position centrale, bien que ce ne soit pas vraiment utile.

À noter que la carte possède 2 petits cavaliers (en dessous, par soudure), qui pourraient être utilisés pour choisir d'autres modes de fonctionnement sans devoir recompiler le firmware. Non utilisés pour l'instant.

Code

Voici le code à charger dans le module Sparkfun.

/*
    DondoLOG.c
*/
 
#include <avr/interrupt.h>
#include <util/delay.h>
 
// Constants
 
// Pots for positions A, B and time T.
// Numbers are ADC channels, not pins
static const uint8_t POTA_CHAN = 0;
static const uint8_t POTB_CHAN = 3;
static const uint8_t POTT_CHAN = 7;
 
// Servo input timing (µs)
// if pulse in is:
//   - below PULSE_IN_POS_A -> move servo to pos A
//   - between PULSE_IN_POS_MIN_CENTER and PULSE_IN_POS_MAX_CENTER -> move servo to pos CENTER
//   - above PULSE_IN_POS_B -> move servo to pos B
static const uint16_t PULSE_IN_POS_A = 1350;
static const uint16_t PULSE_IN_POS_MIN_CENTER = 1450;
static const uint16_t PULSE_IN_POS_MAX_CENTER = 1550;
static const uint16_t PULSE_IN_POS_B = 1650;
 
static const uint16_t GAP_IN_TIMEOUT = 50000;  // no input detection (50ms)
 
// Servo output timing (µs)
// Pots are used to adjust pulse out between MIN/MAX values
static const uint16_t PULSE_OUT_POS_MIN_A =  500;
static const uint16_t PULSE_OUT_POS_MAX_A = 1000;
static const uint16_t PULSE_OUT_POS_MIN_CENTER = 1250;
static const uint16_t PULSE_OUT_POS_MAX_CENTER = 1750;
static const uint16_t PULSE_OUT_POS_MIN_B = 2000;
static const uint16_t PULSE_OUT_POS_MAX_B = 2500;
 
// Misc
static const int32_t ADC_MAX = 0x0000ffff;  // ADC values are left-justified into 16-bits
 
// Global vars
static volatile uint32_t countTimer0;
static volatile uint8_t measureDone;
 
 
void initClock()
{
    // 2-step procedure to keep stray pointers from mangling the value.
    // Using 16 MHz resonator, we'll divide by 2 for 8 MHz.
    CLKPR = 0x80;
    CLKPR = 0x01;  // clk div 2 = 8 MHz.
}
 
 
void initIO()
{
    DDRA &= ~0x02;  // PA1 as input
    PORTA |= 0x02;  // pulled up
}
 
 
void initPWM()
{
    // Holdoff timer prescalar counting while we configure
    // This disables the peripheral
    GTCCR = 1 << TSM;
 
    // Enable timer output compare A bit as output
    DDRA |= 0x40;
 
    // Note: WGM is 4 bits split between Ctrl registers A and B.
    // We want mode FAST PWM, top set by ICR1
    // Selected by 0b1110.
 
    // Set control reg A
    // Bits 7,6 - Output compare mode chan A = 0b10 = clear output on match, set output at bottom
    // Bits 5,4 - Output compare mode chan B = 0b00 = disconnected
    // Bits 3,2 - RFU
    // Bits 1,0 - Waveform gen mode LSBs 11,10 : 0b10
    // WGM = fast pwm, top is ICR1
    TCCR1A = 0x82;
 
    // Set control reg B
    // Bit 7 - input noice calcel = 0b0 = off
    // bit 6 - input capture edge sel = off
    // bit 5 - RFU
    // bits 4:3 - WGM MSBs = 0b11
    // bits 2:0 - clk scale: 0b010 = IO clk div by 8 = 1 MHz
    TCCR1B = 0x1a;
 
    // Set control reg C
    // Force output compare - inactive in PWM modes
    TCCR1C = 0;
 
    // No interrupts
    TIMSK1 = 0;
 
    // ICR1 is 16-bit reg.  In Fast PWM, it sets the TOP value for the counter
    // 50 Hz = 20000 1 mHz pulses.
    ICR1 = 20000 - 1;
 
    // Initialize the counter to 0
    TCNT1 = 0x0;
 
    // Initialize the output compare
    OCR1A = PULSE_OUT_POS_MIN_CENTER - 1;
 
    // Reset & restart prescalar counter
    // Writing 0 to TSM (bit 7) causes counter to start counting,
    // Writing 1 to PSR10 (bit 0) causes prescalar to reset, starting prescale from 0
    GTCCR = 1 << PSR10;
}
 
 
void initInt()
{
    // Enable pin-change interrupt on PCINT1
    GIMSK |= (1 << PCIE0);  // enable interrupt
    PCMSK0 |= (1 << PCINT1);  // enable interrupt pin
}
 
 
uint32_t readADC(uint8_t chan)
/*
    Read a given channel of ADC
*/
{
    uint32_t value;
 
    // Only allow the pins we've selected to read...
    if (!((chan == POTA_CHAN) || (chan == POTB_CHAN) || (chan == POTT_CHAN))) {
        return 0;
    }
 
    // Turn on adc
    // TODO: perhaps turn on and leave on?
    ADCSRA = 0x86; // power bit, prescale of 64
 
    ADCSRB |= 0x10; // left justify
 
    // set digital input disable
    DIDR0 = 0x89;
 
    // input mux, vref selection
    ADMUX = chan;
 
    // pause a moment...
    volatile uint8_t i;
    for (i = 0xff; i != 0; i--);
 
    // bit 6 starts conversion
    // write bit 4 to clear it
    ADCSRA |= 0x50;
    //ADCSRA = 0x40;
 
    // start bit clears when complete
    while (ADCSRA & 0x40);
 
    value = ADCW;
 
    // Pots acts backwards, so reverse these readings
    return (0xffff - value);
}
 
 
ISR(TIM0_OVF_vect)
/*
    Timer0 overflow interrupt
*/
{
    // Increase time passed
    countTimer0 += 256;
}
 
 
ISR(PCINT0_vect)
/*
    Pin-change interrupt handler
 
    Read PCINT1 (PA1 - pin 12)
*/
{
    //
 
    // Rising edge
    if (PINA & (1 << PA1)) {
 
        // Reset counter in timer0
        TCNT0 = 0;
 
        // Start 8-bit timer
        // Divide by 1
        TCCR0B |= 1 << CS00;
 
        // Clear overflow flag
        TIFR0 = 1 << TOV0;
 
        // Set overflow interrupt flag
        TIMSK0 |= 1 << TOIE0;
    }
 
    // Falling edge
    else {
 
        // Stop timer
        TCCR0B &= ~(1 << CS00);
 
        // Increase time passed
        countTimer0 += TCNT0;
 
        // Account for extra overflow
        if (TIFR0 & (1 << TOV0)) {
            countTimer0 += 256;
        }
 
        measureDone = 1;
    }
}
 
 
int main(void)
{
    uint16_t timeoutCounter;
 
    // Disable interrupts
    cli();
 
    initClock();
    initIO();
    initPWM();
    initInt();
 
    // Main loop
    while (1) {
 
        // Reset measure
        measureDone = 0;
        countTimer0 = 0;
        timeoutCounter = GAP_IN_TIMEOUT / 10;
 
        // Enable interrupts, so we can measure input pulse
        sei();
 
        // Measure
        while (1) {
 
            // Wait a little bit
            _delay_us(10);
 
            if (measureDone) {
 
                // Disable interrupts
                cli();
 
                // Read pots
                uint32_t a = readADC(POTA_CHAN);
                uint32_t b = readADC(POTB_CHAN);
                uint32_t t = readADC(POTT_CHAN);
 
                // Compute input servo pulse
                uint32_t inputServoPulse = countTimer0 / (F_CPU / 1000000);  // µs
 
                // Decode pulse in and move servo accordingly
                if ((!timeoutCounter) ||
                    ((inputServoPulse > PULSE_IN_POS_MIN_CENTER) &&
                    (inputServoPulse < PULSE_IN_POS_MAX_CENTER))) {
                    OCR1A = PULSE_OUT_POS_MIN_CENTER +
                            (PULSE_OUT_POS_MAX_CENTER - PULSE_OUT_POS_MIN_CENTER) * t / ADC_MAX
                            - 1;
                }
                else if (inputServoPulse < PULSE_IN_POS_A) {
                    OCR1A = PULSE_OUT_POS_MIN_A +
                            (PULSE_OUT_POS_MAX_A - PULSE_OUT_POS_MIN_A) * a / ADC_MAX
                            - 1;
                }
                else if (inputServoPulse > PULSE_IN_POS_B) {
                    OCR1A = PULSE_OUT_POS_MIN_B +
                            (PULSE_OUT_POS_MAX_B - PULSE_OUT_POS_MIN_B) * b / ADC_MAX
                            - 1;
                }
 
                // Leave measure
                break;
            }
            else {
 
                // Decrement timeout counter
                timeoutCounter--;
 
                // No input signal
                if (!timeoutCounter) {
                    measureDone = 1;
                }
            }
        }
    }
}

Dans le Makefile ci-dessous, il faut utiliser des vraies tabulations.

# Makefile
 
TARGET=DondoLOG.hex
CFLAGS=-mmcu=attiny84 -Os -Wall -Wextra -DF_CPU=8000000
AVRDUDE=avrdude -p t84 -c usbtiny
 
all:	$(TARGET)
 
upload:	$(TARGET)
	$(AVRDUDE) -e -U flash:w:$(TARGET)
 
test:
	$(AVRDUDE) -n  -v
 
%.elf:	%.c
	avr-gcc $(CFLAGS) $< -o $@
 
%.hex:	%.elf
	avr-objcopy -j .text -j .data -O ihex $< $@
 
clean:
	rm -f $(TARGET)