带霍尔编码器的 STM32F407 定时器
STM32F407 timers with hall encoders
鉴于我对 STM32 的了解,我有点不确定解决该问题的最佳方法是什么。我想用每转 6400 rising/falling 个边缘的集成霍尔编码器测量电机的速度和位置,分为两个通道(一个 CH 提供 3200 rising/falling 个边缘)。
最好的方法是什么?
问题是...我有 4 个电机要测量。
我考虑了很多选择,但我想要一个只在位置数据已知时产生中断的选项(基本上,所以我不会在每个脉冲时增加自己的位置变量,而是让计时器为我做)。
据我所知,一些计时器支持一种称为 "Encoder mode" 的模式。我不知道有关此模式的详细信息,但我希望(如果可能的话)能够在固定的时间内(比如大约 20 毫秒)计算我的速度。
在编码器模式下,是否有可能使用一个计时器来了解 rising/falling 边沿计数(我猜这将在 CNT 寄存器中)并让它每 20 毫秒触发一次中断,所以我可以将 CNT 寄存器除以 20ms 以获得 ISR 内的 count/sec 速度?
我的另一个选择是用输入捕捉直接模式计数,每个定时器有两个通道(每个电机一个),还有另一个定时器,固定周期为 20 毫秒,并计算 4 个的所有速度电机在那里。但是它需要5个定时器...
如果还有其他问题,DMA 是否可以帮助将其保持为 4 个定时器?例如,我们可以用 DMA 计数吗?
谢谢!
一个计时器可以计算一种类型的事件
它可以依靠某些外部信号(例如您的传感器)或时钟信号来计数,但不能同时依靠它们。如果你想每 20 毫秒做一些事情,你需要 一些东西 依靠稳定的时钟源。
DMA 当然可以计算它正在执行的传输,但是要让它每 20 毫秒执行一次操作,它必须以固定的时间间隔被某些东西触发。
因此,您需要第五个计时器。
幸运的是,有很多计时器可供选择
- 另外 10 个计时器
F407有14个硬件定时器。您不想使用超过 4 个,我假设其中 10 个在您的应用程序的其他地方使用。检查它们的使用情况。也许有一个依靠合适的时钟频率,并且可以生成频率适合编码器采样的中断。
- SysTick 计时器
Cortex-M 内核有一个名为 SysTick 的内部定时器。许多应用程序使用它每 1ms 产生一次中断,用于计时和其他周期性任务。如果是这种情况,您可以在每 20 个 SysTick 中断中读取编码器值 - 这具有不需要额外中断 entry/exit 开销的优点。否则你可以直接设置它每20ms产生一个中断。请注意,您不会在参考手册中找到 SysTick,它记录在 STM32F4 程序员手册中。
- 实时时钟
RTC具有周期性自动唤醒功能,每20ms产生一次中断。
- UART
除非你使用所有 6 个 UART,否则你可以将其中一个设置为非常慢的波特率,如 1000 波特,并继续传输虚拟数据(你不必为其分配物理引脚)。以 1000 波特、8 位、一个起始位和一个停止位进行传输,每 10 毫秒产生一次中断。 (它不会让你降低到 500 波特,除非你的 APB 频率低于允许的最大值)
定时器 1 和 8(高级控制定时器 - 16 位)和定时器 2 至 5(通用定时器 - 16/32 位)支持 STM32F407 上的编码器接口模式。定时器 9 到 14(也是通用的)不支持正交编码输入。
重要的是,在此模式下,计时器作为 计数器 而不是计时器运行。正交输入允许 up/down 根据方向计数,因此它将提供 相对位置 .
请注意,如果您的电机只会沿一个方向行驶,则不需要编码器模式,您可以简单地从单个通道为定时器计时,尽管这会显着降低分辨率,因此低速时的精度可能会受苦。
要确定速度,您需要计算相对位置的变化 时间。
所有 ARM Cortex-M 设备都有一个 SYSTICK 定时器,它会产生一个周期性的中断。你可以用它来计算时间。
那么你有两种可能:
- 定期读取编码器计数器,计数的变化与速度成正比(因为时间的变化是常数),
- 不定期读取编码器并计算位置变化超过时间变化
编码器接口模式的重载值是可配置的,对于这个应用程序(速度而不是位置),您应该将其设置为最大值(0xffff 或 0xffffffff),因为它使算法更简单,因为您不会处理环绕(只要它在读取之间不环绕两次)。
对于非周期性方法,假设您在 32 位模式下使用定时器 2 到 5,以下伪代码将生成以 RPM 为单位的速度,例如:
int speedRPM_Aperiodic( int timer_id )
{
int rpm = 0 ;
static struct
{
uint32_t count ;
uint32_t time ;
} previous[] = {{0,0},{0,0},{0,0},{0,0}} ;
if( timer_id < sizeof(previous) / sizeof(*previous) )
{
uint32_t current_count = getEncoderCount( timer_id ) ;
int delta_count = previous[timer_id].count - current_count ;
previous[timer_id].count = current_count ;
uint32_t current_time = getTick() ;
int delta_time = previous[timer_id].time - current_time ;
previous[timer_id].time = current_time ;
rpm = (TICKS_PER_MINUTE * delta_count) /
(delta_time * COUNTS_PER_REVOLUTION) ;
}
return rpm ;
}
该函数需要足够频繁地调用,以确保计数不会循环多次,并且调用速度不能太快以至于计数太小而无法进行准确测量。
这可以适用于周期性方法,其中 delta_time
是固定的并且非常准确(例如来自定时器中断或定时器处理程序):
int speedRPM_Periodic( int timer_id )
{
int rpm = 0 ;
uint32_t previous_count[] = {0,0,0,0} ;
if( timer_id < sizeof(previous_count) / sizeof(*previous_count) )
{
uint32_t current_count = getEncoderCount( timer_id ) ;
int delta_count = previous[timer_id].count - current_count ;
previous_count[timer_id] = current_count ;
rpm = (TICKS_PER_MINUTE * delta_count) /
(SPEED_UPDATE_TICKS * COUNTS_PER_REVOLUTION) ;
}
return rpm ;
}
此函数必须每隔 SPEED_UPDATE_TICKS
.
调用一次
非周期方法可能更容易实现,并且适用于您想要了解经过一段时间内的平均速度的应用程序。适用于更新速度相对较慢的人类可读显示器。
周期性方法更适合速度控制 应用,在这些应用中您使用反馈环路来控制电机速度。如果反馈时间不恒定,您将无法控制。
当然可以定期调用非周期函数,但在增量时间是确定性的情况下会产生不必要的开销。
鉴于我对 STM32 的了解,我有点不确定解决该问题的最佳方法是什么。我想用每转 6400 rising/falling 个边缘的集成霍尔编码器测量电机的速度和位置,分为两个通道(一个 CH 提供 3200 rising/falling 个边缘)。
最好的方法是什么?
问题是...我有 4 个电机要测量。 我考虑了很多选择,但我想要一个只在位置数据已知时产生中断的选项(基本上,所以我不会在每个脉冲时增加自己的位置变量,而是让计时器为我做)。
据我所知,一些计时器支持一种称为 "Encoder mode" 的模式。我不知道有关此模式的详细信息,但我希望(如果可能的话)能够在固定的时间内(比如大约 20 毫秒)计算我的速度。 在编码器模式下,是否有可能使用一个计时器来了解 rising/falling 边沿计数(我猜这将在 CNT 寄存器中)并让它每 20 毫秒触发一次中断,所以我可以将 CNT 寄存器除以 20ms 以获得 ISR 内的 count/sec 速度?
我的另一个选择是用输入捕捉直接模式计数,每个定时器有两个通道(每个电机一个),还有另一个定时器,固定周期为 20 毫秒,并计算 4 个的所有速度电机在那里。但是它需要5个定时器...
如果还有其他问题,DMA 是否可以帮助将其保持为 4 个定时器?例如,我们可以用 DMA 计数吗?
谢谢!
一个计时器可以计算一种类型的事件
它可以依靠某些外部信号(例如您的传感器)或时钟信号来计数,但不能同时依靠它们。如果你想每 20 毫秒做一些事情,你需要 一些东西 依靠稳定的时钟源。
DMA 当然可以计算它正在执行的传输,但是要让它每 20 毫秒执行一次操作,它必须以固定的时间间隔被某些东西触发。
因此,您需要第五个计时器。
幸运的是,有很多计时器可供选择
- 另外 10 个计时器
F407有14个硬件定时器。您不想使用超过 4 个,我假设其中 10 个在您的应用程序的其他地方使用。检查它们的使用情况。也许有一个依靠合适的时钟频率,并且可以生成频率适合编码器采样的中断。
- SysTick 计时器
Cortex-M 内核有一个名为 SysTick 的内部定时器。许多应用程序使用它每 1ms 产生一次中断,用于计时和其他周期性任务。如果是这种情况,您可以在每 20 个 SysTick 中断中读取编码器值 - 这具有不需要额外中断 entry/exit 开销的优点。否则你可以直接设置它每20ms产生一个中断。请注意,您不会在参考手册中找到 SysTick,它记录在 STM32F4 程序员手册中。
- 实时时钟
RTC具有周期性自动唤醒功能,每20ms产生一次中断。
- UART
除非你使用所有 6 个 UART,否则你可以将其中一个设置为非常慢的波特率,如 1000 波特,并继续传输虚拟数据(你不必为其分配物理引脚)。以 1000 波特、8 位、一个起始位和一个停止位进行传输,每 10 毫秒产生一次中断。 (它不会让你降低到 500 波特,除非你的 APB 频率低于允许的最大值)
定时器 1 和 8(高级控制定时器 - 16 位)和定时器 2 至 5(通用定时器 - 16/32 位)支持 STM32F407 上的编码器接口模式。定时器 9 到 14(也是通用的)不支持正交编码输入。
重要的是,在此模式下,计时器作为 计数器 而不是计时器运行。正交输入允许 up/down 根据方向计数,因此它将提供 相对位置 .
请注意,如果您的电机只会沿一个方向行驶,则不需要编码器模式,您可以简单地从单个通道为定时器计时,尽管这会显着降低分辨率,因此低速时的精度可能会受苦。
要确定速度,您需要计算相对位置的变化 时间。
所有 ARM Cortex-M 设备都有一个 SYSTICK 定时器,它会产生一个周期性的中断。你可以用它来计算时间。
那么你有两种可能:
- 定期读取编码器计数器,计数的变化与速度成正比(因为时间的变化是常数),
- 不定期读取编码器并计算位置变化超过时间变化
编码器接口模式的重载值是可配置的,对于这个应用程序(速度而不是位置),您应该将其设置为最大值(0xffff 或 0xffffffff),因为它使算法更简单,因为您不会处理环绕(只要它在读取之间不环绕两次)。
对于非周期性方法,假设您在 32 位模式下使用定时器 2 到 5,以下伪代码将生成以 RPM 为单位的速度,例如:
int speedRPM_Aperiodic( int timer_id )
{
int rpm = 0 ;
static struct
{
uint32_t count ;
uint32_t time ;
} previous[] = {{0,0},{0,0},{0,0},{0,0}} ;
if( timer_id < sizeof(previous) / sizeof(*previous) )
{
uint32_t current_count = getEncoderCount( timer_id ) ;
int delta_count = previous[timer_id].count - current_count ;
previous[timer_id].count = current_count ;
uint32_t current_time = getTick() ;
int delta_time = previous[timer_id].time - current_time ;
previous[timer_id].time = current_time ;
rpm = (TICKS_PER_MINUTE * delta_count) /
(delta_time * COUNTS_PER_REVOLUTION) ;
}
return rpm ;
}
该函数需要足够频繁地调用,以确保计数不会循环多次,并且调用速度不能太快以至于计数太小而无法进行准确测量。
这可以适用于周期性方法,其中 delta_time
是固定的并且非常准确(例如来自定时器中断或定时器处理程序):
int speedRPM_Periodic( int timer_id )
{
int rpm = 0 ;
uint32_t previous_count[] = {0,0,0,0} ;
if( timer_id < sizeof(previous_count) / sizeof(*previous_count) )
{
uint32_t current_count = getEncoderCount( timer_id ) ;
int delta_count = previous[timer_id].count - current_count ;
previous_count[timer_id] = current_count ;
rpm = (TICKS_PER_MINUTE * delta_count) /
(SPEED_UPDATE_TICKS * COUNTS_PER_REVOLUTION) ;
}
return rpm ;
}
此函数必须每隔 SPEED_UPDATE_TICKS
.
非周期方法可能更容易实现,并且适用于您想要了解经过一段时间内的平均速度的应用程序。适用于更新速度相对较慢的人类可读显示器。
周期性方法更适合速度控制 应用,在这些应用中您使用反馈环路来控制电机速度。如果反馈时间不恒定,您将无法控制。
当然可以定期调用非周期函数,但在增量时间是确定性的情况下会产生不必要的开销。