本帖最后由 hurry 于 2023-5-16 12:42 编辑
新人第一次发帖,如有不妥请批评指正。
我想使用定时器的输出比较和DMA做时间和数量可控的脉冲,基于HAL库,以下是我的思路:
CubeMX配置:
定时器
另外中断和DMA配置:
--------------------------------------------------------------------------------------------
代码思路
- 创建一个大小为N的DMA缓存BUF用来给DMA运到捕获/比较寄存器CCRx,由于定时器是到达比较值就翻转所以一次DMA的最大输出脉冲数为N/2;
- 清空TIM的计数值CNT,设置第一个比较值BUF[0]到CCRx寄存器(当定时器CNT等于BUF[0],发生第一次DMA传输);
- 使用 HAL_TIM_OC_Start_DMA(htimx, channel, BUF[1], N-1) 来启动DMA并开启定时器;
- DMA传输完成后进入DMA传输完成中断(此时最后一个比较值还没触发,DMA只是搬运到CCRx,计数值CNT还没等于CCRx),通过开启定时器的捕获/比较通道的中断TIM_IT_CCx使得最后一次比较完成时也就是CNT=CCRx=BUF[N]时再次中断,在这个中断里关闭捕获/比较通道的中断再使用HAL_TIM_OC_Stop_DMA(htimx, channel)关闭DMA传输,如果脉冲数超过N/2可以继续重复步骤(3)开启DMA传输数据;
代码实现
这个函数用来开始一次DMA传输和定时器
[C] 纯文本查看 复制代码 StepperDriverState_t StepperDriverMoveSteps(stepperDriver_t *hsd, long steps)
{
StepperAssert(hsd);
if (StepperDriverGetEnableStatus(hsd) == 0) return STEPPER_DRIVER_ERROR;
if (hsd->curStep > 0) return STEPPER_DRIVER_BUSY;
/* 方向处理 */
if (steps > 0)
{
StepperDriverSetDir(hsd, 1);
hsd->curStep = steps;
hsd->curDir = 1;
}
else if (steps < 0)
{
StepperDriverSetDir(hsd, -1);
hsd->curStep = -steps;
hsd->curDir = -1;
}
else return STEPPER_DRIVER_ERROR;
/* 脉冲数计算 */
StepperLog("channel:%d, startStep:%d\n", hsd->stepCtrl.channel, steps);
if (hsd->curStep*2 > STEPPER_DATAMAP_SIZE) /* 超过DMA缓存的部分存起来,分段多次传输 */
{
hsd->planStep = hsd->curStep - STEPPER_DATAMAP_SIZE/2;
hsd->curStep = STEPPER_DATAMAP_SIZE/2;
}
/* 定时器设置 */
__HAL_TIM_SET_COUNTER(hsd->stepCtrl.htim, 0);
__HAL_TIM_SET_COMPARE(hsd->stepCtrl.htim, hsd->stepCtrl.channel, hsd->stepMap[0]);
PinDEBUG2(1);
HAL_TIM_OC_Start_DMA(hsd->stepCtrl.htim,
hsd->stepCtrl.channel,
(uint32_t*)&hsd->stepMap[1],
hsd->curStep*2-1);
LOG_DBG(LOGTAG, "Pulse start :Cnt%d, CMP:%d\n",hsd->stepCtrl.htim->Instance->CNT, hsd->stepCtrl.htim->Instance->CCR1);
return STEPPER_DRIVER_OK;
}
在中断中的处理
[C] 纯文本查看 复制代码 void StepperDriverPulseFinishedCallback(stepperDriver_t *hsd, TIM_HandleTypeDef *htim)
{
if (htim != hsd->stepCtrl.htim)
return;
PinDEBUG1(1);//Debug1引脚设置高电平,表示进入中断
if (__HAL_TIM_GET_ITSTATUS(htim, timitIndex[hsd->stepCtrl.channel>>2]) == RESET)
{
//DMA传输完成,第一次中断进入到这里
__HAL_TIM_ENABLE_IT(htim, timitIndex[hsd->stepCtrl.channel>>2]);
hsd->endPulse = 1; //开启了捕获/比较中断,定时器中断马上调用了一次回调,使用标志位跳过
PinDEBUG1(0); //Debug1引脚设置低电平,表示退出中断
return;
}
if (hsd->endPulse == 1)
{
//跳过因开启中断而导致立即进入的中断
hsd->endPulse = 0;
#if 0 //解决起始输输出高电平的临时方案
// 如果定时器在启动时是低电平,因为传输奇数个数据这里应该是高电平
if (hsd->stepCtrl.gpio->IDR & hsd->stepCtrl.pinBit != hsd->stepCtrl.pinBit)
{
LOGS("DMA Cplt , Voltage is Low\n");
goto end; //低电平直接结束输出
}
else
{
LOGS("Waiting Next OC IT\n");
}
#endif
PinDEBUG1(0); //Debug1引脚设置低电平,表示退出中断
return;
}
//最后一个输出比较结束,第三次进入回调
end:
PinDEBUG2(0); //debug2引脚设置低电平,高电平的持续时间就是本次定时器脉冲的总时长
__HAL_TIM_DISABLE_IT(htim, timitIndex[hsd->stepCtrl.channel>>2]);
HAL_TIM_OC_Stop_DMA(hsd->stepCtrl.htim, hsd->stepCtrl.channel);
hsd->stepCnt += hsd->curStep*hsd->curDir; // 计算累计步数
if (hsd->planStep > 0) //还有计划的步数没走完
{
if (hsd->planStep*2 > STEPPER_DATAMAP_SIZE)
{
hsd->planStep -= STEPPER_DATAMAP_SIZE/2;
hsd->curStep = STEPPER_DATAMAP_SIZE/2;
}
else
{
hsd->curStep = hsd->planStep;
hsd->planStep = 0;
}
__HAL_TIM_SET_COUNTER(hsd->stepCtrl.htim, 0);
__HAL_TIM_SET_COMPARE(hsd->stepCtrl.htim, hsd->stepCtrl.channel, hsd->stepMap[0]);
PinDEBUG2(1); //debug2引脚高电平代表开始DMA和定时器
HAL_TIM_OC_Start_DMA(hsd->stepCtrl.htim,
hsd->stepCtrl.channel,
(uint32_t*)&hsd->stepMap[1],
hsd->curStep*2-1);
}
else
{
StepperLog("channel:%d stepCnt:%lld\n",hsd->stepCtrl.channel, hsd->stepCnt);
hsd->planStep = 0;
hsd->curStep = 0;
hsd->curDir = 0;
}
PinDEBUG1(0); //Debug1引脚设置低电平,表示退出中断
}
测试
发生100个脉冲测试
第一个红框
第二个红框
第三个红框
现象:
- 脉冲数是100个,分为两段,一共翻转了200次
- 脉冲的数量和顺序都是对的
- 定时器开始时是低电平
经过了多次验证代码是没有问题的......
直到我编写并使用停止当前输出脉冲后就开始出现问题了
[C] 纯文本查看 复制代码 void StepperDriverStopMove(stepperDriver_t *hsd)
{
uint16_t waitStep;
if (hsd->endPulse == 1 || hsd->curStep == 0) return;
HAL_TIM_OC_Stop_DMA(hsd->stepCtrl.htim, hsd->stepCtrl.channel);
waitStep = (__HAL_DMA_GET_COUNTER(hsd->stepCtrl.htim->hdma[dmaIndex[hsd->stepCtrl.channel>>2]])+1)/2;
if(waitStep && hsd->curStep) //DMA还有数据等待传输,当前计划的脉冲不为零
{
hsd->stepCnt += (hsd->curStep - waitStep)*hsd->curDir; //计算累计脉冲数
hsd->planStep = 0; //清空未完成的计划
hsd->curDir = 0;
hsd->curStep = 0;
StepperLog("stepper stop pos:%lld,wait:%d\n", hsd->stepCnt,waitStep);
}
}
反复很多次使用之后初始电平从原来的低电平变为了高电平
现象
上面生成的测试数据为 stepMap[0] = 1,stepMap[1] = 1 + 10 , stepMap[3] = 11 + 10......
原来第一个脉冲长度为10us也就是第一次触发到第二次触发之间是高电平,
现在是1us,因为开启定时器到第一次触发比较之间的时间是1us,判断出初始电平可能不对了?
把测试数据更改为一个100us的脉冲再次测试:
正常情况
异常出现后
目前怀疑在DMA为完成传输的情况下使用HAL_TIM_OC_Stop_DMA后可能会造成这个现象。
目前的临时解决方法是在DMA传输完成中断里面判断是否为高电平,如果是低电平就直接结束,不等待最后一个比较值触发,不然会多一个脉冲,
实际验证个方法只能减小出现问题的概率,不能根除初始电平翻转的问题
附件为STM32F103ZE的CubeMX和Keil工程,相关文件为
UserDriver/Stepper_Driver.c Line94、Line132、Line 193 (应用代码)
UserApp/board_base.cpp Line135 (中断回调)
UserApp/task_main.c Line205、Line255 (串口指令)
复现方法:
[C] 纯文本查看 复制代码 串口1/3/4发送命令
ARM:D; (把stepMap的数据替换为测试数据)
MC:2,ms,1000; (表示PA2(TIM5 CH3)产生1000个脉冲)
MC:2,stop; (表示在产生脉冲时强行停止)
反复多次产生然后停止就有概率看到现象
把MC:2后面的2换为1可以在PA0(TIM2 CH1)上输出脉冲
临时解决方案在Stepper_Driver.c 211行,经过测试产生异常的概率会减小但是还是会有,求大佬解答为何初始电平会变化
STM32F103ZE_RTOS_Project0515.rar
(1.35 MB, 下载次数: 8)
|