I am working on a project in which I need to repetitively grab values from a lookup table and write them, in parallel, to digital output pins. The value is 6 bit, so I plan to do it with a port wrote command to both make the write fast and make sure the pins all update at once (I need to avoid in-between states). The problem is, I need this to happen exactly every 7 microseconds.
I know port writes are pretty fast, but there's also an array lookup and a counter increment that needs to happen, plus the loop overhead. I can turn off interrupts while this runs.
The 2 questions are:
Can a 16 MHz arduino do this fast enough?
How can I set up the timing to fire at this rate? Just count micros() (or direct read the port for better speed)? Is there a way to set a timer and have it run the code on every "tick"?
I don't think you will manage a 7µsecs interval on a 16MHz Arduino. That would require an output value every 112 clock cycles. I guess it might just be possible with some highly optimised code - but if you know how to do that I doubt if you would be asking us for help.
The micros() function is quite unsuitable as it only updates every 4 µsecs. You would have to use one of the hardware timers to manage the timing
It is very much easier to offer useful advice or suggestions if you give a full description of your project.
I think it would be easy to do that in 7 us intervals.
A byte wide table lookup, direct port write, increment and branch should certainly take less than 30 clock cycles.
The loop delay might have to be optimized by hand, as timer interrupt latency could be an issue. On the other hand, polling a timer has predictable timing.
You need to specify the allowable variability in loop timing, if any.
I'm trying to generate a sine wave in what may be a particularly inefficient way. I know there are other ways to do it, but I am really trying to see if I can do it this way.
Basically, I have a 6 bit R2R ladder acting as a DAC (followed by a buffer and low pass filter to clean it up). Each leg of the ladder is driven by a digital pin. The highest frequency I need to generate is 2200Hz, which, at 6 bit resolution, means a write every 7uS (assuming I did my math correctly).
Since I only need a pair of frequencies, there is no problem pre-computing the sine values and having them in a pair of tables (one pre freq).
The problem is how to get the Arduino to write to the dac fast, and at the correct rate.
One option is to reduce resolution, I may be able to get away with as little as 4 bits, which would give me 28 uS in which to work.
I second jremington's idea. Set up a timer to fire every 112 clock cycles and just poll the bit after sending your data.
If you have interrupts enabled, clock interrupts would interfere. You might have to disable interrupts and just run your one function. Is there anything else your Arduino needs to do?
Jimmus:
I second jremington's idea. Set up a timer to fire every 112 clock cycles and just poll the bit after sending your data.
Can you unpack this a bit? What do you mean by "fire"? How do I make my code execute every time that timer ticks? And what do you mean by "poll the bit"?
If you have interrupts enabled, clock interrupts would interfere. You might have to disable interrupts and just run your one function. Is there anything else your Arduino needs to do?
Not while running this bit of code. Turning off interrupts is perfectly fine, it's what I was expecting.
If you are generating APRS tones, an open source Arduino-compatible software modem is available at MicroModem | unsigned.io
It uses 8 samples/bit at 1200 bits per second, and a sine lookup table to generate those two tones, with plenty of time left over to process incoming and outgoing data.
Interesting. Will definitely need to look at this more closely. I'm not looking for an all-up modem, but the actual signal generation code is highly relevant. Thanks!
First pass at this went as follows:
Trying to generate a 1200 Hz tone, with 5 uS "ticks". The array sine12[] holds 64 values between 0 and 63, representing a sinewave.
#include "sinewave.h"
int count;
unsigned long timepassed;
void setup() {
DDRD = DDRD | B11111100; //set PORTD 2-7 to write (0 and 1 are rx/tx)
//noInterrupts();
count=0;
timepassed=micros();
}
void loop() {
//1200 Hz
if (micros() >= timepassed+5) //sample timing
{
PORTD = (sine12[count]<<2) | (PORTD & B11);
timepassed=micros();
count=((count+1) % 64);
}
}
Good news is, it generates a sine wave. Bad news is, it maxes out at about 600Hz, regardless of what I use for the sample timing (even reducing from 5 to 1).
It would have been helpful to others answering your question to have mentioned this, APRS has been done in this way on an Arduino before .........................
Its also not quite as simple as generating tones, they have to be of a fairly precise length to keep the baud correct and there is at least one other complication.
I see several completely unnecessary and rather lengthy operations in the inner loop.
No need for <<2, (just store the appropriate constant in sine12) or to access micros() TWICE every pass.
As a test, just execute the loop a certain number of times (N) and measure the total elapsed time. Adjust N and insert NOP delays as needed.
Note that the micromodem software does Direct Digital Synthesis, which requires you to keep track of the tone phase as you progress. The phase of the generated signal must be continuous as you switch from one frequency to the next, or you will generate unintelligible high frequency "splatter" in the audio spectrum.
Yeah, the phase issue was something I was concerned with. Need to learn more about it.
Trying to figure out the MicroModem code, but it's hard to follow. Took forever to find where the actual output pins are pushed - turns out it was in an interrupt handler, that triggers when the ADC finishes reading. Huh?
I can't just use the code as is, since I'm looking to do TX only (this is meant for a tracker), so don't need the overhead of the RX code. But the two are really intertwined....
Interesting. Getting rid of the delay functionality (including the micros() calls), but leaving the << alone, the arduino still only gets about 1kHz.
However, recompiling the code in Atmel Studio (again, no delays) gets it up to about 13kHz.
I'm not really using any Arduino libraries, so is there just that much overhead in the stuff Arduino adds to the code? It's also a lot smaller - 268 bytes vs. 690 bytes.
Ok, got it. Had to drop from Arduino to Atmel Studio to do it, but it works.
Here's the short version:
Single lookup table with 64 values. Volatile global phase counter variable.
Timer0 is placed into CTC mode, no prescaler. At 16MHz clock speed, CTC value of 207 means that the timer triggers an interrupt every 1200Hz*64 data points. On each interrupt, the phase counter is incremented by 1 modulo 64 and the corresponding entry from the table is pushed to the DAC.
To change frequency, the CTC value is simply changed to 113 (formula said 111, but 113 worked better experimentally). Since the phase counter is retained, phase transitions smoothly.
Timer2 is also placed into CTC mode, 256 prescaler. CTC value of 52 causes an interrupt at 1200 Hz - this is the baud counter. On each triggering of the interrupt, the function either changes between 1200 and 2200Hz or not (for now it always changes, since the rest of the code is not yet written.
If there's interest, I'll post the code.
Can't take credit for the overall idea - it was given to me by a friend who did something similair on a Nucleo.
const byte sin12[64] = {}; // Please shift the data left two bits
void setup() {
DDRD = 0xFC; // D Port is output.
cli();
OCR2A = 111; // Top = 112 clock cycles, which is 7 uSec
TCCR2B = 1; // Prescale = 1
TIMSK2 = 0; // No interrupts. We will be polling the bit
TIFR2 = 2; // Reset the OutputCompareFlag2A bit
for (byte i = 0;; i = (i + 1) & 63) // Forever loop
{
while (!(TIFR2 & 2)); // Spin here until the timer fires
TIFR2 |= 2; // Reset the "timer has fired" bit
PORTD = sin12[i];
__asm__("nop"); // To make the loop more accurate
}
}
void loop() {
}
I'm quite impressed with the compiler. The entire for loop takes exactly 16 clock cycles. 1 uSec. That leaves 96 clock cycles for the spin loop. The spin loop is 3 clock cycles.