Background
This question has been asked a few times on this forum and didn't have a solid solution to it. I wanted to provide my solution to generating an inverted PWM signal. I needed a PWM and it's inverted output on two different pins. Having 2 PWM signals that are inverted and in-sync can be useful in a lot of applications.
Proposed Solutions on the forum: [Thread 1], [Thread 2][Thread 3] 1. Use external inverter. Yes, but I did not want to add any hardware. 2. Change timer registers, TCCR0. This approach doesn't translate well to other hardware like the ESP8266. 3. Use analogWrite(PIN1,i); analogWrite(PIN2,255-i);. This doesn't work due to how Arduinos handle the PWM phase. See more at [Arduino PWM].
The solution attached requires no external hardware. P1 is setup as the primary PWM driver while P2 follows it using an attached interrupt on P1. The following solution was tested on ESP8266 and Arduino Uno.
Your thoughts...
Nikhil Jali
Solution
#define P1 3 // Use interrupt/PWM enabled PIN. INT1 in Arduino UNO
#define P2 5 // Any other PIN
#define P1_INT digitalPinToInterrupt(P1) // Converts PIN to Interrupt number. Required only for UNO
// Make P1_INT=P1 for ESP8266
void setup() {
pinMode(P1, OUTPUT); // Declare pin modes
pinMode(P2, OUTPUT);
attachInterrupt(P1_INT, &falling, FALLING); // Attach interrupt on falling edge of P1 and direct to falling()
analogWrite(P1, 125); // Set analogWrite to begin PWM
}
// Runs on falling edge of P1
void falling() {
digitalWrite(P2, HIGH); // Set P2 as HIGH as P1 was falling when this ISR was triggered
attachInterrupt(P1_INT, &rising, RISING); // Reset the P1 interrupt to the rising edge
}
// Runs on rising edge of P1
void rising() {
digitalWrite(P2, LOW); // Turn-off P2 as P1 is rising
attachInterrupt(P1_INT, &falling, FALLING); // Reset interrupt to Falling edge
}
void loop() {
}
How does this perform in more complex situations? Doesn't the interrupt, which seems to be triggered at PWM frequency, cost quite a lot of clock cycles?
that should work indeed if you accept a somewhat "noticeable" delay
if you want better time-sync between the Rising/Falling front of your 2 signals:
do not mess around with attaching/detaching different ISR. keep one on CHANGE event
do not use digitalWrite, it's too slow for good sync
use PORT manipulation techniques to toggle the right PIN. you could check if the PIN is HIGH and set it to low by reading/writting the right PORT register, but there is a little know feature of the PINx register which is that if you write to (lets say) PIND (which is a read only officially register), what happens is that you toggle the pins. for example
PIND = B00010000; // will toggle pin 4
this translates into assembly on standard arduino as
ldi r29,0x10
out 0x09,r29
LDI takes 1 clock cycle and so does OUT --> so total of 2 clock cycles to flip the bits. if you add a bit of overhead for entering the ISR you are probably at less than 10 clock cycles for toggling the other bit so that gives you a possibility to inverse your second input 0,625 micro-second after the change occurred in the first one instead of probably 4 or 5 micro-second with your approach.
The draw back is that this becomes micro-processor dependant and if you want to do that on an ESP or a Zero then you need to use the right register that flips the bits. could easily be done with the right ifdef in the ISR
There is probably also a way to handle that with pure PWM settings in the right way by manipulating the right registers in which case you don't even need to mess with building your own interrupt. I'll explore a bit if I've time (but again will be CPU dependant)
J-M-L:
but there is a little know feature of the PINx register which is that if you write to (lets say) PIND (which is a read only officially register), what happens is that you toggle the pins. for example
I know right? I mean, if it was an official feature, it would be documented in the datasheet. There would probably be a section called something like "Toggling the Pin" with it's own section number, for example 14.2.2 in the I/O Ports section. The Register Description section for the PINx registers would have a note that says "Writting to the pin register provides toggle functionality for IO (see ”Toggling the Pin” on page 77)".
Since those things don't exist, it's totally a fun Easter Egg.
I know Or they would write in the ATmega328/P [DATASHEET] page 97 something like
The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing '1' to a bit in the PINx Register will result in a toggle in the corresponding bit in the Data Register
do not mess around with attaching/detaching different ISR. keep one on CHANGE event
do not use digitalWrite, it's too slow for good sync
More often than not, the CHANGE interrupt doesn't trigger. Also, as you rightly pointed out, toggling of pins needs to be done based on the state of the PWM pin. One needs to use a pin-read function for which the signal needs to be already stabilized to read the right state of the PWM pin.
While PORTx is the right way to go over digitalWrite, it doesn't scale well for other platforms and the code becomes too platform specific.
ElCaron:
How does this perform in more complex situations? Doesn't the interrupt, which seems to be triggered at PWM frequency, cost quite a lot of clock cycles?
It does unfortunately use over 4-5 cycles given the specific hardware. It is mostly a trade off on the PWM frequency used and the application load. I tested this on the ESP8266 with a PWM frequency of 1kHz and system clock at 80MHz.
My application requires a PWM frequency of around 30Hz and this leaves ample room for the rest of my application. I would personally design in an external inverter, but wanted the option of having a programmatic approach to this as well.
PS: I haven't investigated the effects of this in cases when the WiFi stack is running on teh ESP8266.
I've never seen change fail for me... so not sure what you are talking about there... and agree handling pin toggle through the dedicated low level registers is platform dépendant but don't you have a specific platform in mind? And the abstraction can be coded in a function you could inline and using ifdef... nothing major there but indeed if 30Hz is your constraint and a few microsecond delay not a pb it might be overkill to do that. Actually 30 hertz can probably be dealt with just in the loop with a test against millis or a simple timer interrupt