Bug复现方法:
使用HAL_TIM_OC_Start_DMA开启定时器后如果DMA传输未完成时用HAL_TIM_OC_Stop_DMA来结束会导致下次开启定时器的初始发生电平变化。
CubeMX的配置

复现Bug的程序
主程序
__weak void TaskOutput(void const * argument)
{
/* USER CODE BEGIN TaskOutput */
int cnt = 0;
const int size = 20;
uint16_t BUF[size]; //DMA传输比较值到CCR1寄存器
/* 生成测试数据 */
BUF[0] = 100;
for (size_t i = 1; i < size; i++)
{
BUF[i] = BUF[i-1] + 100;
}
/* Infinite loop */
for(;;)
{
TIM2->CNT = 0;
TIM2->CCR1 = BUF[0];
PinDEBUG1(1); //翻转IO,Debug用
HAL_TIM_OC_Start_DMA(&htim2,
TIM_CHANNEL_1,
(uint32_t*)&BUF[1],
19);
osDelay(1);
cnt ++;
if (cnt > 5 && cnt < 15)
{
HAL_TIM_OC_Stop_DMA(&htim2, TIM_CHANNEL_1);
}
osDelay(100);
}
/* USER CODE END TaskOutput */
}
在中断回调里面**
一次传输会进入三次中断,DMA传输中断,开启输出比较的中断和最后一次比较发生的中断,
**下面分别对三次中断做处理。
int endflag = 0; //用来跳过因使能中断而产生的中断
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
PinDEBUG2(1);
if (endflag == 1)
{
endflag = 0;
PinDEBUG2(0);
return;
}
if (__HAL_TIM_GET_ITSTATUS(&htim2, TIM_IT_CC1) == RESET)
{
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC1);
endflag = 1;
PinDEBUG2(0);
return;
}
__HAL_TIM_DISABLE_IT(&htim2, TIM_IT_CC1);
HAL_TIM_OC_Stop_DMA(&htim2, TIM_CHANNEL_1);
PinDEBUG2(0);
PinDEBUG1(0);
}
测试
打开逻辑分析仪,复位单片机可以看到以下波形**

异常后每次是11个脉冲,图片打错了
先看正常波形
再看看异常波形
**
附件
Demo下载
程序位于 Core/Src/freertos.c Line182、Line271
测试引脚
TIM2_CH0 --- PA0
debug1Pin --- PF0
debug2Pin --- PF1 |
这种方式是保证定时器工作起来后的比较条件正确。如果已经正确,那么就要考虑其他方面了。
在配置定时器寄存器前一步,这时的输出电平是否正确,如不正确考虑在关断前设置成空闲状态的电平。
[md]每一次OC DMA Stop的时候,此时的tim2->CNT跟ccr1比是大还是小,也就是toggle了多少次,如果是奇数,那么此时电平就跟初始电平不同,在DMA stop时读出cnt比较ccr1看看。
它的功能就是关闭CC事件的DMA请求,并关闭相应DMA传输通道。
你现在是通过DMA适时地修改CCR值来输出自己预期的PWM。
如果你中途关闭DMA传输和CC事件的DMA请求,之后CCR值
是不会改变了。看不到你停止DMA后定时器是怎么处理的。
你说下次启动时输出电平跟预期不一致,这要看你再次启动
定时器时的PWM模式、计数器的值和CCR值,这三者最终决定
输出电平。
你调用的的功能就是关闭CC事件的DMA请求,并关闭相应DMA传输通道。
你现在是通过DMA适时地修改CCR值来输出自己预期的PWM。
如果你中途关闭DMA传输和CC事件的DMA请求,之后CCR值
是不会改变了。看不到你停止DMA后定时器是怎么处理的。
你说下次启动时输出电平跟预期不一致,这要看你再次启动
定时器时的PWM模式、计数器的值和CCR值,这三者最终决定
输出电平,应该说还有个极性控制位。
[md]你好,xmshao。
1.我使用HAL_TIM_OC_Stop_DMA来关闭未完成的输出比较和DMA传输之后,DMA会终止,捕获比较通道会关闭,定时器会停止计数,通过在线调试可以看到,这些都是这个函数完成的。我可以认为下次我能使用HAL_TIM_OC_Start_DMA来重新开启DMA输出比较吧?
2.启动时定时器的模式我没有改变它,仅在CubeMX中配置初始化,所以极性不应该有变化。定时器配置具体内容在上面的图片中有展示,72分频,65535重装值使用的是CH1的输出比较,极性是High,比较值触发翻转电平模式,所有代码都在上面有展示,你也可以下载Demo来测试。
[md]我重新修改了程序进行测试,发现如果Stop_DMA时为高电平那么,下次Start_DMA就是高电平,也就是说会保持在上次关闭前的状态,请问如何使得下次开启仍然为低电平,而不是保持在上一次的状态?毕竟我们无法得知上次关闭时准确的电平状态。或许HAL库需要为我们做这个事情。
楼上的大神已经提到过了,再次启动的状态由定时器的配置决定。
所以你需要什么状态,提前配置好定时器初始状态,再启动相应的工作模式。
在上面的代码中我有设置计数值CNT=0、比较值CRR1=BUF[0],极性和通道模式没有更改,然后再启动OC_DMA,结果和预期不一样,因此我才来此提问。
CCR1是否配置了预装载功能? 试试再配置后使能一下UG位(软件更新事件)把CCR1值真正加载进去。
你好,我根据你建议,在更新计数值和比较值之后在TIM->EGR寄存器中使能了UG位,但这没有起到任何作用。另外在初始化代码中没有使能预装载功能OC1PE,我通过在线调试查看CCMR1寄存器确认了这一点。
[md]你好,yr。
是的这是一个解决方法,开启定时器后发生第一次比较前的这段时间里的电平和上次关断前保持一致,所以可以考虑在关闭前判断当前电平是否有效,翻转有效就翻转到无效,这样下次开启就是无效电平。我尝试过读取DMA的剩余数量和直接读取GPIO_IDR寄存器电平,若为有效电平,则使比较值为计数值+1,这样确实可以奏效,但是我还在验证,昨天验证读取DMA剩余传输值来处理关断前的电平的方案发现长时间后还是出现了问题,我还在验证这个方案。
本问题已暂时解决,先使用while循环读取GPIO_IDR卡住有效电平,退出循环后立刻将CCR赋值为CNT-1,避免再次翻转电平,然后再使用HAL_TIM_OC_Stop_DMA。此法会消耗一个定时器计数一次的时间来等待空闲电平。
建议HAL库在函数注释中写下这个问题,查阅了很多资料都没有写明这一点。在DMA没有将数据全部传输到CCR前关闭通道会使下次开启的初始电平会保持上次关闭前的电平。
在此,谢谢回答了这个问题的所有人,也期待一个更好的解决方案。