Photon Hardware PWM - Any Examples?

Hi

I last looked at this when my Photon first arrived and there didn’t seem to be a solution then. So I figured out how to do a ‘software’ PWM as an interim solution.

I am wondering, is there an example available of using Hardware PWM with Photon available anywhere?

…failing that are there any plans/timescale to provide an working example or PWM library?

For example: generating a 38kHZ PWM output. (For Infrared remote control)

TIA

@AnalysIR, once the new firmware rolls out I will be working on an entirely new SparkIntervalTimer library which will support all timer modes, including PWM, output capture (used by Tone for example), input capture and straight timer interrupts. The catch with the PWM pins is that they are channels of a common timer so changing that base timer will affect all of its channels as well. I have started a topic on this development and @maghis will be contributing. You are more than welcome to contribute as well :smile:

@peekay123
Unfortunately, I don’t think I have anything to contribute to the effort, but would be willing ot test out any hardware PWM stuff, when available.

When is the new Firmware due to be released?

If the single timer is a big issue, it may be possible at 120MHz clock speed to flip the outputs in s/w (ISR) linked to another timer. Should be workable for IR, given the relatively short bursts.

In the meantime I will track the other thread.

@AnalysIR, the new firmware is due soon since @mdma is on a well deserved vacation. I believe a 0.4.3rc? release is due this week. The release will have the ISR hooks for the timer interrupts but not the “optimized” fast GPIO library which will replace the ubiquitous PIN_MAP macros. The optimized version (when Mat returns) will have functions that are as fast as the macros,about 45ns per bit write, versus this week’s release having about 140ns per bit write.

The library will allow a common set of commands (pinSetFast(), pinResetFast() and digitalWriteFast()) for the Photon/Electron so code can be written more easily.

@peekay123
great, I wasnt aware these goodies were coming so soon. I will try to get some decent IR PWM with this…140ns should be fine (45ns will be even better :slight_smile: ).

Do these new releases turn up automaticcaly in the web IDE? or is special action required to access a 0.4.3rc?

@AnalysIR, yup, it would turn up on the IDE/CLI/DEV with an announcement. I believe it will be 0.4.3rc2 which is available for local compile already (“latest” branch). :smile:

@peekay123
Are any of these items mentioned above, included in or available with the recent firmware 0.4.4?

A quick check of the docs didn’t show anything new.

i.e.

  • SparkIntervalTimer
  • Fast GPIO
  • ISR hooks for the timer interrupts
  • hardware PWM

If not, any insight into timelines?

(I plan to get writing some IR related code on Photons once there has been some progress, meanwhile will continue to wait.)

@AnalysIR, SparkIntervalTimer is a library I created. I will be releasing the Photon compatible version in the next couple of days after having found a few issues.

  • Fast GPIO is in the new firmware release and documented here. A pinSetFast or pinResetFast operation takes about 41ns each!
  • ISR hooks for timers are available though not fully documented yet. I hope to start work on a new timer library soon though I have no ETA.
  • Hardware PWM. I assume you mean vs the PWM done by analogueWrite(). The goal is to have that as part of the new timer library.

:smile:

Hi,

Are there hardware PWM examples now? I’ve spent an hour or two googling and reading forum posts, and it seems there are a lot of stalled git pull requests and threads that didn’t quite finish.

I simply want to be able to send a signal at 25khz for 10100% duty cycles.

Mark

Hi Mark

I struggled with this for a while, I agree there isn’t much info on it! In the end I searched Google for examples from STMF2xx series micros. I combined this with some older code someone had done for the Core and managed to get PWM working (with custom frequency / duty cycles) on the P1. The following works for me on PA6. Good luck!

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //Time base structure for timer 3

/* Compute the period value to generate a clock freq of 1KHz */
/* Period of one clock cycle is approx 8.33ns at 120MHz so period required for 1kHz is x60000*/
period = (uint16_t)(SystemCoreClock / 4000)-1; //SystemCoreClock = 120MHz

/* GPIOA clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

/* Initialise GPIO for PWM function */
initGPIO();



/* TIM3 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

/* Timer Base configuration */

TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned3;
TIM_TimeBaseStructure.TIM_Period = period;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

//Initialise Output Compare Structure
/* Compute the CCR1 value to generate a PWM signal with variable duty cycle */
uint16_t pulse = (uint16_t) ((period-1)/(100.0/dutyCycle));

/* Channel 1 PWM output configuration */
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = pulse;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
}

void initGPIO()
{
  GPIO_InitTypeDef        GPIO_InitStructure;    //GPIO Initialisation Structure
  /* Initialize PA6, Alternative Function, 100Mhz, Output, Push-pull*/
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3);
}

void updateOC()
{
  /* Compute the CCR1 value to generate a PWM signal with variable duty cycle */
  uint16_t pulse = (uint16_t) ((period-1)/(100.0/dutyCycle));

  /* Update pulse in ouput compare register */
  TIM_OCInitStructure.TIM_Pulse = pulse;
  TIM_OC1Init(TIM3, &TIM_OCInitStructure);
}
1 Like

You’ll also need to initialise period and define duty cycle somewhere as well

uint16_t period;
uint8_t dutyCycle=15;

Also you’ll need to declare the initialisation structures (they can be done in the PWM initialisation function at the top, I just had them as globals because it was required by my program).

NVIC_InitTypeDef        NVIC_InitStructure;    //Nested Vector Interrupt Controller Initialisation Structure
TIM_OCInitTypeDef       TIM_OCInitStructure;   //Output compare init structure for PWM

Let me know if that doesn’t work! Sorry I only had time for a quick copy paste, I’m not an expert in the ST micros (used to Atmel!) so there may be aspects of that code that could be cleaned up…

1 Like

Thanks! It’s very similar to the code I ended up writing, though with my version some things didn’t appear to check out on the multimeter. I’ll try yours tonight!

1 Like

ps, also found https://github.com/spark/firmware/pull/497/files which is very similar

@mterrill, at some point I will find the time to expand on that PR to create a new, more generalized timer library with a simple API. The SparkIntervalTimer library auto-allocates timers and is fine for simple repetitive timing but lacks support for other timer modes.

@G65434_2’s code looks good. One bit of confusion seems to be the root clock for all timers which I have found to be SystemCoreClock/2 or 60MHz. I have yet to get clarification on this from the Particle Team. @G65434_2, did you actually measure the pin output with a scope to make sure it matches your expected output (frequency/period, duty cycle)?

1 Like

Hi @peekay123

As I said this was a bit of a quick hack to get things working (I found the ST datasheet a bit overwhelming coming from 8bit micros!). But to answer your question, yes I’ve checked this on a scope and it’s running exactly 1kHz with accurately variable duty cycle.

1 Like

Hi guys, it’s definitely 60Mhz for TIM3.

Section 3.2 in http://www.st.com/web/en/resource/technical/document/datasheet/CD00237391.pdf

1 Like

Hi @G65434_2,

Happy to report last night it was working, I’ve spent some more time tonight tidying some things up.

Months ago I was pretty close, just couldn’t get the period to update correctly, was driving me crazy. The gist of the working code is available for posterity at: https://gist.github.com/markterrill/b88759eaaee4995c2c68

You may notice I’ve tweaked a few things, ie you don’t need to init on each pulse update, etc. It could be further enhanced by someone with spare time, ie using the dictionary structs of TIMx that are used by @maghis in his pull request. https://github.com/spark/firmware/pull/497/files
Note though that pull request doesn’t set pulses, though with another function or two, just like my updateCCR function (nudge nudge) and a new helper function so folk can nominate a simply pin in D0/A0 format and a period and the timer is selected, it’d be pretty close to done.

Cheers!

3 Likes

@mterrill
I loaded up your example & got 7500Hz on D0. or 7.5kHz

I made the following change

#define PWM_FREQ 120000 // in Hertz (SET YOUR FREQUENCY)  

and got 30kHz measured to within 0.1% accuracy.

Could there be some x4 factor or pre-scaler at play here?

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
FYI: when this line is commented out it seems to work as well.

Also changed the pin to D1 and that worked also - as TIM4 is associated with D0 & D1.

#define FAN1 D1
1 Like

Very interesting!

You may have seen my comment about the clock freq being 60mhz, but all kinds of starting numbers and combinations don’t seem to work for me.

A result of 30khz after using a period of 1000hz (?120mhz?/120khz per your vars) calls into question the prescaler which was zero and the actual systemcoreclock.

I’ll put it in excel shortly and see if anything jumps out

Definitely agreed the 4x is highly suspicious!