SoftPwmSTM32 [Libray]

What are you developing?
semakers
Posts: 1
Joined: Fri Jan 15, 2021 1:31 am

SoftPwmSTM32 [Libray]

Post by semakers »

SoftPwmSTM32

With this library it is possible to have up to 20 pwm outputs on the digital pins of the development boards and microcontrollers compatible with STM32 for Arduino.

To work with the library, it must be included in the following way:

Code: Select all

#include "softPwmStm32.h"
Inside the setup block we initialize our library and configure the pins that we want to use with pwm using the softPwmSTM32Attach function passing as arguments the pin we want and the percentage of pwm we want in said pin as follows:

Code: Select all

void setup() {
  softPwmSTM32Init();
  softPwmSTM32Attach(LED_BUILTIN,0);
}

To update the pwm of our pin we use the softPwmSTM32Set function, as shown in the following example:

Code: Select all

void loop() {
  for(uint8_t i=0;i<100;i++){
    softPwmSTM32Set(LED_BUILTIN,i);
    delay(2);
  }
  for(uint8_t i=100;i>0;i--){
    softPwmSTM32Set(LED_BUILTIN,i);
    delay(2);
  }
}
To deactivate the softPwm on one of our pins we use the softPwmSTM32Dettach function:

Code: Select all

softPwmSTM32Dettach(LED_BUILTIN);
You can find the library in this Github repository.
Lou79
Posts: 3
Joined: Mon Sep 04, 2023 4:10 pm

Re: SoftPwmSTM32 [Libray]

Post by Lou79 »

I hope this reply reaches you. I understand it's been about two and a half years now since your announcement.

Can you please provide a multi channel example?

On Blue Pill this code makes PA4 work as expected, but PA5 remains low:

Code: Select all

#include "softPwmStm32.h"

void setup() {
  pinMode(PA4, OUTPUT);
  pinMode(PA5, OUTPUT);
  softPwmSTM32Init();
  softPwmSTM32Attach(PA4, 0);
  softPwmSTM32Attach(PA5, 0);
}

void loop() {
  softPwmSTM32Set(PA4, 50);
  softPwmSTM32Set(PA5, 50);
}
Swapping PA4 and PA5 in the attach functions makes PA5 work as expected but then PA4 remains low. Whichever pin is attached first in the setup works, but the one attached after it remains low. I also tried setting SOFTPWM_MAXCHANNELS to 2 with the same negative result.

Many thanks in advance!
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

your code is constantly setting the duty cycle.

try moving the two statements into the set-up sectin and giving them a different duty cycle.

btw, such software pwms are a huge cpu cycle hog. they require very high cpu speed to work.
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

tw, such software pwms are a huge cpu cycle hog. they require very high cpu speed to work.
i looked up my own software pwm code.
1. it takes 200 - 500 ticks to execute one step over 4 channels, in a fairly ISR heavy environment.
2. it has a max of 256 steps for one pwm period.
3. So to achieve 40Hz pwm frequency (bare minimum in my view for non-flickering), you need 200 x 256 x 40 = 2 - 5million ticks per second.

for a stm32f103 running at 72Mhz, that's about 3 - 7% of cpu cycle.

and my code is much more efficient than his. so unless you have absolutely no other way around this, avoid software pwm.
Lou79
Posts: 3
Joined: Mon Sep 04, 2023 4:10 pm

Re: SoftPwmSTM32 [Libray]

Post by Lou79 »

dannyf, please share a link to your code.

I need to output 15 control voltages to an analog board as PWM signals and the duty cycles have to be updated in the loop. I have TIM2, TIM3 and TIM4 unavailable for this, they are fully occupied with outputting even more CVs at much higher PWM frequencies (20-100 kHz), maxing out their hardware PWM channels.

I've also been testing this other library, it uses only TIM1 for up to 16 extra channels of PWM at 1000 Hz:

https://github.com/khoih-prog/STM32_Slow_PWM
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

i don't have the code published anywhere so no link.

but i will post the files here if you wish to use.

Background:
I am actually combining two implementations together here. they can be selected by the user by commenting in or out of "sPWM_APPROACH1".

The first approach is my favorite: it uses a user-defined sPWM_TypeDef variable / struct, each supporting up to 4 channels, simulating a hardware OC/PWM module. You can declare as many as you can, based on your needs.

The second approach is to use an array, very much like the two implementations you linked to. This approach has the simplest user interface but the biggest footprint.
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

now the files.

first the sPWM.h file:

Code: Select all

#ifndef _sPWM_H
#define _sPWM_H

//software PWM - up to 8bits
// configuration:
// 1. sPWM_STEPPING. [1..255]: controls how fast to advance the internal tick
// 2. sPWM_ChMAX: controls the maximum number of software pwm channels.
//
//two implementation approaches:
//1. uses individual sPWM struct, each supporting up to 4 channels.
//   it has the smallest memory footprint. but requires more user intervention.
//2. uses an array to support multiple channels in one sPWM struct.
//   it has the most memory footprint, but is the simplest to use
//
//users can select which one to use by commenting in / out of sPWm_APPROACH1
//
//

#include "py32duino.h"								//use py32 arduino port

//sPWM configure
//comment out if you want to configure individual challens
//#define sPWM_APPROACH1								//using approach 1->4-chs per sPWM struct.

#define sPWM_STEPPING	1							//1->8bit pwm, 2->7bit PWM, 4->6bit PWM, ... doesn't need to be power of 2
#define sPWM_ChMAX		4							//max number of channels - used for the second approach only
//end sPWM configure

//software PWM
#if defined(sPWM_APPROACH1)

//usage example:
//
//define two spwm modules, each with 4 channels
//
//sPWM_TypeDef sPWM0, sPWM1;	//define two software pwm modules
//
//define an isr to be call periodicially
//void sPWMUpdateAll(void) {
//	sPWMUpdate(&sPWM0);			//update pwm0 module
//	sPWMUpdate(&sPWM1);			//update pwm1 module
//}
//
//in setup():
//	sPWMInit(&sPWM0);			//initialize spwm0
//	sPWMInit(&sPWM1);			//initialize spwm1
//	//set up pwm parameters
//	sPWMPin0(sPWM0, LED0); sPWMSetDC0(sPWM0, 1);	//set output LED0 + duty cycle for software PWm0 channel 0
//	sPWMPin2(sPWM1, LED1); sPWMSetDC2(sPWM1, 10);	//set output LED1 + duty cycle for software PWm1 channel 2
//	//running the software pwm at 50hz
//	tmr16Init(1); tmr16OC1SetPR(F_CPU / 256 / 50); tmr16OC1AttachISR(sPWMUpdateAll);	//install user handler
//
//in loop():
//	//you can change duty cycles in the loop
//	sPWMSetDC0(sPWM0, newDC);	//change duty cycle on sPWM0, channel 0
//
//end example

//using individual struct
//software pwm - max 8-bit
//each pwm has four output channels
typedef struct {
	uint8_t CNT;									//count
	PIN_TypeDef pin0, pin1, pin2, pin3;				//output pins
	uint8_t DC0, DC1, DC2, DC3;						//duty cycle
} sPWM_TypeDef;

void sPWMInit(sPWM_TypeDef *sPWMx);					//initialize software pwm
void sPWMUpdate(sPWM_TypeDef *sPWMx);				//update sPWM - to be called periodically. ~200 ticks / 4 ch
#define sPWMPin0(sPWMx, pin)	sPWMx.pin0 = (pin)	//set channel's output pin
#define sPWMPin1(sPWMx, pin)	sPWMx.pin1 = (pin)	//set channel's output pin
#define sPWMPin2(sPWMx, pin)	sPWMx.pin2 = (pin)	//set channel's output pin
#define sPWMPin3(sPWMx, pin)	sPWMx.pin3 = (pin)	//set channel's output pin
#define sPWMSetDC0(sPWMx, dc)	sPWMx.DC0 = (dc)	//set channels' duty cycle
#define sPWMSetDC1(sPWMx, dc)	sPWMx.DC1 = (dc)	//set channels' duty cycle
#define sPWMSetDC2(sPWMx, dc)	sPWMx.DC2 = (dc)	//set channels' duty cycle
#define sPWMSetDC3(sPWMx, dc)	sPWMx.DC3 = (dc)	//set channels' duty cycle
#define sPWMGetDC0(sPWMx)		(sPWMx.DC0)			//get channels' duty cycle
#define sPWMGetDC1(sPWMx)		(sPWMx.DC1)			//get channels' duty cycle
#define sPWMGetDC2(sPWMx)		(sPWMx.DC2)			//get channels' duty cycle
#define sPWMGetDC3(sPWMx)		(sPWMx.DC3)			//get channels' duty cycle
//end software pwm
#else
//usage example:
//
//define two spwm modules, each with 4 channels
//
////configure max number of channels
//#define sPWM_ChMAX		8	//8 max number of channels
//
//in setup():
//	sPWMInit();					//initialize spwm, all 8 channels
//	//set up pwm parameters
//	sPWMPin(0, LED0); sPWMSetDC(0, 1);		//set output LED0 + duty cycle for software PWm channel 0
//	sPWMPin(2, LED1); sPWMSetDC(2, 10);		//set output LED1 + duty cycle for software PWm channel 2
//	//running the software pwm at 50hz
//	tmr16Init(1); tmr16OC1SetPR(F_CPU / 256 / 50); tmr16OC1AttachISR(sPWMUpdate);	//install user handler
//
//in loop():
//	//you can change duty cycles in the loop
//	sPWMSetDC(0, newDC);			//change duty cycle on sPWM0, channel 0
//
//end example

//using individual channels
typedef struct {
	uint8_t CNT;					//cycle count
	PIN_TypeDef pin[sPWM_ChMAX];	//pin
	uint8_t DC[sPWM_ChMAX];			//duty cycle
} sPWM_TypeDef;						//pwm defs
extern sPWM_TypeDef sPWM;			//sPWM module

void sPWMInit(void);				//reset the pwm
void sPWMUpdate(void);				//update pwm - to be called periodically. ~400ticks/4 ch
#define sPWMPin(ch, pinx)			sPWM.pin[ch] = (pinx)
#define sPWMSetDC(ch, dc)			sPWM.DC[ch] = (dc)
#define sPWMGetDC(ch)				(sPWM.DC[ch])
#endif	//sPWM_APPROACH1
#endif	//_sPWM_h
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

and then the sPWM.c file:

Code: Select all

#if defined(sPWM_APPROACH1)
//approach 1: using individual sPWM_TypeDef -> 4 channels per struct

//global defines

//global variables

//initialize software pwm
void sPWMInit(sPWM_TypeDef *sPWMx) {
	sPWMx->CNT=0;
	sPWMx->pin0=sPWMx->pin1=sPWMx->pin2=sPWMx->pin3=PMAX;
	sPWMx->DC0=sPWMx->DC1=sPWMx->DC2=sPWMx->DC3=0;
}

//update sPWM - to be called periodically
void sPWMUpdate(sPWM_TypeDef *sPWMx) {
	uint8_t tick=sPWMx->CNT;
	//CHANGE pin state only if this is a valid pin (!= PMAX)
	if (sPWMx->pin0!=PMAX) {if (tick < sPWMx->DC0) digitalWrite(sPWMx->pin0, HIGH); else digitalWrite(sPWMx->pin0, LOW);}
	if (sPWMx->pin1!=PMAX) {if (tick < sPWMx->DC1) digitalWrite(sPWMx->pin1, HIGH); else digitalWrite(sPWMx->pin1, LOW);}
	if (sPWMx->pin2!=PMAX) {if (tick < sPWMx->DC2) digitalWrite(sPWMx->pin2, HIGH); else digitalWrite(sPWMx->pin2, LOW);}
	if (sPWMx->pin3!=PMAX) {if (tick < sPWMx->DC3) digitalWrite(sPWMx->pin3, HIGH); else digitalWrite(sPWMx->pin3, LOW);}
	sPWMx->CNT += sPWM_STEPPING;							//advance tick
}

#else
//global defines

//global variables
sPWM_TypeDef sPWM;					//the only struct we need

//using individual channels
//initialize the pwm
void sPWMInit(void) {
	uint8_t ch;
	
	sPWM.CNT = 0;					//reset the counter
	for (ch=0; ch<sPWM_ChMAX; ch++) {sPWM.pin[ch]=PMAX; sPWM.DC[ch]=0;}
}

//update the pwm - to be called periodically
void sPWMUpdate(void) {
	uint8_t ch;
	uint8_t tick=sPWM.CNT;			//tick
	for (ch=0; ch<sPWM_ChMAX; ch++)
		if (sPWM.pin[ch]!= PMAX) {if (tick < sPWM.DC[ch]) digitalWrite(sPWM.pin[ch], HIGH); else digitalWrite(sPWM.pin[ch], LOW);}
	sPWM.CNT += sPWM_STEPPING;
}
	
#endif	//1
//end software pwm
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

user configuration:
1. the files should be included as part of your project.
2. they support software pwm, up to 8 bit resolution.
3. user can configure i) resolution of the pwm: by choosing sPWM_STEPPING; and ii) the number of channels, by choosing sPWM_ChMAX.

Porting:
1. as provided, they are implemented on my arduino clone on PY32. You will replace "py32duino.h" with "arduino.h"
2. my implementation uses PIN_TypeDef to define my pins. the corresponding datatype in ardino is "uint8_t"
3. I use an enum "PMAX" to indicate invalid pins. you just need something sufficiently large. like "-1".

Cost:
1. I did a quick benchmarking: using approach 2, driving 16 channels, each update takes about 2200 ticks.
2. that means that for 50Hz pwm, we will need 2200*256*50 = 28MIPS. at 72Mhz, that's about 50% of the cpu cycle.
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: SoftPwmSTM32 [Libray]

Post by dannyf »

application:

the .h file has some examples but to highlight it:

in setup():

Code: Select all

	sPWMInit();					//initialize spwm, all 8 channels
	//set up pwm parameters
	sPWMPin(0, LED0); sPWMSetDC(0, 1);		//set output LED0 + duty cycle for software PWm channel 0
	sPWMPin(2, LED1); sPWMSetDC(2, 10);		//set output LED1 + duty cycle for software PWm channel 2
	//running the software pwm at 50hz
	tmr16Init(1); tmr16OC1SetPR(F_CPU / 256 / 50); tmr16OC1AttachISR(sPWMUpdate);	//install user handler
you essentially initilalize the pwm, and then set up two channels, ch 0 and ch2 in this case, driving LED0 @ 1/256 duty cycle and LED1 @ 10/256 duty cycle, respectively.

you will then invoke sPWMUpdate() periodically. In this case, we are using TIM16 Output Compare ch 1 interrupt.

After that, you may change your duty cycle in the loop():

Code: Select all

	if (t++>500) {t=0; sPWMSetDC(0, sPWMGetDC(0) + 1);}	//increment sPWM ch0 duty cycle
so we simply drive up ch0's duty cycle from 0 to 255 and then back to 0 against.
Post Reply

Return to “Projects”