2

I wrote the code to produce the periodic signal by using arduino and DAC0808 where i turned on and off the respectively pins of PORTA by using a logic in iterative manner and created the periodic signal. But even without using delay logic, i couldn't get signal time period as comparable to 50 Hz voltage signal. And what i seek to get is to decrease the time period of generated signal. Pls note, blue signal is reference signal of 50Hz frequency.

float increment = 3.14159 / 500;
float initial = 0;
float value = 0;

void setup() {
  Serial.begin(115200);
  DDRA  = B11111111;
}

void loop() {
  // put your main code here, to run repeatedly:

  PORTA = B00000000;
  while (1)
  {
    initial = initial + increment;
    value = sin(initial)  * 255;
    PORTA = abs(value);
  }

}

enter image description here

migrated from electronics.stackexchange.com Jun 14 '16 at 17:53

This question came from our site for electronics and electrical engineering professionals, students, and enthusiasts.

  • 1
    Your goal is to optimize the code to run faster? Start by using a look-up table instead of sin() and minimize float arithmetics. Are you sure your increment is correct? – Vicente Cunha Jun 14 '16 at 19:12
  • You should make more clear what you expect to get and what you get instead. – Alphonsos_Pangas Jun 14 '16 at 19:22
  • Please edit question and indicate the timebase (seconds per division). Also, if wiring diagram as shown is different than what you actually have, say so. (The diagram apparently wires the bits backwards, LSB to MSB and vice versa.) – James Waldby - jwpat7 Jun 14 '16 at 19:36
  • jwpat7 sir, blue signal is output signal of 50hz. So, time period per box is around 25 milli second.sorry for the inconvenience as am i operating from mobile. – Abhaya Singh Pandey Jun 14 '16 at 20:08
  • As Vicente said, floating point math, especially transcendental functions, can be time-consuming. Instead of calculating the sine inside every repetition, you should build up a lookup table in your setup() function, then get the values from this table inside loop(). In order to minimise the size of the lookup table, note that you only need to calculate the sines of values from 0 to pi/2. If you still can not get your arduino to produce a 50Hz signal after that, consider decreasing the 'resolution' - that is, increasing the value of 'increment' from pi/500 to, say, pi/100. – Alphonsos_Pangas Jun 14 '16 at 20:51
1

As has been mentioned before, in comments and in jwpat7's answer, your main problem here is the time taken to compute the sines. I already provided a link to a faster fixed-point implementation of sin() but, since you have an Mega2560 with loads of RAM, I would go to the simpler look-up table method. I would not even bother optimizing the size of the table.

For controlling the timing, you can use the canonical method described in the Blink Without Delay Arduino tutorial, with one twist: times should be measures with micros() instead of millis(). Here is a first version of the program:

const float FREQUENCY = 50;            // hertz
const float PERIOD = 1e6 / FREQUENCY;  // microseconds
const size_t TABLE_SIZE = 512;
const uint16_t SAMPLE_TIME = PERIOD/TABLE_SIZE + 0.5;
uint8_t wavetable[TABLE_SIZE];

void setup()
{
    // Prepare the wavetable.
    for (size_t i = 0; i < TABLE_SIZE; i++) {
        wavetable[i] = 255 * abs(sin(M_PI * i / TABLE_SIZE));
    }

    // Set port A as output.
    DDRA = 0xff;
}

void loop()
{
    uint16_t now = micros();
    static uint16_t last_sample;
    static size_t phase;

    if (now - last_sample > SAMPLE_TIME) {
        last_sample += SAMPLE_TIME;
        PORTA = wavetable[phase];
        phase = (phase + 1) % TABLE_SIZE;
    }
}

A few things worth noting:

  1. The float constants FREQUENCY and PERIOD are used only at compile time.
  2. The table size is a power of two because wrapping a table index modulo a power of two is way faster than modulo, say, 500: the compiler optimizes the modulo into a bitwise AND.
  3. The + 0.5 in the expression of SAMPLE_TIME is intended to ensure that the result is rounded to the nearest integer. In this instance it happens to be useless because rounding down would give the same result.
  4. The micros() function returns a 32-bit integer, but in this application it is sufficient and faster to track only the 16 least significant bits, hence the uint16_t timing variables. And no, these variables overflowing is not a problem.

This should give you, in theory, a frequency of 50.08 Hz, i.e. 0.16% too fast. The discrepancy is due to SAMPLE_TIME, which should be 39.0625 µs, being rounded to 39 µs. You could fix this by counting time with a higher resolution, but given the poor accuracy of the ceramic resonator clocking the Arduino, this is probably not worth the trouble.

A potentially worse problem is the large jitter you have with this approach, on the order of 10 µs. This is due both to the time taken to run through the loop and the occasional timer interrupt taking some extra time. You can get rid of this jitter by using a timer interrupt instead of micros(). Here is a second version of the program that uses this approach:

#include <avr/sleep.h>

const float FREQUENCY = 50;              // hertz
const float PERIOD = F_CPU / FREQUENCY;  // CPU cycles
const size_t TABLE_SIZE = 512;
const uint16_t SAMPLE_TIME = PERIOD/TABLE_SIZE + 0.5;
uint8_t wavetable[TABLE_SIZE];

int main()
{
    // Prepare the wavetable.
    for (size_t i = 0; i < TABLE_SIZE; i++) {
        wavetable[i] = 255 * abs(sin(M_PI * i / TABLE_SIZE));
    }

    // Set port A as output.
    DDRA = 0xff;

    // Configure Timer 1.
    OCR1A = SAMPLE_TIME - 1;  // set the period
    TIMSK1 = _BV(OCIE1A);     // enable TIMER1_COMPA interrupt
    TCCR1B = _BV(WGM12)       // CTC mode with TOP = OCR1A
           | _BV(CS10);       // clock at F_CPU / 1

    // Sleep while waiting for interrupts.
    sei();
    sleep_enable();
    for (;;) sleep_cpu();
}

// Service routine for the Timer 1 interrupt.
ISR(TIMER1_COMPA_vect)
{
    static size_t phase;

    PORTA = wavetable[phase];
    phase = (phase + 1) % TABLE_SIZE;
}

Again, a few notes:

  1. The SAMPLE_TIME is now computed in CPU cycles and, since it is an integer number of cycles (namely 625), there should in principle be no frequency discrepancy.
  2. For counting 625 cycles we need a 16-bit timer clocked at the full CPU speed (prescaler = 1). We are using Timer 1 here. See the datasheet of the ATmega2560, chapter 17, for details on using the timer.
  3. Sleeping between interrupts is needed in order to obtain cycle-accurate timings. If we had simply an infinite loop, there could be a few CPU cycles of jitter, which may not be a big deal.
  4. Writing main() instead of setup() and loop() is a way to skip the initialization normally done by the Arduino core library. In this application that initialization is better avoided, because it creates an extra jitter-inducing interrupt and it messes with Timer 1 in a way that we would need to undo.
1

I'm interpreting your phrase “even without using delay logic, i couldn't get signal time period as comparable to 50 Hz” as meaning that the code shown runs Too Slow. At 1000 samples per cycle and 50 Hz on an 8-bit 16-MHz processor, you only have 320 clocks to compute a sin(), which probably is not enough to compute a 32-bit floating sin().

To speed it up, you can either issue fewer samples per cycle or make the loop go faster.

For the “fewer samples per cycle” approach, you would make increment bigger; for example, instead of 3.14159/500, or 1000 samples per cycle, perhaps an interval of 2π/500, or 500 samples per cycle.

For the “make the loop go faster” approach, you could use a lookup table or fixed-point integer arithmetic or both. Note, the lookup table only needs to cover 90° (or π/2 radians); index in reverse for quadrants 2 and 4, and negate values for quadrants 3 and 4.

Note, after you speed up the code, you will need to set up a time base for issuing samples. This could use a clock-based interrupt routine that gets and issues the next array value, or that sets a flag so your main loop can get and issue a value. Or you could spin until micros() exceeds a deadline, at which time you (1) issue a value, (2) set the next deadline, and (3) compute the next value.

  • 2
    320 clock cycles is indeed not enough for sin(): I have timed it at 1725.1 cycles average. I've also written a fixed-point sin() that takes 110.25 cycles (page in French, but code documented in English). It's 16 bits, with a maximum error of 9.53e-5, so somewhat overkill for this 8-bit application. But a simpler alternative polynomial is given in the text. – Edgar Bonet Jun 14 '16 at 20:13
  • @EdgarBonet, thanks for mentioning that – it provides a viable alternative to table lookup methods. – James Waldby - jwpat7 Jun 15 '16 at 3:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.