Please help me !

I am using an Arduino Mega 2560 and I want to generate a complementary 40 kHz PWM signal, but I need to adjust the duty cycle via the serial port. For example, when I set a 20% duty cycle for the first signal, the other signal should automatically adjust itself to an 80% duty cycle, and I should get a 40 kHz PWM output. I am relatively new to Arduino and can't figure this out. Please help.

Your other topic on the same subject deleted.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

Well, unless you're asking us to write code on your behalf (we don't), start writing at least a code to generate the signal with fixed duty cycle, then if you need help for the cycle change, post here your code and we'll see what we can do to help you.

for more information read the already linked " How to get the best out of this forum" especially the "Code problems" section:

We can only find problems in code we can see. Please supply your complete code in code tags <CODE/>

and "Ready written code":

Asking for the code for what you are trying to do will not get you anywhere, other than possibly some annoyed replies. You either have to write your own, which we are here to help with when you get stuck, or you can pay for someone else to write it for you.

In order to complement a digital signal, we use an inverter. The when the input is high, the output will be low, and when the input is low, the output will be high.

const int pwmPin1 = 2; // 1. PWM pin
const int pwmPin2 = 3; // 2. PWM pin

void setup() {
  // PWM pinlerinin çıkış olarak ayarlanması
  pinMode(pwmPin1, OUTPUT);
  pinMode(pwmPin2, OUTPUT);

 
  analogWrite(pwmPin1, 128); // 50% duty cycle
  analogWrite(pwmPin2, 128); // 50% duty cycle
}

void loop() {

}

The code I've written is getting 490 Hz unfortunately, I need to make this value 40 KHz.I apologize for bothering you, but this code isn't sending complementary PWM. I'm new to Arduino and forum pages.

Yes, because I'm sorry to say Arduino pins generates PWM fixed frequencies (pins 3, 9, 10, 11 of 490Hz and pins 5, 6 980Hz). The problem here is the frequency before the duty cycle.

To get 40 kHz is a pretty high frequency to be generated by software, so you either need some "tricks" to have it, or switch to a hardware solution (a circuit generating the frequency, with duty cycle controleld by an input from Arduino).

Anyway have a look at this code I have found (here) searching the forum for you (I haven't tested it anyway):

const byte LED = 3;  // Timer 2 "B" output: OC2B

const long frequency = 40000L;  // Hz

void setup() 
{
  pinMode (LED, OUTPUT);

  TCCR2A = _BV (WGM20) | _BV (WGM21) | _BV (COM2B1); // fast PWM, clear OC2B on compare
  TCCR2B = _BV (WGM22) | _BV (CS21);         // fast PWM, prescaler of 8
  OCR2A =  ((F_CPU / 8) / frequency) - 1;    // zero relative  
  OCR2B = ((OCR2A + 1) / 2) - 1;             // 50% duty cycle
  }  // end of setup

void loop() { }

You're welcome, no need to apologize. :wink:

// Timer1 pinlerle ilişkilidir
const int timer1Pin = 10; // Timer1 çıkışı (OC1A), Arduino Uno'da 9 numaralı pin ile bağlantılıdır
int sayici;
//const int timer1Pin = 9;
void setup() {
  pinMode(timer1Pin, OUTPUT); // Timer1 çıkışını çıkış olarak ayarla
  pinMode(11, OUTPUT); 
  PORTB |= (1 << 5);
  Serial.begin(9600); // Seri portu başlat
  setupTimer1(); // Timer1'i yapılandır
}

void loop() {
  if (Serial.available() > 0) {
    unsigned long newPrescaler = Serial.parseInt(); // Seri porttan yeni prescaler değerini oku
    if (newPrescaler > 0) {
      updatePrescaler(newPrescaler); // Yeni prescaler değerini güncelle
    }
  }
}

void setupTimer1() {
  // Timer1 yapılandırması
  noInterrupts(); // Kesmeleri devre dışı bırak

  // Timer1'i ayarla
  TCCR1A = 0; // Timer1 kontrol register A'yı sıfırla
  TCCR1B = 0; // Timer1 kontrol register B'yi sıfırla
  TCNT1 = 0;  // Timer1 sayıcıyı sıfırla

  updatePrescaler(510); // Başlangıçta prescaler değerini ayarla

  // Timer1'i ayarla
  TCCR1B |= (1 << WGM12); // CTC modunu ayarla
  TIMSK1 |= (1 << OCIE1A); // Karşılaştırma kesmesini etkinleştir
  interrupts(); // Kesmeleri etkinleştir
}

void updatePrescaler(unsigned long newPrescaler) {
  // Prescaler değerini güncelle
  unsigned long period = 1000000 / 100000; // 100 kHz frekans için periyod (1 saniye / 100000)
  unsigned long prescaler = newPrescaler; // Yeni prescaler değeri

  OCR1A = prescaler - 1; // Karşılaştırma değerini ayarla
  TCCR1B |= (1 << CS10); // Timer1'i ön bölücü ile ön böl ve başlat (1 ön bölücü)
}

// Timer1 karşılaştırma kesme işleyicisi
ISR(TIMER1_COMPA_vect) {
  

  // Kare dalga için çıkışı tersle
  TCNT1 = 0;

  //sayici++;
  //if(sayici> 9 )sayici=0;

 //if(sayici==7||sayici==3)
 //{
  PINB= (1 << 5); // PH5 pinini tersle (9 numaralı pin)
 PINB= (1 << 4);
 //}

}

I had previously written a code like this, but with this code, I can adjust the frequency with the timer value we enter into the serial port and it generates complementary PWM. However, I cannot adjust the duty cycle with this code. What changes can I make to be able to adjust the duty cycle with the serial port in this code?

I thought i would give chatgtp a go. May or may not work

void setup() {
  // Initialize serial communication
  Serial.begin(9600);
  
  // Set pins 11 and 12 as outputs
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);

  // Stop the timer
  TCCR1A = 0;
  TCCR1B = 0;
  
  // Set the mode to Fast PWM and set TOP value
  // WGM13:0 = 14 (mode 14, fast PWM with ICR1 as TOP)
  TCCR1B |= (1 << WGM13) | (1 << WGM12);
  TCCR1A |= (1 << WGM11);
  
  // Set the prescaler to 1
  TCCR1B |= (1 << CS10);
  
  // Calculate and set TOP value to achieve 40 kHz frequency
  // TOP = (Clock Speed / (Prescaler * Frequency)) - 1
  // For 40 kHz, with 16 MHz clock: TOP = (16000000 / (1 * 40000)) - 1 = 399
  ICR1 = 399;

  // Set initial duty cycle to 50%
  OCR1A = 199;
  OCR1B = 199;

  // Enable the PWM output on pins 11 and 12
  TCCR1A |= (1 << COM1A1) | (1 << COM1B1);
}

void loop() {
  // Check if there is any data available in the serial buffer
  if (Serial.available() > 0) {
    // Read the incoming byte
    int dutyCycle = Serial.parseInt();

    // Constrain the duty cycle value between 0 and 100
    dutyCycle = constrain(dutyCycle, 0, 100);
    
    // Calculate the OCR1A value based on the duty cycle
    OCR1A = (dutyCycle * ICR1) / 100;
    OCR1B = ICR1 - OCR1A;

    // Print the set duty cycle for debugging
    Serial.print("Duty Cycle Set: ");
    Serial.print(dutyCycle);
    Serial.print("% for OCR1A and ");
    Serial.print(100 - dutyCycle);
    Serial.println("% for OCR1B");
  }
}

Explanation

  1. Serial Communication: Serial.begin(9600); initializes the serial communication at 9600 baud rate for reading the duty cycle value.

  2. Pin Modes: pinMode(11, OUTPUT); and pinMode(12, OUTPUT); set the PWM pins as outputs.

  3. Stop the Timer: TCCR1A = 0; TCCR1B = 0; stops Timer 1 to configure it.

  4. Set Timer Mode: Configure Timer 1 for Fast PWM mode with ICR1 as TOP:

  • TCCR1B |= (1 << WGM13) | (1 << WGM12);
  • TCCR1A |= (1 << WGM11);
  1. Set Prescaler: TCCR1B |= (1 << CS10); sets the prescaler to 1.

  2. Set TOP Value: Set the TOP value to 399 for a 40 kHz PWM:

  • ICR1 = 399;
  1. Initial Duty Cycle: Set an initial duty cycle to 50%:
  • OCR1A = 199;
  • OCR1B = 199;
  1. Enable PWM Output: Enable the PWM output on pins 11 and 12:
  • TCCR1A |= (1 << COM1A1) | (1 << COM1B1);
  1. Adjust Duty Cycle via Serial Port:
  • if (Serial.available() > 0) checks if there is data available in the serial buffer.
  • int dutyCycle = Serial.parseInt(); reads the incoming integer value.
  • dutyCycle = constrain(dutyCycle, 0, 100); constrains the value between 0 and 100.
  • OCR1A = (dutyCycle * ICR1) / 100; calculates the OCR1A value based on the duty cycle.
  • OCR1B = ICR1 - OCR1A; calculates the complementary duty cycle for OCR1B.

This code allows you to input a duty cycle percentage via the serial monitor. For example, if you input 20, pin 11 (OCR1A) will have a 20% duty cycle, and pin 12 (OCR1B) will have an 80% duty cycle, both operating at a 40 kHz frequency.

It is the nature of the way computers work that you can't have both a change of PWM frequency to any value and still have full control of the duty cycle. You can have either but not both.

Interesting puzzle.

The solution I’ll give is board-specific for the mega2560, though the algoritm may fit other boards too.

The Arduino IDE will standard initialize the timers for use with PWM. You mentioned pins 2 and 3, these are connected to Timer/Counter 3 (so is pin 5, but will still be useable as input or output) and these will give you normally 490Hz.

To change the settings that make the PWM signals, direct register manipulation is necessary.

You want to create a PWM-signal and it’s complement. You can do that by creating one PWM-signal for one pin and another PWM-signal for the other pin. By using 2 signals from the same timer running parallel is ensured.

Be aware that normally Arduino IDE monitors and controls interactions between digitalRead/Write and analogRead/Write actions, and that is now defunct for those two pins.

I don’t have that board so I can’t test it fully, but this compiles.

// to create a PWM signal on pin2 and its complement at pin3

void setDutycycle(int d) {  // 0 - 400 => 0 - 100 %
  OCR3B = d;
  OCR3C = d;
}

void setup() {
  TCCR3A = 0b00101110;  // pin 2 (PE4) normal, pin 3 (PE5) inverted
  TCCR3B = 0b00011001;  // WGM 14 = fast PWM, Clock = main clock (16MHz)
  ICR3 = 400;           // main clock frequency / desired PWM frequency
}

void loop() {
  setDutycycle(80);  // set 20%
}

By the way, I advise you to read the datasheet to understand your processor.

I realized there might be a problem if, by chance, the changing of the dutycle setting occurs close before the turn-over of the timer. The settings are buffered and at the turn-over the values from the buffers will be transferred to the actual registers, with the possibility that one buffer is filled and the other not yet.
So I put in a wait state.
And I prevented interruptions.

// to create a PWM signal on pin2 and its complement at pin3

void setDutycycle(int d) {
  uint8_t tmp = SREG;     // save status tregister
  cli();                  // disable interrupts
  while (TCNT3 > 350) {}  // wait if too close to update time
  OCR3B = d;
  OCR3C = d;
  SREG = tmp;  // write back statusregister
}

void setup() {
  TCCR3A = 0b00101110;  // pin 2 (PE4) normal, pin 3 (PE5) inverted
  TCCR3B = 0b00011001;  // WGM 14 = fast PWM, Clock = main clock (16MHz)
  ICR3 = 399;           // main clock frequency / desired PWM frequency - 1
}

void loop() {
  setDutycycle(80);  // set 20%, 0 - 400 => 0 - 100 %
}

So it shows you that it has zero intelligence. Doesn't it?

If you disagree, then you do not understand the question.

1 Like

You could, in theory, use a PCA9685. Get the Arduino to generate a fixed frequency signal and synchronise this to the internal oscillator of the PCA9685.

The data sheet for this device says:-

The PCA9685 has an external clock input pin that will accept user-supplied clock
(50 MHz max.) in place of the internal 25 MHz oscillator. This feature allows synchronization of multiple devices.

However, devices like the
Adafruit 16-channel pwm servo driver
have no provisions to do this and runs at the standard 25 MHz speed.

Also the refresh rate must be able to cope with the internal dividers in the chip so the actual frequency you need to use is given by the formula:-
refresh_rate = External Clock / (4096 * (prescale + 1))
See page 14 of the data sheet.

For normal operations this external clock pin (pin 25) is grounded, and to use it as an external clock it needs not to be.

Now the external clock is grounded on the PCB layout of the Adafruit board, via the plated through hole immediately after the chip, so it is hard to see how this could be bypassed to provide access to this signal / pin.

So you are left with the option of building your own equivalent function with the raw chip and having access to this external clock input. How are your soldering skills?

This is the best solution I could think of.

Still, chatGTP came pretty close. Just timing is off.
The outputs start at both HIGH, and will go LOW at different times.
And it threw in serial unrequested.
All things together, an inverter like in post#4 may be the simplest solution.

No it is not just the timing, it is the whole concept it looks like you and chatGTP fail to understand. The concept of not being able to change the frequency and still get the full range of PWM values, at the same time.

What do you mean by full range?
In my sketch he’ll get from zero to full on in 400 steps, while Arduino IDE allows only 256 steps.
No, you can’t get the full 65536 steps at a frequency over 245Hz.

PWM will deliver a duty cycle, for using in the analogWrite function, ranging from 0 to 255. With 0 meaning full off and 255 meaning full on.

This can only work when using one of the standard frequency values. At anything other than these then you can't get the full range of PWM values.

A fact that both chatGPT and apparently you, do not know.

It occurred to me that by ‘full range’ you might mean : no spikes at zero and no spikes at max.

The Arduino IDE controls the spikes by translating analogWrite(pin, 0) to digitalWrite(pin, LOW) and analogWrite(pin, 255) to digitalWrite(pin, HIGH).

I’ve done some testing today, bypassing this translation. At dutycycle 0: no spikes. At dutycycle 255: spikes. Because Arduino IDE treats the timers used for PWM as 8 bit timers.

However, when using a PWM timer at less than it’s maximum period it is possible to set the number for the dutycycle 1 higher than the number for the period, in which case there will be no spikes.

I changed the number for the period in my sketch from 400 to 399, so the number for the dutycycle can stay at 0 - 400.

No.
an analogWrite(pin, 0) will not produce anything because analogWrite only works with PWM capable pins. Yes we all (should) know that it is a badly named function, but you can only apply it to a certain number of pins.