OskarBot小车驱动(三)、编码器读取与速度计算

OskarBot小车驱动(三)、编码器读取与速度计算

【目录】

    - 1、编码器分类与介绍

        - 1.1 编码器分类

        - 1.2 霍尔编码器(增量,正交)

        - 1.3 增量式光电编码器

    - 2、软件设计与定时器配置

        - 2.1 软件4倍频时计算转速

        - 2.1 定时器配置编码器模式

        - 2.2 软件配置注意事项

        - 2.3 定时器的编码器接口模式配置&读取编码器计数值

            - (1)定时器的编码器接口模式配置

            - (2)读取编码器计数

            - (3)编码器的定时器中断处理

                - 1)方法一、定时器溢出中断,清中断标志位

                - 2)方法二、利用定时器溢出中断判断圈数

        - 2.4 获取编码器脉冲计数值的时机

            - (1)循环获取计数值

                - 1)调用函数

                - 2)SysTick系统定时器设置

            - (2)定时获取计数值

                - 1)定时器中断处理读取编码器数据

                - 2)定时器设置

            - (3)外部中断操作

                - 1)外部中断初始化

                - 2)外部中断处理函数

    - 3、读取旋转方向&计算转速

        - 3.1 读取旋转方向

        - 3.2 计算转速

            - (1)每秒脉冲数/每米脉冲数

            - (2)定时器中断读取编码器计算速度

 

1、编码器分类与介绍

1.1 编码器分类

根据工作原理划分:霍尔编码器(磁性),光电编码器;

 

旋转编码器,根据码盘结构划分:增量型(相对型)编码器,绝对编码器。

增量型编码器,利用检测脉冲的方式来计算转速及位置,输出有关旋转轴运动的信息,一般会由其他设备或电路进一步转换为速度、距离、每分钟转速或位置的信息。

绝对型编码器,会输出旋转轴的位置,可视为一种角度传感器。

 

正交编码器,是一种用于测量旋转速度和方向的传感器,通过积分(累加)运算后,还可以用来测算距离。

最常见的正交编码器有两个输出信号:A相 和 B相。

有些编码器会有 Z相 的校准功能(用于消除累计误差)。第三个通道称为索引信号,可用于对位置计数器进行复位,从而确定绝对位置。

“正交”一词来源于 AB 两个信号的特征,一般情况下 A相 和 B相 的输出信号总是有 π/2 的相位差。

来自

 

信号连接形式:有单相连接(用于单方向测速、计数);AB两相连接(用于双向测速、计数及判断方向);ABZ三相连接(用于带参考位修正的位置测量);差分连接(用于远距离传输)。

参考:【编码器正交编码工作原理】

来自

 

增量型编码器,英文名称“Incremental encoder”,它的码盘被分成大小相等的明暗相间的光栅,随着码盘的转动,接收端会检测到光的0和1的变化,并转换成电信号脉冲向外输出。通过对脉冲的计数,就能确定位移的大小,如下图:

为了区分正反转及检测零点,实际使用的码盘比上图要复杂些,通常包括三个部分:A相,B相和Z相,A相与B相相差1/4周期(相位差90度),可以用来区分正转还是反转;Z相为单圈脉冲,码盘转一圈产生一次,可以用作编码器的参考零位,如下图:

增量型编码器的输出波形如下图:

由于采用脉冲计数的方式,增量型编码器在测量前必须先寻找参考零位,因此它的测量结果是相对的。另外增量型编码器的数据断电后会丢失。

来自

 

参考:【AB相编码器与正交解码,绝对值编码器】

光电编码器:

AB相输出:

发光二极管发射的光通过光栅到达光敏管,引起电平变化。

如果正转,A相输出超前B相90度,如果反转A相滞后B相90度。

每转一周,索引相,即Z相经过发光二极管一次,输出一个脉冲,可作为编码器的机械零位。

来自

 

1.2 霍尔编码器(增量,正交)

霍尔编码器只有A、B两相的正交信号,没有Z轴的机械零点信号。

测速的编码器是双通道霍尔效应编码器,它包含一个磁栅和磁敏检测电路,输出两个通道正交相位角为90度的方波。

来自

这是一款增量式输出的霍尔编码器。

有 AB 相输出,所以不仅可以测速,还可以辨别转向。

根据上图的接线说明可以看到,我们只需给编码器电源 5V 供电, 在电机转动的时候即可通过 AB 相输出方波信号。

编码器自带了上拉电阻,所以无需外部上拉,可以直接连接到微机 IO 读取。

 

参考一种硬件连接方式

采用USB接口与编码器连接,线序定义为V+,A相,B相,GND,正好将USB的所有连线用完,可以同时完成给编码器提供电力以及返回信号的功能。

同时MINIUSB接口良好的物理连接特性也保证了应用的稳定性。

由于编码器为NPN集电极输出,所以需要在信号线上提供一定的上拉电阻。

 

 

1.3 增量式光电编码器

参考:

 

2、软件设计与定时器配置

2.1 软件4倍频时计算转速

软件4倍频技术:上下沿同时检测,检测到的脉冲数。通过AB相输入,一个周期检测上升沿,下降沿一共四个

已知:编码器,每转每相输出360个脉冲(含减速比);

车轮直径,65mm。

每秒脉冲数(定时器捕获) ,当转速为0.1m/s时,脉冲数:360*4*0.1/(0.065*3.14)=705 (个脉冲/s)

每米脉冲数 = 360*4/(0.065*3.14)=7051.787051(个脉冲/m);

转速= 每秒接收脉冲数(计数值,定时器获取)/每米脉冲数(7051,固定不变)= 705/7051=0.1m/s;

 

2.1 定时器配置编码器模式

参考《STM32F103中文参考手册》

编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计

数器只在 0 到 TIM1_ARR 寄存器中自动装载值之间连续计数(根据方向,或是 0

到 ARR 计数,或是 ARR 到 0 计数)。

 

TI 1波形先于TI 2波形90°时,每当边沿变化,计数器加1(可通过寄存器设置加减),可以看出一个光栅,被计数了4次。

TI 1波形后于TI 2波形90°时 ,每遇到一次边沿变化,计数器减1。

当T1,T2脉冲是连续产生的时候计数器加一或减一一次,而当某个接口产生了毛刺或抖动,则计数器计数不变,也就是说该接口能够容许抖动。

来自

 

根据两个输入信号的跳变顺序,产生了计数脉冲和方向信号。依据两个输入信号的跳变顺序,计数器向上或向下计数,因此TIM1_CR1 寄存器的DIR位由硬件进相应的设置。管计数器是依靠TI1 计数、依靠TI2 计数或者同时依靠TI1 和TI2 计数。在任一输入(TI1 或者TI2)跳变时都会重新计算DIR位。

 

信号连接:定时器的两个输入TI1和TI2直接与增量式正交编码器接口。

当定时器设为正交编码器模式时,这两个信号的边沿作为计数器的时钟。

而正交编码器的第三个输出(机械零位),可连接外部中断口来触发定时器的计数器复位。

 

2.2 软件配置注意事项

正转向上计数,反转向下计数,方向在CR1的DIR位里。TIMx_CR1寄存器的 DIR 位指示当前旋转方向

 

1、编码器有个转速上限,超过这个上限是不能正常工作的,这个是硬件的限制,原则上线数越多转速就越低,这点在选型时要注意,编码器的输出一般是开漏的,所以单片机的io一定要上拉输入状态。

 

2。定时器初始化好以后,任何时候CNT寄存器的值就是编码器的位置信息,

正转他会加、反转他会减这部分是不需要软件干预的

初始化时给的TIM_Period 值应该是码盘整圈的刻度值,

在减溢出会自动修正为这个数。加超过此数值就回0。

 

3。如果要扩展成多圈计数需要溢出中断像楼主说的,程序上圈计数加减方向位就行了。

 

4。每个定时器的输入脚可以通过软件设定滤波

 

5。应用中如果没有绝对位置信号,或者初始化完成后还没有收到绝对位置信号前的计数只能是相对计数。

收到绝对位置信号后重新修改一次CNT的值就行了。

码盘一般都有零位置信号,结合到定时器捕获输入就行。

上电以后要往返运动一下找到这个位置。

 

6。即便有滤波计数值偶尔也会有出错误的情况,一圈多计一个或少计一个数都是很正常的特别是转速比较高的时候尤其明显,有个绝对位置信号做修正是很有必要的。

绝对位置信号不需要一定在零位置点,收到这个信号就将CNT修正为一个固定的数值即可。

 

7。开启定时器的输入中断可以达到每个步计数都作处理的效果,但是高速运转的时候你可能处理不过来。

原文: https://blog.csdn.net/qq_17280755/article/details/73770598

 

8编码器输出给单片机建议使用比较器进行隔离,以提高抗干扰能力

一个外部的增量编码器直接和 MCU 连接不需要外部接口逻辑。

但是,一般使用比较器将编码器的差动输出转换到数字信号,这大大增加抗噪声干扰能

编码器输出的第三个信号表示机械零点,可以连接到一个外部中断输入和触发一个

计数器复位。

 

9、可以对输入信号TI1,TI2进行滤波处理,数字滤波器由事件器组成,每N个事件才视为一个有效边沿,可以在TIMx_CCMR1、TIMx_CCMR2中的IC1F位域设置

 

10、计数溢出后,定时器会装载“重装载值”,并清零重新计数,此值可设置为编码器旋转一周的脉冲个数,这样既可利用溢出中断次数判断转了几圈。但若只要求旋转角度,此值可以任意。

任意时刻角度为:溢出中断次数*重装载值+当前计数值

来自

 

2.3 定时器的编码器接口模式配置&读取编码器计数值

(1)定时器的编码器接口模式配置

定时器周期,重装值的设置:

一、设为最大可计数值 65535,0xFFFF;

二、有Z相信号时,设为一圈的脉冲数,考虑4倍频,设为 (360-1)*4;  //设定计数器重装值   TIMx_ARR = 359*4

#define ENCODER_TIM_PERIOD (u16)(65535)   //不可大于65535 因为F103的定时器是16位的。

 

void Left_Encoder_Init(void)

{

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 

  TIM_ICInitTypeDef TIM_ICInitStructure; 

  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器2的时钟

   

    /*使能GPIOAGPIOBAFIO外设时钟*/

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

   

   

    /*初始化PA.15端口为IN_FLOATING模式*/

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

   

 

  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

  TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器

  TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值

  //TIM_TimeBaseStructure.TIM_Period =  (360-1)*4;  //设定计数器重装值   TIMx_ARR = 359*4

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数 

  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

  TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3

  TIM_ICStructInit(&TIM_ICInitStructure);

  TIM_ICInitStructure.TIM_ICFilter = 10;

  TIM_ICInit(TIM2, &TIM_ICInitStructure);

  TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位

  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

  //Reset counter

  TIM_SetCounter(TIM2,0);

  TIM_Cmd(TIM2, ENABLE);

}

 

void Right_Encoder_Init(void)

{

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 

  TIM_ICInitTypeDef TIM_ICInitStructure; 

  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);//使能定时器8的时钟

   

  /*使能GPIOBAFIO外设时钟*/

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);

   

   

  /*初始化PB.04端口和PB.05端口为IN_FLOATING模式*/

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

  GPIO_Init(GPIOC, &GPIO_InitStructure);

 

  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

  TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器

  TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数 

  TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);

  TIM_EncoderInterfaceConfig(TIM8, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//使用编码器模式3

  TIM_ICStructInit(&TIM_ICInitStructure);

  TIM_ICInitStructure.TIM_ICFilter = 10;

  TIM_ICInit(TIM8, &TIM_ICInitStructure);

  TIM_ClearFlag(TIM8, TIM_FLAG_Update);//清除TIM的更新标志位

  TIM_ITConfig(TIM8, TIM_IT_Update, ENABLE);

  //Reset counter

  TIM_SetCounter(TIM8,0);

  TIM_Cmd(TIM8, ENABLE);

}

 

(2)读取编码器计数

/**************************************************************************

函数功能:单位时间读取编码器计数

入口参数:定时器

返回  值:速度值

**************************************************************************/

int Read_Encoder(u8 TIMX)

{

    int Encoder_TIM;   

    switch(TIMX)

    {

        case 2:  Encoder_TIM= (short)TIM2 -> CNT;  TIM2 -> CNT=0;break;

        case 3:  Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=0;break;

        case 4:  Encoder_TIM= (short)TIM4 -> CNT;  TIM4 -> CNT=0;break;

        case 5:  Encoder_TIM= (short)TIM5 -> CNT;  TIM5 -> CNT=0;break;

        case 8:  Encoder_TIM= (short)TIM8 -> CNT;  TIM8 -> CNT=0;break;

        default:  Encoder_TIM=0;

    }

    return Encoder_TIM;

}

 

//未调用以下函数,采用Read_Encoder()代替,编码器读取TIM2TIM8的数值

void ReadEncoder(void)

{

    Encoder_Left = (short)TIM2 -> CNT;

    TIM2 -> CNT = 0;

   

    Encoder_Right = (short)TIM4 -> CNT;

    TIM4 -> CNT = 0;

}

 

// 读取编码器,因为两个电机的旋转了180度的,所以需要对其中一个取反

Encoder_Left = Read_Encoder(2);

Encoder_Right = -Read_Encoder(8);  

 

(3)编码器的定时器中断处理

1)方法一、定时器溢出中断,清中断标志位

/*********************************************

函数功能:TIM2中断服务函数

入口参数:无

返回  值:无

**********************************************/

void TIM2_IRQHandler(void)

{                                  

    if(TIM2->SR&0X0001)//溢出中断

    {                                                  

    }                  

    TIM2->SR&=~(1<<0);//清除中断标志位      

}

 

/*******************************************

函数功能:TIM8中断服务函数

入口参数:无

返回  值:无

**********************************************/

void TIM8_IRQHandler(void)

{                                  

    if(TIM8->SR&0X0001)//溢出中断

    {                                                  

    }                  

    TIM8->SR&=~(1<<0);//清除中断标志位      

}

2)方法二、利用定时器溢出中断判断圈数

在中断服务函数中进行圈数计算,利用CR1判断旋转方向

int circle_count=0;//全局变量-圈数

void TIM3_IRQHandler(void)

{

    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)

    {      

        if((TIM3->CR1>>4 & 0x01)==0) //DIR==0,向上计数,

            circle_count++;

        else if((TIM3->CR1>>4 & 0x01)==1)//DIR==1,向下计数

            circle_count--;

    }

    TIM_ClearITPendingBit(TIM3,TIM_IT_Update);

}

 

获取当前角度值

int Encoder=0;

extern int circle_count;

 

Encoder=TIM_GetCounter(TIM3)+3600*circle_count;//当前角度

任意时刻角度为:溢出中断次数*重装载值+当前计数值

重装载值不是很重要。

计算转过角度,只要知道总计数值就好了(每收到一个正转脉冲计数值+1,反转脉冲计数值-1)。

如果重装载值等于编码器一圈脉冲数,那么进溢出中断的次数恰好就是转过的圈数。

来自

 

2.4 获取编码器脉冲计数值的时机

(1)循环获取计数值

利用SysTick系统定时器获取准确时间

1)调用函数

  /* 主循环 Infinite loop */

  while (1)

  {

        // 航姿参考系统算法 (AHRS)Attitude and heading reference system

        if(ahrs_task == true)

        {

            AHRS();

            Motion_PID();

            ahrs_task = false;

        }

}

 

2)SysTick系统定时器设置

定时器函数,参数初始化,设定重装值,定时1ms

SysTick 定时器的计数器是向下递减计数的,计数一次的时间 T DEC =1/CLK_AHB ,当重装

载寄存器中的值 VALUE LOAD 减到 0 的时候,产生中断,可知中断一次的时间

T_INT =VALUE LOAD *  T DEC 中断= VALUE_LOAD /CLK_AHB

72MHz的系统时钟,1ms时重装值7200

// 毫秒中断初始化 Millisecond interrupt initialization
SysTick_Int_Init();

void SysTick_Int_Init(void)

{

SYSTICK_CURRENT = 0;

SYSTICK_RELOAD = 72000;//定时时间,72k/72M~1kHz~1ms;

SYSTICK_CSR|=0x07;

}

 

SysTick 定时器中断处理函数

1mssystick_ms+1

100ms时,调用航迹姿态计算函数ahrs,读取编码器数值。

void SysTick_Handler(void)

{

    SYSTICK_CURRENT=0;  

    systick_ms++;

    

    if(systick_ms % 100 == 0)

    {

        ahrs_task = true;

    }

}

20ms时,调用PS2按键值读取函数

void handle_ps2(void)

{

    static u32 systick_ms_bak = 0;

    

    if(systick_ms - systick_ms_bak < 20)

    {

        return;

    }

    

    systick_ms_bak = systick_ms;

    psx_write_read(psx_buf);

    

    return;

}

 

(2)定时获取计数值

Z相信号,依靠定时器计算脉冲数,一段时间(10ms)读取一次编码器数值;

1)定时器中断处理读取编码器数据

定时器1溢出计数中断

void TIM1_IRQHandler(void)

{

    if(TIM_GetITStatus(TIM1,TIM_IT_Update)!=RESET)判断是否是溢出中断,是则处理

    {

        TIM_ClearITPendingBit(TIM2,TIM_ITUpdate);       /清除中断标志

        Frequency_valueTIM_GetCounter(TIM3);      /200ms

        获取TM3当前的计数值并计算,单位为Hz

        Tim_SetCounter(TIM1,0);0开始计数

    }

}

2)定时器设置

定时器初始化设置

TIM1_Int_Init(10, 72);               //10us,100Khz的计数频率,计数到1010us

TIM1_10ms_Init(1000,720);             //10ms, Tout=arr*psc/(Tclk=72M)=1000*720/72M=10ms;

TIM1_10ms_Init(4000,720);             //40ms, Tout=arr*psc/(Tclk=72M)=4000*720/72M=40ms;

    

/*** @param[in]     arr:自动重装值。psc:时钟预分频数

* @par History   这里时钟选择为APB12倍,而APB136M

*/

void TIM1_Int_Init(u16 arr,u16 psc)

{

    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

    NVIC_InitTypeDef NVIC_InitStructure;

 

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //时钟使能

    

    //定时器TIM1初始化

    TIM_TimeBaseStructure.TIM_Period = (arr-1); //设置在下一个更新事件装入活动的自动重装载寄存器周期的值    

    TIM_TimeBaseStructure.TIM_Prescaler = (psc-1); //设置用来作为TIMx时钟频率除数的预分频值

    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim   //36Mhz

    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式

    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;    //重复计数关闭

    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE ); //使能指定的TIM1中断,允许更新中断

    //中断优先级NVIC设置

    NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;  //TIM1中断

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能

    NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器

 

    TIM_Cmd(TIM1, ENABLE);  //使能TIMx                  

}

定时器中断处理函数

/**主要控制6路舵机运行,100Khz的计数频率,计数到1010us

* @param[in]     arr:自动重装值。psc:时钟预分频数

* @par History   这里时钟选择为APB12倍,而APB136M

*/

int num = 0;     /*用来计算舵机时间*/

long g_count = 0; /*用来上报数据*/

 

void TIM1_UP_IRQHandler(void)   //TIM1中断

{

    if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否

    {

        TIM_ClearITPendingBit(TIM1, TIM_IT_Update);  //清除TIMx更新中断标志

        num++;

        g_count++;

        

        if(num == 2000) //2000*10us = 20ms 周期

        {

            num = 0;

        }      

    }

}

 

(3)外部中断操作

有Z相信号,以Z相信号触发中断,精确判断一圈结束。

可考虑:外部中断函数中,执行计数器清0,圈数+1,获取定时器时间。

 

转速通过1min多少圈,或者说1S多少圈这种 有个Z相信号 一圈计一个

来自

 

自动重装值都是65535,每次读取CNT后都清零,避免溢出的情况,您的重装值是编码器线数×4,也就是每转一圈定时器清零吗?

三相编码器 A B Z的! 用Z相清零是最好的,这里可以不一定用编码器线数来!但是要比它大

 

1)外部中断初始化

//Z相归零

void EXTI_PA1_Config(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;

    EXTI_InitTypeDef EXTI_InitStructure;

 

    /* config the extiline(PA1) clock and AFIO clock */

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA| RCC_APB2Periph_AFIO,ENABLE);

    /* config the NVIC(PA1) */

    NVIC_Config(1);

 

    /* EXTI line gpio config(PA1) */    

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;      

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;    // 上拉输入

    GPIO_Init(GPIOA, &GPIO_InitStructure);

 

    /* EXTI line(PA1) mode config */

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1);

    EXTI_InitStructure.EXTI_Line = EXTI_Line1;

    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断

    EXTI_InitStructure.EXTI_LineCmd = ENABLE;

    EXTI_Init(&EXTI_InitStructure);

}

 

2)外部中断处理函数

计数器归零,重新使能定时器中断

//外部中断1,编码器Z相归零  优先级--  0 0

void EXTI1_IRQHandler(void)

{

    TIM4->CNT = 0;//每次遇到相对零(Z信号)就将计数归0

    TIM_Cmd(TIM4, ENABLE);

    EXTI_ClearITPendingBit(EXTI_Line1);  

}

 

3、读取旋转方向&计算转速

3.1 读取旋转方向

定时器CR1的DIR位

#define  TIM_CR1_DIR   ((uint16_t)0x0010)     /*!< Direction */

 

void ENCODER_Read(uint32_t *Dir, uint32_t *Cnt)

{

  *Dir = (TIM4->CR1) & TIM_CR1_DIR;//

  *Cnt = TIM_GetCounter(TIM4);

}

 

while(1)

{

    ENCODER_Read(&dir, &cnt);

    if(16==dir)dir=1;// Dir = (TIM4->CR1) & TIM_CR1_DIR;// 0x0010,16,计数器向下计数;0x0000,0,计数器向上计数

    count = TIM4->CNT/4;//获取计数值,4倍频,恢复原来编码器数值

    Last_count = count;

    if(count>Last_count);//计数值增大,编码器数值为正,递增

    else if(count//计数值减小,编码器数值为负,递减

    

    //delay_ms(100);//每隔1s打印一次编码器角度,用手去拨动编码器  使其慢速旋转

    printf("count = %d\r\n",count);

    

     GPIO_ResetBits(GPIOB, GPIO_Pin_5);

    //LED0=~LED0;

    printf("Dir = %x; Cnt = %d;\r\n", dir, cnt);

}    

 

 

3.2 计算转速

(1)每秒脉冲数/每米脉冲数

每秒脉冲数(定时器捕获) ,当转速为0.1m/s时,脉冲数:360*4*0.1/(0.065*3.14)=705 (个脉冲/s)

每米脉冲数 = 360*4/(0.065*3.14)=7051.787051(个脉冲/m);

转速= 每秒接收脉冲数(计数值,定时器获取)/每米脉冲数(7051,固定不变)= 705/7051=0.1m/s;

 

(2)定时器中断读取编码器计算速度

int num = 0;     /*用来计算时间*/

void TIM1_UP_IRQHandler(void)   //TIM1中断

{

    if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)  //检查TIM1更新中断发生与否

    {

        TIM_ClearITPendingBit(TIM1, TIM_IT_Update);  //清除TIMx更新中断标志

        num++;

        if(num == 25) //25*40ms = 1s 周期

        {

            num = 0;

        }

 

        Encoder_Left = Read_Encoder(2);

        Encoder_Right = -Read_Encoder(8);

 

        Encoder_Left_Velocity = Encoder_Left*10/7051;//计算速度,放大10

        Encoder_Right_Velocity = Encoder_Right*10/7051;//计算速度,放大10

 

        //debug

       //printf("current_enc_Left:%d\r\n",Encoder_Left);

       //printf("currenC_enc2_Right:%d\r\n",Encoder_Right);

        //printf("current_enc_Left_Velocity_X10:%d\r\n",Encoder_Left_Velocity);

        //printf("currenC_enc_Right_Velocity_X10:%d\r\n",Encoder_Right_Velocity);

 

    }

}

 

 

你可能感兴趣的:(小车,STM32)