A timer interrupt will be periodically toggling the PWM bit and might do so between your port read and port write.
The easy solution is to turn off interrupts during the read/modify/write portion of your port update sequence. This could cause a stretched PWM cycle. If you don't turn it off, you'd just glitch the PWM - creating an extra, short cycle. Which one is worse for your PWM recipient?
A better solution might be to to watch the port for the PWM bit to change, then jump in right afterward and make your port update. You could do his in either a blocking or a non-blocking manner.
Update:
Watching the PWM bits seems as if it would be fairly slow, which
somewhat defeats the purpose of the direct port writes.
Yes, it's less than ideal. There's a tradeoff here. Can your PWM consumer tolerate the potential occasional glitch, instead?
To be complete, there are a number of motivations to use direct I/O:
- Smaller code;
- Less I/O skew with multiple I/Os, or zero skew if the I/Os are in the same port;
- Less execution time on a time-critical path;
- Precise output timing;
among others. Those software solutions won't help much in any of those cases.
Can you reassign your output to another pin on a different port, avoiding the whole problem? That might be better than any of the "solutions". Adding hardware might accomplish the same thing - perhaps moving just the PWM to an ATtiny, for example.
Update 2:
This seems so baked into the structure of the Arduino... does
digitalWrite() itself actually overcome this?
It "addresses" it - overcomes it? As is so often the case, "that depends".
Here is the the source-code to digitalWrite() from: /Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino /wiring_digital.c (on a Mac; the top-level folder or two will be different on Windows):
void digitalWrite(uint8_t pin, uint8_t val)
{
uint8_t timer = digitalPinToTimer(pin);
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *out;
if (port == NOT_A_PIN) return;
// 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);
out = portOutputRegister(port);
uint8_t oldSREG = SREG;
cli();
if (val == LOW) {
*out &= ~bit;
} else {
*out |= bit;
}
SREG = oldSREG;
}
Notice that the interrupts are saved and restored around the read/modify/write of the port register. So digitalWrite() already addresses the possibility. This method will stretch one phase of an occasional PWM pulse by a few microseconds.
Alternatively, ignoring the possibility of a collision and having one occur, seems more severe: the pulse could be begun or ended prematurely. This occurrence will take longer - the rest of the disturbed phase - to get back in sync.
So the criterion for how to manage x becomes "Which way has the lesser - or more tolerable - consequence for your PWM consumer?"