/* This code was quick and dirty, based on a PCM audio example in the arduino playground: http://playground.arduino.cc/Code/PCMAudio It's been heavely modified for use with RC to generate something that's a bit like an engine sound. I've started work on making the program readable, still some to do though. https://github.com/BeigeMatchbox/mojoEngineSim/blob/master/README.md Changes, done by TheDIYGUY999 2017 - 2019: https://github.com/TheDIYGuy999/Rc_Engine_Sound - more engine sounds added - removed SPI throttle mode, digipot support (I had no use for it in an RC vehicle) - added engine start sound - added engine switch off (fader) - added throttle zero autocalibration - added simulated engine shifting points */ // All the required vehicle specific settings are done in settings.h! #include "settings.h" // <<------- SETTINGS const float codeVersion = 1.33; // Software revision // // ======================================================================================================= // LIRBARIES & TABS // ======================================================================================================= // #include "curves.h" // load nonlinear throttle curve arrays // // ======================================================================================================= // PIN ASSIGNMENTS & GLOBAL VARIABLES (Do not play around here) // ======================================================================================================= // #define SPEAKER 3 // Amplifier PAM8403 connected to pin 3 (via 10kOhm potentiometer) #define THROTTLE_INPUT 2 // RC Signal connected to pin 2 #define FREQ 16000000L // 16MHz clock frequency // Define global variables volatile uint16_t currentSmpleRate = BASE_RATE; // Current playback rate, this is adjusted depending on engine RPM volatile uint16_t fixedSmpleRate = FREQ / BASE_RATE; // Current playback rate, this is adjusted depending on engine RPM volatile uint8_t engineState = 0; // 0 = off, 1 = starting, 2 = running, 3 = stopping volatile boolean engineOn = false; // Signal for engine on / off volatile uint16_t curEngineSample; // Index of currently loaded engine sample volatile uint16_t curStartSample; // Index of currently loaded start sample volatile uint16_t curHornSample; // Index of currently loaded horn sample uint16_t currentThrottle = 0; // 0 - 1000, a top value of 1023 is acceptable volatile int16_t pulseWidth = 0; // Current RC signal pulse width volatile boolean pulseAvailable; // RC signal pulses are coming in int16_t pulseMaxNeutral; // PWM throttle configuration storage variables int16_t pulseMinNeutral; int16_t pulseMax; int16_t pulseMin; int16_t pulseMaxLimit; int16_t pulseMinLimit; // // ======================================================================================================= // MAIN ARDUINO SETUP (1x during startup) // ======================================================================================================= // void setup() { attachInterrupt(0, getPulsewidth, CHANGE); // wait for RC receiver to initialize delay(1000); // then compute the RC channel offset (only, if "engineManualOnOff" inactive) if (!engineManualOnOff) pulseZero = pulseWidth; // Calculate throttle range pulseMaxNeutral = pulseZero + pulseNeutral; pulseMinNeutral = pulseZero - pulseNeutral; pulseMax = pulseZero + pulseSpan; pulseMin = pulseZero - pulseSpan; pulseMaxLimit = pulseZero + pulseLimit; pulseMinLimit = pulseZero - pulseLimit; // setup complete, so start making sounds setupPcm(); } // // ======================================================================================================= // PCM setup // ======================================================================================================= // void setupPcm() { pinMode(SPEAKER, OUTPUT); // Set up Timer 2 to do pulse width modulation on the speaker pin. ASSR &= ~(_BV(EXCLK) | _BV(AS2)); // Use internal clock (datasheet p.160) TCCR2A |= _BV(WGM21) | _BV(WGM20); // Set fast PWM mode (p.157) TCCR2B &= ~_BV(WGM22); TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0); // Do non-inverting PWM on pin OC2B (p.155) TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0)); // On the Arduino this is pin 3. TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // No prescaler (p.158) OCR2B = pgm_read_byte(&idle_data[0]); // Set initial pulse width to the first sample. // Set up Timer 1 to send a sample every interrupt. cli(); TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); // Set CTC mode (Clear Timer on Compare Match) (p.133) TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10)); // Have to set OCR1A *after*, otherwise it gets reset to 0! TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // No prescaler (p.134) OCR1A = FREQ / BASE_RATE; // Set the compare register (OCR1A). // OCR1A is a 16-bit register, so we have to do this with // interrupts disabled to be safe. TIMSK1 |= _BV(OCIE1A); // Enable interrupt when TCNT1 == OCR1A (p.136) curEngineSample = 0; curStartSample = 0; //curHornSample = 0; sei(); } // // ======================================================================================================= // MAP PULSEWIDTH TO THROTTLE // ======================================================================================================= // void mapThrottle() { // Input is around 1000 - 2000us, output 0-500 for forward and backwards // check if the pulsewidth looks like a servo pulse if (pulseWidth > pulseMinLimit && pulseWidth < pulseMaxLimit) { if (pulseWidth < pulseMin) pulseWidth = pulseMin; // Constrain the value if (pulseWidth > pulseMax) pulseWidth = pulseMax; // calculate a throttle value from the pulsewidth signal if (pulseWidth > pulseMaxNeutral) currentThrottle = map(pulseWidth, pulseMaxNeutral, pulseMax, 0, 500); else if (pulseWidth < pulseMinNeutral) currentThrottle = map(pulseWidth, pulseMinNeutral, pulseMin, 0, 500); else currentThrottle = 0; } } // // ======================================================================================================= // ENGINE MASS SIMULATION // ======================================================================================================= // void engineMassSimulation() { static int16_t mappedThrottle = 0; static int16_t currentRpm = 0; static unsigned long throtMillis; if (millis() - throtMillis > 5) { // Every 5ms throtMillis = millis(); // compute unlinear throttle curve mappedThrottle = currentThrottle; // currentThrottle remplace (pour les bateaux) : reMap(curveShifting, currentThrottle) // Accelerate engine if (mappedThrottle + acc > currentRpm && engineState == 2) { currentRpm += acc; if (currentRpm > maxRpm) currentRpm = maxRpm; } // Decelerate engine else if (mappedThrottle - dec < currentRpm) { currentRpm -= dec; if (currentRpm < minRpm) currentRpm = minRpm; } // Speed (sample rate) output currentSmpleRate = FREQ / (BASE_RATE + long(currentRpm * TOP_SPEED_MULTIPLIER) ); } } // // ======================================================================================================= // SWITCH ENGINE ON OR OFF // ======================================================================================================= // void engineOnOff() { static unsigned long pulseDelayMillis; static unsigned long idleDelayMillis; if (engineManualOnOff) { // Engine manually switched on or off depending on presence of servo pulses if (pulseAvailable) pulseDelayMillis = millis(); // reset delay timer, if pulses are available if (millis() - pulseDelayMillis > 100) { engineOn = false; // after delay, switch engine off } else engineOn = true; } else { // Engine automatically switched on or off depending on throttle position and 15s delay timne if (currentThrottle > 80) idleDelayMillis = millis(); // reset delay timer, if throttle not in neutral if (millis() - idleDelayMillis > 15000) { engineOn = false; // after delay, switch engine off } else { if (currentThrottle > 100) engineOn = true; } } } // // ======================================================================================================= // MAIN LOOP // ======================================================================================================= // void loop() { // Map pulsewith to throttle mapThrottle(); // Simulate engine mass, generate RPM signal engineMassSimulation(); // Switch engine on or off engineOnOff(); } // // ======================================================================================================= // INTERRUPTS // ======================================================================================================= // // Uses a pin change interrupt and micros() to get the RC signal pulsewidth at pin 2 ---------------- void getPulsewidth() { unsigned long currentMicros = micros(); boolean currentState = PIND & B00000100; // Pin 2 is PIND Bit 2 ( = digitalRead(2) ) static unsigned long prevMicros = 0; static boolean lastState = LOW; if (lastState == LOW && currentState == HIGH) { // Rising edge prevMicros = currentMicros; pulseAvailable = true; lastState = currentState; } else if (lastState == HIGH && currentState == LOW) { // Falling edge pulseWidth = currentMicros - prevMicros; pulseAvailable = false; lastState = currentState; } } // This is the main sound playback interrupt, keep this nice and tight!! ------------------------------ ISR(TIMER1_COMPA_vect) { static float attenuator; // Float required for finer granularity! switch (engineState) { case 0: // off ---- OCR1A = fixedSmpleRate; // fixed sample rate (speed)! OCR2B = 0; if (engineOn) engineState = 1; break; case 1: // starting ---- if (curStartSample >= start_length) { // Loop the sample curStartSample = 0; engineState = 2; } OCR1A = fixedSmpleRate; // fixed sample rate (speed)! OCR2B = pgm_read_byte(&start_data[curStartSample]); curStartSample++; break; case 2: // running ---- if (curEngineSample >= idle_length) { // Loop the sample curEngineSample = 0; attenuator = 1; } OCR1A = currentSmpleRate; // variable sample rate (RPM)! OCR2B = pgm_read_byte(&idle_data[curEngineSample]); curEngineSample++; if (!engineOn) { engineState = 3; } break; case 3: // stopping ---- if (curEngineSample >= idle_length) { // Loop the sample curEngineSample = 0; } OCR1A = fixedSmpleRate; //OCR1A = fixedSmpleRate * attenuator; // engine slowing down OCR2B = pgm_read_byte(&idle_data[curEngineSample]) / attenuator; curEngineSample++; attenuator += 0.001; // fade engine sound out 0.002 if (attenuator >= 20) { // 3 - 20 engineOn = false; if (!engineOn) engineState = 0; // Important: ensure, that engine is off, before we go back to "starting"!! } break; } // end of switch case } |