基于RT-Thread的STM32开发第十一讲——编码器模式

目录

前言

一、工程创建

二、工程完善

[2.1 board.c](#2.1 board.c)

[2.2 board.h](#2.2 board.h)

[2.3 drv_pulse_encoder.c](#2.3 drv_pulse_encoder.c)

[2.4 pulse_encoder_config.h](#2.4 pulse_encoder_config.h)

三、代码编写

[3.1 encoder.h](#3.1 encoder.h)

[3.2 encoder.c](#3.2 encoder.c)

[3.3 main.c](#3.3 main.c)

四、结果展示


前言

对于很多电机都带有速度反馈,速度信号是依靠编码器输出A、B两相相位相差90度的方波信号来反馈的,本文介绍如何使用RT-Thread studio来实现编码器输出信号的测量的。

使用的芯片是STM32F407ZGT6,rt_thread的版本是5.2.2


一、工程创建

最近RT-Thread studio迎来的2.3.0版本。SDK管理器添加了很多的更新,祝愿国产嵌入式操作系统越来越好。

在rt_thread的版本5.2.2中对于STM32系列芯片的工程创建还有很多警告要处理,几乎都是函数未命名和类型不符合,这种情况之前的版本就存在,可能官方遗漏了。

修改方式很简单,方法和《RT-Thread studio的驱动5.1.0报错修改》的修改方式相同。

修改完成后去cubemx配置定时器的Encoder mode。配置方法可以参考《STM32LL库编程系列第六讲------定时器编码器模式+电机驱动》这篇文章。

接着去RT-Thread Settings开启脉冲编码器设备驱动

这样初始工程就创建成功了。

二、工程完善

pulse_encoder整体的工作模式如下:

先配置一个定时器在Encoder mode,它会根据A、B相编码器信号计数。再配置另一个定时器定时。比如说TIM2负责定时,500ms中断一次,TIM3负责编码器信号计数。当进入TIM2更新中断后读取TIM3的计数值,再根据编码器的精度计算成电机转速。然后清空TIM3的计数值,退出中断,等待下个中断到来。

上面的工作模式要求再500ms内TIM3的计数值不能超过预重装载值,否则读数就不对。下面按这个思路进行配置。

2.1 board.c

和其他设备初始化一样,首先在board.c的末尾添加IO复用函数,我这里是使用的是定时器TIM3

cpp 复制代码
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(tim_encoderHandle->Instance==TIM3)
  {
    /* TIM3 clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**TIM3 GPIO Configuration
    PA6     ------> TIM3_CH1
    PA7     ------> TIM3_CH2
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

void HAL_TIM_Encoder_MspDeInit(TIM_HandleTypeDef* tim_encoderHandle)
{

  if(tim_encoderHandle->Instance==TIM3)
  {
    /* Peripheral clock disable */
    __HAL_RCC_TIM3_CLK_DISABLE();

    /**TIM3 GPIO Configuration
    PA6     ------> TIM3_CH1
    PA7     ------> TIM3_CH2
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_6|GPIO_PIN_7);
  }
}

2.2 board.h

官方没有预定义pulse_encoder的宏,我们按其他设备的风格自行定义如下,位置放哪都行。

cpp 复制代码
/*-------------------------- PULSE ENCODER CONFIG BEGIN --------------------------*/
#ifdef RT_USING_PULSE_ENCODER
// if using TIMx: #define BSP_USING_PULSE_ENCODERx
#define BSP_USING_PULSE_ENCODER3
#endif

/*-------------------------- PULSE ENCODER CONFIG END --------------------------*/

2.3 drv_pulse_encoder.c

文件位于drivers文件夹下。先找到初始化函数pulse_encoder_init

注意项如下:

  • 预重装值Period预定为AUTO_RELOAD_VALUE = 0x7fff。可更改为当前定时器允许的最大值,防止计数值达到Period。
  • AutoReloadPreload从ENABLE一般改为DISABLE,代表不自动重装载Period,因为我们不允许计数值达到Period
  • IC1Filter和IC2Filter是滤波器值,具体含义参考推荐文章 STM32LL库编程系列第六讲------定时器编码器模式+电机驱动》。如果电机工作时转速过快,可以进行滤波设置,最后计算时计数值要乘以相应滤波值。设置为0代表不滤波。

下面是定时器中断初始化,我们使用pulse_encoder模式,是不开定时器中断的。官方使能了更新中断一般是没关系的,因为不允许计数值达到Period,也就不会触发更新中断。

但是有个问题是TIM2不知道什么原因会频繁进入更新中断,导致程序无法卡死在这。相同的配置下TIM1和TIM3都不会产生这个问题,其他定时器没测试不知道是否也会有这个情况。要解决的话就把下面的函数注释掉,这样TIM2也就正常了。

cpp 复制代码
        HAL_NVIC_SetPriority(stm32_device->encoder_irqn, 3, 0);

        /* enable the TIMx global Interrupt */
        HAL_NVIC_EnableIRQ(stm32_device->encoder_irqn);

        /* clear update flag */
        __HAL_TIM_CLEAR_FLAG(&stm32_device->tim_handler, TIM_FLAG_UPDATE);
        /* enable update request source */
        __HAL_TIM_URS_ENABLE(&stm32_device->tim_handler);

2.4 pulse_encoder_config.h

文件位于: drivers\include\config\pulse_encoder_config.h

这里是定时器的设备绑定,我这只有TIM1~4,如果使用的是其他定时器,就按相同配置添加。

注意:

有些低型号芯片本身也具有编码器模式,当系统创建的工程没有drv_pulse_encoder.c和pulse_encoder_config.h文件导致无法使用。需要我们去自行添加

在RT-ThreadStudio的安装目录找到HAL_Drivers文件夹,类似地址如下
F:\RT-ThreadStudio\repo\Extract\RT-Thread_Source_Code\RT-Thread\5.2.2\bsp\stm32\libraries\HAL_Drivers

找到后复制文件drv_pulse_encoder.c和pulse_encoder_config.h到工程相应的文件夹下,这样就可以了。

三、代码编写

下面就比较简单了,跟着官方文档《Pulse Encoder 设备》去做就行。自行创建文件encoder.c和encoder.h

3.1 encoder.h

cpp 复制代码
#ifndef APPLICATIONS_ENCODER_H_
#define APPLICATIONS_ENCODER_H_
#include <board.h>

extern rt_device_t pulse_encoder_dev;
void encoder_init(void);

#endif /* APPLICATIONS_ENCODER_H_ */

3.2 encoder.c

cpp 复制代码
#include "encoder.h"

#define DBG_TAG "encoder"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

#define PULSE_ENCODER_DEV_NAME    "pulse3"    /* 脉冲编码器名称 */
rt_device_t pulse_encoder_dev;                /* 脉冲编码器设备句柄 */

void encoder_init(void)
{
    uint8_t flag;
    uint32_t arg = 0;
    pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME);
    if(pulse_encoder_dev == 0)
    {
        LOG_D("pulse encoder find failed");
    }
    flag = rt_device_open(pulse_encoder_dev, RT_DEVICE_OFLAG_RDONLY);
    if(flag == RT_EOK)
    {
        LOG_D("pulse encoder open succeed");
    }
    flag = rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_GET_TYPE, &arg);
    if(flag == RT_EOK){
        LOG_D("%d",arg);
    }
    rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_ENABLE, RT_NULL);
    rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_CLEAR_COUNT, RT_NULL);
}

细节说明:

官方介绍rt_device_control有四个功能如下:

在rt-thread\components\drivers\misc\pulse_encoder.c中的函数rt_pulse_encoder_control负责对这些控制字返回处理。

cpp 复制代码
static rt_err_t rt_pulse_encoder_control(struct rt_device *dev, int cmd, void *args)
{
    rt_err_t result;
    struct rt_pulse_encoder_device *pulse_encoder;

    result = RT_EOK;
    pulse_encoder = (struct rt_pulse_encoder_device *)dev;
    switch (cmd)
    {
    case PULSE_ENCODER_CMD_CLEAR_COUNT:
        result = pulse_encoder->ops->clear_count(pulse_encoder);
        break;
    case PULSE_ENCODER_CMD_GET_TYPE:
        *(enum rt_pulse_encoder_type *)args = pulse_encoder->type;
        break;
    case PULSE_ENCODER_CMD_ENABLE:
    case PULSE_ENCODER_CMD_DISABLE:
        result = pulse_encoder->ops->control(pulse_encoder, cmd, args);
        break;
    default:
        result = -RT_ENOSYS;
        break;
    }

    return result;
}

当想要获取脉冲编码器类型,可以看到始终会返回宏AB_PHASE_PULSE_ENCODER。(函数位于drv_pulse_encoder.c)

cpp 复制代码
int hw_pulse_encoder_init(void)
{
    int i;
    int result;

    result = RT_EOK;
    for (i = 0; i < sizeof(stm32_pulse_encoder_obj) / sizeof(stm32_pulse_encoder_obj[0]); i++)
    {
        stm32_pulse_encoder_obj[i].pulse_encoder.type = AB_PHASE_PULSE_ENCODER;
        stm32_pulse_encoder_obj[i].pulse_encoder.ops = &_ops;

        if (rt_device_pulse_encoder_register(&stm32_pulse_encoder_obj[i].pulse_encoder, stm32_pulse_encoder_obj[i].name, RT_NULL) != RT_EOK)
        {
            LOG_E("%s register failed", stm32_pulse_encoder_obj[i].name);
            result = -RT_ERROR;
        }
    }

    return result;
}

宏位于rt-thread\components\drivers\include\drivers\pulse_encoder.h中,分别为0、1、2对应不知、单相、双相。

cpp 复制代码
enum rt_pulse_encoder_type
{
    UNKNOWN_PULSE_ENCODER_TYPE = 0x00,    /* Unknown pulse_encoder type */
    SINGLE_PHASE_PULSE_ENCODER,           /* single phase pulse_encoder */
    AB_PHASE_PULSE_ENCODER                /* two phase pulse_encoder */
};

所以获取脉冲编码器类型这个控制字没什么用,如下使用,arg就打印数字2。

cpp 复制代码
    
    uint8_t flag;
    uint32_t arg = 0;
    flag = rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_GET_TYPE, &arg);
    if(flag == RT_EOK){
        LOG_D("%d",arg);
    }

3.3 main.c

main函数每500ms打印一次计数值,接着清空计数值。实际中这500ms要用定时中断产生才够准确。我这测试就无所谓了。

cpp 复制代码
#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

#include "encoder.h"
int main(void)
{
    encoder_init();
    rt_int32_t count = 0;

    while (1)
    {
        rt_thread_mdelay(500);
        rt_device_read(pulse_encoder_dev, 0, &count, 1);
        rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_CLEAR_COUNT, RT_NULL);
        rt_kprintf("get count %d\n",count);
    }

    return RT_EOK;
}

四、结果展示

我这没接入编码器设备,所以数据就是0了。这么看,初步是没问题,后面如果遇到对应工程再补充实际的测量数据。

相关推荐
一路往蓝-Anbo12 小时前
第五篇:硬件接口的生死劫 —— GPIO 唤醒与测量陷阱
c语言·驱动开发·stm32·单片机·嵌入式硬件
Y1rong14 小时前
STM32之时钟
stm32·单片机·嵌入式硬件
斌蔚司李15 小时前
Windows 电源高级选项
windows·stm32·单片机
YouEmbedded17 小时前
解码按键检测、Systick 定时器
stm32·systick·pll·时钟树·按键检测·时钟源·状态机按键检测
ting_zh1 天前
定时器输出PWM信号同步控制传感器开关与 ADC 采样
stm32·tim·pwm·adc
锻炼²1 天前
USB 设备/配置/接口/端点/描述符 和 HID类请求详解
stm32·usb·hid·全速传输·sof包·中断传输
小何code1 天前
STM32入门教程,第10课(下),Keil调试模式
stm32·单片机·嵌入式硬件
Y1rong1 天前
STM32之中断
stm32·单片机·嵌入式硬件
先知后行。2 天前
STM32F103的启动过程
stm32·单片机·嵌入式硬件