打印
[研电赛技术支持]

GD32高级定时器正交编码器模式详解

[复制链接]
278|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
1.前言
本文记录笔者在使用高级定时器正交解码器模式时,遇到的一些问题。本文程序实现的是高级定时器TIMER7捕获 电机编码器反馈的AB相正交脉冲信号,来获得电机脉冲值。

2.定时器选择
以GD32F407为例,只有高级定时器TIMER0/TIMER7或者通用(L0)定时器才具有正交译码功能。
并且所用的两个通道只能是前两个通道,不可选。





3. 什么是正交脉冲信号?可以用来做什么?
所谓正交脉冲信号,是指 相位相差90°的脉冲信号。在步进和伺服电机中应用广泛。笔者认为读者还是有必要了解一下正交脉冲信号是什么?如果用来计量电机所走的位置、方向和速度的。请参考 正交脉冲信号在电机中的作用

同时,还需要注意选取的IO口,是否能满足 输入的速率要求,需要推导,请参考伺服电机AB相输出,接入定时器通道,对定时器IO口的速率有何要求【详细分析】

4.定时器的正交译码功能描述
定时器的正交译码功能使用由TIMERx_CH0和TIMERx_CH1引脚生成的CI0(A相)和CI1(B相)正交信号各自相互作用产生计数值。



正交译码器有三种模式可供选择。这两路正交的输入信号,根据实际需要,可以设置只捕获某个通道的上升沿或下降沿,也可以设置同时捕获两个通道的上升沿与下降沿,这样就可以提高编码器的计数精度,实现倍频。



如下图所示,鉴于该不好理解,笔者在网上的文档中借用了一张好理解的图。



此图不好理解



此图易于理解

5.代码实现
5.1 标准库函数介绍
主要用到的库函数声明都在gd324xx_timer.h文件中。

/* TIMER timebase*/
/* deinit a TIMER */
void timer_deinit(uint32_t timer_periph);
/* initialize TIMER init parameter struct */
void timer_struct_para_init(timer_parameter_struct* initpara);
/* initialize TIMER counter */
void timer_init(uint32_t timer_periph, timer_parameter_struct* initpara);
/* enable a TIMER  使能TIMER*/
void timer_enable(uint32_t timer_periph);
/* disable a TIMER */
void timer_disable(uint32_t timer_periph);
/* configure TIMER quadrature decoder mode 配置TIMER正交解码器模式*/
void timer_quadrature_decoder_mode_config(uint32_t timer_periph, uint32_t decomode, uint16_t ic0polarity, uint16_t ic1polarity);
/* select TIMER slave mode  选择TIMER从属模式*/
void timer_slave_mode_select(uint32_t timer_periph,uint32_t slavemode);

/* enable the auto reload shadow function 使能TIMER7自动重载寄存器影子*/
void timer_auto_reload_shadow_enable(uint32_t timer_periph);

/* configure TIMER counter register value 给TIMER计数器配置一个初值,从初值开始计数*/
void timer_counter_value_config(uint32_t timer_periph , uint32_t counter);
/* read TIMER counter value 读取TIMER计数器值*/
uint32_t timer_counter_read(uint32_t timer_periph);





5.2 实验环境搭建
使用GD32F407的开发板。复用PC6和PC7作为TIMER7 正交译码器的输入。一个带有AB相反馈的伺服电机。把电机AB相分别和PC6、PC7直连(需要注意AB相的电压是否和单片机IO口电压匹配,笔者AB相电压是5V, 芯片手册中查阅PC6和PC7可以容纳5V的输入,因此可以直连)。



5.3 代码实现
bsp_timer.c

static void GPIO_Config1(void)
{
    /* 使能GPIO时钟 */
    rcu_periph_clock_enable(TIMX_IC_CHANNEL1_CLK);
    /* 配置GPIO引脚复用 */
                gpio_af_set(TIMX_IC_CHANNEL_PORT, GPIO_AF_3, TIMX_IC_CHANNEL_PIN);
    gpio_af_set(TIMX_IC_CHANNEL1_PORT, GPIO_AF_3, TIMX_IC_CHANNEL1_PIN);
    /* 配置GPIO引脚模式 */
                gpio_mode_set(TIMX_IC_CHANNEL_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, TIMX_IC_CHANNEL_PIN);
    gpio_mode_set(TIMX_IC_CHANNEL1_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, TIMX_IC_CHANNEL1_PIN);
}


void Timer_Advance_Config(void)
{
    /* 使能TIMER7时钟 */
    rcu_periph_clock_enable(RCU_TIMER7);
    /* 使能SYSCFG时钟 用于主从模式的映射 */
    //rcu_periph_clock_enable(RCU_SYSCFG);

    /* TIMER7复位 */
    timer_deinit(TIMER7);

    /* ===============================配置时基单元======================= */
    timer_parameter_struct timer_initpara;

    /* TIMER1结构体初始化 */
    timer_struct_para_init(&timer_initpara);
    /* 配置TIMER7 */
    timer_initpara.prescaler = 0;//299
    timer_initpara.alignedmode = TIMER_COUNTER_EDGE;// 使用边缘对齐模式
    timer_initpara.counterdirection = TIMER_COUNTER_UP;
    timer_initpara.period = 65535;
    timer_initpara.clockdivision = TIMER_CKDIV_DIV1;//时钟分频设置
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER7, &timer_initpara);

    /* =============================配置输入捕获单元===================== */
    timer_ic_parameter_struct timer_ic_initpara;
    /* 输入捕获结构体初始化 */
    timer_channel_input_struct_para_init(&timer_ic_initpara);
    /* 配置过滤器 */
    timer_ic_initpara.icfilter = 15;
    /* 配置边沿极性 */
    timer_ic_initpara.icpolarity = TIMER_IC_POLARITY_RISING;
    /* 配置分频系数 */
    timer_ic_initpara.icprescaler = TIMER_IC_PSC_DIV1;
    /* 配置 */
    timer_ic_initpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_input_pwm_capture_config(TIMER7, TIMX_IC_CHANNEL0, &timer_ic_initpara);
        timer_input_pwm_capture_config(TIMER7, TIMX_IC_CHANNEL1, &timer_ic_initpara);

        /* 配置编码器接口模式 */
        timer_quadrature_decoder_mode_config(TIMER7, TIMER_QUAD_DECODER_MODE2, TIMER_IC_POLARITY_RISING, TIMER_IC_POLARITY_RISING);

        /* select the encoder mode */
        timer_slave_mode_select(TIMER7, TIMER_QUAD_DECODER_MODE2);

    /* 使能TIMER7自动重载寄存器影子 */
    timer_auto_reload_shadow_enable(TIMER7);

        //设置初始计数值为0x50,用于判断计数个数和计数方向
        timer_counter_value_config(TIMER7,60000);//0x50
       
    /* 使能TIMER7 */
    timer_enable(TIMER7);
    /* GPIO初始化 */
    GPIO_Config1(); //使用这样的名字,让后面读代码的人更直观认识
}



main.c

int main(void)
{
        uint8_t xx;
        uint32_t counter;
        systick_config();

       
  /* 通用TIMER初始化 */
  //Timer_Config();
       
        /* 基础TIMER初始化 */
        //TIMER_Basic_Config();
       
        /* 高级TIMER初始化 */
        Timer_Advance_Config();

//       

//       
//  /* 初始化232 */
//  RS232_Config();
//       
//  /* 初始化485 */
//  RS485_Config();       
//  
//  
//  /* 初始化调试串口 */
                USART_Config();        
       
        while(1)
        {
         counter = timer_counter_read(TIMER7); // 读取TIMER计数器值
         printf("counter=%d\n",counter); // 把值输出到串口
         delay_1ms(100);
        }



5.4 溢出修正
还需要考虑计数器溢出的情况。

5.3节的代码仍有缺陷,没有考虑计数器溢出的情况。高级定时器 计数器是16位,能够表示的数的范围是0~65535。当脉冲值超过计数器能表示的数值范围时,就产生了溢出。计数器会从初值从新开始计数。
为了解决上述问题,有两个方案:
方案1:
选用通用定时器TIMER1和TIMER4, 这两个定时器 计数器是32位。能够表示更大的范围。
方案2:
编程时,考虑计数器溢出的情况,做处理。
常规上:
a.启动定时器的更新中断TIMER_INT_UP(当发生溢出时,触发中断)。
b.配置配置中断优先级,并通过NVIC使能定时器中断
c.在中断处理函数中,处理溢出

下面的代码使用了方案2.
在中断处理函数中,通过 (TIMER_CTL0(TIMER7) & 0x10) >> 4 判断脉冲的方向。根据方向来决定是 加上还是减去 周期(重装载值)。

在主程序中,想要获取当前的脉冲数(带方向),使用如下写法即可。
total_counter记录了全部溢出的周期值。
counter记录了当前的脉冲值(带方向)。
两者相加,即是修正计数器溢出, 实际情况下的电机当前的脉冲值(带方向)。

bsp_timer.c

static void GPIO_Config1(void)
{
    /* 使能GPIO时钟 */
    rcu_periph_clock_enable(TIMX_IC_CHANNEL1_CLK);
    /* 配置GPIO引脚复用 */
                gpio_af_set(TIMX_IC_CHANNEL_PORT, GPIO_AF_3, TIMX_IC_CHANNEL_PIN);
    gpio_af_set(TIMX_IC_CHANNEL1_PORT, GPIO_AF_3, TIMX_IC_CHANNEL1_PIN);
    /* 配置GPIO引脚模式 */
                gpio_mode_set(TIMX_IC_CHANNEL_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, TIMX_IC_CHANNEL_PIN);
    gpio_mode_set(TIMX_IC_CHANNEL1_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, TIMX_IC_CHANNEL1_PIN);
}
#include "timer/bsp_timer_general.h"

void Timer_Advance_Config(void)
{
    /* 使能TIMER7时钟 */
    rcu_periph_clock_enable(RCU_TIMER7);
    /* 使能SYSCFG时钟 用于主从模式的映射 */
    //rcu_periph_clock_enable(RCU_SYSCFG);

    /* TIMER7复位 */
    timer_deinit(TIMER7);

    /* ===============================配置时基单元======================= */
    timer_parameter_struct timer_initpara;

    /* TIMER1结构体初始化 */
    timer_struct_para_init(&timer_initpara);
    /* 配置TIMER7 */
    timer_initpara.prescaler = 0;//299
    timer_initpara.alignedmode = TIMER_COUNTER_EDGE;// 使用边缘对齐模式
    timer_initpara.counterdirection = TIMER_COUNTER_UP;
    timer_initpara.period = 65535;
    timer_initpara.clockdivision = TIMER_CKDIV_DIV1;//时钟分频设置
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER7, &timer_initpara);

    /* =============================配置输入捕获单元===================== */
    timer_ic_parameter_struct timer_ic_initpara;
    /* 输入捕获结构体初始化 */
    timer_channel_input_struct_para_init(&timer_ic_initpara);
    /* 配置过滤器 */
    timer_ic_initpara.icfilter = 15;
    /* 配置边沿极性 */
    timer_ic_initpara.icpolarity = TIMER_IC_POLARITY_RISING;
    /* 配置分频系数 */
    timer_ic_initpara.icprescaler = TIMER_IC_PSC_DIV1;
    /* 配置 */
    timer_ic_initpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_input_pwm_capture_config(TIMER7, TIMX_IC_CHANNEL0, &timer_ic_initpara);
        timer_input_pwm_capture_config(TIMER7, TIMX_IC_CHANNEL1, &timer_ic_initpara);

        /* 配置编码器接口模式 */
        timer_quadrature_decoder_mode_config(TIMER7, TIMER_QUAD_DECODER_MODE2, TIMER_IC_POLARITY_RISING, TIMER_IC_POLARITY_RISING);

        /* select the encoder mode */
        timer_slave_mode_select(TIMER7, TIMER_QUAD_DECODER_MODE2);

    /* 清除中断标志位 */
    timer_interrupt_flag_clear(TIMER7, TIMER_INT_FLAG_UP);
    /* 使能TIMER1中断 */
    timer_interrupt_enable(TIMER7, TIMER_INT_UP);
    /* 使能NVIC */
    nvic_irq_enable(TIMER7_UP_TIMER12_IRQn, 2, 0);


//    /* 清除中断标志位 */
//    timer_interrupt_flag_clear(TIMER7, TIMER_INT_FLAG_CH0);
//    /* 使能TIMER1中断 */
//    timer_interrupt_enable(TIMER7, TIMER_INT_CH0);
//    /* 使能NVIC */
//    nvic_irq_enable(TIMER7_Channel_IRQn, 4, 0);
               
               
    /* 使能TIMER7自动重载寄存器影子 */
    timer_auto_reload_shadow_enable(TIMER7);

        //设置初始计数值为0x50,用于判断计数个数和计数方向
        timer_counter_value_config(TIMER7,65535);//0x50
       
    /* 使能TIMER7 */
    timer_enable(TIMER7);
    /* GPIO初始化 */
    GPIO_Config1(); //使用这样的名字,让后面读代码的人更直观认识



main.c

int main(void)
{
        uint8_t xx;
        uint32_t counter;
        systick_config();

       
  /* 通用TIMER初始化 */
  //Timer_Config();
       
        /* 基础TIMER初始化 */
        //TIMER_Basic_Config();
       
        /* 高级TIMER初始化 */
        Timer_Advance_Config();

//       

//       
//  /* 初始化232 */
//  RS232_Config();
//       
//  /* 初始化485 */
//  RS485_Config();       
//  
//  
//  /* 初始化调试串口 */
                USART_Config();        
       
        while(1)
        {
         counter = timer_counter_read(TIMER7);
         printf("counter=%d\n",counter);
         printf("total_counter=%d\n",total_counter+counter);
         delay_1ms(100);
        }



gd32f4xx_it.c

void TIMER7_UP_TIMER12_IRQHandler(void)
{
    if (timer_interrupt_flag_get(TIMER7, TIMER_INT_FLAG_UP)!= RESET)
    {
        /* 清除中断标志位 */
        timer_interrupt_flag_clear(TIMER7, TIMER_INT_FLAG_UP);
                               
                                uint8_t dir = (TIMER_CTL0(TIMER7) & 0x10) >> 4;
                                if (dir) {
                                        total_counter-= 65535;
                                } else {
                                        total_counter+= 65535;
                                }
    }
}


————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/t18438605018/article/details/148093870

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

91

主题

4286

帖子

2

粉丝