Close

Shrinking

A project log for Mechatronic Ears

Wearable cat ears that move

Radomir Dopieralski 11/26/2016 at 22:300 Comments

OK, now that I've shrunk the code for #Nyan Board to 470 bytes, it's time to look at the code for the ears. This time I'm going to got straight into plain C. All I need to do is to figure out how to do analogRead()... Turns out it's super-simple!

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


#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))


int16_t adc(int8_t pin) {
    ADMUX = 0<<ADLAR | 0<<REFS1 | 0<<REFS0 | pin & 0x03;
    ADCSRA |= 1<<ADSC;
    while (ADCSRA & 1<<ADSC) {
        // Pass.
    }
    return ADC - 512;
}

void update_servos(uint8_t left_position, uint8_t right_position) {
    static uint8_t current_left = 0;
    static uint8_t current_right = 0;

    uint8_t left = 17 + MIN(left_position, 15);
    if (current_left < left) {
        ++current_left;
        OCR0A = current_left;
    } else if (current_left > left) {
        --current_left;
        OCR0A = current_left;
    } else {
        OCR0A = 255;
    }
    uint8_t right = 33 - MIN(right_position, 15);
    if (current_right < right) {
        ++current_right;
        OCR0B = current_right;
    } else if (current_right > right) {
        --current_right;
        OCR0B = current_right;
    } else {
        OCR0B = 255;
    }
}

int running_average(int16_t *buffer, uint8_t *cursor,
                    int16_t *total, int16_t value) {
    *total -= buffer[*cursor];
    buffer[*cursor] = value;
    *total += value;
    *cursor = (*cursor + 1) & 0x0F;
    return *total >> 4;
}

int main () {
    static uint8_t left = 7;
    static uint8_t right = 7;
    static int16_t x_buffer[16] = {};
    static int16_t y_buffer[16] = {};
    static int16_t z_buffer[16] = {};
    static uint8_t x_cursor = 0;
    static uint8_t y_cursor = 0;
    static uint8_t z_cursor = 0;
    static int16_t x_total = 0;
    static int16_t y_total = 0;
    static int16_t z_total = 0;

    // Init ADC.
    ADCSRA = 1<<ADEN | 1<<ADPS2 | 1<<ADPS1 | 1<<ADPS0; // Pre-scaler 128.

    // Output pins PB0 and PB1.
    DDRB = 1<<0 | 1<<1;

    // Setup the PWM clock to ~62.5Hz for the servos.
    TCCR0A = 2<<COM0A0 | 2<<COM0B0 | 3<<WGM00;
    TCCR0B = 0<<WGM02 | 1<<CS00 | 1<<CS01 | 0<<CS02;

    OCR0A = 0;
    OCR0B = 0;

    while (1) {
        int16_t z = running_average(x_buffer, &x_cursor, &x_total, adc(1) - 16);
        int16_t y = running_average(y_buffer, &y_cursor, &y_total, adc(2));
        int16_t x = running_average(z_buffer, &z_cursor, &z_total, adc(3));

        if (x > 20) {
            left = 15;
            right = 15;
        } else if (x < -20) {
            left = 0;
            right = 0;
        } else if (y > 20) {
            left = 15;
            right = 0;
        } else if (y < -20) {
            left = 0;
            right = 15;
        } else {
            left = 7;
            right = 7;
        }

        update_servos(left, right);
        _delay_ms(60);
    }
}

The new code is not as minimalistic as in the case of Nyan Board -- in particular, I'm using much more memory here, for all the running average buffers -- but it's nice and small.

AVR Memory Usage
----------------
Device: attiny85

Program:     484 bytes (5.9% Full)
(.text + .data + .bootloader)

Data:        109 bytes (21.3% Full)
(.data + .bss + .noinit)

Discussions