目录
[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了。这么看,初步是没问题,后面如果遇到对应工程再补充实际的测量数据。