注意一个很大很大的陷阱,深陷不能自拔.高级定时器和其他定时器的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*/
从字面意思也可以理解:
- LL_TIM_OCMODE_FROZEN OC功能无效,不会改变引脚电平.
- LL_TIM_OCMODE_ACTIVE 当比较匹配时候,引脚置高电平,因为IO是AF功能,自己不能改变的,除非后面再改其他模式.
- LL_TIM_OCMODE_INACTIVE 同上,这个是强行低电平.
- LL_TIM_OCMODE_TOGGLE 当比较匹配时候翻转电平.实际情况过程是从0开始,到OC比较,翻转电平,然后到ARR溢出,归零,再到OC比较,再翻转电平,如果OC的数值不改变,翻转频率等于ARR中断频率.
- LL_TIM_OCMODE_FORCED_ACTIVE 还没比较就强制高电平.
- 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); } }