/* Trains by Chris Ritchie - 28.03.2018 */ #include "horn.h" #include <stdint.h> #include <avr/interrupt.h> #include <avr/io.h> #include <avr/pgmspace.h> #include <EEPROM.h> #define SAMPLE_RATE 8000 // Mode to turn off train lights when horn button is pressed #define SWITCH_MODE true bool turnOnLights = 1; // Define Arduino Nano pins const byte redPin = 10; const byte greenPin = 9; const byte bluePin = 6; const byte locomotivePin = 13; const byte boxcarPin = 8; const byte caboosePin = 12; const byte speakerPin = 3; const byte speakerBttn = 2; // Delay for rbg led color cycle const byte cycleDelay = 30; // Variables for horn sound volatile uint16_t sample; byte lastSample; unsigned long lastHorn; // Cycle RGB LED colours class RgbLeds { unsigned int red; unsigned int green; unsigned int blue; public: void Cycle() { unsigned int rgbColour[3]; // Start off with red. rgbColour[0] = 255; rgbColour[1] = 0; rgbColour[2] = 0; // Choose the colours to increment and decrement. for (int decColour = 0; decColour < 3; decColour += 1) { int incColour = decColour == 2 ? 0 : decColour + 1; // cross-fade the two colours. for (int i = 0; i < 255; i += 1) { rgbColour[decColour] -= 1; rgbColour[incColour] += 1; SetColourRgb(rgbColour[0], rgbColour[1], rgbColour[2]); delay(cycleDelay); } } } void SetColourRgb(unsigned int red, unsigned int green, unsigned int blue) { analogWrite(redPin, red); analogWrite(greenPin, green); analogWrite(bluePin, blue); } }; class Horn { public: void stopPlayback() { // Disable playback per-sample interrupt. TIMSK1 &= ~_BV(OCIE1A); // Disable the per-sample timer completely. TCCR1B &= ~_BV(CS10); // Disable the PWM timer. TCCR2B &= ~_BV(CS10); digitalWrite(speakerPin, LOW); } void startPlayback() { pinMode(speakerPin, OUTPUT); // Set up Timer 2 to do pulse width modulation on the speaker // pin. // Use internal clock (datasheet p.160) ASSR &= ~(_BV(EXCLK) | _BV(AS2)); // Set fast PWM mode (p.157) TCCR2A |= _BV(WGM21) | _BV(WGM20); TCCR2B &= ~_BV(WGM22); if (speakerPin == 11) { // Do non-inverting PWM on pin OC2A (p.155) // On the Arduino this is pin 11. TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0); TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0)); // No prescaler (p.158) TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // Set initial pulse width to the first sample. OCR2A = pgm_read_byte(&sounddata_data[0]); } else { // Do non-inverting PWM on pin OC2B (p.155) // On the Arduino this is pin 3. TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0); TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0)); // No prescaler (p.158) TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // Set initial pulse width to the first sample. OCR2B = pgm_read_byte(&sounddata_data[0]); } // Set up Timer 1 to send a sample every interrupt. cli(); // Set CTC mode (Clear Timer on Compare Match) (p.133) // Have to set OCR1A *after*, otherwise it gets reset to 0! TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10)); // No prescaler (p.134) TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // Set the compare register (OCR1A). // OCR1A is a 16-bit register, so we have to do this with // interrupts disabled to be safe. OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000 // Enable interrupt when TCNT1 == OCR1A (p.136) TIMSK1 |= _BV(OCIE1A); lastSample = pgm_read_byte(&sounddata_data[sounddata_length - 1]); sample = 0; sei(); } }; RgbLeds rgb; Horn horn; void setup() { //Serial.begin(115200); // Prevent speaker from making faint humming noise after playing horn digitalWrite(speakerPin, LOW); pinMode(speakerPin, INPUT); // If SWITCH_MODE is true read setting from memory #if SWITCH_MODE == true turnOnLights = EEPROM.read(0); #endif // Serial.println(value); if (turnOnLights == 0) { digitalWrite(locomotivePin, LOW); analogWrite(boxcarPin, 0); analogWrite(caboosePin, 0); } else trainLights(); // Start off with the LED off. rgb.SetColourRgb(0, 0, 0); pinMode(speakerBttn, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(speakerBttn), PlayHorn, FALLING); } void PlayHorn() { // If SWITCH_MODE is true save setting to memory #if SWITCH_MODE == true if (turnOnLights == 1) EEPROM.write(0, 0); else EEPROM.write(0, 1); #endif unsigned long currMillis = millis(); if (currMillis - lastHorn > 900) { rgb.SetColourRgb(255, 0, 0); // Set LED colour to prevent them from turning off when playing horn lastHorn = currMillis; horn.startPlayback(); delay(400); horn.startPlayback(); delay(1000); asm volatile (" jmp 0"); // Reset Arduino } } void trainLights() { // Turn on LED's on trains digitalWrite(locomotivePin, HIGH); delay(500); analogWrite(boxcarPin, 255); delay(500); analogWrite(caboosePin, 255); delay(500); } void loop() { rgb.Cycle(); } // This is called at 8000 Hz to load the next sample. ISR(TIMER1_COMPA_vect) { if (sample >= sounddata_length) { if (sample == sounddata_length + lastSample) { horn.stopPlayback(); } else { if (speakerPin == 11) { // Ramp down to zero to reduce the click at the end of playback. OCR2A = sounddata_length + lastSample - sample; } else { OCR2B = sounddata_length + lastSample - sample; } } } else { if (speakerPin == 11) { OCR2A = pgm_read_byte(&sounddata_data[sample]); } else { OCR2B = pgm_read_byte(&sounddata_data[sample]); } } ++sample; }