0

I try to use three PWM pins on an Arduino Nano, each with 10kHz. I know that there are timer0, timer1 and timer3 but I wonder if I can combine them while ensuring all three PWM pins to be in sync and working at 10kHz. Additionally, I want to vary their duty cycle to have 3 cases.

Case 1: pin1 = OFF, pin2 = OFF, pin3 = OFF

Case 2: pin1 = ON, pin2 = ON, pin3 = OFF

Case 3: pin1 = ON, pin2 = OFF, pin3 = ON

The cases are changed at a frequency (f_act) of e.g. 0.25Hz

My code tried to just add a third PWM pin to the timer, which obviously does not work but illustrates my intention.

// ---------------------------------------------------------------------------
// 3-Channel synchronized PWM (software using Timer1) for high freq. PWM (10kHz)
// + State machine for custom actuation frequency (0.25Hz) (case1 → case2 → case3)
// ---------------------------------------------------------------------------

// choose desired HV output: HV = -0.0104*D^2 + 0.5791*D + 1.3643
// --> D_BUCK = (0.5791 - sqrt(0.5791*0.5791 - 4 * 0.0104 * (HV - 1.3643))) / (2 * 0.0104)
const uint16_t HV = 5; // [kV]
const uint16_t D_HV = (0.5791 - sqrt(0.5791 * 0.5791 - 4 * 0.0104 * (HV - 1.3643))) / (2 * 0.0104);

// prescaler*(1+TOP)=f_arduinoNano/f_PWM=16MHz/10kHz   and TOP=resolution
const uint16_t prescaler = 1; 
const uint16_t fPWM = 10000; // [Hz]
const uint16_t TOP = 16000000 / fPWM / prescaler - 1; // 1599 for 10 kHz with prescaler=1

volatile uint16_t dutyBUCK = 0;
volatile uint16_t dutyOC1 = 0;
volatile uint16_t dutyOC2 = 0;

// choose PWM 3 pins:
const uint8_t BUCK_pin = 5;
const uint8_t OC1_pin = 6;
const uint8_t OC2_pin = 7;

// Case switching variables
uint8_t caseNum = 1; // start in idle
unsigned long lastSwitch = 0;
const unsigned long switchInterval = 2000; // [ms]    4s = 0.25 Hz --> Charge&dis- same duration --> 4s/2

void setup() {
  pinMode(BUCK_pin, OUTPUT);
  pinMode(OC1_pin, OUTPUT);
  pinMode(OC2_pin, OUTPUT);

  // Timer1 configuration
  cli();   // disable interrupts

  TCCR1A = 0;
  TCCR1B = 0;

  // fast mode of Timer1: WGM13 1 WGM12 1 WGM11 1 WGM10 0
  TCCR1A |= (1 << WGM11); // contains WGM10 and WGM11
  TCCR1B |= (1 << WGM13) | (1 << WGM12); // contains WGM12 and WGM13

  ICR1 = TOP; // set TOP (100% duty cycle)

  TIMSK1 |= (1 << TOIE1);  // overflow interrupt
  TIMSK1 |= (1 << OCIE1A); // compare A
  TIMSK1 |= (1 << OCIE1B); // compare B
  TIMSK1 |= (1 << OCIE1C); // compare C

  TCCR1B |= (1 << CS10); // set prescaler=1 for 10 kHz

  sei();   // enable interrupts (TIMSK1) once I finished assigning values to them
}

void loop() {

  unsigned long now = millis();

  // Switch cases every 2 seconds
  if (now - lastSwitch >= switchInterval) {
    lastSwitch = now;

    caseNum++;
    if (caseNum > 3) caseNum = 1;

    switch (caseNum) {

      // ------------------- IDLE ------------------------
      case 1: // BUCK=OFF, OC1=OFF, OC2=OFF
        dutyBUCK = 0;//D_HV;
        dutyOC1 = 0;
        dutyOC2 = 0;
        break;

      // ------------------- CHARGING ------------------------
      case 2: // BUCK=ON, OC1=ON, OC2=OFF
        dutyBUCK = D_HV;   
        dutyOC1 = TOP; // 100% duty
        dutyOC2 = 0;
        break;

      // ------------------- DISCHARGING ------------------------
      case 3: // BUCK=ON, OC1=OFF, OC2=ON
        dutyBUCK = 0;//D_HV;
        dutyOC1 = 0;
        dutyOC2 = TOP;
        break;
    }
  }
}


// ---------------------------------------------------------------------------
// check if On time for Duty cycles are over for each MOSFET at 10kHz
// ---------------------------------------------------------------------------

// Timer1 overflow → start of new PWM cycle
ISR(TIMER1_OVF_vect) {
  digitalWrite(BUCK_pin, HIGH); // turn all HIGH at beginning
  digitalWrite(OC1_pin, HIGH);
  digitalWrite(OC2_pin, HIGH);

  OCR1A = dutyBUCK; // when to turn BUCK off
  OCR1B = dutyOC1;  // when to turn OC1 off
  OCR1C = dutyOC2;  // when to turn OC2 off
}

// Compare Match A → turn BUCK off
ISR(TIMER1_COMPA_vect) {
  if (dutyBUCK > 0) digitalWrite(BUCK_pin, LOW);
}

// Compare Match B → turn OC1 off
ISR(TIMER1_COMPB_vect) {
  if (dutyOC1 > 0) digitalWrite(OC1_pin, LOW);
}

// Compare Match C → turn OC2 off
ISR(TIMER1_COMPC_vect) {
  if (dutyOC2 > 0) digitalWrite(OC2_pin, LOW);
}
New contributor
Jana is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
3
  • 1
    As you found, each of the 3 timers has only 2 PWM channels each. Would some web research leading to external resources like openmusiclabs.com/learning/digital/synchronizing-timers/… not help you? Commented Nov 27 at 7:01
  • are you saying that you are trying to vary the pulse width from 0 to 100 µs (0% to 100%) on three PWM channels ? ... what step value do you want? Commented Nov 27 at 18:07
  • 1
    the Additionally section is not relevant to this question, please ask it separately after you get the three PWM channels working Commented Nov 27 at 18:08

1 Answer 1

0

You can generate three synchronized 10 kHz PWM signals on an Arduino Nano. The Nano uses an ATmega328P, which has only two Timer1 channels. So, hardware PWM alone cannot give three synced outputs. The solution is using Timer1 with interrupts. Set Timer1 to fast PWM mode with TOP = 1599. Turn all three pins high on the overflow interrupt. Use compare match interrupts to turn pins low when needed. This gives perfect synchronization and adjustable duty cycles. Direct port writes instead of digitalWrite() make it faster and reliable at 10 kHz. You can implement your three cases with a simple state machine. Use millis() to switch states every 2 seconds for 0.25 Hz actuation. Case 1: all pins off. Case 2: first two pins on, third off. Case 3: first pin off, second off, third on. This approach avoids phase mismatch and timing errors. For a detailed introduction to the Arduino Nano, including pin mapping and timers, check this guide. Using Timer1 interrupts ensures precise PWM edges. You can vary duty cycles smoothly with variables. This method works better than adding a third PWM to hardware. It keeps all outputs perfectly in sync. At 10 kHz, using direct port manipulation avoids slow digitalWrite() calls. The Nano cannot provide three independent hardware PWM channels. Using a software-controlled third channel is the best solution. You maintain accurate timing and simple code. This method scales for similar multi-channel PWM projects.

2
  • The library digitalWriteFast provides the same speed without needing to fiddle with direct register accesses. Commented 10 hours ago
  • 1
    A software method will incur some jitter, at least from the TIMER0_OVF interrupt occasionally delaying your TIMER1 interrupts by a few microseconds. Regarding direct port writes: you have to ensure all three channels are on the same port, and write them with a single PORTx = value; instruction rather than three PORTx |= bit(channel); instructions. Otherwise the edges will not be perfectly aligned. Re “hardware PWM alone cannot give three synced outputs”: it can. See the description of the “Timer/Counter Synchronization Mode” bit of GTCCR register in the datasheet. Commented 7 hours ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.