Hey guys,
My current project has lights, sounds, and motion. The lights are leds on a shift register, and they’re updated occasionally in my main loop. The sounds are produced by a DAC, which I use hardware SPI to to write to at a samplerate of 30khz. And the motion is produced by a couple of servos, one of which has been twitching quite a bit.
I’ve narrowed the twitching problem down to the fact that my sound update interrupt is delaying the servo update interrupt ever so slightly. But I am at a loss for how to solve the problem.
I tried adding ISR_NOBLOCK to my DAC interrupt, but while that stopped the servos from twitching, the sound went crazy and the sketch crashed, presumably because a backlog of sound updates was occuring.
I was hoping to solve this problem on the servo side, so I took a look at the servo lib and tried to understand what it was doing.
It seems that what the servo lib does is set up a timer which updates once every 8 system clocks, and when that timer matches the compare reigster, it calls the servo ISR.
Meanwhile, the servo ISR sets the pin of a particular servo high, and then sets the compare register to tell the timer how long it should wait before triggering the ISR again, and when the ISR is next triggered, that servo’s pin is set low, and the pin of the next servo is set high, and the compare resister is set to a new value that tells the timer how long that servo’s pin must be high.
This means that the servo ISR isn’t really being called that often. It’s only being called once for each servo, every 20ms. And I only have two servos. So that’s one update every 10ms roughly.
So trying to optimize the servo ISR isn’t likely to help. The real problem it seems is that timer2, which the DAC is on, seems to be delaying timer1. I’m at a loss for how to fix this though.
Setting timer1 to NOBLOCK allows timer2 to interrupt it so it gets called at the proper time, but then timer1 continues executing and this seems to cause enough of a delay that a backlog then occurs.
Is there perhaps some way then that if timer1 needs to interrupt timer2 that timer2’s call could simply be aborted, thus skipping that update completely? Losing a sample here for the sound probably isn’t going to be a major issue since the sample I’m playing is noisy and I’m playing it on a piezo. I’m not going for high fidelity sound. But I do need the servos to stop twitching like crazy.
So, any ideas?
Here’s the code for the DAC update interrupt. I can optimize it a bit with direct port access I imagine, but I’m not sure that I won’t need to do anything else in the function so I’d like to solve the problem some other way than making the DAC update a little faster.
// This is called at x Hz to load the next sample.
ISR(TIMER2_COMPA_vect) { //, ISR_NOBLOCK) {
static int bufferIndex = 0;
unsigned int sample;
byte dataHigh, dataLow;
//static unsigned long lastToggle = 0;
//static int count = -1;
/*
if (sample >= sounddata_length) {
if (sample == sounddata_length + lastSample) {
stopPlayback();
}
else {
// Ramp down to zero to reduce the click at the end of playback.
OCR2A = sounddata_length + lastSample - sample;
}
}
else {
OCR2A = pgm_read_byte(&sounddata_data[sample]);
}
++sample;
*/
/*
DAC data format:
bit 15 A/B: DACA or DACB Select bit
1 = Write to DACB
0 = Write to DACA
bit 14 BUF: VREF Input Buffer Control bit
1 = Buffered
0 = Unbuffered
bit 13 GA: Output Gain Select bit
1 = 1x (VOUT = VREF * D/4096)
0 = 2x (VOUT = 2 * VREF * D/4096)
bit 12 SHDN: Output Power Down Control bit
1 = Output Power Down Control bit
0 = Output buffer disabled, Output is high impedance
bit 11-0 D11:D0: DAC Data bits
12 bit number “D” which sets the output value. Contains a value between 0 and 4095.
*/
// Simulate square wave.
// count++;
/* // 5000 hz
if (count > 5) { count = 0; }
switch (count) {
case 0:
case 1:
case 2:
sample = 0;
break;
case 3:
case 4:
case 5:
sample = 4095;
break;
}
*/
/*
// 7500hz
if (count > 3) { count = 0; }
switch (count) {
case 0:
case 1:
sample = 0;
break;
case 2:
case 3:
sample = 4095;
break;
}
*/
// if (sample == 0) { sample = 4095; } else { sample = 0; }
// Simulate noise.
//sample = random(4096);
// Determine next sample in wavetable to play.
bufferIndex = bufferIndex + 1;// playbackSpeed; // Used a floating point index into buffer to ?
//bufferIndex = bufferIndex - int(bufferIndex/soundBufferSize)*soundBufferSize; // Wrap around when we reach the end of the buffer.
if (bufferIndex > 1023) { bufferIndex = 0; }
sample = soundBuffer[int(bufferIndex)];
//wavetablesample / wavetablesamplerate = 0.5 if halfway
//0.5 * globalsamplerate =
// Transmit data:
//sample = (float(sample) / 255.0) * 4095.0; // Scale sample from 0..255 to 0..4095 to maximize volume.
// Scale sample from 0..255 to 0..4095 to maximize volume.
//sample = ((sample-127) << 4) + 2047; // Subtract 127 from sample to produce -127 to 127. Multiply by 16 to produce -2047 to 2047. Finally, add 2047 to convert to the 0 to 4097 the DAC needs.
sample = sample << 4;
// Take the SS pin low to select the DAC.
//digitalWrite(pinSPI_SS, LOW);
digitalLOW(pinSPI_SS);
// Send the 16 bits needed to reconfigure the DAC.
//dataHigh = B00110000 | (sample >> 8);
//dataLow = sample & B11111111;
SPI.transfer(B00110000 | (sample >> 8));
SPI.transfer(sample & B11111111);
// take the SS pin high to de-select the chip:
//digitalWrite(pinSPI_SS, HIGH);
digitalHIGH(pinSPI_SS);
}
/* ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
void setupDACISR() {
// Set up Timer 1 to send a sample every interrupt.
// Set timer 1 to trigger 8000 times a second.
// Disable interrupts:
cli();
// Set Timer2 to mode 2 - Clear timer and trigger interrupt on match with OCR2A. (p.161):
bitClear(TCCR2B, WGM22);
bitSet(TCCR2A, WGM21);
bitClear(TCCR2A, WGM20);
// Set Timer2 prescalar to /32 - Trigger timer at a rate of 16mhz/32 :
// bitClear(TCCR2B, CS22);
// bitSet(TCCR2B, CS21);
// bitSet(TCCR2B, CS20);
// Set Timer2 prescalar to /256 - Trigger Timer2 at a rate of 16mhz/256 or 62500 times a second.
bitSet(TCCR2B, CS22);
bitSet(TCCR2B, CS21);
bitClear(TCCR2B, CS20);
// Set compare register. This is how many times Timer2 must go off before it triggers an interrupt and clears itself. OCR2A = 2 gives us 31250hz for our sampling rate.
// (One half of that is 15625hz which is the highest frequency tone we can produce.)
//OCR2A = F_CPU / 32 / 8000; // For 8000hz
OCR2A = 1; // 1 for 31250hz, 3 for 15625
// F_CPU is the speed of the CPU in MHZ as defined in the bootloader. It is 16mhz on an Atmega328.
// We divide F_CPU by 32 because our presclar is 32. Our timer will tick at a rate of only 0.5mhz.
// We then divide the result by 8000, which is the number of times per second we want the interrupt to be called, and the number of times per second we must toggle our DAC output to generate a 4000hz tone.
// At 16mhz, OCR2A should be 62.5, but the value will be truncated to 62 which means our interrupt will be called at a rate of around 8064.5hz.
// Enable Timer2 compare match A interrupt when TCNT2 == OCR2A (p.163)
bitSet(TIMSK2, OCIE2A);
// Reenable interrupts.
sei();
}