Generating 3 sine waves with nano every

Hey Everyone I'm trying to get my nano every to out 3 different sine waves (one wave being delayed by 120 degrees and the last wave being delayed by 240 degrees). I already have one wave being formed but I can't get the second wave to be sent to any of the pins for me to monitor. I'm expecting them to show up on pins 3 and 4 but those pins are dead. Any advice would be appreciated.

#include <Arduino.h>
#include <math.h>

// 360-point sine table, values in [0..255]
#define SINE_STEPS 360
static uint8_t sineTable[SINE_STEPS];

// Build sine table

void setupSineTable() {
  for (int i = 0; i < SINE_STEPS; i++) {
    float angle = (float)i * 2.0f * M_PI / 360.0f;
    float s = sinf(angle);           // in [-1..+1]
    float shifted = (s + 1.0f) * 127.5f; // map to [0..255]
    if (shifted < 0.0f)   shifted = 0.0f;
    if (shifted > 255.0f) shifted = 255.0f;
    sineTable[i] = (uint8_t)(shifted + 0.5f);
  }
}
// (Phase A: LCMP0/LCMP1 -> D2/D7, Phase B: LCMP2/HCMP0 -> D4/D3)
void setupTCA0SplitPWM() {
  // Route TCA0 outputs to default pins (WO0..WO5 on PA0..PA5 => D2..D7, plus others).
  PORTMUX.TCAROUTEA = 0x00;

  // Enable SPLIT mode => two 8-bit timers with 3 "low" channels + 3 "high" channels.
  TCA0.SINGLE.CTRLD = 0;
  TCA0.SPLIT.CTRLD  = TCA_SPLIT_SPLITM_bm;

  // Each half is 8-bit from 0..255
  TCA0.SPLIT.LPER = 0xFF; // Low half
  TCA0.SPLIT.HPER = 0xFF; // High half

  // Initialize all channels to 0% duty
  // Phase A: D2=LCMP0, D7=LCMP1
  TCA0.SPLIT.LCMP0 = 0; 
  TCA0.SPLIT.LCMP1 = 0; 
  // Phase B: D4=LCMP2, D3=HCMP0
  TCA0.SPLIT.LCMP2 = 0;
  TCA0.SPLIT.HCMP0 = 0;

  // Make sure pins are outputs
  pinMode(2, OUTPUT);  // A High => WO0 => LCMP0
  pinMode(7, OUTPUT);  // A Low  => WO1 => LCMP1
  pinMode(4, OUTPUT);  // B High => WO2 => LCMP2
  pinMode(3, OUTPUT);  // B Low  => WO3 => HCMP0

  // Enable the 4 channels we need:
  //   LCMP0EN, LCMP1EN, LCMP2EN (Phase A + B Highs)
  //   HCMP0EN                   (Phase B Low)
  TCA0.SPLIT.CTRLB =
      TCA_SPLIT_LCMP0EN_bm |
      TCA_SPLIT_LCMP1EN_bm |
      TCA_SPLIT_LCMP2EN_bm |
      TCA_SPLIT_HCMP0EN_bm;

  // Start timer with prescaler=64 => ~1.22 kHz at 20MHz CPU
  TCA0.SPLIT.CTRLA = TCA_SINGLE_CLKSEL_DIV64_gc | TCA_SINGLE_ENABLE_bm;
}


void setup() {
  setupSineTable();
  setupTCA0SplitPWM();
}

void loop() {
  static uint16_t i = 0;
  i = (i + 1) % SINE_STEPS;

  // Phase A: index i
  uint8_t A_duty = sineTable[i];
  uint8_t A_comp = 255 - A_duty;

  // Phase B: index i+120° => +120 steps
  uint8_t B_duty = sineTable[(i + 120) % SINE_STEPS];
  uint8_t B_comp = 255 - B_duty;

  // Assign to hardware compare registers:
  // Phase A => "High" (LCMP0 => D2) & "Low" (LCMP1 => D7)
  TCA0.SPLIT.LCMP0 = A_duty;  
  TCA0.SPLIT.LCMP1 = A_comp; 

  // Phase B => "High" (LCMP2 => D4) & "Low" (HCMP0 => D3)
  TCA0.SPLIT.LCMP2 = B_duty;
  TCA0.SPLIT.HCMP0 = B_comp;

  // ~10ms => ~100 Hz update for the sine wave
  delay(10);
}

Hi,

you can create something like this using the DDS method with a table. delay() is deadly here. An RC filter is then applied to the timer output.

Split mode. Do you know this AppNote Microchip TB3217 - Getting Started with TCA ?
If this works for you, you can take care of everything else.

Is the delay why I’m not getting my phase B to output anywhere on the nano every?

Hi,

no. Have you got the split mode to work on its own? Can you get the split mode to run without sine wave code?

Edit:
Looking through the code, I don't see any obvious errors. But your pins are not correct. You have configured Port.A with PA0 to PA5. Correct this with the pinout from Every.

I didn’t realize that, I’ve been mapping the pins based off their WO numbers. I’ll try this, thanks!

Also, the split mode works how I want it to for phase A so I feel confident that it’s working.

Hi,

It's certainly not easy to understand at first. :wink:
You have set the TCA outputs to Port.A using PortMux. The Arduino pin numbers have nothing to do with the port numbers or IC pin numbers. Therefore you have to look in the Arduino Every Pinout how the Arduino numbers are distributed.

According to the pinout = WOn = Arduino Pin

PA.0 = 0-WO0 = D2
PA.1 = 0-WO1 = D7
PA.2 = 0-WO2 = D18
PA.3 = 0-WO3 = D19
PA.4 = 0-WO4 = N/A
PA.5 = 0-WO5 = N/A

PortMux on Port.D you have

PD.0 = 0-WO0 = D17
PD.1 = 0-WO1 = D16
PD.2 = 0-WO2 = D15
PD.3 = 0-WO3 = D14
PD.4 = 0-WO4 = D20
PD.5 = 0-WO5 = D21

Hey Doc,
Should I be able to use one timer for all three phases?

So after doing some digging, it turns out that the pins I'm trying to send the Phase B output to aren't accessible pins so that's why I can't see them

Hi,

Yes.

I forgot to tell you that you must reset all TCA registers before configuring it yourself. The Arduino framework configures the timers for analogWrite. If this still does not work, please show the current sketch.

A general question. Does the split mode work without a sine wave? So does the AppNote Microchip TB3217 - Getting Started with TCA work? Otherwise we are talking at cross purposes.

Yes split mode works as far as I can tell, I added a reset function for TCA and still can't see any Phase B output:

#include <Arduino.h>
#include <math.h>

// 360-point sine table, values in [0..255]
#define SINE_STEPS 360
static uint8_t sineTable[SINE_STEPS];

// Hard-reset TCA0 to clear previous configuration
void TCA0_hardReset(void)
{
  // Disable TCA0
  TCA0.SINGLE.CTRLA &= ~(TCA_SINGLE_ENABLE_bm);
  // Issue a hard reset command
  TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESET_gc;
}

// Build sine table: one full cycle spread over 360 steps.
void setupSineTable() {
  for (int i = 0; i < SINE_STEPS; i++) {
    float angle = (float)i * 2.0f * M_PI / 360.0f;  // Use 360 so table covers one period
    float s = sinf(angle);                          // Range: -1 to +1
    float shifted = (s + 1.0f) * 127.5f;            // Map to [0,255]
    if (shifted < 0.0f)
      shifted = 0.0f;
    if (shifted > 255.0f)
      shifted = 255.0f;
    sineTable[i] = (uint8_t)(shifted + 0.5f);         // Round to nearest int
  }
}

// Configure TCA0 in SPLIT mode for two phases:
// Phase A: LCMP0/LCMP1 -> D2/D7
// Phase B: LCMP2/HCMP0 -> D4/D3
void setupTCA0SplitPWM() {
  // Route TCA0 outputs to default pins (WO0..WO5 on PA0..PA5, typically D2..D7)
  PORTMUX.TCAROUTEA = 0x00;

  // Enable SPLIT mode: clear SINGLE mode config and set SPLIT mode bit.
  TCA0.SINGLE.CTRLD = 0;
  TCA0.SPLIT.CTRLD  = TCA_SPLIT_SPLITM_bm;

  // Set period for both halves to 0xFF (full 8-bit)
  TCA0.SPLIT.LPER = 0xFF;  // Low half period
  TCA0.SPLIT.HPER = 0xFF;  // High half period

  // Initialize compare registers for Phase A and Phase B to 0%
  // Phase A: D2 = LCMP0, D7 = LCMP1
  TCA0.SPLIT.LCMP0 = 0;
  TCA0.SPLIT.LCMP1 = 0;
  // Phase B: D4 = LCMP2, D3 = HCMP0
  TCA0.SPLIT.LCMP2 = 0;
  TCA0.SPLIT.HCMP0 = 0;

  // Set pin directions (using Arduino's pinMode)
  pinMode(2, OUTPUT);  // Phase A High: WO0 → LCMP0 → D2
  pinMode(7, OUTPUT);  // Phase A Low:  WO1 → LCMP1 → D7
  pinMode(4, OUTPUT);  // Phase B High: WO2 → LCMP2 → D4
  pinMode(3, OUTPUT);  // Phase B Low:  WO3 → HCMP0 → D3

  // Enable the required channels:
  // LCMP0, LCMP1 for Phase A and LCMP2, HCMP0 for Phase B
  TCA0.SPLIT.CTRLB =
      TCA_SPLIT_LCMP0EN_bm |
      TCA_SPLIT_LCMP1EN_bm |
      TCA_SPLIT_LCMP2EN_bm |
      TCA_SPLIT_HCMP0EN_bm;

  // Start TCA0 with a prescaler of 64 (~1.22 kHz PWM at a 20 MHz clock)
  TCA0.SPLIT.CTRLA = TCA_SINGLE_CLKSEL_DIV64_gc | TCA_SINGLE_ENABLE_bm;
}

void setup() {
  TCA0_hardReset();     // Ensure TCA0 starts from a known state
  setupSineTable();       // Build our sine table
  setupTCA0SplitPWM();    // Configure TCA0 in SPLIT mode for our PWM outputs
}

void loop() {
  static uint16_t i = 0;
  i = (i + 1) % SINE_STEPS;

  // Phase A: index i
  uint8_t A_duty = sineTable[i];
  uint8_t A_comp = 255 - A_duty;

  // Phase B: index i+120 (120° offset)
  uint8_t B_duty = sineTable[(i + 120) % SINE_STEPS];
  uint8_t B_comp = 255 - B_duty;

  // Update PWM compare registers:
  // Phase A: High on LCMP0 (D2), Low on LCMP1 (D7)
  TCA0.SPLIT.LCMP0 = A_duty;
  TCA0.SPLIT.LCMP1 = A_comp;

  // Phase B: High on LCMP2 (D4), Low on HCMP0 (D3)
  TCA0.SPLIT.LCMP2 = B_duty;
  TCA0.SPLIT.HCMP0 = B_comp;

  delay(10);  // Update about 100 times per second
}

Hi,

You should correct your troubleshooting method.
How did you arrive at this pin selection? Explain that once.

Test with static duty for all channels without your sine wave.
To make matters worse, pins 18 and 19 are assigned to 2 ports. Port A and F depending on use. See the board schematic for more details. To avoid further problems, use port D and the correct pins.

Update: I've decided to move to the nano to get 6 pwm pins, but now I can't see any waveforms at all even though I could see all of them on the nano every, do I have to adjust my code to work for the nano:

#include <Arduino.h>
#include <math.h>

// Pin assignments for the Arduino Nano (PWM-capable pins):
int highA = 3;   // Phase A "High"
int lowA  = 5;   // Phase A "Low"
int highB = 6;   // Phase B "High"
int lowB  = 9;   // Phase B "Low"
int highC = 10;  // Phase C "High"
int lowC  = 11;  // Phase C "Low"

int enable = 8;    // Enable pin
int freqpin = A0;  // Frequency input (unused)

const float pi = 3.14159265f;
const float twoPi = 2.0f * pi;

// We'll increment 'x' each loop. 'y' sets how fast x moves.
float x = 0.0f;
const float y = pi / 30.0f;  // Adjust for desired wave speed

// Phase offsets for a 3-phase system
const float offsetA = 0.0f;          // Phase A: 0°
const float offsetB = 2.0f * pi / 3; // Phase B: 120°
const float offsetC = 4.0f * pi / 3; // Phase C: 240°

void setup() {
  pinMode(highA, OUTPUT);
  pinMode(lowA, OUTPUT);
  pinMode(highB, OUTPUT);
  pinMode(lowB, OUTPUT);
  pinMode(highC, OUTPUT);
  pinMode(lowC, OUTPUT);

  pinMode(enable, INPUT);
  pinMode(freqpin, INPUT); // not used

  // Make sure all outputs are off initially
  analogWrite(highA, 0);
  analogWrite(lowA, 0);
  analogWrite(highB, 0);
  analogWrite(lowB, 0);
  analogWrite(highC, 0);
  analogWrite(lowC, 0);
}

void loop() {
  // Only run the wave logic while enable pin is HIGH
  while (digitalRead(enable) == HIGH) {
    // Increment angle
    x += y;
    // Wrap around 2π
    if (x >= twoPi) {
      x -= twoPi;
    }

    // Compute each phase angle with offset
    float angleA = fmod(x + offsetA, twoPi);
    float angleB = fmod(x + offsetB, twoPi);
    float angleC = fmod(x + offsetC, twoPi);

    // For each phase, we do:
    //  if sin(angleX) >= 0 => "High" pin at 255, "Low" pin at 0
    //  otherwise => "High" pin at 0, "Low" pin at 255
    // This yields a strict square wave (full on/off).

    // -------- Phase A --------
    if (sinf(angleA) >= 0.0f) {
      analogWrite(highA, 255);
      analogWrite(lowA, 0);
    } else {
      analogWrite(highA, 0);
      analogWrite(lowA, 255);
    }

    // -------- Phase B --------
    if (sinf(angleB) >= 0.0f) {
      analogWrite(highB, 255);
      analogWrite(lowB, 0);
    } else {
      analogWrite(highB, 0);
      analogWrite(lowB, 255);
    }

    // -------- Phase C --------
    if (sinf(angleC) >= 0.0f) {
      analogWrite(highC, 255);
      analogWrite(lowC, 0);
    } else {
      analogWrite(highC, 0);
      analogWrite(lowC, 255);
    }

    delay(10); // ~100 Hz update rate
  }

  // If 'enable' goes LOW, turn off everything & reset
  analogWrite(highA, 0);
  analogWrite(lowA, 0);
  analogWrite(highB, 0);
  analogWrite(lowB, 0);
  analogWrite(highC, 0);
  analogWrite(lowC, 0);
  x = 0;
}

I just stumbled on this thread and it raises a lot of questions.

Do you know how 3 phase power works?
If you connect a 3 phase motor, either in star or in delta configuration, you only need 3 wires.
There will be a virtual star point at Vcc/2.

Do you understand the datasheet of the ATmega4809?
Apparently you did read some of it, as you know about split mode.
But in post #1 you define only 4 of the 6 waveform outputs, and yet you complain you’re missing 2.
You used port multiplexing, but you failed to see that all 6 waveform outputs of TCA0 will be connected to 1 port. (And not all port pins are connected to board pins.)

Do you understand the datasheet of the Nano Every?
The numbers used in digitalWrite() and such are not the board pin numbers, but the numbers preceded by a ‘D’.
Port numbers are presented like PE2, which is D13 or board pin 1.

Do you understand the Arduino environment?
The clockspeed is set to 16MHz, and yet you mention 20MHz in post #10.
You mess with TCA0, and yet you use delay() without considering that you messed with all timing functions of Arduino.

I ran the sketch from post #12 on an UNO R3. After fiddling with the speed of change

const float y = pi / 10.0f;  // Adjust for desired wave speed

it showed all 6 outputs.
I guess your observation was caused by the calculation of the sines, not by the outputs.

Sidenote: a quirk of Arduino is that analogWrite(pin, 0) or analogWrite(pin, 255) will result in digitalWrite(pin, LOW) or digitalWrite(pin, HIGH).

Just saw the replies, not at my computer right now but I’m trying to use timers directly to get the pwm frequencies to 10kHz on all 3 phases. From what I understand I’ll have to use all available timers to make this work. I’ll post my code later tonight but if anyone has a suggestion I’m open to it.

Edit: if there’s a better arduino board for this I’d be happy to look at it.

Hey everyone, ended up getting a lot more busy than I thought, anyway I have it working almost the way I need it too. The only thing I need to figure out is lowering the carrier frequency to 10kHz. Currently it's outputting at 62.5kHz.

#include <Arduino.h>
#include <math.h>

// ---------------- PIN ASSIGNMENTS ----------------
// Timer1 (8-bit Fast PWM):
static const uint8_t phaseAHigh = 9;   // OC1A => D9
static const uint8_t phaseALow  = 10;  // OC1B => D10

// Timer2 (8-bit Fast PWM):
static const uint8_t phaseBHigh = 3;   // OC2B => D3
static const uint8_t phaseBLow  = 11;  // OC2A => D11

// Timer0 (8-bit Fast PWM):
static const uint8_t phaseCHigh = 5;   // OC0B => D5
static const uint8_t phaseCLow  = 6;   // OC0A => D6

// Enable pin
static const uint8_t enablePin  = 8;   // D8

// Analog input pin for pot
static const uint8_t potPin     = A0;

// -------------- SINE TABLE --------------
#define TABLE_SIZE 256
uint8_t sineTable[TABLE_SIZE];

// Using a single index for Phase A,
// offset phases B, C by +85, +170 => ~120°, 240° difference
volatile uint8_t waveIndexA = 0;

// Storing the step interval (in microseconds)
// Need to define a range ~13..130 for 300..30 Hz
static unsigned int waveStepInterval = 130; 

// ---------------- Build the Sine Table -----------
void buildSineTable() {
  for (int i = 0; i < TABLE_SIZE; i++) {
    float angle = 2.0f * PI * ((float)i / (float)TABLE_SIZE);
    float s = fabsf(sinf(angle));   // absolute value => [0..1]
    float val = 255.0f * s + 0.5f;  // scale to [0..255.5]
    if (val > 255.0f) val = 255.0f;
    sineTable[i] = (uint8_t) val;
  }
}

// -------------- Timer1 => 8-bit Fast PWM ~62.5kHz -----------
void setupTimer1_62k5Hz() {
  TCCR1A = 0; 
  TCCR1B = 0;
  // Mode=5 => WGM13=0, WGM12=1, WGM11=0, WGM10=1 => 8-bit Fast PWM (0..255)
  TCCR1A |= (1 << WGM10); 
  TCCR1B |= (1 << WGM12);

  // Non-inverting on OC1A, OC1B => COM1A1=1, COM1B1=1
  TCCR1A |= (1 << COM1A1) | (1 << COM1B1);

  // Prescaler=1 => CS10=1
  TCCR1B |= (1 << CS10);

  pinMode(phaseAHigh, OUTPUT);
  pinMode(phaseALow,  OUTPUT);

  OCR1A = 0;
  OCR1B = 0;
}

// -------------- Timer2 => 8-bit Fast PWM ~62.5kHz -----------
void setupTimer2_62k5Hz() {
  TCCR2A = 0;
  TCCR2B = 0;
  // Mode=3 => WGM20=1, WGM21=1 => top=0xFF
  TCCR2A |= (1 << WGM20) | (1 << WGM21);
  // WGM22=0

  // Non-inverting on both => COM2A1=1, COM2B1=1
  TCCR2A |= (1 << COM2A1) | (1 << COM2B1);

  // Prescaler=1 => CS20=1
  TCCR2B |= (1 << CS20);

  pinMode(phaseBHigh, OUTPUT);
  pinMode(phaseBLow,  OUTPUT);

  OCR2A = 0;
  OCR2B = 0;
}

// -------------- Timer0 => 8-bit Fast PWM ~62.5kHz -----------
void setupTimer0_62k5Hz() {
  TCCR0A = 0;
  TCCR0B = 0;
  // Mode=3 => WGM00=1, WGM01=1 => top=0xFF
  TCCR0A |= (1 << WGM00) | (1 << WGM01);

  // Non-inverting on both => COM0A1=1, COM0B1=1
  TCCR0A |= (1 << COM0A1) | (1 << COM0B1);

  // Prescaler=1 => CS00=1
  TCCR0B |= (1 << CS00);

  pinMode(phaseCHigh, OUTPUT);
  pinMode(phaseCLow,  OUTPUT);

  OCR0A = 0;
  OCR0B = 0;
}

// ------------------------------------------------
void setup() {
  pinMode(enablePin, INPUT);
  pinMode(potPin, INPUT);

  // Build the 256-entry absolute sine table
  buildSineTable();

  // Configure all timers for 8-bit Fast PWM at ~62.5kHz
  setupTimer1_62k5Hz();
  setupTimer2_62k5Hz();
  setupTimer0_62k5Hz();
}

// ------------------------------------------------
void loop() {
  static unsigned long lastUpdate = 0;
  unsigned long now = micros();

  // Read pot => map to waveStepInterval in [13..130] microseconds
  // => about 300 Hz down to 30 Hz for one cycle of 256 steps
  int potVal = analogRead(potPin); // 0..1023
  waveStepInterval = map(potVal, 0, 1023, 13, 130);

  // Step the sine table index every waveStepInterval
  if ((now - lastUpdate) >= waveStepInterval) {
    lastUpdate = now;

    if (digitalRead(enablePin) == HIGH) {
      // Compute each phase index
      uint8_t idxA = waveIndexA;
      uint8_t idxB = (waveIndexA + 85) & 0xFF;
      uint8_t idxC = (waveIndexA + 170)& 0xFF;

      // amplitude from table
      uint8_t ampA = sineTable[idxA];
      uint8_t ampB = sineTable[idxB];
      uint8_t ampC = sineTable[idxC];

      // ----- Phase A => Timer1 => pins D9 (OCR1A), D10 (OCR1B) -----
      if (idxA < 128) {
        OCR1A = ampA;
        OCR1B = 0;
      } else {
        OCR1A = 0;
        OCR1B = ampA;
      }

      // ----- Phase B => Timer2 => pins D3 (OCR2B), D11 (OCR2A) -----
      if (idxB < 128) {
        OCR2B = ampB; 
        OCR2A = 0;    
      } else {
        OCR2B = 0;
        OCR2A = ampB;
      }

      // ----- Phase C => Timer0 => pins D5 (OCR0B), D6 (OCR0A) -----
      if (idxC < 128) {
        OCR0B = ampC; 
        OCR0A = 0;    
      } else {
        OCR0B = 0;
        OCR0A = ampC;
      }

      // increment wave index
      waveIndexA++;
    }
    else {
      // if disabled => turn off
      waveIndexA = 0;
      OCR1A=0; OCR1B=0; 
      OCR2A=0; OCR2B=0;
      OCR0A=0; OCR0B=0;
    }
    //delayMicroseconds(10000000); //This was a test line just to make the waveforms easier to view on Oscope
    delayMicroseconds(waveStepInterval);
  }
}

I get the impression you’re still underestimating the project you’ve taken on.

I’ve figured out how your 6 outputs would make sense.

If you’re really planning to connect something like this to the mains, I hope you know more about electricity than about Arduino, because chances are that not only your Arduino will start smoking but you too.

You talk about PWM frequency 10 kHz. You found the way to change the prescalers of the counters, but you failed to see the possibility of prescaler 8, resulting in 7.8kHz, the closest you get in the ATmega328.

You want steps of 13μs, but you’re using millis() to achieve that, again ignoring that you killed the millis()-system.

You want to write to 6 registers in 13μs, but you need to calculate the values too, any idea how much times that takes?

Hi,

to change the frequency, you need to understand the relationship between the prescaler and TOP. The formula for each timer mode can be found in the controller manual. My recommendation is to use the DDS method. With accumulator etc., reduced a lot of calculation time.

Please explain the DDS method. I’m playing around with Arduino for some years, haven’t heard of it. Likely OP hasn’t either.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.