Nano 33 IOT analogWrite and digitalWrite interaction

Hi,

I'm trying to get a view on how analogWrite and digitalWrite work together for the Nano 33 IOT.

If I look at the page detailing analogWrite, I see the following text:

After a call to analogWrite() , the pin will generate a steady rectangular wave of the specified duty cycle until the next call to analogWrite() (or a call to digitalRead() or digitalWrite() ) on the same pin.

When I look at the actual implementation of analogWrite inside of C:\Users\...\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino\wiring_analog.c , I can see that the timers for the PWMs are being enabled/updated based on whether they were already active.
So indeed, when I do an analogWrite, PWM will be running. Everything fine up to this point.

For the second part of the quote I need to look at a different part of the code.

(or a call to digitalRead() or digitalWrite() ) on the same pin.

Inside C:\Users\...\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino\wiring_digital.c we find the source:

void digitalWrite( pin_size_t ulPin, PinStatus ulVal )
{
  // Handle the case the pin isn't usable as PIO
  if ( g_APinDescription[ulPin].ulPinType == PIO_NOT_A_PIN )
  {
    return ;
  }

  EPortType port = g_APinDescription[ulPin].ulPort;
  uint32_t pin = g_APinDescription[ulPin].ulPin;
  uint32_t pinMask = (1ul << pin);

  if ( (PORT->Group[port].DIRSET.reg & pinMask) == 0 ) {
    // the pin is not an output, disable pull-up if val is LOW, otherwise enable pull-up
    PORT->Group[port].PINCFG[pin].bit.PULLEN = ((ulVal == LOW) ? 0 : 1) ;
  }

  switch ( ulVal )
  {
    case LOW:
      PORT->Group[port].OUTCLR.reg = pinMask;
    break ;

    default:
      PORT->Group[port].OUTSET.reg = pinMask;
    break ;
  }

  return ;
}

PinStatus digitalRead( pin_size_t ulPin )
{
  // Handle the case the pin isn't usable as PIO
  if ( g_APinDescription[ulPin].ulPinType == PIO_NOT_A_PIN )
  {
    return LOW ;
  }

  if ( (PORT->Group[g_APinDescription[ulPin].ulPort].IN.reg & (1ul << g_APinDescription[ulPin].ulPin)) != 0 )
  {
    return HIGH ;
  }

  return LOW ;
}

Nowhere in this code do I find any part that actually disables the PWM timers and thus disables the PWM output on the pin.

Indeed, if I actually test this behavior with f.e. digital pins 9 - 12 (which support PWM on the Nano 33 IOT), I see the behavior that is derived from the code (PWM continues after digitalWrite) which clashes with the behavior one would expect from the documentation (and from a usage standpoint).

Testcode for Nano 33 IOT:

const int pwm12 = 12;
const int pwm11 = 11;
const int pwm10 = 10;
const int pwm09 = 9;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(5000);
  Serial.write("Setup started\n");
  pinMode(pwm12, OUTPUT);
  pinMode(pwm11, OUTPUT);
  pinMode(pwm10, OUTPUT);
  pinMode(pwm09, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.write("Main code started\n");
  Serial.write("Set pwm for pin 12\n");
  analogWrite(pwm12, 51);
  analogWrite(pwm11, 102);
  analogWrite(pwm10, 153);
  analogWrite(pwm09, 204);
  delay(1000);
  digitalWrite(pwm12, LOW);
  delay(1000);
  digitalWrite(pwm11, LOW);
  delay(1000);
  digitalWrite(pwm10, LOW);
  delay(1000);
  digitalWrite(pwm09, LOW);
  delay(1000);
  analogWrite(pwm12, 51);
  analogWrite(pwm11, 102);
  analogWrite(pwm10, 153);
  analogWrite(pwm09, 204);
  delay(2000);
  while(1);
}

I took some measurements of my arduino running this code with my logic analyzer. At no time did the actual output go back to 0/LOW after the digitalWrites.

So either
a. this is a bug in arduino core (which I expect as I would expect the output to go low after I do a digitalWrite(pin, LOW) to a pin)
or
b. the documentation does not line up

Until this is fixed I'll have to use my own implementation of digitalRead/digitalWrite which actually turns of the timers/PWM when doing a digitalRead or digitalWrite.

I think this is a bug

Apparently there's already a github issue for this.
https://github.com/arduino/ArduinoCore-samd/issues/385

I'll look at the code of the uno and will see if they're quick to accept pull requests with a fix for this.

edit: and another one where they say this won't be fixed:
https://github.com/arduino/Arduino/issues/476

Anyway, if someone is interested, I've just added a new function:

void digitalWritePWMOff( pin_size_t ulPin, PinStatus ulVal )
{
  pinMode(ulPin, OUTPUT);
  digitalWrite(ulPin, ulVal);
}

And I call that instead at the points where I know PWM is on.
New code:

const int pwm12 = 12;
const int pwm11 = 11;
const int pwm10 = 10;
const int pwm09 = 9;

void digitalWritePWMOff( pin_size_t ulPin, PinStatus ulVal )
{
  pinMode(ulPin, OUTPUT);
  digitalWrite(ulPin, ulVal);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(5000);
  Serial.write("Setup started\n");
  pinMode(pwm12, OUTPUT);
  pinMode(pwm11, OUTPUT);
  pinMode(pwm10, OUTPUT);
  pinMode(pwm09, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.write("Main code started\n");
  analogWrite(pwm12, 51);
  analogWrite(pwm11, 102);
  analogWrite(pwm10, 153);
  analogWrite(pwm09, 204);
  delay(1000);
  digitalWritePWMOff(pwm12, LOW);
  delay(1000);
  digitalWritePWMOff(pwm11, LOW);
  delay(1000);
  digitalWritePWMOff(pwm10, LOW);
  delay(1000);
  digitalWritePWMOff(pwm09, LOW);
  delay(1000);
  analogWrite(pwm12, 51);
  analogWrite(pwm11, 102);
  analogWrite(pwm10, 153);
  analogWrite(pwm09, 204);
  delay(2000);
  while(1);
}

And corresponding waveforms from the logic analyzer:

So this function does what I need and I can continue with my project.

I guess the documentation at least needs to be updated then for analogWrite.
Code for arduino boards with digitalWrite implementations that don't do a turnoffPWM should turn off the PWM on its own. The AVR implementation does have a turnoffPWM call in digitalWrite, which probably wasn't ported to other platforms for optimization reasons.

The design of the SAMD port interface is such that doing an analogWrite followed by a digitalWrite does not need to turn off the Timer/Counter that is generating the PWM. It can keep on running.

The first analogWrite to a pin will cause code to configure and enable the Timer/Counter for the pin to be executed. From that point until the microcontroller is reset the Timer/Counter is left running! The only real significance of this is the microcontroller will consume slightly more power than it would if the timer/counter were turned off when no longer needed.

EDITED TO ADD: While it is true that the Timer/Counter is never disabled once it is enabled with an analogWrite, the connection from the Timer/Counter to the pin is controlled by the PINCFG register in the PORT. This connection is made with calls to analogWrite and disconnected with calls to pinMode (but not digitalRead or digitalWrite).

Before tracking down this thread, I noted the same problem on a Nano BLE's 3 colour LED, and in desperation added the code above (pinMode (LEDR, OUTPUT) followed by digitalWrite (LEDR,HIGH)); the PWM still seems to have ultimate authority and digitalWrite seems to be ineffective once the analogWrite (LEDR, ramp) has been executed. Admittedly, the 3 colour LED does not seem to be connected to PWM-able pins, yet the PWM evidently works, oddly.

Has a further fix been suggested?

FWIW: a PWM pin (10 in this case) on an UNO can be changed between a PWM output (analogWrite (led,ramp)) and a digital output (digitalWrite (led, HIGH)) as required, with no pinMode command beyond setup's initial pinMode (led,OUTPUT) command, and works exactly as expected.

The comments in the source (for the AVR Arduinos) "tell the story." They have the following in digitalRead and digitalWrite:

// If the pin that support PWM output, we need to turn it off
	// before doing a digital write.
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);

But then there is this comment for the turnOffPWM function:

// But shouldn't this be moved into pinMode? Seems silly to check and do on
// each digitalread or write.

So it looks like it was at least considered to do it the SAMD way for the AVR.

Thank you for looking into this! Much appreciated.

When I get back to the shop, I'll give it a whirl. Your note suggests that 'turnOffPWM' is a recognized keyword, although it doesn't appear to be one on this laptop (Arduino 1.8.19). If it's not built into the compiler, what does such a function look like?

turnOffPWM is declared static which makes it unaccessible outside of the file it is declared in, which is wiring_digital.c. The function only exists for the AVR core library. If you want the Nano 33 IoT to work the same as an AVR Arduino, and don't mind modifying the system library, in the SAMD version of wiring_digital.c you could have digitalRead and digitalWrite call pinMode however, IMHO, it would be best to leave it alone and just call pinMode when switching from analogWrite to digitalWrite or digitalRead. That would be a safe approach for any Arduino model.

My (non-working) belt-and-suspenders approach has grown to this when attempting to go from PWM to on/off:

[code]
void RGBLedsOff()
{
 // if (timer != NOT_ON_TIMER) turnOffPWM(timer); // from Tom Almy
  pinMode(LEDR, INPUT); 
  pinMode(LEDG, INPUT);
  pinMode(LEDB, INPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
  digitalWrite(LEDR, HIGH);
  digitalWrite(LEDG, HIGH);
  digitalWrite(LEDB, HIGH);
  Serial.println ("RGBLedsOff");
}

[/code]

but the PWM control of the RGB LED seems to survive; the LED is stuck at the 3 PWM levels extant when this is called. (My 40 years writing assembly was tiresome, but sometimes better.)

Oh, my, I just realized that you are talking about Nano 33 BLE, which uses an nRF52840 and not a SAMD21 microcontroller, and of course that means still other library code! I don't have this board and never studied its microcontroller. I just downloaded the Arduino library for it and find it completely different in its design, bearing no relationship to the AVR or SAMD21 versions (even though the nRF52840 does use an ARM processor core).

You should probably repost to the Nano 33 BLE forum.

Trust me to poke a stick into the wrong hornets' nest. And so much for the touted micro-agnostic virtues of higher-level languages! Thank you for your polite ministrations thus far! Off I go in search of a different hornets' nest...