请选择 进入手机版 | 继续访问电脑版

硬汉嵌入式论坛

 找回密码
 立即注册
查看: 630|回复: 3
收起左侧

[技术讨论] STM32F1使用TIM输出比较+DMA时遇到的问题

[复制链接]

1

主题

3

回帖

6

积分

新手上路

积分
6
发表于 2023-5-15 22:06:57 | 显示全部楼层 |阅读模式
本帖最后由 hurry 于 2023-5-16 12:42 编辑

新人第一次发帖,如有不妥请批评指正。
我想使用定时器的输出比较和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传输和定时器
[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个脉冲测试
img4.png
第一个红框
img5.png
第二个红框
img6.png
第三个红框
img7.png
现象:
  • 脉冲数是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);
    }
}

反复很多次使用之后初始电平从原来的低电平变为了高电平
现象

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 (串口指令)
复现方法:
[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, 下载次数: 1)
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
发表于 2023-5-16 00:52:43 | 显示全部楼层
我做脉冲控制,一般使用的TIM UP更新事件 + DMA触发任意GPIO做脉冲控制,如果做停止,我是设置全高或者全低

STM32H7视频教程第16期:DMA双缓冲实现32路脉冲并行同步控制(2022-05-26)
https://www.armbbs.cn/forum.php? ... d=112560&fromuid=58
(出处: 硬汉嵌入式论坛)


回复

使用道具 举报

1

主题

3

回帖

6

积分

新手上路

积分
6
 楼主| 发表于 2023-5-16 12:40:10 | 显示全部楼层
eric2013 发表于 2023-5-16 00:52
我做脉冲控制,一般使用的TIM UP更新事件 + DMA触发任意GPIO做脉冲控制,如果做停止,我是设置全高或者全低 ...

get到思路了,有没有F1相关的代码可以参考呢
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
105914
QQ
发表于 2023-5-16 15:32:15 | 显示全部楼层
hurry 发表于 2023-5-16 12:40
get到思路了,有没有F1相关的代码可以参考呢

F1系列没做例子,也可以方便的实现,你可以试试,使用UP更新事件触发DMA执行,发送数据给GPIO引脚输出寄存器。

QQ截图20230516153237.png
回复

使用道具 举报

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

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-3-28 17:38 , Processed in 0.173485 second(s), 29 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

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