Barrière virtuelle Roomba

De Wiki LOGre
Aller à : navigation, rechercher
Langue : Français  • English

Projet réalisé par fma38.

Abouti

Présentation

Le but est de faire une petite barrière virtuelle pour aspirateurs Roomba à faible coût (environ 20€).

Ce projet est basé sur celui-ci.

Hardware

L'électronique est basée sur un ATtiny85, amplement suffisant pour cette application. Il pilote une Led IR, et une led pour indiquer le fonctionnement.

L'alimentation se fera directement par un accu 18650, qui donnera une autonomie suffisante (plusieurs dizaines d'heures, sachant qu'un cycle de Roomba ne dure qu'une heure). Afin de ne pas décharger l'accu au delà de la valeur limite, on utilisera le Brow Out Detector de l'ATtiny. Comme la valeur du BOD est de 2.7V (+-0.2V), Marc a suggéré de rajouter une diode en série pour alimenter l'ATtiny ; du coup, le valeur limite d'utilisation de l'accu sera 0.6V plus haute, soit 3.3V environ, ce qui est acceptable.

Schéma et PCB

Le PCB est réalisé sous Kicad.

Roomba virtual wall (Esschema).png Roomba virtual wall (Pcbnew).png Roomba virtual wall (3D).png

Software

À la mise sous tension, le led IR enverra le code 162 correspondant à la barrière virtuelle Roomba. Comme le cycle du Roomba est d'environ 1h, la barrière se coupera automatiquement au bout de 80mn, pour éviter de vider l'accu (veille profonde).

En plus du BOD (cf section Hardware), qui ne servira qu'en extrême limite, l'ATtiny surveillera sa tension d'alimentation, et se mettra en veille profonde si la tension tombe en dessous de 3.5V.

Le code est une remise en forme du code du projet cité dans la section Présentation.

Boîtier

Un petit boîtier prévu pour être imprimé en 3D permet de loger le circuit, et de pouvoir le positionner au sol au bon endroit.

TODO

Annexes

Évolutions possibles

  • ajouter un récepteur IR pour détecter l'approche du Roomba, et ne déclencher le faisceau IR que dans ce cas. Cela permettrait de laisser la barrière en marche, et d'économiser la batterie

Code

Version actuelle du code, avec mesure de la tension d'alimentation, mais ni bouton start, ni arrêt automatique :

/*
roomba.cpp

Authors: Frédéric Mantegazza (code cleanup)
         Peter Dunshee (original Roomba Virtual Wall)
         probono (send IR to Roomba)
         Ken Shirriff (IRremote functions)

IR Remote Control
129 Left
130 Forward
131 Right
132 Spot
133 Max
134 Small
135 Medium
136 Large / Clean
137 Stop
138 Power
139 Arc Left
140 Arc Right
141 Stop

Scheduling Remote
142 Download
143 Seek Dock

Roomba Discovery Drive-on Charger
240 Reserved
248 Red Buoy
244 Green Buoy
242 Force Field
252 Red Buoy and Green Buoy
250 Red Buoy and Force Field
246 Green Buoy and Force Field
254 Red Buoy, Green Buoy and Force

Roomba 500 Drive-on Charger
160 Reserved
161 Force Field
164 Green Buoy
165 Green Buoy and Force Field
168 Red Buoy
169 Red Buoy and Force Field
172 Red Buoy and Green Buoy
173 Red Buoy, Green Buoy and Force Field

Roomba 500 Virtual Wall
162 Virtual Wall

Roomba 500 Virtual Wall Lighthouse (not yet supported)
0LLLL0BB (LLLL = Virtual Wall Lighthouse ID, assigned automatically by Roomba 560 and 570 robots)
1-10: Valid ID
11: Unbound
12-15: Reserved
BB = Which Beam
00 = Fence
01 = Force Field
10 = Green Buoy
11 = Red Buoy
*/

#include <stdint.h>
#include <avr/io.h>
#include <util/delay.h>

#define STATUS_LED_PIN  0
#define IR_PWM_PIN      1

#define VIRTUAL_WALL_CODE  162

#define LED_STATUS_OK           100  // 1 blink (50ms long) every ~5s (loop is 50ms)
#define LED_STATUS_LOW_VOLTAGE   20  // 1 blink (50ms long) every ~second
#define LED_STATUS_ERROR          4  // 1 blink (50ms long) every ~100ms

#define VOLTAGE_LOW  2900  // -> battery ~= 3500 (mA)

void setupStatusLed()
{
    DDRB |= _BV(STATUS_LED_PIN);    // set status led pin to digital output
    PORTB &= ~_BV(STATUS_LED_PIN);  // set status led pin set to LOW
}


void setupAdc()
{
    ADCSRA = _BV(ADEN);             // enable ADC
    ADMUX = _BV(MUX3) | _BV(MUX2);  // use Vcc as ref. voltage, and internal ref. voltage (1.1V) as input
    _delay_ms(2);                   // wait for ref. to settle
}


// The khz value controls the modulation frequency
// The IR output will be on pin 6 (PB1/OC0B)
// This routine is designed for 36-40KHz; if you use it for other values,
// it's up to you to make sure it gives reasonable results
// (watch out for overflow / underflow / rounding)
// Timer/Counter0 is used in phase-correct PWM mode, with OCR0A controlling the frequency,
// and OCR0B controlling the duty cycle
// There is no prescaling, so the output frequency is 8MHz / (2 * OCR0A)
// To turn the output on and off, we leave the PWM running, but enable and disable the output pin
void setupIROut(uint16_t khz)
{
    DDRB |= _BV(IR_PWM_PIN);    // set PORTB PWM pin to digital output
    PORTB &= ~_BV(IR_PWM_PIN);  // when not sending PWM, we want it low

    // COM0Ax = 00: disconnect OC0A
    // COM0Bx = 00: disconnect OC0B; to send signal set to 10: OC0B non-inverted
    // WGMx = 101: phase-correct PWM with OCRA as top
    // CS0x = 001: no prescaling
    // the top value for the timer. The modulation frequency will be F_CPU / 2 / OCR0A.
    TCCR0A = _BV(WGM00);
    TCCR0B = _BV(WGM02) | _BV(CS00);

    const uint8_t pwmValue = F_CPU / 1000 / khz / 2;
    OCR0A = pwmValue;
    OCR0B = pwmValue / 3;
}


void delay10us(uint16_t us10)
{
    do
        _delay_us(0.5);
    while (--us10);
}


void delay1us(uint16_t us)
{
    delay10us(us/10);
}


// Sends an IR 1
// A space is no output, so the PWM output is disabled
void oneIR()
{
    TCCR0A |= _BV(COM0B1);   // enable IR PWM output
    _delay_ms(3);
    TCCR0A &= ~_BV(COM0B1);  // disable IR PWM output
    _delay_ms(1);
}


// Sends an IR 0
// The mark output is modulated at the PWM frequency
void zeroIR()
{
    TCCR0A |= _BV(COM0B1);   // enable IR PWM output
    _delay_ms(1);
    TCCR0A &= ~_BV(COM0B1);  // disable IR PWM output
    _delay_ms(3);
}


void sendIR(int code)
{
    for (int8_t counter = 7; counter >= 0; --counter) {
        if (code & (1 << counter)) {
            TCCR0A |= _BV(COM0B1);   // enable IR PWM output
            _delay_ms(3);
            TCCR0A &= ~_BV(COM0B1);  // disable IR PWM output
            _delay_ms(1);
        }
        else {
            TCCR0A |= _BV(COM0B1);   // enable IR PWM output
            _delay_ms(1);
            TCCR0A &= ~_BV(COM0B1);  // disable IR PWM output
            _delay_ms(3);
        }
    }

    // Always end with the LED off
    TCCR0A &= ~_BV(COM0B1);  // disable IR PWM output

    _delay_ms(18);
}


uint16_t readVcc()
{
    ADCSRA |= _BV(ADSC);         // start conversion
    while (ADCSRA & _BV(ADSC));  // wait for end of conversion...

    return 1125300L / ADC;       // calculate Vcc in mV: 1125300 = 1.1 * 1023 * 1000
}


int main(void)
{
    uint8_t ledStatus = LED_STATUS_OK;

    setupStatusLed();
    setupAdc();
    setupIROut(38);

    // Dummy battery voltage reading
    readVcc();

    int8_t i = 0;
    while (1) {

        // Status led management
        i++;
        if (i == 1) PORTB |= _BV(STATUS_LED_PIN);   // set status led pin to HIGH
        if (i == 2) PORTB &= ~_BV(STATUS_LED_PIN);  // set status led pin to LOW
        if (i == ledStatus) i = 0;

        sendIR(VIRTUAL_WALL_CODE);  // Virtual Wall code (takes ~50ms)

        // Battery voltage reading
        // Note: status is never reset
        if (readVcc() < VOLTAGE_LOW) {
            ledStatus = LED_STATUS_LOW_VOLTAGE;
        }
    }
}
# Makefile

MCU=attiny85
AVRDUDEMCU=t85
F_CPU=8000000
CC=/usr/bin/avr-gcc
CFLAGS=-g -Os -Wall -mcall-prologues -mmcu=$(MCU) -DF_CPU=$(F_CPU)
OBJ2HEX=/usr/bin/avr-objcopy
AVRDUDE=/usr/bin/avrdude
AVRSIZE=/usr/bin/avr-size
SOURCES=roomba.c
TARGET=roomba

all :
	$(CC) $(CFLAGS) $(SOURCES) -o $(TARGET)
	$(AVRSIZE) $(TARGET)
	$(OBJ2HEX) -R .eeprom -O ihex $(TARGET) $(TARGET).hex
	rm -f $(TARGET)

install : all
	$(AVRDUDE) -p $(AVRDUDEMCU) -P usb -c usbtiny -U flash:w:$(TARGET).hex

fuse :
	$(AVRDUDE) -p $(AVRDUDEMCU) -P usb -c usbtiny -U lfuse:w:0xe2:m -U hfuse:w:0xdd:m -U efuse:w:0xff:m

clean :
	rm -f *.hex *.obj *.o