stm32实现多个引脚的软PWM

在stm32中 如果硬件PWM不够的情况下,会利用定时中断来控制引脚PWM,通常有两种做法

1. 一个定时器控制一个引脚,这样一个周期只需要产生2个中断。优点是一个周期只需要产生2次中断,缺点是每个引脚都需要一个定时器。

2. 一个定时器产生最小的中断,根据中断内计数与每个引脚设定值做比较。有点是一个定时器可以控制多个引脚,缺点是要频繁进入中断。

本程序设计了一种集合上面两个优点,即一个定时器可以生成多个引脚的PWM,同时周期内总的中断次数为引脚数+1。

设计思想:

1. 所有引脚PWM周期相同,周期结束的中断,置位所有引脚。

2. 设计链表,按照引脚周期进行排序。写PWM值是调整一次链表。

3. 新的中断都为下个链表中的时间减去当前周期已经经过的时间。

4.最后一个链表结束后。产生周期结束中断。

伪代码如下,代码中封装了引脚操作和定时器操作,不能直接使用,但是不影响其思想。

/**
  ******************************************************************************
  * @file    SoftPWM.h
  * @brief   作为脉宽体调速的补充,本库使用的定时器中断产生PWM,会占用大量系统资源,不到
  * 		万不得已,不要使用本库。仅作为补充使用,同时定时频率不要太高。
  *
  *
  ******************************************************************************
  * @attention
  *	
  *	@ex:void UserApp_Task(void *pvParameters)
		{
			uint8_t pwm = 0;
			SoftPWM_init(TIM8, 50);
			SoftPWM_attach(PC0);
			SoftPWM_attach(PC1);
			SoftPWM_attach(PC2);
			while (1)
			{
				pwm += 10;
				SoftPWM_write(PC0, pwm);
				SoftPWM_write(PC1, pwm+20);
				SoftPWM_write(PC2, pwm+30);
				vTaskDelay(1000);
			}
		}
  ******************************************************************************
  */

#ifndef __SOFTPWM_H_
#define __SOFTPWM_H_

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "shGpio.h"

/* Exported types ------------------------------------------------------------*/
typedef struct SoftPWMHandle SoftPWM;

struct SoftPWMHandle {
    SoftPWM* NextPtr;
    SoftPWM* PrevPtr;
    uint16_t 	Value;
    uint16_t 	Period;
   	PinName pin;
};
/* Exported constants --------------------------------------------------------*/
#define SOFTPWM_NUM	(20)  //最多使用多少个GPIO,提前静态分配数组,不影响速度。
/* Exported macro ------------------------------------------------------------*/

/* Exported variables --------------------------------------------------------*/

/* Exported functions prototypes ---------------------------------------------*/
/**
 * @brief  注册一个定时为通用定时器,并设置频率
 * @param[in] TIM_TypeDef:使用哪个定时器
 * @param[in] period: 周期单位为10us; 推荐50Hz,周期为20ms,period = 2000。
 * @retval 0:成功 -1:失败
 */
int32_t SoftPWM_init(TIM_TypeDef *TIMx, uint32_t frequency);
//int32_t SoftPWM_init(TIM_TypeDef *TIM, uint32_t period);

/**
  * @brief  注册引脚为PWM引脚。
  * @param[in] pin: PWM引脚
  * @retval 0:成功 -1:失败
  */
int32_t SoftPWM_attach(PinName pin);

/**
  * @brief  输出PWM信号
  * @param  Pin: 引脚编号
  * @param  val:PWM占空比值 单位10us
  * 		ex: val = 100,则高电平为1000us。
  * @retval PWM占空比值
  */
int32_t SoftPWM_write(PinName pin,uint16_t val);

#ifdef __cplusplus
}
#endif

#endif  /* PWMTIMER_SoftPWM_H_ */


/* Private includes ----------------------------------------------------------*/
#include "SoftPWM.h"
#include "elog.h"
#include "shTimer.h"
/* Private types -------------------------------------------------------------*/

/* Private macro -------------------------------------------------------------*/

/* Private constants ---------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
static SoftPWM softPWM[SOFTPWM_NUM] = {0}; 	/* 静态分配需要使用的结构体数组 */
static SoftPWM *PWMFisrt = NULL;			/* 链表首地址 */
static uint8_t 	UsedPWM_num = 0;			/* 已经使用了多少个 */
TIM_TypeDef 	*USETIM;					/* 用户使用的哪个定时器作为基本定时器 */
uint32_t	 	TIM_ARR;					/* 整个定时器周期的重载值 */

/* Public variables ----------------------------------------------------------*/

/* Private function prototypes -----------------------------------------------*/
void SoftPWM_Callback(void);

/* Public function prototypes ------------------------------------------------*/
int32_t SoftPWM_init(TIM_TypeDef *TIMx, uint32_t period);
int32_t SoftPWM_attach(PinName pin);
int32_t SoftPWM_write(PinName pin, uint16_t val);
/* Function ------------------------------------------------------------------*/

/**
 * @brief  注册一个定时为通用定时器,并设置频率
 * @param[in] TIM_TypeDef:使用哪个定时器
 * @param[in] frequency: 频率单位HZ; 推荐50Hz,周期为20ms;
 * @retval 0:成功 -1:失败
 */
int32_t SoftPWM_init(TIM_TypeDef *TIMx, uint32_t frequency) {

	uint32_t psc;
	uint32_t f;

	if(!IS_TIM_INSTANCE(TIMx))
		return -1;

	USETIM = TIMx;
	TIM_ARR = 100000/frequency; /* 定时器周期重装载值 = 100000us/frequency 单位10us */
    if(!IS_APB2_TIM(USETIM)){
    	f = F_CPU/2;
    }else{
    	f = F_CPU;
    }
    psc = (f / 100000);	/* 10us 的分频值 */

    Timer_SetInterruptBase(USETIM, 0, psc, SoftPWM_Callback, 6, 0);
    return 0;
}
/**
 * @brief  注册引脚为PWM引脚。
 * @note   新注册的值为0,注册到第一个。
 * @param[in] pin: PWM引脚
 * @retval 0:成功 -1:失败
 */
int32_t SoftPWM_attach(PinName pin) {

	SoftPWM *pwm;
	if (pin >= PIN_NUM) {
		log_e("pwm pin error");
		return -1;
	}
	if(UsedPWM_num >= SOFTPWM_NUM){
		log_e(" softpwm num error");
		return -1;
	}
	pinMode(pin, OUTPUT);
	pwm = &softPWM[UsedPWM_num++];
	pwm->pin = pin;
	pwm->Value = 0;
	if (PWMFisrt == NULL)	//第一个
	{
		PWMFisrt = &softPWM[0];
		PWMFisrt->NextPtr = NULL;
		PWMFisrt->PrevPtr = NULL;
	} else {
		/* 插入到最前面 */
		PWMFisrt->PrevPtr = pwm;
		pwm->NextPtr = PWMFisrt;
		PWMFisrt = pwm;
		pwm->PrevPtr = NULL;
	}
	return 0;
}

/**
 * @brief  输出PWM信号
 * @note   插入链表中,链表中的数据为从小到大排列
 * @param  Pin: 引脚编号
 * @param  val:PWM占空比值 单位10us
 * 		ex: val = 100,则高电平为1000us。
 * @retval 0:成功,-1:失败
 */
int32_t SoftPWM_write(PinName pin, uint16_t val){

	SoftPWM *pwm,*nextPWM;
	uint8_t i;

	if (PWMFisrt == NULL){
		return -1;
	}
	/* 查找对应 pin 的指针 */
	for (i = 0; i < UsedPWM_num; i++) {
		if(softPWM[i].pin == pin){
			pwm = &softPWM[i];
			break;
		}
	}
	if(i == UsedPWM_num ){	/* 未找到对应pin*/
		log_e(" find softpwm pin err");
		return -1;
	}
	/* 相等直接退出*/
	if(pwm->Value == val){
		return 0;
	}else{
		if(val > TIM_ARR) {
			pwm->Value = TIM_ARR;
		}else{
			pwm->Value = val;
		}
	}

	/* 删除本链表 */
	if(pwm->NextPtr != NULL){
		pwm->NextPtr->PrevPtr = pwm->PrevPtr;
	}
	if(pwm->PrevPtr != NULL){
		pwm->PrevPtr->NextPtr = pwm->NextPtr;
	}else{
		/* 前指针为空说明是第一个 */
		PWMFisrt = pwm->NextPtr;
	}

	/* 值最小,插入到第一个 */
	if(pwm->Value < PWMFisrt->Value){
		PWMFisrt->PrevPtr 	= pwm;
		pwm->NextPtr 		= PWMFisrt;
		PWMFisrt 			= pwm;
		PWMFisrt->PrevPtr 	= NULL;
	}else{
		nextPWM = PWMFisrt;
		for (;;) {
			/* 已经最后一个*/
			if(nextPWM->NextPtr == NULL){
				nextPWM->NextPtr = pwm;
				pwm->PrevPtr 	 = nextPWM;
				pwm->NextPtr 	 = NULL;
				break;
			}else if (pwm->Value < nextPWM->Value ) {
				nextPWM->PrevPtr->NextPtr = pwm;
				pwm->PrevPtr 			  = nextPWM->PrevPtr;
				pwm->NextPtr 			  = nextPWM;
				nextPWM->PrevPtr		  = pwm;
				break;
			}
			nextPWM = nextPWM->NextPtr;
		}
	}
	/* 如果不为0,且之前定时器未打开,则打开定时器*/
	if(pwm->Value != 0 && ( LL_TIM_IsEnabledCounter(USETIM) == 0)){
		LL_TIM_SetCounter(USETIM, 0);
		LL_TIM_SetAutoReload(USETIM, pwm->Value);
		LL_TIM_EnableCounter(USETIM);
	}
	return 0;
}
/**
 * @brief  定时器回调函数
 * @note   1.所有值已经从小到大拍好序。
 * 		   2.先查找与累计cnt一样的值,如果cnt大于等于设定周期,则所有清零,重新开始。
 * 		   3.顺序查找与cnt相等的引脚输出低电平,同时查找下一个设定值,如果下一个为空,则下次定时器为周期末触发。
 * @retval
 */
static void SoftPWM_Callback(void){
	static uint32_t cnt = 0;
	uint8_t i;
	SoftPWM *nextPWM = PWMFisrt;
	cnt += USETIM->ARR;
	uint32_t arr = 0;	/* 下个周期定时器值 */
	/* 到达一个周期 所有引脚清零*/
	if(cnt >= TIM_ARR){
		cnt = 0;
		for (i = 0; i < UsedPWM_num; i++) {
			digitalWrite_HIGH(softPWM[i].pin);
		}
		/* 寻找小的一个值,如果所有值都为0,则关闭定时器*/
		for(;;){
			if(nextPWM->Value != 0){
				arr = nextPWM->Value;
				break;
			}
			nextPWM = nextPWM->NextPtr;
			if(nextPWM == NULL){
				break;
			}
		}
	}else{
		/* 查找符合的哪个引脚的定时器*/
		for(;;){
			if(nextPWM->Value == cnt){
				digitalWrite_LOW(nextPWM->pin);
				if(nextPWM->NextPtr == NULL){
					arr = TIM_ARR - cnt;
					break;
				}else if (nextPWM->NextPtr->Value > cnt){
					arr = nextPWM->NextPtr->Value - cnt;
					break;
				}
			}
			if(nextPWM->NextPtr != NULL){
				nextPWM = nextPWM->NextPtr;
			}else{
				arr = TIM_ARR - cnt;
				break;
			}
		}
	}
	if(arr != 0){
		LL_TIM_SetAutoReload(USETIM, arr);
		LL_TIM_SetCounter(USETIM, 0);
		LL_TIM_EnableCounter(USETIM);
	}else{
		LL_TIM_DisableCounter(USETIM);
	}
}

你可能感兴趣的:(stm32,嵌入式硬件,单片机)