Contrôleur de zoom pour reflex numérique

De Wiki LOGre
Aller à : navigation, rechercher


projet réalisé par : Edgar Bonet et Alexandre Bonet.

état d'avancement : documentation et réflexions préliminaires.

Quand le reflex est monté en caméra d'épaule, la commande du zoom se trouve mal placée

Les reflex numériques sont aujourd'hui une alternative intéressante aux caméscopes quand on veut un maximum de qualité d'image. Mais il n'est pas pratique de contrôler la focale du zoom lorsque le reflex est monté en caméra d'épaule. Le but de ce projet est de motoriser le zoom pour en déporter la commande ; et de recycler au passage un moteur de tête d'impression d'une imprimante hors service. Mon frère (Alexandre) s'occupe de la partie mécanique, alors que je (Edgar) m'occupe de la partie électronique.

Principe général

La commande se fait par un bouton à bascule permettant de zoomer dans les deux sens à plusieurs vitesses. On pense acheter une télécommande filaire toute faite, dans le genre de ce modèle de chez Magnus. Ces télécommandes parlent un protocole appelé LANC, créé par Sony pour leurs caméscopes.

La motorisation sera assurée par un moteur DC équipé d'un réducteur. On envisage d'utiliser un moteur recyclé d'une imprimante qui est coupé à un codeur incrémental. Si nécessaire, le codeur pourra servir pour asservir en vitesse, mais dans un premier temps le moteur sera piloté en boucle ouverte.

Ci-dessous le schéma d'ensemble. La boîte de gauche est la télécommande filaire. La boîte « contrôleur » est l'électronique à réaliser, à base d'un microcontrôleur AVR et d'un pont en H.

  ┌──────────┐ bus  ┌────────────┐   ┌────────┐ ┌──────┐
  │ commande ┝━━━━━━┥ contrôleur ┝━━━┥ moteur ╞═╡ zoom │
  └──────────┘ LANC └────────────┘   └────────┘ └──────┘

Bus LANC

Ce bus sert normalement à raccorder une caméra à un périphérique. Le périphérique reçoit des informations sur le fonctionnement de la caméra, et peut à son tour envoyer des commandes. Ici, le périphérique est la télécommande filaire, et le contrôleur du schéma ci-dessus va se faire passer pour une caméra.

Au niveau physique, c'est un bus série à trois fils :

  • GND ;
  • +5 V, fourni par la caméra au périphérique ;
  • data, bidirectionnel en collecteur ouvert.

D'après les projets trouvés sur le Web, il semble que ce soit au périphérique de fournir un pullup de 4,7 kΩ.

Le format des trames ressemble à une connexion série classique à 9600 bps, mais avec une logique inversée (+5 V représente ‘0’), et des bits de stop plus longs. Toutes les 20 ms une trame de 8 octets est échangée, suivie d'un silence. Les bits de start sont toujours envoyés par la caméra. Les quatre premiers octets de données sont émis par le périphérique, les quatre suivants par la caméra.

On trouve sur le Web beaucoup de projets interfaçant le bus LANC avec un Arduino ou un microcontrôleur. Seulement, dans tous les projets que j'ai vus le microcontrôleur joue le rôle de périphérique, alors qu'ici il va jouer le rôle de caméra.

Références

Le microcontrôleur AVR en maitre LANC

On peut utiliser le port série physique de l'AVR pour recevoir les commandes LANC, mais il faut envoyer les bits de start sur la ligne RX. Ceci n'est pas possible avec le port série des ATmega 48/88/168/328. D'après la fiche technique : « When the USART Receiver is enabled this pin is configured as an input regardless of the value of DDD0. ». Il faudrait donc relier à la broche RX une broche GPIO dédiée à l'émission des bits de start.

Le port USI des ATtinies n'a pas cet inconvénient. D'après la datasheet des ATtiny24A/44A/84A, « USI Three-wire mode does not override normal port functions, so pin must be configure as an input for DI function. » Ce port n'est a-priori pas prévu pour fonctionner en tant qu'UART, mais on trouve des codes permettant de le faire :

Je me suis basé sur ce dernier exemple pour écrire ce programme qui reçoit des commandes LANC:

/*
 * Prototype LANC receiver for ATtiny84A.
 * Based on the Simple ATtiny USI UART at
 *   http://www.technoblogy.com/show?VSX
 *
 * Connect the LANC controller to pin 7 = PA6 = DI (data in).
 * Look at pin 13 = PA0 for pulses signaling the ISRs.
 * Look at pin 12 = PA1 for pulses showing read data.
 * Pin numbers are for the DIP14 package.
 *
 *======================================================================
 * __        ____  ____  ____  ____  ____  ____  ____  ____  _______
 *   \      / D0 \/ D1 \/ D2 \/ D3 \/ D4 \/ D5 \/ D6 \/ D7 \/
 *    \____/\____/\____/\____/\____/\____/\____/\____/\____/
 *   B  A  B  A  B
 *
 * Timings:
 *   1 bit   =    104 us (9615 bps, i.e. 0.16% too fast)
 *   1 byte  =  1.248 ms = 12 bits (1 start, 8 data, 3 stop)
 *   1 frame = 19.968 ms = 16 bytes (4 rx-ed, 4 tx-ed, 8 skipped)
 *
 */
 
#define F_CPU 8000000
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
 
uint8_t byte_cnt, bit_cnt;  /* byte and bit counters, could be fused */
volatile uint8_t recvd[2];       /* bytes received */
volatile uint8_t data_received;  /* boolean */
 
 
/***********************************************************************
 * Debugging pulses.
 */
 
/* PA0 signals when an ISR is running. */
static void isr_start() { PORTA |=  _BV(PA0); }
static void isr_end()   { PORTA &= ~_BV(PA0); }
 
 
/***********************************************************************
 * USI LANC driver.
 */
 
unsigned char reverse_byte (unsigned char x) {
    x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
    x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
    x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
    return x;
}
 
void init_usi_lanc(void) {
    DDRA |= _BV(PA6);           /* define DI as output */
    TCCR0A = 1<<WGM01;          /* timer in CTC mode */
    TCCR0B = 2<<CS00;           /* prescaler to /8 */
    OCR0A = 103;                /* shift every 104 us */
    OCR0B = 51;                 /* COMPB between successive COMPA */
    TIFR0 = _BV(OCF0B);         /* clear output compare flag */
    TIMSK0 |= _BV(OCIE0B);      /* enable output compare interrupt */
}
 
/* At every bit transition, even during the pause. */
ISR(TIM0_COMPB_vect)
{
    isr_start();
 
    /* Are we in the pause? */
    if (byte_cnt & 8) goto done;
 
    /* Begin start bit. */
    if (bit_cnt == 0) {
        PORTA &= ~_BV(PA6);     /* floating */
        DDRA |= _BV(PA6);       /* output low */
    }
 
    /* End start bit. */
    else if (bit_cnt == 1) {
 
        /* Here we may want to transmit data instead... */
        DDRA &= ~_BV(PA6);      /* floating */
        PORTA |= _BV(PA6);      /* enable pullup */
 
        /* Start the USI receiver on bytes 0 and 1. */
        if (byte_cnt < 2) {
            USICR = 1<<USIOIE | 1<<USICS0;
            USISR = 1<<USIOIF | 8;
        }
    }
 
done:
    /* Increment (byte_cnt, bit_cnt) before returning. */
    if (++bit_cnt >= 12) {
        byte_cnt = (byte_cnt + 1) % 16;
        bit_cnt = 0;
    }
    isr_end();
}
 
/* When a byte is received. */
ISR(USI_OVF_vect) {
    isr_start();
    USICR = 0;                      /* Disable USI */
    recvd[bit_cnt] = reverse_byte(USIBR);
    if (byte_cnt == 1) data_received = 1;
    isr_end();
}
 
 
/***********************************************************************
 * Main program.
 */
 
/* Display received data on PA1. */
static void display()
{
    uint8_t x = recvd[1];
    if (x == 0 || x > 4) x = 4;  /* 4 means anything other than 1..3 */
    while (x--) {
        PORTA |= _BV(PA1);
        _delay_us(30);
        PORTA &= ~_BV(PA1);
        _delay_us(30);
    }
}
 
int main(void)
{
    /* Set main clock prescaler to 1 => f_CPU = 8 MHz. */
    CLKPR = 1<<CLKPCE;
    CLKPR = 0;
 
    init_usi_lanc();
    sei();
 
    /*  Config PA0 and PA1 as outputs. */
    DDRA |= _BV(PA0) | _BV(PA1);
 
    /* Main loop. */
    for (;;) {
        if (data_received) {
            display();
            data_received = 0;
        }
        sleep_mode();
    }
}

Moteur

Les réflexions concernant la motorisation sont dans la sous-page Moteur.