本帖最后由 CreateTree 于 2023-5-16 12:33 编辑
新人第一次发帖,如有不妥请批评指正。 我想使用定时器的输出比较和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传输和定时器 - 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;
- }
复制代码
在中断中的处理 - 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次 脉冲的数量和顺序都是对的 定时器开始时是低电平
经过了多次验证代码是没有问题的...... 直到我编写并使用停止当前输出脉冲后就开始出现问题了 - 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 (串口指令)
复现方法: 串口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, 下载次数: 11)
|