/*
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
}
|