Archive for the ‘Electronics’ category

vibrating art

the idea is simple:

  • take an old electric toothbrush;
  • add a led and a vibrator;
  • shape something around it;

i am using polycaprolactone (FDA approuved). the schematic is pretty simple, but i had to play with the capacitor and inductor to get more power into the vibrator. it’s overkill to use an attiny for cycling the colors of the led (i didn’t know that you can buy RGB led with built-in pwm). even so, i am sharing the small code, maybe it can be useful to someone. since i am using an electric toothbrush there’s no physical connection to recharge it, thus completely waterproof. i will need to remelt the polycaprolactone to change the battery someday.

 #define F_CPU 9600000UL
 #include <avr/io.h>
 #include <util/delay.h>
 #define LED PB0
 #define LED2 PB1
 int i;
 
 int main (void)
 {
	 DDRB = 0xff;
	 TCCR0A |= ((1 << COM0A1) | (1 << COM0A0) | (1 << COM0B1) | (1 << COM0B0) | (1 << WGM01) | (1 << WGM00)); // WGM01 - WGM00 (set fast PWM)
	 OCR0A = 0; // initialize Output Compare Register A to 0
	 OCR0B = 0;
	 TCCR0B |= (1 << CS01); // Start timer at Fcpu / 256
 
	 for (;;)
	 {
		 for (i = 0 ; i < 255 ; i++ ) // For loop (Up counter 0 - 255)
		 {
			  OCR0A = i; // Update Output Compare Register (PWM 0 - 255)
			 _delay_ms(8);
		 }
		 for (i = 255 ; i > 1 ; i-- ) // For loop (down counter 255 - 0 )
		 {
			 OCR0B = i; // Update Output Compare Register (PWM 0 - 255)
			 _delay_ms(8);
		 }
		 for (i = 255 ; i > 1 ; i-- ) // For loop (down counter 255 - 0 )
		 {
			  OCR0A = i; // Update Output Compare Register (PWM 0 - 255)
			 _delay_ms(8);
		 }
		 for (i = 0 ; i < 255 ; i++ ) // For loop (Up counter 0 - 255)
		 {
			  OCR0B = i; // Update Output Compare Register (PWM 0 - 255)
			 _delay_ms(8);
		 }
	 }
 }

19

12 2010

video tour of akihabara

if you are curious to see the electronic town (Akihabara) in Tokyo:

24

09 2010

cheap avr intervalometer

this project is obsolete if you own a Canon supported by http://wiki.magiclantern.fm
open source tool that runs from the SD/CF card at camera startup.

now that i have a digital camera (but my heart will always belong to film-based photography) i can do time lapse photography. sadly the firmware of my camera isn’t supporting this feature, so i had to build an intervalometer. i didn’t want to use an arduino (overkill & pricy), so i went with an attiny45, a generic optocoupler, a voltage regulator and a potentiometer for adjusting the timer from 1 second to 1 minute.

i just found out that it’s possible to power the avr from the camera (focus ring). have a look at this complete and small solution: http://www.doc-diy.net/photo/hdr-jack/

//Intervalometer from 1 second to 1 minute
 
//Author: Patrick Sebastien Coulombe
//Website: www.workinprogress.ca
//Date: 2010-07-24
 
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>
 
// use PB2 for led, pin 7
#define LED_BIT 2
// select ADC2, PB4, pin 3
#define CHANNEL 2
// shutter on (in ms)
#define HOLD 300 
 
// Return the 10bit value of the selected adc channel.
uint16_t get_adc(uint8_t channel) {
 
	// ADC setup
	ADCSRA = (1 << ADEN) | (1 << ADPS1) | (1 << ADPS0);
 
	// select channel
	ADMUX = channel;
 
	// warm up the ADC, discard the first conversion
	ADCSRA |= (1 << ADSC);
	while (ADCSRA & (1 << ADSC)); 
 
	ADCSRA |= (1 << ADSC); // start single conversion
	while (ADCSRA & (1 << ADSC)); // wait until conversion is done
 
	return ADCW;
}
 
// Scale
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
 
// Main program
int main(void) {
 
	// vars
	uint16_t adcvalue = 0;
	uint16_t i;
 
	// define LED as outputs
	DDRB |= (1 << LED_BIT);	
 
	while (1) {	
 
		//release the shutter
		PORTB |= (1 << LED_BIT);
 
		//exposure length
		for (i=0; i<HOLD; i++) { 
			_delay_ms(1); 
		}
		PORTB &= ~(1 << LED_BIT);
 
		//interval time (using a potentiometer to adjust)
		adcvalue = map(get_adc(CHANNEL), 0, 1023, 1, 60);
		adcvalue = adcvalue * 1000;
 
		//one way to achieve long delay
		for (i=0; i<adcvalue; i++) { 
			_delay_ms(1); 
		}
 
	}
	return 0;
 
}

Time lapse test (3 hours) from my balcony.

I used Blender for making the tilt / shift effect. Here’s the source of the Blender file.

24

07 2010

singing birds

10 watts amplifier with built-in 16-bit / 48kHz RIFF-WAVE player (music on a SD-CARD). 2 servos for controlling the beaks. I did this project mostly to learn stuff. The result is a bit stupid.

VIDEO:

PHOTOS:

Schematic & Board (Kicad)
How to filter PWM for music

sd player source code

/*---------------------------------------------------------------*/
/* 8-pin SD audio player R0.02                     (C)ChaN, 2009 */
/*---------------------------------------------------------------*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "diskio.h"
#include "pff.h"

char strbuff[10];
unsigned int position = 0;
unsigned long count = 0;
unsigned int nbwave = 0;

#define FCC(c1,c2,c3,c4)  (((DWORD)c4<<24)+((DWORD)c3<<16)+((WORD)c2<<8)+(BYTE)c1)  /* FourCC */

/*---------------------------------------------------------*/
/* Work Area                                               */
/*---------------------------------------------------------*/

volatile BYTE FifoRi, FifoWi, FifoCt;  /* FIFO controls */

BYTE Buff[256];    /* Wave output FIFO */

FATFS fs;      /* File system object */
DIR dir;      /* Directory object */
FILINFO fno;    /* File information */

WORD rb;      /* Return value. Put this here to avoid bugs of avr-gcc */

EMPTY_INTERRUPT(WDT_vect);

/*---------------------------------------------------------*/

static
DWORD load_header (void)  /* 0:Invalid format, 1:I/O error, >1:Number of samples */
{
  DWORD fcc, sz;
  UINT i;
  FRESULT res;

  res = pf_read(Buff, 256, &rb);    /* Load file header (256 bytes) */
  if (res) return 1;

  if (rb != 256 || LD_DWORD(Buff+8) != FCC('W','A','V','E')) return 0;

  i = 12;
  while (i < 200) {
    fcc = LD_DWORD(&Buff[i]);  /* FCC */
    sz = LD_DWORD(&Buff[i+4]);  /* Chunk size */
    i += 8;
    switch (fcc) {

    case FCC('f','m','t',' ') :    /* 'fmt ' chunk */
      if (sz > 100 || sz < 16)        /* Check chunk size */
        return 0;
      if (Buff[i+0] != 1)            /* Check coding type (1) */
        return 0;
      if (Buff[i+2] != 1 && Buff[i+2] != 2)  /* Check channels (1/2) */
        return 0;
      GPIOR0 = Buff[i+2];    /* Channel flag */
      if (Buff[i+14] != 8 && Buff[i+14] != 16)  /* Check resolution (8/16) */
        return 0;
      GPIOR0 |= Buff[i+14];  /* Resolution flag */
      OCR0A = (BYTE)(F_CPU / 8 / LD_WORD(&Buff[i+4])) - 1;  /* Sampling freq */
      break;

    case FCC('f','a','c','t') :    /* 'fact' chunk (skip) */
      break;

    case FCC('d','a','t','a') :    /* 'data' chunk (start to play) */
      fs.fptr = i;
      return sz;

    default :            /* Unknown chunk (error) */
      return 0;
    }
    i += sz;
  }

  return 0;
}

static
UINT play (
  const char *fn
)
{
  DWORD sz;
  FRESULT res;
  BYTE sw;
  WORD btr;

  if ((res = pf_open(fn)) == FR_OK) {
    sz = load_header();      /* Load file header */
    if (sz < 256) return (UINT)sz;

    if (!TCCR1) {        /* Enable audio out if not enabled */
      PLLCSR = 0b00000110;  /* Select PLL clock for TC1.ck */
      GTCCR =  0b01100000;  /* Enable TC1.OCB as PWM out (L-ch) */
      OCR1B = 128; OCR1A = 128;
      TCCR1 = STEREO ? 0b01100001 : 0b00000001;  /* Start TC1 with TC1.OCA is enabled as PWM out (R-ch) */
      TCCR0A = 0b00000010;  /* Enable TC0.ck = 2MHz as interval timer */
      TCCR0B = 0b00000010;
      TIMSK = _BV(OCIE0A);
    }

    FifoCt = 0; FifoRi = 0; FifoWi = 0;    /* Reset FIFO */
    pf_read(0, 512 - fs.fptr, &rb);      /* Snip sector unaligned part */
    sz -= rb;

    sw = 1;    /* Button status flag */
    do {
      /* Forward audio data */
      btr = (sz > 1024) ? 1024 : (WORD)sz;
      res = pf_read(0, btr, &rb);
      if (res != FR_OK || btr != rb) break;
      sz -= rb;
      /* Check button down and break on button down */
      sw <<= 1;
      //if (bit_is_clear(PINB, 0) && ++sw == 1) break;
      if (bit_is_clear(PINB, 0) && ++sw == 1) {
        
        count = 0;
        
        do { 
          count++;
          wdt_reset();
        } while (bit_is_clear(PINB, 0));
        
        if(count > 500000) {
          position--;
          position--;
        }
        
        break;
        
      }
      wdt_reset();
    } while (rb == 1024);  /* Repeat until all data read */
  }

  while (FifoCt) ;      /* Wait for FIFO empty */
  OCR1A = 128; OCR1B = 128;

  return res;
}

long smallrandom(long howbig)
{
  if (howbig == 0) {
    return 0;
  }
  return random() % howbig;
}

long rangerandom(long howsmall, long howbig)
{
  if (howsmall >= howbig) {
    return howsmall;
  }
  long diff = howbig - howsmall;
  return smallrandom(diff) + howsmall;
}

int main (void)
{
  PORTB = 0b111011;    /* Initialize port: - - H H H L H P */
  DDRB  = 0b111110;
  
  sei();
  
  //RANDOM (SEED)
  unsigned long seednumber = 1;
  unsigned char i;
  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);  //ADC Prescalar set to 64 - 125kHz@8MHz
  ADMUX |= (1 << ADLAR);
  ADCSRA |= (1 << ADEN);  // Enable ADC
    for(i = 0; i < 100; i++) {
    wdt_reset();
    ADCSRA |= (1 << ADSC);  // Start A2D Conversions
    while (ADCSRA & (1 << ADSC)); 
    seednumber = seednumber + ADCH;
  }
  ADCSRA |= (0 << ADATE);
  ADCSRA |= (0 << ADEN);  // Disable ADC
    ADCSRA |= (0 << ADSC);  // Stop A2D Conversions
  
  MCUSR = 0;
  WDTCR = _BV(WDE) | 0b110;  /* Enable WDT reset in timeout of 1s */

  BYTE res;
  //mount
  do {
    res = pf_mount(&fs);
    wdt_reset();
    seednumber++;
  } while (res != FR_OK);
  
  Buff[0] = 0;
  do {
    res = pf_opendir(&dir, (char*)Buff);
    wdt_reset();
    seednumber++;
  } while (res != FR_OK);
  
  //nb files
  for (;;) {
    res = pf_readdir(&dir, &fno);
    if (res != FR_OK || fno.fname[0] == 0) break;
    wdt_reset();
    nbwave++;
    seednumber++;
  }
  
  srandom(seednumber);
  //start with a random position
  position = rangerandom(1, nbwave + 1);

  for (;;) {
    
    //limit
    if(position > (nbwave + 1)) {
      position = 1;
    } else if(position < 1) {
      position = 1;
    }
    
    //play
    sprintf(strbuff,"%d.wav", position);
    play(strbuff);
    
    //increment song
    position++;

  }
}

servo source code

#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <avr/sleep.h>
#include <util/delay.h> 

#define F_CPU 8000000
#define nop()  __asm__ __volatile__("nop")

void wait100us(void) {
    unsigned char i;
    for(i = 0; i < 100; i++) {
      nop();
    }
}

void Delay_ms(unsigned int t)

  {
   unsigned int i, aika;
   aika = 140;

   while(t--)
    for(i = 0; i < aika; i++)

       if(t==0) return;

  } 

void adcinit (void) {

      // PRESCALER 128
      ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);

      // AUTO TRIGGER ENABLE
      ADCSRA |= (1 << ADATE);

      // VCC ref
      ADMUX |= (0 << REFS1) | (0 << REFS0);

      // RIGHT ADJUST
      ADCSRB |= (0 << ADLAR);

      // CHANNEL 0
      ADMUX |= (0 << MUX5) | (0 << MUX4) | (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0);

      // FREE RUNNING MODE
      ADCSRB |= (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0);

      // ENABLE ADC
      ADCSRA |= (1 << ADEN);

    // PORT A: INPUT
    DDRA = 0x00;
    PORTA = 0x00;

}

void pwminit (void) {
    // disable PWM while configuring it
    TCCR1A = 0;

    // used for TOP, makes for 50 hz PWM = (8,000,000 / (8 * 50)) - 1
    ICR1 = 19999;

    // servo pins outputs
    DDRA = _BV(PA5) | _BV(PA6);

    // set up counter 1 (16 bit) to act as a dual channel PWM generator
    // comparator mode = non-inverting, use ICR1 as TOP and have a prescale of 8.
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);
}

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

int main() {

  adcinit();
  pwminit();
    sei();

    // START A2D
    ADCSRA |= (1 << ADSC);

  while(1) {
    
    //700 to 2400
    //but here we need only 50 degress
    OCR1A = map(ADC, 0, 1023, 700, 1100);
    ADMUX |= (1 << MUX1);
    ADMUX &= ~(1 << MUX0);
    Delay_ms(2);
    OCR1B = map(ADC, 0, 1023, 700, 1100);
    ADMUX |= (1 << MUX0);
    ADMUX &= ~(1 << MUX1);
    Delay_ms(2);
  }
    return(0);
}

13

04 2010

biscuit box computer

i am sure there’s plenty of projects like this one. the idea is to get a “cheap” and “small” full featured linux box. a good keyword to start with: itx motherboard cpu combo. this one have hdmi 1080p, wifi, dual-core 1.6 ghz. i paid 147$ used. got also 1 gig of used ram for 20$. total of 167$ CAD. weight is 0.8 kg for the box and 0.3 kg for the supply. Just 18W at idle, and 23W full load (someone measured it with a Kill-a-watt device). the power supply is 90W. for the storage, i went with a laptop hard-drive (it’s smaller). i paid 100$ for a 500 gig, 7200 rpm, sata.

all this information will be outdated, now.

running a realtime kernel with enlightenment and blender:

01

04 2010