| Weitere Artikel aus dem Elo-Magazin |
Aus Ping Pong wird Sing Song: eine JukeBox
von Michael Gaus
Es können über die 2 Potis wie bei einer JukeBox bis zu 100 Lieder ausgewählt werden, die dann nach Münzeinwurf über eine an der Ping-Pong-Platine angeschlossene Zusatzhardware mit wenig Bauteilaufwand, der sogenannte "Simple SD Audio Player", abgespielt werden. Der "Simple SD Audio Player" kann WAV-Dateien von einer SD-Karte abspielen und benötigt nur einen ATtiny85 mit wenigen externen Bauteilen. Er wurde so modifiziert, dass per I2C-Bus ein bestimmtes Lied von der SD-Karte abgespielt werden kann.
Zusätzlich benötigte Hardware
a) Ein zusätzlicher Taster (1-poliger Schließer), der zwischen PortD.3 (also Lötpad D3) und GND angeschlossen wird. Der benötigte Pullup-Widerstand wird im Controller intern hinzugeschaltet.
b) Bauteile für "Simple SD Audio Player": ATTiny85, SD-Kartenhalter, vier Kondensatoren, Lautsprecher, Spannungsregler ADP3303-3.0
Ganzen Artikel lesen...

Da die SD-Karte mit Spannungspegeln von 2,7 ... 3,3 V arbeitet, wurde ein 3,0 V LowDrop Linearspannungsregler ADP3303-3.0 verwendet, der zusätzlich über einen Shutdown-Pin verfügt. Wenn Shutdown auf GND gezogen wird, dann werden die 3V abgeschaltet und der Spannungsregler verbraucht im Standbymodus nur wenige µA. Der Shutdown-Pin wird am Portpin D.1 der Ping Pong Platine angeschlossen, sodass der Atmega8 die Spannungsversorgung für den Simple SD Audio Player ein- und ausschalten kann.
Da mit einer minimalen Anzahl von Bauelementen ausgekommen werden soll, wurde auf den eigentlich erforderlichen Tiefpass am PWM-Ausgang des ATtiny85 verzichtet. Stattdessen wird über einen Elko direkt ein kleiner Lautsprecher angeschlossen. Zu beachten ist, dass hier kein Verstärker und keine Aktivboxen angeschlossen werden dürfen, da diese ohne Tiefpass zerstört werden könnten.
Simple SD Audio Player
Der Attiny85 liest die Daten einer WAV-Datei von der SD-Karte ein und erzeugt über eine integrierte PWM-Einheit eine Soundausgabe. Unterstützt werden Dateien im RIFF-WAVE format/LPCM, 8/16-bit, mono/stereo und bis zu 48 kHz Samplingrate. Trotz des geringen Hardwareaufwands wird eine erstaunliche gute Soundqualität erreicht, wenn die WAV-Dateien eine entsprechend gute Qualität haben, z.B. 44 kHz Samplingrate.
Das Originalprojekt mit Beschreibung stammt von einem Japaner und ist hier zu finden: http://elm-chan.org/works/sd8p/report.html
Die Software wurde so modifiziert, dass per I2C-Bus ein bestimmtes Lied von der SD-Karte abgespielt werden kann. Der Simple SD Audio Player ist dabei ein I2C-Slave, der über das USI realisiert wurde. Da die I2C-Pins des ATtiny85 eigentlich schon für die Ansteuerung der SD-Karte belegt sind, wurde hier eine softwaremäßige Umschaltung vorgenommen: nach PowerOn werden diese als I2C geschaltet und es wird gewartet, bis die Liednummer empfangen wurde. Anschließend werden die Pins als SPI Interface für die SD-kartenansteeunrg verwendet. Der Atmega8 der Ping Pong Platine ist der I2C-Master und nutzt das on-chip I2C-Interface. Die erforderlichen Pullups für I2C werden im ATmega8 intern hinzugeschaltet. Nachdem die Liednummer übertragen wurde, wird im Atmega8 das I2C-Interface abgeschaltet und beide Pins SCL und SDA werden als Eingänge beschaltet. Hierüber kann dann erkannt werden, wann das Lied zu Ende ist, da dann keine Pegelwechsel mehr auf der CLK-Leitung der SD-Karte vorhanden sind.
In der ZIP-Datei ist im Unterverzeichnis singsong_attiny85 die Hexdatei sd8p_mo.hex sowie ein Screenshot attiny85_fusebits.jpg mit den notwendigen Fusebits für den ATtiny85 enthalten. Die Programmierung des ATtiny85 kann über ISP erfolgen. Hierzu sollte noch keine SD-Karte eingelegt sein.
Anzeige und Bedienung
Durch Einwurf einer Münze wird das Sing Song gestartet. Im Display erscheinen die Buchstaben SI NG SO NG nacheinander, gefolgt von einem Notensymbol zur Symbolisierung einer JukeBox. Danach erscheint die Anzeige des gewählten Lieds, bestehend aus einem Buchstaben (A-J) und einer Ziffer (0-9).
Mit dem linken Poti oberhalb des Münzschlitzes kann der Buchstabe ausgewählt werden. Der Einstellbereich des Potis ist hierzu in 10 Teile unterteilt (Buchstaben A-J).
Mit dem rechten Poti wird die gewünschte Ziffer ausgewählt. Der Einstellbereich des Potis ist hierzu ebenfalls in 10 Teile unterteilt (Ziffern 0-9).
Durch kurzes Betätigen des Tasters wird die Auswahl bestätigt, die Liednummer blinkt kurz und das entsprechende Lied wird abgespielt. Das Ende eines Lieds wird vom ATmega8 dadurch erkannt, dass für ein paar Sekunden keine Pegelwechsel mehr auf der CLK-Leitung der SD-Karte vorhanden sind, d.h. der ATtiny85 führt nach Ende des Lieds keine weiteren SD-Kartenzugriffe mehr aus. Danach wird der ADP3303 in den Shutdown-Modus versetzt und das Sing Song geht in den stromsparenden Standby-Mode, bis erneut eine Münze eingeworfen wird.
Ein abgespieltes Lied kann durch Drücken des Tasters vorzeitig abgebrochen werden, das Sing Song geht dann in den Standbymode. Dies ist für den Testbetrieb nützlich.
Der Code für den ATmega8 auf der Ping Pong Platine wurde mit dem C-Compiler CodeVision AVR (Version 2.04.6 Evaluation) erstellt. Diese Evaluation-Version kann für den privaten nicht-kommerziellen Gebrauch kostenlos verwendet werden und ist auf eine Codegröße von 3 kB beschränkt, was für diese Anwendung ausreicht. Das komplette Projekt ist in der ZIP-Datei enthalten (Projektdatei für CodeVison AVR ist singsong.prj).
Der Code für den Simple SD Audio Player auf dem ATtiny85 wurde mit der Freeware WinAVR (Version WinAVR-20100110.exe) erstellt. Hierzu wurde der Originalcode für das Sing Song Projekt so modifiziert, dass nach PowerOn zunächst gewartet wird, bis über I2C-Bus die Nummer des abzuspielenden Lieds (WAV-Datei) empfangen wurde. Anschließend wird das gewünschte Lied abgespielt, falls vorhanden und danach gestoppt.
Download: Projektdateien und HEX-Files
Quellen/Links:
Pinbelegung SD-Karte: http://elm-chan.org/docs/mmc/mmc_e.html
Simple SD Audio Player: http://elm-chan.org/works/sd8p/report.html
USI Slave für ATtiny: www.mikrocontroller.net/topic/38917
C-Compiler CodeVision AVR: http://www.hpinfotech.ro/html/download.htm
C-Compiler WinAVR: http://sourceforge.net/projects/winavr/files/
/*****************************************************
Compiler : CodeVisionAVR 2.04.6 Evaluation
Chip type : ATmega8
Clock frequency : 8 MHz (int. RC-OSC)
*****************************************************/
//***************************************************
// Sing Song
// auf Basis des Franzis-Pingpong
// http://www.elo-web.de/ping-pong-start
//***************************************************
#include <mega8.h>
#include <stdio.h>
#include <delay.h>
#include <sleep.h>
#define P_CLK PORTB.3
#define P_DATA PORTB.4
#define P_STROBE PORTB.2
#define PIN_COIN PIND.2
#define PULLUP_COIN PORTD.2
#define DIR_COIN DDRD.2
#define COIN_INSERTED (PIN_COIN == 0)
#define PIN_KEY PIND.3
#define PULLUP_KEY PORTD.3
#define KEY_PRESSED (PIN_KEY == 0)
#define P_SHUTDOWN_SD PORTD.1 // Portpin for /Shutdown of SD card player
#define DP_SHUTDOWN_SD DDRD.1
#define SD_PLAYER_OFF (P_SHUTDOWN_SD = 0)
#define SD_PLAYER_ON (P_SHUTDOWN_SD = 1)
#define P_SDA PORTC.4 // Portpin for SDA
#define DP_SDA DDRC.4
#define PIN_SDA PINC.4
#define P_SCL PORTC.5 // Portpin for SCL
#define DP_SCL DDRC.5
#define PIN_SCL PINC.5
#define WIDTH 12 // number of fields in horizontal direction
#define HEIGHT 10 // number of fields in vertical direction
unsigned int leds[WIDTH]; // current state of each LED (organized in columns)
unsigned int ledsBlink[WIDTH]; // current blink state of each LED (organized in columns)
flash unsigned char font[] =
{
0x3E,0x51,0x49,0x45,0x3E, // 0
0x00,0x42,0x7F,0x40,0x00, // 1
0x42,0x61,0x51,0x49,0x46, // 2
0x22,0x41,0x49,0x49,0x36, // 3
0x18,0x14,0x12,0x7F,0x10, // 4
0x27,0x45,0x45,0x45,0x39, // 5
0x3C,0x4A,0x49,0x49,0x30, // 6
0x01,0x71,0x09,0x05,0x03, // 7
0x36,0x49,0x49,0x49,0x36, // 8
0x06,0x49,0x49,0x29,0x1E, // 9
0x7E,0x09,0x09,0x09,0x7E, // A
0x7F,0x49,0x49,0x49,0x36, // B
0x3E,0x41,0x41,0x41,0x22, // C
0x7F,0x41,0x41,0x41,0x3E, // D
0x7F,0x49,0x49,0x49,0x49, // E
0x7F,0x09,0x09,0x09,0x09, // F
0x3E,0x41,0x49,0x49,0x3A, // G
0x7F,0x08,0x08,0x08,0x7F, // H
0x00,0x41,0x7F,0x41,0x00, // I
0x21,0x41,0x41,0x41,0x3F, // J
0x7F,0x08,0x14,0x22,0x41, // K
0x7F,0x40,0x40,0x40,0x40, // L
0x7F,0x02,0x04,0x02,0x7F, // M
0x7F,0x02,0x04,0x08,0x7F, // N
0x3E,0x41,0x41,0x41,0x3E, // O
0x7F,0x09,0x09,0x09,0x06, // P
0x3E,0x41,0x61,0x41,0xBE, // Q
0x7F,0x09,0x19,0x29,0x46, // R
0x26,0x49,0x49,0x49,0x32, // S
0x01,0x01,0x7F,0x01,0x01, // T
0x3F,0x40,0x40,0x40,0x3F, // U
0x1F,0x20,0x40,0x20,0x1F, // V
0x7F,0x20,0x10,0x20,0x7F, // W
0x63,0x14,0x08,0x14,0x63, // X
0x03,0x04,0x78,0x04,0x03, // Y
0x61,0x51,0x49,0x45,0x43 // Z
};
// clear the whole screen, all LEDs off
void clearScreen(void)
{
unsigned char i;
for(i = 0; i < (WIDTH); i++)
{
leds[i] = 0;
}
}
// wait until key is released
void waitUntilKeyreleased(void)
{
while(KEY_PRESSED);
delay_ms(20);
}
// check if key is pressed
// return: 0=key not pressed, 1=key pressed
unsigned char keyPressed(void)
{
unsigned char result = 0;
if(KEY_PRESSED)
result = 1;
delay_ms(20);
return result;
}
// Timer 2 overflow interrupt service routine
// multiplexing of LEDs
// approx. every 1ms
interrupt [TIM2_OVF] void timer2_ovf_isr(void)
{
static unsigned char count = 0;
unsigned char cValue, cValue2;
static unsigned char cBlink = 0x00;
static unsigned char cBlinkcnt = 0;
cBlinkcnt++;
if(cBlinkcnt >= 250)
{
cBlinkcnt = 0;
cBlink = ~cBlink;
}
count++;
if(count >= 12)
{
count = 0;
P_DATA = 0;
}
else
{
P_DATA = 1;
}
P_CLK = 1;
P_CLK = 0;
PORTC &= (~0x0F);
PORTD &= (~0xF0);
PORTB &= (~0x03);
P_STROBE = 1;
P_STROBE = 0;
cValue = (unsigned char)leds[count] & 0x0F;
cValue2 = (unsigned char)ledsBlink[count] & cBlink;
cValue &= (~cValue2);
PORTC |= cValue;
cValue = (unsigned char)leds[count] & 0xF0;
cValue &= (~cValue2);
PORTD |= cValue;
cValue = (unsigned char)(leds[count]>>8) & 0x03;
cValue2 = (unsigned char)(ledsBlink[count]>>8) & cBlink;
cValue &= (~cValue2);
PORTB |= cValue;
}
// initialization of Timer2
void initTimer2(void)
{
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: 250,000 kHz
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x03;
TCNT2=0x00;
OCR2=0x00;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x40;
}
// set controller in Powerdown mode
void setStandbyMode(void)
{
TCCR2 &= 0xF8; //stop Timer2
PORTC = 0;
PORTD = 0;
PORTB = 0;
ADCSRA &= 0x7F; // stopADC
DDRD.2 = 0;
PORTD.2 = 1;
GICR |= 0x40; //enable INT0
sleep_enable();
powerdown();
GICR &= (~0x40); //disable INT0
PULLUP_KEY = 1;
DDRD.2 = 1;
PORTD.2 = 0;
ADCSRA |= 0x80; //startADC
TCCR2 |= 0x03; //start Timer2
}
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
}
// initialization of INT0
void initInt0(void)
{
// External Interrupt(s) initialization
// INT0: disabled
// INT0 Mode: Low level
GICR &= (~0x40); //disable INT0
MCUCR &= 0xFC;
GIFR=0x40;
}
// initialization of ports
void init(void)
{
PORTB = 0;
DDRB = 0x1F;
PORTC = 0;
DDRC = 0x0F;
PORTD = 0;
DDRD = 0xF4;
PULLUP_KEY = 1;
P_SHUTDOWN_SD = 0;
DP_SHUTDOWN_SD = 1;
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
}
// display a char value
// x: char value ('0'-'9', 'A'-'Z')
// row: row to begin displaying value
// col: column to begin displaying value
void showChar(unsigned int x, unsigned char row, unsigned char col)
{
unsigned char j, value;
if(x <= '9')
value = x - '0';
else
value = x - 'A' + 10;
for(j = 0; j < 5; j++)
{ // show decimal using stored font
leds[col++] = ((unsigned int)font[value*5 + j] << row);
}
}
#define ADC_VREF_TYPE 0x00
// initialization of ADC
void initADC(void)
{
// ADC initialization
// ADC Clock frequency: 125,000 kHz
// ADC Voltage Reference: AREF pin
ADMUX=ADC_VREF_TYPE & 0xff;
ADCSRA=0x86;
}
// Read the AD conversion result
// adc_input: ADC channel (0..7)
// return: ADC value (0..1023)
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}
// select song to be played
// return: selected song "digit" (0...9)
unsigned char selectSongDigit(void)
{
unsigned char x;
x = 9 - (read_adc(7) / (1023/10 + 1));
return(x);
}
// select song to be played
// return: selected song "letter" (0=A, 1=B, ..., 9=J)
unsigned char selectSongLetter(void)
{
unsigned char x;
x = (read_adc(6) / (1023/10 + 1));
return(x);
}
// show startlogo logo on LED display
void showStartLogo(void)
{
leds[0] = 0;
leds[1] = 0;
leds[2] = 64;
leds[3] = 224;
leds[4] = 127;
leds[5] = 2;
leds[6] = 260;
leds[7] = 904;
leds[8] = 504;
leds[9] = 0;
leds[10] = 0;
leds[11] = 0;
}
// switch on blinking for all LEDs
void blinkOn(void)
{
unsigned char i;
for(i = 0; i < 12; i++)
ledsBlink[i] = 0xFFFF;
}
// switch off blinking for all LEDs
void blinkOff(void)
{
unsigned char i;
for(i = 0; i < 12; i++)
ledsBlink[i] = 0;
}
// initialize I2C interface
void initI2C(void)
{
// Two Wire Bus initialization
// Bit Rate: 25,000 kHz
TWBR=0x98;
// Two Wire Bus Slave Address: 0h
// General Call Recognition: Off
TWAR=0x00;
// Generate Acknowledge Pulse: On
// TWI Interrupt: Off
TWCR=0x44;
TWSR=0x00;
}
// disable I2C interface
void disableI2C(void)
{
TWCR=0x00;
TWSR=0x00;
}
#define TWINT 7
#define TWSTA 5
#define TWSTO 4
#define TWEN 2
// I2C start condition
void i2cStart(void)
{
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
}
// I2c stop condition
void i2cStop(void)
{
TWCR = (1<<TWINT) | (1<<TWSTO) | (1<<TWEN);
}
// I2C send a byte
// c: byte to send
void i2cSend(unsigned char c)
{
TWDR = c;
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
}
// main routine
void main(void)
{
#define DELAY_START 500
unsigned char c, s, index;
unsigned int iCountSCL;
bit bKeypressed;
init();
initTimer2();
initADC();
initInt0();
clearScreen();
#asm("sei") // Global enable interrupts
while(1)
{
P_SCL = 1; // enable pullups for I2C
P_SDA = 1;
clearScreen();
showChar('S', 2, 0);
showChar('I', 2, 6);
delay_ms(DELAY_START);
showChar('N', 2, 0);
showChar('G', 2, 6);
delay_ms(DELAY_START);
showChar('S', 2, 0);
showChar('O', 2, 6);
delay_ms(DELAY_START);
showChar('N', 2, 0);
showChar('G', 2, 6);
delay_ms(DELAY_START);
showStartLogo();
delay_ms(2000);
clearScreen();
do
{
s = selectSongDigit(); // read song number "digit" (0-9)
c = selectSongLetter(); // read song number "letter" (A-J)
index = s + c*10 + 1;// calculate song number 1..100 (A0=>1, A1=>2, ..., J0=>91, ..., J9=>100)
showChar(c + 'A', 2, 0);
showChar(s + '0', 2, 6);
}
while(!keyPressed());
waitUntilKeyreleased();
blinkOn();
SD_PLAYER_ON;
delay_ms(500);
initI2C();
i2cStart();
i2cSend(0xAA); // I2C address
i2cSend(index); // I2C data
i2cStop();
disableI2C();
delay_ms(1000);
blinkOff();
iCountSCL = 0;
bKeypressed = 0;
do
{
if(PIN_SCL)
{
iCountSCL++;
}
else
{
iCountSCL = 0;
}
delay_us(100);
if(iCountSCL >= 20000)
{
break;
}
if(KEY_PRESSED)
{
bKeypressed = 1;
break;
}
}
while(1);
if(bKeypressed)
{
waitUntilKeyreleased();
}
P_SCL = 0; // disable pullups for I2C
P_SDA = 0;
SD_PLAYER_OFF; // switch OFF SD card player
clearScreen();
setStandbyMode();
}
}













