[STM32定时器学习-002]PWM和OC输出

/ 0评 / 3

注意一个很大很大的陷阱,深陷不能自拔.高级定时器和其他定时器的PWM输出(OC输出)有一点点不一样,高级定时器的输出还有个MOE位控制,而其他没有.具体如下,其他疑问还可以查看STM32F072-NUCLEO-EXAMPLE_LL文件夹的内容:

		LL_TIM_EnableAllOutputs(TIM1);

之前一直不输出,切换到TIM2又输出了,然后不断对比,发现这个差异.具体寄存器位置是这样的.

坑惨了,当然,我们现在要说的是PWM模式,对于TIM1的PWM来说,太强大了.
查阅资料,知道TIM1的PWM输出引脚在PA8,PA9,PA10和PA11,而复用功能是AF2.

所以配置代码是这样的.

    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_8, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_8, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_8, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_8, LL_GPIO_AF_2);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_9, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_9, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_9, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_9, LL_GPIO_AF_2);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_10, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_10, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_10, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_10, LL_GPIO_AF_2);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_11, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_11, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_11, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_11, LL_GPIO_AF_2);

然后跟上一季做TimeBase一样,设置一个ARR自动重载,比如我现在设置成2kHz中断频率.并且其实这个TimeBase就是总长.比如下图中使用CountUp计数,然后ARR=500,到达500时候就归0,而OC可以是0~500以内的任意数值.并不规定OC1>OC2或者OC1<OC2,他们之间没什么关联.甚至设置OC1=OC2也可以.

比如我设置OC数值.

    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH1(TIM1, (LL_TIM_GetAutoReload(TIM1) / 2));
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH2(TIM1, (LL_TIM_GetAutoReload(TIM1) / 4));
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH3(TIM1, (LL_TIM_GetAutoReload(TIM1) / 8));
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH4, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH4(TIM1, (LL_TIM_GetAutoReload(TIM1) / 16));

我在TimeBase里面翻转了IO,用来让我看到TimeBase在跳变,但是PWM输出还要设置OE.

    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1);
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH2);
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH3);
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH4);
    LL_TIM_EnableAllOutputs(TIM1);
    LL_TIM_EnableCounter(TIM1);

设置OE后,OC的引脚也可以输出了,组合起来上面代码就实现了这样的波形.

可以看出,比较匹配时候置位为0,直到下一次归零.在最左边测量点,大家都对齐从0开始,整个TimeBase变化一次需要0.5ms (PS:不要纠结测量问题,这个山寨货很难说的.) ,而代码中,CH1的比较值是1/2个ARR,也就是0.5ms除以2,也就是0.25ms,符合第一通道.第二通道是再除以2,也就是0.125ms.这个PWM1模式决定了起始电平是H.要发生起始电平H的PWM,就是LL_TIM_OCMODE_PWM1.
现在改成LL_TIM_OCMODE_PWM2,那么电平就翻转.PWM1通过改变其他数值,也做不到PWM2效果.

当然,除了PWM模式外,还有其他几个OC模式.

#define LL_TIM_OCMODE_FROZEN                   ((uint32_t)0x00000000U)                                  /*!<The comparison between the output compare register TIMx_CCRy and the counter TIMx_CNT has no effect on the output channel level */
#define LL_TIM_OCMODE_ACTIVE                   TIM_CCMR1_OC1M_0                                         /*!<OCyREF is forced high on compare match*/
#define LL_TIM_OCMODE_INACTIVE                 TIM_CCMR1_OC1M_1                                         /*!<OCyREF is forced low on compare match*/
#define LL_TIM_OCMODE_TOGGLE                   (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0)                    /*!<OCyREF toggles on compare match*/
#define LL_TIM_OCMODE_FORCED_INACTIVE          (TIM_CCMR1_OC1M_2)                                       /*!<OCyREF is forced low*/
#define LL_TIM_OCMODE_FORCED_ACTIVE            (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_0)                    /*!<OCyREF is forced high*/

从字面意思也可以理解:

  1. LL_TIM_OCMODE_FROZEN OC功能无效,不会改变引脚电平.
  2. LL_TIM_OCMODE_ACTIVE 当比较匹配时候,引脚置高电平,因为IO是AF功能,自己不能改变的,除非后面再改其他模式.
  3. LL_TIM_OCMODE_INACTIVE 同上,这个是强行低电平.
  4. LL_TIM_OCMODE_TOGGLE 当比较匹配时候翻转电平.实际情况过程是从0开始,到OC比较,翻转电平,然后到ARR溢出,归零,再到OC比较,再翻转电平,如果OC的数值不改变,翻转频率等于ARR中断频率.
  5. LL_TIM_OCMODE_FORCED_ACTIVE 还没比较就强制高电平.
  6. LL_TIM_OCMODE_FORCED_INACTIVE 还没比较就强制低电平.

主要看看TOGGLE的波形.最下面依然是TimeBase.

之前都是在讨论OC的数值不改变的情况,如果是改变的情况呢?我们开影子寄存器,也就是这串代码.

    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1);
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH2);
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH3);
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH4);

这样只有等Update事件,也就是ARR重载时候,OC的数值才会改变.

for(;;)
{
    vTaskDelay(50);
    LL_TIM_OC_SetCompareCH1(TIM1, (LL_TIM_GetAutoReload(TIM1) / 2));
    LL_TIM_OC_SetCompareCH2(TIM1, (LL_TIM_GetAutoReload(TIM1) / 4));
    LL_TIM_OC_SetCompareCH3(TIM1, (LL_TIM_GetAutoReload(TIM1) / 8));
    LL_TIM_OC_SetCompareCH4(TIM1, (LL_TIM_GetAutoReload(TIM1) / 16));
    vTaskDelay(50);
    LL_TIM_OC_SetCompareCH1(TIM1, (LL_TIM_GetAutoReload(TIM1) / 16));
    LL_TIM_OC_SetCompareCH2(TIM1, (LL_TIM_GetAutoReload(TIM1) / 8));
    LL_TIM_OC_SetCompareCH3(TIM1, (LL_TIM_GetAutoReload(TIM1) / 4));
    LL_TIM_OC_SetCompareCH4(TIM1, (LL_TIM_GetAutoReload(TIM1) / 2));
}

我们延迟一段时间,然后改变OC,再延迟.记得要处于PWM模式.然后看图.

比如改变舵机的角度,改变LED亮度等等.这样就更明显了.

按照实践来驱动舵机,舵机一般是20ms的周期.先要发生50Hz的基础波形.

    /* 1 MHz */
    LL_TIM_SetPrescaler(TIM1, __LL_TIM_CALC_PSC(SystemCoreClock, 1000000));
    /* 50 Hz */
    InitialAutoreload = __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(TIM1), 50);

然后0.5ms - 2.5ms代表0-180度线性变化.因为我是1MHz分下来的,所以时间精度是1us.所以计算公式是这样的.

	LL_TIM_OC_SetCompareCH1(TIM1, 500+(2*1000*deg/180));

500就是其实0.5ms,然后就加上角度.比如现在要45度其实就是:

	LL_TIM_OC_SetCompareCH1(TIM1, 500+(2*1000*45/180));

然后得出1ms高,50Hz周期.

当然推荐写成一个函数.是不是很简单.然而其实舵机发一次指令就够了,我们还可以在开始定时器时候加OneShot.(默认是周期性),具体做法如下.

	LL_TIM_SetOnePulseMode(TIM1,LL_TIM_ONEPULSEMODE_SINGLE);

这次先废话到这里,下次继续.这次代码整理.

void StartDefaultTask(void const *argument)
{
    /* PA8 => PWM,PC6 => GPIO,UPDATE_EVENT */
    static uint32_t InitialAutoreload = 0;
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOC);
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
    LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM1);
    LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_6, LL_GPIO_MODE_OUTPUT);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_8, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_8, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_8, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_8, LL_GPIO_AF_2);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_9, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_9, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_9, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_9, LL_GPIO_AF_2);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_10, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_10, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_10, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_10, LL_GPIO_AF_2);
    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_11, LL_GPIO_MODE_ALTERNATE);
    LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_11, LL_GPIO_PULL_DOWN);
    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_11, LL_GPIO_SPEED_FREQ_VERY_HIGH);
    LL_GPIO_SetAFPin_8_15(GPIOA, LL_GPIO_PIN_11, LL_GPIO_AF_2);
    /* Count Up */
    LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP);
    /* 1 MHz */
    LL_TIM_SetPrescaler(TIM1, __LL_TIM_CALC_PSC(SystemCoreClock, 1000000));
    /* 50 Hz */
    InitialAutoreload = __LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(TIM1), 50);
    LL_TIM_SetAutoReload(TIM1, InitialAutoreload);
    LL_TIM_EnableARRPreload(TIM1);
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH1(TIM1, 500 + (2 * 1000 * 45 / 180));
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH2(TIM1, (LL_TIM_GetAutoReload(TIM1) / 4));
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH3(TIM1, (LL_TIM_GetAutoReload(TIM1) / 8));
    LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH4, LL_TIM_OCMODE_PWM1);
    LL_TIM_OC_SetCompareCH4(TIM1, (LL_TIM_GetAutoReload(TIM1) / 16));
    /* Enable TIM2_CCR1 register preload. Read/Write operations access the      */
    /* preload register. TIM2_CCR1 preload value is loaded in the active        */
    /* at each update event.                                                    */
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1);
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH2);
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH3);
    LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH4);
    /**************************/
    /* TIM2 interrupts set-up */
    /**************************/
    /* Enable the capture/compare interrupt for channel 1*/
    LL_TIM_EnableIT_CC1(TIM1);
    LL_TIM_EnableIT_CC2(TIM1);
    LL_TIM_EnableIT_CC3(TIM1);
    LL_TIM_EnableIT_CC4(TIM1);
    /**********************************/
    /* Start output signal generation */
    /**********************************/
    /* Enable output channel 1 */
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1);
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH2);
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH3);
    LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH4);
    LL_TIM_EnableAllOutputs(TIM1);
    LL_TIM_EnableCounter(TIM1);
    for(;;)
    {
        vTaskDelay(50);
    }
}

 

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注