在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);
}
}