The ultimate geek's Birthday Cake

Discussion in 'The Completed Projects Collection' started by Maksym Ganenko, Mar 9, 2015.

  1. Maksym Ganenko

    Thread Starter New Member

    Mar 9, 2015
    5
    1
    Dear All About Circuits community,

    I would like to show you my open source electronic toy design based on Arduino - BitCake. That's electronic cake with candles you can blow out.

    I use ATTiny44 MCU programmed as Arduino. There's piezo sensor and air trap to detect blowing using resonance effect. The program constantly takes signal samples and makes partial Fourier transform to read amplitude around 750 Hz frequency. The device has astounding sensitivity and selectivity!​

    When blowing is detected, LEDs are blinked with randomly generated period, phase and time-to-live. The phase hops ±180 degrees depending on the blowing amplitude so candles behave very lively.

    Official webpage of the project is http://bitcake.eu

    Maksym​

    [​IMG]

     
    darrough likes this.
  2. Maksym Ganenko

    Thread Starter New Member

    Mar 9, 2015
    5
    1
    Hardware:

    [​IMG]

    The heart of the device is Atmel ATTiny44 microcontroller. Its built-in differential ADC channel with 20x gain is connected to a piezo sensor. The rest nine general input-output pins are connected to nine separate LEDs circuits.

    A step-up DC-to-DC power converter is used to increase output voltage to 3.3V at the expense of increased current provided by the battery. For that purpose, Holtek 7733 integrated circuit is used. Its SOT-23-5 package has additional Chip Enable (CE) input that turns off the converter into deep shutdown with ultra-low supply current.

    By pushing the button we open transistor to charge 10 µF capacitor that enables the step-up converter. At the same time a reset signal is sent to the microcontroller, so it starts to execute its program. The 10 µF capacitor slowly discharges via 10 MOhm resistor so that it will turn off the converter in 3 minutes.


    Firmware:

    Code (Text):
    1. /////////////////////////////////////////////////////////////////////////////////////////////////
    2. // bitcake v1.1.2 / December 24, 2014
    3. // by Maksym Ganenko <buratin.barabanus at Google Mail>
    4. /////////////////////////////////////////////////////////////////////////////////////////////////
    5.  
    6. #include <avr/interrupt.h>
    7. #include <avr/pgmspace.h>
    8. #include <avr/power.h>
    9. #include <avr/sleep.h>
    10. #include <time.h>
    11. #include <util/delay.h>
    12.  
    13. /////////////////////////////////////////////////////////////////////////////////////////////////
    14.  
    15. // set fixed delta loop time in milliseconds
    16. // 0 to use internal timer
    17. #define DELTA_LOOP_TIME_MS            14
    18.  
    19. // amplify intermediate values to get better calculation accuracy
    20. const uint8_t     SAMPLES_GAIN_ORDER  = 5; // x32
    21. const uint8_t     RESULT_GAIN_ORDER   = 2; // x4
    22.  
    23. // LEDs encoded by ports ID
    24. const prog_int8_t LEDS[] PROGMEM      = { 0xA6, 0xA7, 0xB2, 0xB1, 0xB0, 0xA2, 0xA3, 0xA4, 0xA5 };
    25. const uint8_t     LEDS_NUM            = sizeof(LEDS) / sizeof(LEDS[0]);
    26.  
    27. // ADMUX register code for ADC
    28. const uint8_t     PIEZO_ADMUX         = 0b10101001; // Vref = 1.1V, (A1 - A0) x 20
    29.  
    30. // MCU prescaler
    31. const uint8_t     MCU_PRESCALER       = 0b000; // 1 => 8 Mhz CPU clock
    32.  
    33. // ADC prescaler
    34. const uint8_t     ADC_PRESCALER       = 0b100; // 16 => 512 kHz ADC clock => 39.4k reads per sec
    35.  
    36. // number of piezo reads to average per sample (for noise reduction)
    37. const uint8_t     SUBSAMPLE_BUF_ORDER = 4; // => 16
    38. const uint8_t     SUBSAMPLE_BUF_SIZE  = (1 << SUBSAMPLE_BUF_ORDER);
    39.  
    40. // FFT samples number - can't be changed without changing FFT calculation code
    41. const uint8_t     SAMPLE_BUF_ORDER    = 5; // => 32
    42. const uint8_t     SAMPLE_BUF_SIZE     = (1 << SAMPLE_BUF_ORDER);
    43.  
    44. // FFT signal threshold to activate blowing logic
    45. const uint8_t     BLOWING_THRESHOLD   = 3;
    46.  
    47. // length of a blowing sequence to start blowing
    48. const uint8_t     BLOWING_COMBO       = 1;
    49.  
    50. // timeouts in milliseconds
    51. const uint32_t    SETUP_TIME_MS       = 750;    // timeout before activating of cake logic
    52. const uint32_t    PROLONG_BLOWING_MS  = 150;    // prolong blowing logic when no blowing detected
    53. const uint32_t    NO_ACTIVITY_MS      = 60000;  // turn off cake if no blowing detected
    54. const uint32_t    TIME_LIMIT_MS       = 150000; // time limit for cake to work
    55.  
    56. // LEDs blinking periods when blowing logic is activated
    57. const uint8_t     LEDS_PERIOD_MIN_MS  = 100;
    58. const uint8_t     LEDS_PERIOD_MAX_MS  = 150;
    59.  
    60. // LEDs time-to-live timeouts
    61. const uint16_t    LEDS_TTL_MIN_MS     = 200;
    62. const uint16_t    LEDS_TTL_MAX_MS     = 1000;
    63.  
    64. /////////////////////////////////////////////////////////////////////////////////////////////////
    65.  
    66. volatile uint8_t  samplePos           = SAMPLE_BUF_SIZE;
    67.  
    68. // FFT10 specific accumulators
    69. int16_t           sampleAccA          [5];
    70. int16_t           sampleAccB          [5];
    71.  
    72. // LEDs state variables
    73. uint16_t          ledsActivity;
    74. uint8_t           ledsPeriod          [LEDS_NUM];
    75. uint8_t           ledsPhase           [LEDS_NUM];
    76. uint8_t           ledsTTL             [LEDS_NUM];
    77.  
    78. // blowing logic state
    79. uint8_t           blowing             = false;
    80. uint8_t           blowingCombo        = 0;
    81. uint32_t          lastBlowingTime     = 0;
    82. int16_t           totalBlowingTime    = 0;
    83.  
    84. uint32_t          globalTime          = 0;
    85. uint32_t          lastLoopTime;
    86. uint32_t          setupPhaseTime;
    87.  
    88. // for compatibility with other Atmel MCUs
    89. uint8_t           portA, portB, portC, portD;
    90.  
    91. /////////////////////////////////////////////////////////////////////////////////////////////////
    92.  
    93. // fast distance approximation
    94. uint32_t approxDist(int32_t dx, int32_t dy)
    95. {
    96.    uint32_t min, max;
    97.  
    98.    if (dx < 0)  dx = -dx;
    99.    if (dy < 0)  dy = -dy;
    100.  
    101.    if (dx < dy) { min = dx; max = dy; }
    102.    else         { min = dy; max = dx; }
    103.  
    104.    // coefficients equivalent to (123/128 * max) and (51/128 * min)
    105.    return (((max << 8) + (max << 3) - (max << 4) - (max << 1) +
    106.             (min << 7) - (min << 5) + (min << 3) - (min << 1)) >> 8);
    107. }
    108.  
    109. const uint8_t FFT_DIVIDER_ORDER = 8; // => 256
    110.  
    111. // approximate multiplication
    112. int32_t mul256(int32_t x) { return x << 8; }
    113. int32_t mul240(int32_t x) { return (x << 8) - (x << 4); }
    114. int32_t mul208(int32_t x) { return (x << 7) + (x << 6) + (x << 4); }
    115. int32_t mul176(int32_t x) { return (x << 7) + (x << 5) + (x << 4); }
    116. int32_t mul144(int32_t x) { return (x << 7) + (x << 4); }
    117. int32_t mul96(int32_t x)  { return (x << 6) + (x << 5); }
    118. int32_t mul48(int32_t x)  { return (x << 5) + (x << 4); }
    119.  
    120. typedef int32_t (*fmul32)(int32_t);
    121. const fmul32 fmulVec[4] = { mul96, mul176, mul240, mul256 };
    122.  
    123. // calculate FFT[10] for 32 samples
    124. uint8_t fft10() {
    125.   int32_t a = 0;
    126.   for (uint8_t i = 0; i < 4; ++i) {
    127.     a += fmulVec[i](sampleAccA[i + 1]);
    128.   }
    129.  
    130.   int32_t b = 0;
    131.   for (uint8_t i = 0; i < 4; ++i) {
    132.     b += fmulVec[i](sampleAccB[i + 1]);
    133.   }
    134.  
    135.   uint32_t result = approxDist(a << RESULT_GAIN_ORDER, b << RESULT_GAIN_ORDER);
    136.   result >>= FFT_DIVIDER_ORDER;
    137.   if (result > 0xff) return 0xff;
    138.   return result;
    139. }
    140.  
    141. /////////////////////////////////////////////////////////////////////////////////////////////////
    142.  
    143. // fft10 specific coefficients
    144. const prog_int8_t sampleAccDestA[SAMPLE_BUF_SIZE / 2] PROGMEM = {
    145.   +4, -1, -2, +3, +0, -3, +2, +1, -4, +1, +2, -3, +0, +3, -2, -1
    146. };
    147. const prog_int8_t sampleAccDestB[SAMPLE_BUF_SIZE / 2] PROGMEM = {
    148.   +0, +3, -2, -1, +4, -1, -2, +3, +0, -3, +2, +1, -4, +1, +2, -3
    149. };
    150.  
    151. const uint8_t HANNING_DIVIDER_ORDER = 6; // => 64
    152.  
    153. // hanning window coefficients
    154. int16_t mul0(int16_t x)   { return 0; }
    155. int16_t mul1(int16_t x)   { return x; }
    156. int16_t mul3(int16_t x)   { return (x << 2) - x; }
    157. int16_t mul6(int16_t x)   { return (x << 3) - (x << 1); }
    158. int16_t mul10(int16_t x)  { return (x << 3) + (x << 1); }
    159. int16_t mul15(int16_t x)  { return (x << 4) - x; }
    160. int16_t mul21(int16_t x)  { return (x << 4) + (x << 2) + x; }
    161. int16_t mul27(int16_t x)  { return (x << 5) - (x << 2) - x; }
    162. int16_t mul34(int16_t x)  { return (x << 5) + (x << 2); }
    163. int16_t mul40(int16_t x)  { return (x << 5) + (x << 3); }
    164. int16_t mul46(int16_t x)  { return (x << 5) + (x << 4) - (x << 2); }
    165. int16_t mul52(int16_t x)  { return (x << 6) - (x << 3) - (x << 2); }
    166. int16_t mul56(int16_t x)  { return (x << 6) - (x << 3); }
    167. int16_t mul60(int16_t x)  { return (x << 6) - (x << 2); }
    168. int16_t mul63(int16_t x)  { return (x << 6) - x; }
    169. int16_t mul64(int16_t x)  { return (x << 6); }
    170.  
    171. // hanning window coefficients
    172. typedef int16_t (*fmul16)(int16_t);
    173. const fmul16 hanningVec[] = {
    174.   mul0, mul1, mul3, mul6, mul10, mul15, mul21, mul27,
    175.   mul34, mul40, mul46, mul52, mul56, mul60, mul63, mul64
    176. };
    177.  
    178. // ADC interrup routine
    179. // we average SUBSAMPLE_BUF_SIZE reads from ADC to reduce noise
    180. // and apply the calculated value on fft10 specific accumulators
    181. ISR(ADC_vect)
    182. {
    183.   static uint8_t subsampleCtr = 0;
    184.   static int16_t subsampleSum = 0;
    185.  
    186.   // read ADC
    187.   uint8_t low = ADCL, high = ADCH;
    188.   int16_t subsample = (high << 8) | low;
    189.  
    190.   if (samplePos < SAMPLE_BUF_SIZE) {
    191.     subsampleSum += subsample;
    192.     ++subsampleCtr;
    193.  
    194.     if (subsampleCtr == SUBSAMPLE_BUF_SIZE) {
    195.       // average of subsamples
    196.       int16_t sample = (subsampleSum >> SUBSAMPLE_BUF_ORDER) << SAMPLES_GAIN_ORDER;
    197.    
    198.       uint8_t halfPos = samplePos & (SAMPLE_BUF_SIZE / 2 - 1);
    199.       uint8_t mulPos = halfPos;
    200.       if (halfPos != samplePos) {
    201.         mulPos = SAMPLE_BUF_SIZE / 2 - mulPos;
    202.       }
    203.       // multiply by hanning window coefficient
    204.       sample = hanningVec[mulPos](sample) >> HANNING_DIVIDER_ORDER;
    205.  
    206.       int8_t destA = pgm_read_byte_near(sampleAccDestA + halfPos);
    207.       int8_t destB = pgm_read_byte_near(sampleAccDestB + halfPos);
    208.  
    209.       if (destA >= 0)  sampleAccA[destA]  += sample;
    210.       else             sampleAccA[-destA] -= sample;
    211.       if (destB >= 0)  sampleAccB[destB]  += sample;
    212.       else             sampleAccB[-destB] -= sample;
    213.    
    214.       ++samplePos;
    215.       subsampleSum = subsampleCtr = 0;
    216.     }
    217.   }
    218. }
    219.  
    220. /////////////////////////////////////////////////////////////////////////////////////////////////
    221.  
    222. void powerDown() {
    223.   // all pins to low
    224.   portA = portB = portC = portD = 0;
    225.   portsUpdateFinish();
    226.  
    227.   // disable ADC
    228.   ADCSRA &= ~_BV(ADEN);
    229.  
    230.   // power down
    231.   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    232.   sleep_mode();
    233. }
    234.  
    235. void portsUpdateStart() {
    236.   #if defined(PORTA)
    237.     portA = PORTA;
    238.   #endif
    239.   #if defined(PORTB)
    240.     portB = PORTB;
    241.   #endif
    242.   #if defined(PORTC)
    243.     portC = PORTC;
    244.   #endif
    245.   #if defined(PORTD)
    246.     portD = PORTD;
    247.   #endif
    248. }
    249.  
    250. void portsUpdateFinish() {
    251.   #if defined(PORTA)
    252.     if (PORTA != portA) { PORTA = portA; }
    253.   #endif
    254.   #if defined(PORTB)
    255.     if (PORTB != portB) { PORTB = portB; }
    256.   #endif
    257.   #if defined(PORTC)
    258.     if (PORTC != portC) { PORTC = portC; }
    259.   #endif
    260.   #if defined(PORTD)
    261.     if (PORTD != portD) { PORTD = portD; }
    262.   #endif
    263. }
    264.  
    265. void writeLed(uint8_t anIndex, uint8_t aValue) {
    266.   uint8_t led = pgm_read_byte_near(LEDS + anIndex);
    267.   uint8_t code = _BV(led & 0x0F);
    268.   if (aValue && bitRead(ledsActivity, anIndex)) {
    269.     switch(led & 0xF0) {
    270.       #if defined(PORTA)
    271.         case 0xA0: portA |= code; break;
    272.       #endif
    273.       #if defined(PORTB)
    274.         case 0xB0: portB |= code; break;
    275.       #endif
    276.       #if defined(PORTC)
    277.         case 0xC0: portC |= code; break;
    278.       #endif
    279.       #if defined(PORTD)
    280.         case 0xD0: portD |= code; break;
    281.       #endif
    282.     }
    283.   } else {
    284.     switch(led & 0xF0) {
    285.       #if defined(PORTA)
    286.         case 0xA0: portA &= ~code; break;
    287.       #endif
    288.       #if defined(PORTB)
    289.         case 0xB0: portB &= ~code; break;
    290.       #endif
    291.       #if defined(PORTC)
    292.         case 0xC0: portC &= ~code; break;
    293.       #endif
    294.       #if defined(PORTD)
    295.         case 0xD0: portD &= ~code; break;
    296.       #endif
    297.     }
    298.   }
    299. }
    300.  
    301. /////////////////////////////////////////////////////////////////////////////////////////////////
    302.  
    303. void setup() {
    304.   portsUpdateStart();
    305.   for (uint8_t i = 0; i < LEDS_NUM; ++i) {
    306.     bitSet(ledsActivity, i);
    307.     uint8_t led = pgm_read_byte_near(LEDS + i);
    308.     uint8_t code = _BV(led & 0x0F);
    309.     switch (led & 0xF0) {
    310.       #if defined(DDRA)
    311.         case 0xA0: DDRA |= code; break;
    312.       #endif
    313.       #if defined(DDRB)
    314.         case 0xB0: DDRB |= code; break;
    315.       #endif
    316.       #if defined(DDRC)
    317.         case 0xC0: DDRC |= code; break;
    318.       #endif
    319.       #if defined(DDRD)
    320.         case 0xD0: DDRD |= code; break;
    321.       #endif
    322.     }
    323.     writeLed(i, HIGH);
    324.   }
    325.   portsUpdateFinish();
    326.  
    327.   // set MCU prescaler
    328.   CLKPR = 0b10000000;
    329.   CLKPR = MCU_PRESCALER;
    330.  
    331.   // set ADC prescaler
    332.   ADCSRA = (ADCSRA & ~0b111) | ADC_PRESCALER;
    333.  
    334.   // activate ADC auto-triggering
    335.   ADCSRA |= _BV(ADATE) | _BV(ADIE);
    336.   ADMUX = PIEZO_ADMUX;
    337.   ADCSRA |= _BV(ADSC);
    338.  
    339.   // disable all digital inputs
    340.   DIDR0 = 0xff;
    341.  
    342.   // disable analog comparator
    343.   ACSR |= _BV(ACD);
    344.  
    345.   // disable timer if delta loop time is defined
    346.   if (DELTA_LOOP_TIME_MS) {
    347.     power_timer0_disable();
    348.     power_timer1_disable();
    349.     set_sleep_mode(SLEEP_MODE_ADC);
    350.   }
    351.  
    352.   _delay_ms(100);
    353.  
    354.   lastLoopTime = DELTA_LOOP_TIME_MS ? 0 : millis();
    355.   setupPhaseTime = lastLoopTime + SETUP_TIME_MS;
    356. }
    357.  
    358. void loop() {
    359.   uint32_t time = DELTA_LOOP_TIME_MS ? globalTime : millis();
    360.   uint16_t loopDeltaTime = time - lastLoopTime;
    361.   uint8_t setupPhase = time < setupPhaseTime;
    362.   rand(); // update random seed
    363.  
    364.   // wait for ADC routine to read all samples for FFT
    365.   memset(sampleAccA, 0, sizeof(sampleAccA));
    366.   memset(sampleAccB, 0, sizeof(sampleAccB));
    367.   samplePos = 0;
    368.   while (samplePos != SAMPLE_BUF_SIZE) {
    369.     if (DELTA_LOOP_TIME_MS) { sleep_mode(); }
    370.   }
    371.  
    372.   portsUpdateStart();
    373.  
    374.   // calculate FFT[10]
    375.   uint8_t signal = fft10();
    376.  
    377.   // blowing detection
    378.   if (signal > BLOWING_THRESHOLD) {
    379.     ++blowingCombo;
    380.     if (!blowing && blowingCombo >= BLOWING_COMBO) {
    381.       blowing = !setupPhase;
    382.       // generate LEDs flickering values
    383.       for (uint8_t i = 0; i < LEDS_NUM; ++i) {
    384.         ledsPeriod[i] = LEDS_PERIOD_MIN_MS + rand() % (LEDS_PERIOD_MAX_MS - LEDS_PERIOD_MIN_MS);
    385.         ledsTTL[i] = (LEDS_TTL_MIN_MS + rand() % (LEDS_TTL_MAX_MS - LEDS_TTL_MIN_MS)) >> 3;
    386.         ledsPhase[i] = rand() % ledsPeriod[i];
    387.       }
    388.     }
    389.     lastBlowingTime = time;
    390.   } else {
    391.     blowingCombo = 0;
    392.   }
    393.  
    394.   if (blowing && time - lastBlowingTime > PROLONG_BLOWING_MS) {
    395.     blowing = false;
    396.   }
    397.  
    398.   if (blowing) {
    399.     totalBlowingTime += loopDeltaTime;
    400.  
    401.     // prolong startup time until noise stabilizes
    402.     if (setupPhase) { setupPhaseTime += SETUP_TIME_MS; }
    403.  
    404.     // update LEDs state
    405.     for (uint8_t i = 0; i < LEDS_NUM; ++i) {
    406.       uint8_t level = ((time + ledsPhase[i]) % ledsPeriod[i] < (ledsPeriod[i] >> 1))
    407.                     ? LOW : HIGH;
    408.       if (signal <= BLOWING_THRESHOLD) { level = !level; }
    409.       writeLed(i, level);
    410.    
    411.       if (!setupPhase && totalBlowingTime > (ledsTTL[i] << 3)) { bitClear(ledsActivity, i); }
    412.     }
    413.   } else {
    414.     totalBlowingTime = max(0, totalBlowingTime - loopDeltaTime);
    415.     if (totalBlowingTime < 0) totalBlowingTime = 0;
    416.     for (uint8_t i = 0; i < LEDS_NUM; ++i) { writeLed(i, HIGH); }
    417.   }
    418.  
    419.   if (setupPhase) {
    420.     if (time >= 1500) { // show busy state
    421.         int lowLed = (time >> 6) % LEDS_NUM;
    422.         for (uint8_t i = 0; i < LEDS_NUM; ++i) {
    423.           writeLed(i, (i == lowLed) ? LOW : HIGH);
    424.         }
    425.     }
    426.   } else {
    427.     const bool DEBUG_MODE     = false;    // trace debug value using LEDs
    428.     const bool INVERT_LEVELS  = true;     // LOW level means 1, HIGH level means 0
    429.     const bool MEASURE_TIME   = false;    // measure time in ms (minus offset, see code)
    430.     const bool SHOW_ORDER     = false;    // show value as binary order
    431.  
    432.     if (DEBUG_MODE) {
    433.       int value = signal; // value to show
    434.       if (MEASURE_TIME) {
    435.         static uint32_t totalLoopTime = 0;
    436.         static uint32_t loopCtr = 0;
    437.         totalLoopTime += loopDeltaTime;
    438.         ++loopCtr;
    439.         // set time offset here
    440.         value = totalLoopTime / loopCtr - 10;
    441.       }
    442.  
    443.       int dbgValue = value;
    444.       if (SHOW_ORDER) {
    445.         dbgValue = 0;
    446.         for (; value > 0; ++dbgValue, value >>= 1);
    447.       }
    448.  
    449.       for (uint8_t i = 0; i < LEDS_NUM; ++i) {
    450.         bitSet(ledsActivity, i);
    451.         writeLed(i, (dbgValue > i)
    452.           ? (INVERT_LEVELS ? LOW  : HIGH)
    453.           : (INVERT_LEVELS ? HIGH : LOW));
    454.       }
    455.  
    456.       // the last LED shows blowing state
    457.       writeLed(LEDS_NUM - 1, blowing ? HIGH : LOW);
    458.    }
    459.   }
    460.  
    461.   portsUpdateFinish();
    462.  
    463.   if (ledsActivity == 0 || time - lastBlowingTime > NO_ACTIVITY_MS || time > TIME_LIMIT_MS) {
    464.     powerDown();
    465.   }
    466.  
    467.   if (DELTA_LOOP_TIME_MS) {
    468.     globalTime += DELTA_LOOP_TIME_MS;
    469.   }
    470.   lastLoopTime = time;
    471. }
    472.  
    473. /////////////////////////////////////////////////////////////////////////////////////////////////
     
  3. Brownout

    Well-Known Member

    Jan 10, 2012
    2,375
    998
    Cute. ... ... ....
     
  4. Maksym Ganenko

    Thread Starter New Member

    Mar 9, 2015
    5
    1
    Thanks! It was initially designed to be cute =)
    At first I posted this project to Russian IT community and got a lot of feedback. I thought the project would get even more appreciation from English-speaking world, so I posted it to several IT communities.
     
  5. alfacliff

    Well-Known Member

    Dec 13, 2013
    2,449
    428
    there used to be a circuit for a "magic candle" which you would light an electric lamp with a match and blow it out to turn it off. worked by the resistance of the filament which changes with temprature. gthe heat radiated by the match would lower the resistance of the filament and blowing on it would raise the temprature.
    looks like a pretty simple but neat project you have there.
     
Loading...