2

I'm trying to control a heating element with PWM with an Arduino Nano, but the problem is even if I set the duty cycle to 0 it gives a tiny spike.

I want to generate a square wave of 1Hz and that's why I override the default PWM registers.

Here is the code:

// Set up fast PWM on the Arduino Uno at 1 Hz on digital pin D9
void setup() {

    // set I/O pins
    pinMode(LDR_INPUT, INPUT);      // A0
    pinMode(HEATER_OUTPUT, OUTPUT); // D9

    // Enable the PWM output OC1A on digital pins 9
    TCCR1A = _BV(COM1A1) | _BV(WGM11);
    // Set fast PWM and prescaler of 256 on timer 1
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS12);
    // Set the PWM frequency to 1Hz: 16MHz/(256 * 1Hz) - 1 = 62499
    ICR1 = 62499;

    // OCR1A = 32149; // this gives me duty cycle of 50%, working fine

    OCR1A = 0;       // but this is not working

    //allow PWM timers to start
    delay(10);
}

For testing, I connected an LED to the PWM pin (D9) and these are the spikes from the LDR reading from analog pin A0 (I don't have an oscilloscope).

Enter image description here

I'm getting the same results with the Nano and Uno. My IDE version is 1.8.14.

How can I fix this problem?

Here is my temperature control function:

void Compute()
{
    unsigned long now = millis();
    int timeChange = (now - lastTime);

    if(timeChange>=SampleTime)
    {
        /*Compute all the working error variables*/
        double error = Setpoint - temprature;

        if (error <= 0)
        {
            Output = 0;
            Serial.println("level: 0");
        }
        else if (error <= 1)
        {
            Output = 5;
            Serial.println("level: 1");
        }
        else if (error <= 2)
        {
            Output = 30;
            Serial.println("level: 2");
        }
        else if (error <= 3)
        {
            Output = 50;
            Serial.println("level: 3");
        }
        else if (error <= 10)
        {
            Output = 60;
            Serial.println("level: 4");
        }
        else if (error <= 100)
        {
            Output = 200;
            Serial.println("level: 5");
        }
        else
        {
            Output = 255;
            Serial.println("level: 6");
        }

        analogWrite(HEATER_OUTPUT, Output);
        //OCR1A = map(Output, 0, 255, 0, 62499);
        delay(10);

        lastErr = error;
        lastTime = now;
    }
}

The problem is that after I override the PWM registers, the analogWrite() seems messed up. It's not working, and for all the values of Output, the result is almost the same.

UPDATE 22/06/2021

I'm still struggling to fix this, if I use analogWrite() or digitalWrite() to turn off the PWM pin, the further changes on OCR1A regs are not taking action. whatever values I gave to the OCR1A the pin remains low, if I manually make the pin high with digitalWrite or analogWrite the pin remains high forever the changes on OCR1A are not taking into action.

I wrote a test function

void testPWM3() {

    Serial.println("f: 1Hz | on 0s");
    digitalWrite(HEATER_OUTPUT, LOW); // turn off the pwm pin to get full of (0% duty cycle)
    delay(5000);
    /*
     After turning off the pin with digitalWrite() further changes on OCR1A reg are not taken into action
    */
    digitalWrite(HEATER_OUTPUT, HIGH);
    Serial.println("f: 1Hz | on 125ms");
    OCR1A =  1953;
    delay(5000);

    Serial.println("f: 1Hz | on 250ms");
    OCR1A =  3906;
    delay(5000);

    Serial.println("f: 1Hz | on 500ms");
    OCR1A =  7812;
    delay(5000);

    Serial.println("f: 1Hz | on 1sec");
    digitalWrite(HEATER_OUTPUT, HIGH);  // this works
    delay(5000);
}

Any advice on this will be greatly helpful, thank you

6

That is how it is supposed to be. In this fast PWM mode, the duty cycle is (OCR1A+1)/(ICR1+1). If you want a duty cycle of zero, you have to disengage the PWM and turn the pin to OUTPUT LOW.

This is what the Arduino's analogWrite() does: it treats the value 0 as a special case. Note that 255 is also treated as a special case, although that is not useful.

Edit 1: about analogWrite() being messed up.

analogWrite(9, value) sets OCR1A to a value between 1 and 254 (0 and 255 being handled in a different manner). The duty cycle you get is, as I wrote before (OCR1A+1)/(ICR1+1). This is not what your test function expects.

I suggest you ditch analogWrite() and instead set directly OCR1A.

Edit 2: Once you have taken control of a PWM channel at low-level, by writing directly to the hardware registers, you should not use the Arduino functions digitalWrite() nor analogWrite() on the associated pin. These functions assume those hardware registers are controlled solely by the Arduino core, and they will fiddle with them in a way that will likely break your program.

To set the duty cycle to zero, disable the PWM mode and set the pin to LOW:

TCCR1A &= ~_BV(COM1A1);  // disable PWM on pin OC1A = PB1
PORTB  &= _BV(PB1);      // set PB1 to LOW
6
  • Thank you for your response, I have tried that, but the analogWrite is not working properly in my case, updated the question.
    – Sumithran
    Jun 20 at 14:25
  • You mean not working properly in a way different from how this answer explains perfectly well?
    – timemage
    Jun 20 at 14:27
  • @timemage, this is a great answer, I got what he says, with analogWrite() I'm getting clean off PWM, but with analogWrite(9,128) I'm supposed to get a duty-cycle of 50%, but what I'm getting is a really thin pulse..
    – Sumithran
    Jun 20 at 14:46
  • Re “analogWrite(9,128) I'm supposed to get a duty-cycle of 50%”: No, you are not. Read my answer more carefully. Jun 20 at 14:47
  • 1
    oh damn me, so for getting 50% I have to give half of ICR1 right..?
    – Sumithran
    Jun 20 at 14:49

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.