Usé un ATtiny85 para conducir un altavoz a través de un LPF a través de dos salidas PWM complementarias (OC1B / nOC1B) al presionar un botón. Menos de 0.5k de código deja 7.5k para el audio. Los chips más grandes dejan más espacio para el audio o la capacidad de usar SPI real o I 2 C para el almacenamiento. El volumen depende de la tensión de alimentación, pero aún puede obtener un rendimiento decente de una celda de ion de litio medio muerta.
Qué diablos, me siento generoso. Tenga en cuenta que nada de esto intenta ser muy consciente del poder; Consulte la licencia para detalles de modificación. Y no intentes conducir a un demasiado a través de un altavoz. Y ten cuidado con los comentarios de mierda.
/***
* Copyright © 2014 Ignacio Vazquez-Abrams
* This work is free. You can redistribute it and/or modify it under the
* terms of the WTFPL, Version 2,
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
*/
#define F_CPU 16000000
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include "audio8k.h"
// Generate audio using PWM on OC1B/~OC1B H-bridge on ATtinyX5
// This leaves the USI pins available for expansion
// Timer 1 is used to drive PWM, timer 0 is used to load the next
// sample
// References:
// http://playground.arduino.cc/Code/PCMAudio
// http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_binarydata
// play state information
typedef enum {
STATE_STOPPED,
STATE_STARTING,
STATE_PLAYING,
STATE_STOPPING
} playState;
playState currentState __attribute__ ((section (".noinit")));
// how far into the audio
unsigned long sampleCount __attribute__ ((section (".noinit")));
static void start_playing();
static void ramp_up();
static void play();
static void ramp_down();
static void stop_playing();
void start_playing()
{
cli();
// connect OC1B
GTCCR |= (_BV(PWM1B) | _BV(COM1B0));
DDRB |= (_BV(PB4) | _BV(PB3));
// clki/o/8 (2MHz) prescaling for timer 0
TCCR0B = (_BV(CS01));
// tune timer 0 to 32kHz
OCR0A = 63;
// pck (64MHz, 500kHz PWM) prescaling for timer 1
TCCR1 = (_BV(CS10));
// prime the PWM
OCR1B = 0x00;
// unprescale clock
clock_prescale_set(clock_div_1);
// prepare for liftoff
sampleCount = 0;
currentState = STATE_STARTING;
sei();
}
void ramp_up()
{
unsigned char firstSample = pgm_read_byte(&audio8k[0]);
OCR1B += 1;
if (OCR1B >= firstSample)
{
OCR1B = firstSample;
currentState = STATE_PLAYING;
}
}
void play()
{
// audio from PROGMEM
OCR1B = pgm_read_byte(&audio8k[sampleCount++ >> 2]);
if ((sampleCount >> 2) >= (audio8k_end - audio8k))
{
currentState = STATE_STOPPING;
}
}
void ramp_down()
{
if (OCR1B > 1)
{
OCR1B -= 1;
}
else
{
OCR1B = 0;
stop_playing();
}
}
void stop_playing()
{
cli();
// clki/o/8 (2MHz) prescaling for timer 0
TCCR0B = (_BV(CS01));
// tune timer 0 to 7812.5Hz
OCR0A = 0x00;
// prescale clock by 256
//XX UNCOMMENT FOR PRODUCTION clock_prescale_set(clock_div_256);
// kthxbye
currentState = STATE_STOPPED;
sampleCount = 0;
sei();
}
void setup()
{
// ssshhhh....
cli();
// speaker is connected to pins 3 and 2, PB4/PB3 (OC1B/~OC1B)
DDRB |= (_BV(PB4) | _BV(PB3));
// button pullup goes here
PORTB |= _BV(PB2);
// use CTC on timer 0 for finer tuning
TCCR0A = _BV(WGM01);
// enable the timer 0 output compare A interrupt
TIMSK |= _BV(OCIE0A);
// enable async clock (PCK) for timer 1
PLLCSR |= _BV(PCKE);
// set sleep mode for later, leave i/o running
set_sleep_mode(SLEEP_MODE_IDLE);
// sane defaults
stop_playing();
// and... go!
sei();
}
// state machine dispatch
ISR(TIM0_COMPA_vect)
{
switch (currentState)
{
case STATE_STOPPED:
{
if (!(PINB & _BV(PB2)))
{
start_playing();
}
break;
}
case STATE_STARTING:
{
ramp_up();
break;
}
case STATE_PLAYING:
{
play();
break;
}
case STATE_STOPPING:
{
ramp_down();
break;
}
}
}
void loop()
{
// this loop should do nothing; everything is handled by interrupts
sleep_enable();
sleep_cpu();
sleep_disable();
}
int main()
{
setup();
while (1)
loop();
}