1
\$\begingroup\$

Microcontroller datasheet

I am in the process of selecting a micro controller for a project where I need a lot of PWM pins (around 30) for LEDs. I'd prefer hardware supported PWM so that my CPU is free for other tasks but it's not a necessity if it can be done comfortably on software side as well. 100 Hz PWM frequency should work for the LEDs and I won't be needing more than 8 brightness levels.

I have narrowed down on STM32F072V8T6 (100 pin LQFP) which has 87 GPIO and other features (like capacitive touch sensing and rtc with Vbat pin) that I require for my project.

Going through the datasheet, I am not able to figure out how many PWM pins can I get out of it (I come from arduino background where no. of PWM pins are mentioned directly). I found a small note saying - "One 16-bit advanced-control timer for six-channel PWM output"

Does that mean I can have only 6 PWM pins?

If this is the really the case, what are my other cheap alternatives to get as many PWM outputs? I saw some PWM output port expanders which communicate over i2c but the cost seems too high.

\$\endgroup\$
  • \$\begingroup\$ While software PWM could work, you should also think about how you are going to handle the current from all those LEDs. There are multi-channel driver chips, a pair of which may be a good fit for this, either sold specifically for LEDs or generically for PWM loads, and often configurable via I2C or SPI or similar. \$\endgroup\$ – Chris Stratton May 27 '17 at 17:11
2
\$\begingroup\$

I don't think that ST has any microcontroller with 30 pwm outputs but with such low frequency you don't really need it. There's a 1 ms interrupt with systick that will provide you with 10 levels of brightness at 100Hz.

Each time the interrupt is triggered set or reset an io pin, you'll have to keep track of 30 variables, one for each pin. It's really easy and you are only limited by the chip pin count.

\$\endgroup\$
  • 1
    \$\begingroup\$ STM32F103 in a 100 pin package does actually have 32 usable PWM output pins, but it has no touch controller \$\endgroup\$ – berendi May 29 '17 at 6:03
2
\$\begingroup\$

As it is 100 pin part, I think we can assume that possible pin configurations are not overlapped, thus you can simply add the factors:

TIM1 has 4 capture-compare registers and configurable outputs. That means it can have 4 independent duty ratio but the same frequency. 3-phase and complementary outputs are duty-ratio dependent of one single CCR, so they are not relevant for your concern.

Look at page 21.

TIM1: 4 CCRs

TIM2: 4

TIM3: 4

TIM6&7: 0

TIM14: 1

TIM15: 2

TIM16&17: 1

+____________

16 CCRs.

So you have 16 hardwave PWM channels. That is the answer.

Even if you have 30 channels, you really shouldn't bother configuring them in this particular case (low frequency, low resolution). More configured timer number means more software effort and bug rate, also more power consumption of the chip. What Paulo Soares said is the common sense and you should implement it this way.

\$\endgroup\$
  • \$\begingroup\$ This is correct. A led driver IC should be used with the mcu. Eg: TLC5947. \$\endgroup\$ – Jeroen3 Jun 19 '17 at 5:33
  • \$\begingroup\$ I didn't mean that :-) What I said is to implement software timers. @Jeroen3 \$\endgroup\$ – Ayhan Jun 21 '17 at 11:19
1
\$\begingroup\$

There are micros with more than 30 HW PWM channels out there (e.g. the Renesas RH850/F1L in 100 pin package: up to 48 PWMs via its "PWM-Diag" unit), but unfortunately not with Touch peripheral.

However, doing software PWM with 8 brightness levels @ 100Hz is ridiculous for any 32 bit microcontroller.
If you don't want to use a CPU time consuming ISR, you could use a DMA mechanism, triggered by a timer, cyclically reading data from a RAM buffer and writing it to one (or more) GPIO port(s).
Of course calculating and filling the buffer also needs CPU time, but only if brightness changes.

\$\endgroup\$
1
\$\begingroup\$

I've made a project with a STM32F100 @ 24MHz, that have 24 PWM at 2kHz, with selectable duty cycle at step of 1%.

For make this work, I had to use all the available timers with PWM output, and some pin in bitband mode activated by another timer.

Please make attention that is impossible to reach hi speed in bitband mode. In my tests, the maximum pwm frequency with 24pwm in bitband mode, driven by a timer in constant time interrupt, is very low, about 600Hz.

This was my configuration:

/*
* PWM 1 : Bit banding
* PWM 2 : Bit banding
* PWM 3 : Bit banding
* PWM 4 : Bit banding
* PWM 5 : TIM1_CH1N (TIM1->CCR1, TIM1->CCER_CC1NP)
* PWM 6 : TIM1_CH2N (TIM1->CCR2, TIM1->CCER_CC2NP)
* PWM 7 : TIM1_CH3N (TIM1->CCR3, TIM1->CCER_CC3NP)
* PWM 8 : Bit banding
* PWM 9 : Bit banding
* PWM 10: Bit banding
* PWM 11: Bit banding
* PWM 12: TIM4_CH1 ()
* PWM 13: TIM4_CH2 ()
* PWM 14: TIM4_CH3 ()
* PWM 15: TIM4_CH4 ()
* PWM 16: TIM3_CH1 ()
* PWM 17: TIM3_CH2 ()
* PWM 18: TIM3_CH3 ()
* PWM 19: TIM3_CH4 () 
* PWM 20: Bit banding
* PWM 21: Bit banding
* PWM 22: Bit banding
* PWM 23: Bit banding
* PWM 24: Bit banding
*/

Set the duty cycle of pwm driven by timer it's easy..

void setPwmDrivenByTimer(uint8_t pwm, uint8_t duty) {
    switch (pwm) {
        case 4:  TIM1->CCR1 = duty; break;
        case 5:  TIM1->CCR2 = duty; break;
        case 6:  TIM1->CCR3 = duty; break;
        case 11: TIM4->CCR1 = duty; break;
        case 12: TIM4->CCR2 = duty; break;
        case 13: TIM4->CCR3 = duty; break;
        case 14: TIM4->CCR4 = duty; break;
        case 15: TIM3->CCR1 = duty; break;
        case 16: TIM3->CCR2 = duty; break;
        case 17: TIM3->CCR3 = duty; break;
        case 18: TIM3->CCR4 = duty; break;
        default: break;
    }
}

For set the pin status of the pwm driven in bitband mode, i've used this code:

/* bitband type */
typedef volatile uint32_t * const bitband_t;
/* base address for bit banding */
#define BITBAND_SRAM_REF                    (0x20000000)
/* base address for bit banding */
#define BITBAND_SRAM_BASE                   (0x22000000)
/* base address for bit banding */
#define BITBAND_PERIPH_REF                  (0x40000000)
/* base address for bit banding */
#define BITBAND_PERIPH_BASE                 (0x42000000)
/* sram bit band */
#define BITBAND_SRAM(address, bit)     ((void*)(BITBAND_SRAM_BASE +   \
        (((uint32_t)address) - BITBAND_SRAM_REF) * 32 + (bit) * 4))
/* periph bit band */
#define BITBAND_PERIPH(address, bit)   ((void *)(BITBAND_PERIPH_BASE + \
        (((uint32_t)address) - BITBAND_PERIPH_REF) * 32 + (bit) * 4))

#pragma diag_suppress 1296
bitband_t pwm[] =  {
    {BITBAND_PERIPH(&GPIOE->ODR,  2)},
    {BITBAND_PERIPH(&GPIOE->ODR,  3)},      
    {BITBAND_PERIPH(&GPIOE->ODR,  4)},
    {BITBAND_PERIPH(&GPIOB->ODR, 12)},
    {BITBAND_PERIPH(&GPIOB->ODR, 13)},
    {BITBAND_PERIPH(&GPIOB->ODR, 14)},
    {BITBAND_PERIPH(&GPIOB->ODR, 15)},
    {BITBAND_PERIPH(&GPIOD->ODR,  8)},
    {BITBAND_PERIPH(&GPIOD->ODR,  9)},
    {BITBAND_PERIPH(&GPIOD->ODR, 10)},
    {BITBAND_PERIPH(&GPIOD->ODR, 11)},
    {BITBAND_PERIPH(&GPIOD->ODR, 12)},
    {BITBAND_PERIPH(&GPIOD->ODR, 13)},
    {BITBAND_PERIPH(&GPIOD->ODR, 14)},
    {BITBAND_PERIPH(&GPIOD->ODR, 15)},
    {BITBAND_PERIPH(&GPIOC->ODR,  6)},
    {BITBAND_PERIPH(&GPIOC->ODR,  7)},
    {BITBAND_PERIPH(&GPIOC->ODR,  8)},
    {BITBAND_PERIPH(&GPIOC->ODR,  9)},      
    {BITBAND_PERIPH(&GPIOA->ODR,  8)},      
    {BITBAND_PERIPH(&GPIOA->ODR, 11)},      
    {BITBAND_PERIPH(&GPIOA->ODR, 12)},      
    {BITBAND_PERIPH(&GPIOC->ODR, 10)},      
    {BITBAND_PERIPH(&GPIOC->ODR, 11)}
};      
#pragma diag_warning 1296

void setPinStatusOfPwmDrivenInBitbandMode(uint8_t pin, GPIO_PinState newStatus) {   
    *(pwm[pin]) = newStatus;
}

And now the difficult but most important part. I've told that I've another timer. I use this timer for setting the pin status of the bitband pwm. I've not set the timer for make an interrut always at the same time, but I set the timer for make an interrupt on next bitband pwm pin state change.

For make this, I have used some sorting function thath make an array of timing and status of the pin.

In this way, the interrupt are made only when necessary and the core have time for doing something else.

For make this work, I've make a function that:

  • Create an array with status and time of pin change,
  • Order this array in crescent order
  • Remove duplicates
  • Set the initial pin state
  • Set the timer for firing an array at first status change

All the code is complex and long, this is why I've in this answer only the functional concept.

Hope this helps.

\$\endgroup\$
-1
\$\begingroup\$

STM32 series don't have fixed PWM pins, they can be configured as GPIO or PWM functions, or other alternative function pins. PWM function is provided with advanced timers. You can use their configuration tool to figure out how many PWM pins can be implemented at most.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.