我试图使用ATMega328制作一个简单的RPM仪表。
我的电机上有一个编码器,每次旋转有 306 中断(因为电机编码器有3个辐条,它们在上升沿和下降沿中断,电机齿轮传动51:1等6个转换* 51 =每轮转动306次中断),我每隔1ms使用一次计时器中断,但是在中断时它设置为每1秒重新计算一次。
似乎有两个问题。 1)RPM永远不会低于60,而是0或RPM> = 60 2)缩短时间间隔使其始终为0(据我所知)
这是代码
int main(void){
while(1){
int temprpm = leftRPM;
printf("Revs: %d \n",temprpm);
_delay_ms(50);
};
return 0;
}
ISR (INT0_vect){
ticksM1++;
}
ISR(TIMER0_COMPA_vect){
counter++;
if(counter == 1000){
int tempticks = ticksM1;
leftRPM = ((tempticks - lastM1)/306)*1*60;
lastM1 = tempticks;
counter = 0;
}
}
在该代码中未声明的任何内容都是全局声明的,并且作为int,ticksM1也是不稳定的。
宏是中断的AVR宏。
leftRPM
乘以1的目的代表时间,理想情况下我想在没有if语句的情况下使用1ms,因此1将为1000
答案 0 :(得分:3)
对于60到120 RPM之间的速度,((tempticks - lastM1)/306)
的结果将为1且低于60 RPM将为零。您的输出将始终是60的倍数
我建议的第一个改进是不要在ISR中执行昂贵的算术。没有必要 - 以原始每秒计数速度存储速度,并仅转换为RPM以供显示。
其次,在除法之前执行乘法以避免不必要地丢弃信息。然后例如在60RPM(306CPS),你有(306 * 60) / 306 == 60
。即使低至1RPM,您也会获得(6 * 60) / 306 == 1
。事实上,它为您提供了大约0.2RPM的潜在分辨率,而不是60RPM!允许容易维护参数;我建议使用符号常量而不是幻数。
#define ENCODER_COUNTS_PER_REV 306
#define MILLISEC_PER_SAMPLE 1000
#define SAMPLES_PER_MINUTE ((60 * 1000) / MILLISEC_PER_SAMPLE)
ISR(TIMER0_COMPA_vect){
counter++;
if(counter == MILLISEC_PER_SAMPLE)
{
int tempticks = ticksM1;
leftCPS = tempticks - lastM1 ;
lastM1 = tempticks;
counter = 0;
}
}
然后在main()
:
int temprpm = (leftCPS * SAMPLES_PER_MINUTE) / ENCODER_COUNTS_PER_REV ;
如果你想要更好的1RPM分辨率,你可以考虑
int temprpm_x10 = (leftCPS * SAMPLES_PER_MINUTE) / (ENCODER_COUNTS_PER_REV / 10) ;
然后显示:
printf( "%d.%d", temprpm / 10, temprpm % 10 ) ;
考虑到通过这种方法可能达到0.2转/分钟的分辨率,更高分辨率的显示是不必要的,尽管你可以使用移动平均值来提高分辨率,而牺牲了一些“显示 - 延迟”#34;
或者现在,在ISR中不再计算RPM,您可能会提供浮点运算:
float temprpm = ((float)leftCPS * (float)SAMPLES_PER_MINUTE ) / (float)ENCODER_COUNTS_PER_REV ;
printf( "%f", temprpm ) ;
另一个潜在的问题是ticksM1++
和tempticks = ticksM1
,并且leftRPM
(或我的解决方案中的leftCPS
)的读取不是原子操作,并且可能导致如果支持中断嵌套,则读取不正确的值(即使不是从中断上下文外部访问的情况)。如果最大速率低于256 cps(42RPM),那么你可能会使用原子8位计数器;您可以选择减少采样周期以确保计数始终小于256.如果最简单的解决方案是在读取或更新在中断和线程上下文之间共享的非原子变量时禁用中断。
答案 1 :(得分:2)
它是整数除法。你可能会得到更好的结果:
leftRPM = ((tempticks - lastM1)/6);