Photon PWM resolution

Hello everyone!

I want to use 16-Bit PWM output on the Photon in my project to drive some LEDs, but I can’t find any resources about how to output 16-Bit PWM on the Photon. I know that it was possible on the Core (something along the lines of this post: Custom PWM Frequency Example).

Anybody have any luck changing the PWM resolution (and frequency) on the Photon? The only other similar thread is kind of dead (12-bit PWM resolution on Photon possible?)

Found out how to do it!

Link to GitHub

1 Like

Hello Bren730,

I was waiting for this feature too, so I tried your code, but couldn’t make it work :confused:

I’m trying to get hi-res PWM on D3, but it seems it is on TIM1 and not TIM3 (using latest firmware 0.4.9).

I’m also using a serial LCD display (with Serial_LCD_SparkFun) for debugging purpose, and it gets all scrambled when I include PhotonPWM.h in my app.

I don’t have an oscilloscope, but it looks like the D3 pin I’m using stays at 0V, whatever PWM value I tried (between 0 and 65535).

What PWM pin are you using to see it working ?

Hey Zun,

To make it work you should first run initTimers(). This initialises all PWM pins on the Photon (D0, D1, D2, D3, WKP, TX, RX) to run on the same speed as the system clock (120MHz) and accept a maximum resolution of 16-Bits. This results in a maximum PWM frequency of 120MHz / 65536 = 1831 Hz. I have not verified this frequency yet, but it is subjectively very high, almost no stroboscope effect is visible when waving your hand over the LED light. After running initTimers(), you can run analogWrite16(pin, value), in which ‘value’ can be 0-65535, it should output that value on that pin. I tested it and it works on all PWM pins of the Photon.
I have no experience with driving LCD displays, but my code does adjust the timers of all the PWM pins, which will likely mess up your display timing signals. I might rewrite the code so that you can choose to enable 16-Bit output on certain timers/pins. The timers it adjusts are: TIM1, TIM3, TIM4 and TIM5. In this image you can see which timer and timer channel drive which pins: https://docs.particle.io/datasheets/photon-datasheet/#pin-out-diagrams

Let me know how it goes!

Actually I already called initTimers() in the setup().

I looked closer and I was wrong about D3 being on TIM1 (I was misleaded by the commented debug line that shoud say “modifying TIM_channel_[X]” instead of “modifying TIM[X]”).

I noticed a possible error, although it doesn’t seem related to my problem: On line 80, shouldn’t it read TIM_OC2PreloadConfig instead of TIM_OC3PreloadConfig ?

I also tried to deactivate hi-res PWM on TX/RX with no success.

It would be nice to be able to select on wich timer/pin you want hi-res PWM. Do you think it could be possible to leave TX/RX untouched while changing D0-D4 ?

Here a simplified version of my code to test:

#include <Particle.h>
#include "build.h"

#include "PhotonPWM/PhotonPWM.h"

#define PWM_PIN D0
#define CTRL_PIN D1

uint32_t pwm_value = 32768;
uint32_t pwm_value_old = 32768;

int set_pwm(String command) {
    uint32_t val = atoi(command);

    pwm_value = val;

    return pwm_value;
}

void setup() {
    initTimers();
    pinMode(PWM_PIN, OUTPUT);
    analogWrite16(PWM_PIN, pwm_value);

    pinMode(CTRL_PIN, OUTPUT);
    digitalWrite(CTRL_PIN, pwm_value % 2);

    Particle.function("pwm", set_pwm);

    Particle.publish("system_version", System.version().c_str(), 60, PRIVATE);
    Particle.publish("build_date", BUILD_DATE, 60, PRIVATE);
}


void loop() {
    digitalWrite(CTRL_PIN, pwm_value % 2);
    if(pwm_value != pwm_value_old) {
        analogWrite16(PWM_PIN, pwm_value);
        pwm_value_old = pwm_value;
    }

    delay(100);
}

The latest firmware 0.4.9 (available now on the Particle Build web IDE) has variable PWM frequency by using analogWrite(pin, dutyCycle, frequencyInHertz) but the duty cycle is still from from 0 to 255. See the docs for more details

Are you more interested in changing the frequency or increasing the resolution of the duty cycle?

Hey Zun,

You are right, on line 80 there was an error. I have updated the file on GitHub, thanks for pointing it out. Also, my code is based partially on someone else’s, so the debugging printlines etc. might not be correct at some places. But I thought I’d share my answer as soon as possible.

As for your question, I think it is possible to leave TX/RX untouched, since they share one timer which is not used by other pins. But I’ll have to dive into my code and set it up properly so you can choose which pins will get hi-res PWM.

As for your code, I see that you call initTimers() and then immediately call pinMode(PWM_PIN, OUTPUT);. All the pins are already set to OUTPUT in the initTimers(), and I think that might be messing with your results. I only got the hi-res PWM to work when I called pinMode(pin, OUTPUT); on each pin before changing all the timer settings.

So I’d comment all pinModes out and check if that works.

@jvanier, thanks for sharing that information. Good to hear the Particle team is working on integrating this into the firmware.

@jvanier, I believe he wants to increase the resolution of the duty cycle.

1 Like

Yes that was it !

Commenting out pinModes made it work :smile:

@jvanier yes, I want more resolution. Although 16-bit is a bit overkill for my application, 8-bit is clearly not enough.

Glad to hear it works!

I believe that if you change “TIM_TimeBaseStructure.TIM_Period = 65535;” to a different value then that will be the max resolution of your PWM. So if you change that to the resolution you need (say 4096) then the frequency of your PWM will go up. Don’t know for sure though :stuck_out_tongue:, I’m rather new to this low-level stuff.

That's a good idea.

I created a GitHub issue for this. It's something that could be supported in the firmware by adding a analogWriteResolution() function.

https://github.com/spark/firmware/issues/841

3 Likes

Hi Guys,
I am trying to use the new exciting analogueWriteResolution function but it does not seem to be recognised “particle build” where am i going wrong?
Thanks
Andy

This hasn’t been merged into the release branch yet - there are still some tasks to be completed first.

1 Like

Hi, is there an easy way to try this ahead of time?
thanks
andy

You could pull the firmware repo from GitHub and build the firmware with a local toolchain.
Instructions are in the readme.md of the repo.

1 Like

I tried to local build, failed and made my head hurt, will try again this weekend. When do you estimate the next version will be released.

Not long I guess since 0.6.0 should go into pre-release testing in the next couple of weeks

But you can keep track of the milestones here

1 Like