OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3380|回复: 1

STM32F1使用TIM-OC+DMA 做任意频率数量可控的脉冲控制

[复制链接]

0

主题

0

帖子

0

精华

新手入门

积分
6
金钱
6
注册时间
2022-3-9
在线时间
5 小时
发表于 2023-5-16 12:16:59 | 显示全部楼层 |阅读模式
5金钱
本帖最后由 CreateTree 于 2023-5-16 12:33 编辑

新人第一次发帖,如有不妥请批评指正。
我想使用定时器的输出比较和DMA做时间和数量可控的脉冲,基于HAL库,以下是我的思路:
CubeMX配置:
定时器
img1.png
另外中断和DMA配置:
img2.png img3.png

--------------------------------------------------------------------------------------------
代码思路
  • 创建一个大小为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传输和定时器
  1. StepperDriverState_t StepperDriverMoveSteps(stepperDriver_t *hsd, long steps)
  2. {
  3.     StepperAssert(hsd);
  4.     if (StepperDriverGetEnableStatus(hsd) == 0) return STEPPER_DRIVER_ERROR;
  5.     if (hsd->curStep > 0) return STEPPER_DRIVER_BUSY;
  6.     /* 方向处理 */
  7.     if (steps > 0)
  8.     {
  9.         StepperDriverSetDir(hsd, 1);
  10.         hsd->curStep = steps;
  11.         hsd->curDir = 1;
  12.     }
  13.     else if (steps < 0)
  14.     {
  15.         StepperDriverSetDir(hsd, -1);
  16.         hsd->curStep = -steps;
  17.         hsd->curDir = -1;
  18.     }
  19.     else return STEPPER_DRIVER_ERROR;
  20.     /* 脉冲数计算 */
  21.     StepperLog("channel:%d, startStep:%d\n", hsd->stepCtrl.channel, steps);
  22.     if (hsd->curStep*2 > STEPPER_DATAMAP_SIZE) /* 超过DMA缓存的部分存起来,分段多次传输 */
  23.     {
  24.         hsd->planStep = hsd->curStep - STEPPER_DATAMAP_SIZE/2;
  25.         hsd->curStep = STEPPER_DATAMAP_SIZE/2;
  26.     }
  27.     /* 定时器设置 */
  28.     __HAL_TIM_SET_COUNTER(hsd->stepCtrl.htim, 0);
  29.     __HAL_TIM_SET_COMPARE(hsd->stepCtrl.htim, hsd->stepCtrl.channel, hsd->stepMap[0]);
  30.     PinDEBUG2(1);
  31.     HAL_TIM_OC_Start_DMA(hsd->stepCtrl.htim,
  32.                           hsd->stepCtrl.channel,
  33.                           (uint32_t*)&hsd->stepMap[1],
  34.                           hsd->curStep*2-1);
  35.     LOG_DBG(LOGTAG, "Pulse start :Cnt%d, CMP:%d\n",hsd->stepCtrl.htim->Instance->CNT, hsd->stepCtrl.htim->Instance->CCR1);
  36.     return STEPPER_DRIVER_OK;
  37. }
复制代码



在中断中的处理
  1. void StepperDriverPulseFinishedCallback(stepperDriver_t *hsd, TIM_HandleTypeDef *htim)
  2. {
  3.     if (htim != hsd->stepCtrl.htim)
  4.             return;
  5.     PinDEBUG1(1);//Debug1引脚设置高电平,表示进入中断
  6.     if (__HAL_TIM_GET_ITSTATUS(htim, timitIndex[hsd->stepCtrl.channel>>2]) == RESET)
  7.     {
  8.         //DMA传输完成,第一次中断进入到这里

  9.         __HAL_TIM_ENABLE_IT(htim, timitIndex[hsd->stepCtrl.channel>>2]);
  10.         hsd->endPulse = 1; //开启了捕获/比较中断,定时器中断马上调用了一次回调,使用标志位跳过
  11.         PinDEBUG1(0); //Debug1引脚设置低电平,表示退出中断
  12.         return;
  13.     }
  14.     if (hsd->endPulse == 1)
  15.     {
  16.         //跳过因开启中断而导致立即进入的中断
  17.         hsd->endPulse = 0;
  18.         #if 0 //解决起始输输出高电平的临时方案
  19.         // 如果定时器在启动时是低电平,因为传输奇数个数据这里应该是高电平
  20.         if (hsd->stepCtrl.gpio->IDR & hsd->stepCtrl.pinBit != hsd->stepCtrl.pinBit)
  21.         {
  22.             LOGS("DMA Cplt , Voltage is Low\n");
  23.             goto end; //低电平直接结束输出
  24.         }
  25.         else
  26.         {
  27.             LOGS("Waiting Next OC IT\n");
  28.         }
  29.         #endif
  30.         PinDEBUG1(0); //Debug1引脚设置低电平,表示退出中断
  31.         return;
  32.     }
  33.     //最后一个输出比较结束,第三次进入回调
  34.     end:
  35.     PinDEBUG2(0); //debug2引脚设置低电平,高电平的持续时间就是本次定时器脉冲的总时长
  36.     __HAL_TIM_DISABLE_IT(htim, timitIndex[hsd->stepCtrl.channel>>2]);
  37.     HAL_TIM_OC_Stop_DMA(hsd->stepCtrl.htim, hsd->stepCtrl.channel);

  38.     hsd->stepCnt += hsd->curStep*hsd->curDir; // 计算累计步数
  39.     if (hsd->planStep > 0) //还有计划的步数没走完
  40.     {
  41.         if (hsd->planStep*2 > STEPPER_DATAMAP_SIZE)
  42.         {
  43.             hsd->planStep -= STEPPER_DATAMAP_SIZE/2;
  44.             hsd->curStep = STEPPER_DATAMAP_SIZE/2;
  45.         }
  46.         else
  47.         {
  48.             hsd->curStep = hsd->planStep;
  49.             hsd->planStep = 0;
  50.         }

  51.         __HAL_TIM_SET_COUNTER(hsd->stepCtrl.htim, 0);
  52.         __HAL_TIM_SET_COMPARE(hsd->stepCtrl.htim, hsd->stepCtrl.channel, hsd->stepMap[0]);
  53.         PinDEBUG2(1); //debug2引脚高电平代表开始DMA和定时器
  54.         HAL_TIM_OC_Start_DMA(hsd->stepCtrl.htim,
  55.                             hsd->stepCtrl.channel,
  56.                             (uint32_t*)&hsd->stepMap[1],
  57.                             hsd->curStep*2-1);
  58.     }
  59.     else
  60.     {
  61.         StepperLog("channel:%d stepCnt:%lld\n",hsd->stepCtrl.channel, hsd->stepCnt);
  62.         hsd->planStep = 0;
  63.         hsd->curStep = 0;
  64.         hsd->curDir = 0;
  65.     }
  66.     PinDEBUG1(0); //Debug1引脚设置低电平,表示退出中断
  67. }
复制代码


测试
发生100个脉冲测试
img4.png
第一个红框
img5.png
第二个红框
img6.png
第三个红框
img7.png
现象:
  • 脉冲数是100个,分为两段,一共翻转了200次
  • 脉冲的数量和顺序都是对的
  • 定时器开始时是低电平

经过了多次验证代码是没有问题的......
直到我编写并使用停止当前输出脉冲后就开始出现问题了
  1. void StepperDriverStopMove(stepperDriver_t *hsd)
  2. {
  3.     uint16_t waitStep;
  4.     if (hsd->endPulse == 1 || hsd->curStep == 0) return;
  5.     HAL_TIM_OC_Stop_DMA(hsd->stepCtrl.htim, hsd->stepCtrl.channel);
  6.     waitStep = (__HAL_DMA_GET_COUNTER(hsd->stepCtrl.htim->hdma[dmaIndex[hsd->stepCtrl.channel>>2]])+1)/2;
  7.     if(waitStep && hsd->curStep) //DMA还有数据等待传输,当前计划的脉冲不为零
  8.     {
  9.         hsd->stepCnt += (hsd->curStep - waitStep)*hsd->curDir; //计算累计脉冲数
  10.         hsd->planStep = 0; //清空未完成的计划
  11.         hsd->curDir = 0;
  12.         hsd->curStep = 0;
  13.         StepperLog("stepper stop pos:%lld,wait:%d\n", hsd->stepCnt,waitStep);
  14.     }
  15. }
复制代码


反复很多次使用之后初始电平从原来的低电平变为了高电平
现象
img8.png
上面生成的测试数据为 stepMap[0] = 1,stepMap[1] = 1 + 10 , stepMap[3] = 11 + 10......
原来第一个脉冲长度为10us也就是第一次触发到第二次触发之间是高电平,
现在是1us,因为开启定时器到第一次触发比较之间的时间是1us,判断出初始电平可能不对了?
把测试数据更改为一个100us的脉冲再次测试:
正常情况
img9.png
异常出现后
img10.png
目前怀疑在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发送命令
  1. ARM:D; (把stepMap的数据替换为测试数据)

  2. MC:2,ms,1000; (表示PA2(TIM5 CH3)产生1000个脉冲)

  3. MC:2,stop; (表示在产生脉冲时强行停止)
复制代码

反复多次产生然后停止就有概率看到现象
把MC:2后面的2换为1可以在PA0(TIM2 CH1)上输出脉冲
临时解决方案在Stepper_Driver.c 211行,经过测试产生异常的概率会减小但是还是会有,求大佬解答为何定时器开启后初始电平会变化

STM32F103ZE_RTOS_Project0515.rar (1.35 MB, 下载次数: 11)

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165352
金钱
165352
注册时间
2010-12-1
在线时间
2108 小时
发表于 2023-5-19 00:17:15 | 显示全部楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-24 08:54

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表