用8051微控制器进行频率测量
Frequency measurement with 8051 microcontroller
我只是想用比较器输入(在下降沿)连续计算正弦信号的频率。有效目标频率约为 ~122 Hz,我的实现大部分时间都能正常工作,但有时它会计算出错误的频率,始终约为 ~61Hz(这是不可能的,我用示波器验证了这一点)。
我的实现似乎有一个弱点,可能是竞争条件或误用计时器的形式,因为它使用并发中断服务例程并手动启动和停止计时器。
我还认为该错误与约 ~122Hz 的测量频率相关,因为一个定时器溢出几乎相同:
One Timer Overflow = 1 / (1/8 MHz * 2^16 [Bits]) = 122.0703125 Hz
我正在使用带有以下代码的 8051 微控制器 (Silicon Labs C8051F121):
// defines
#define PERIOD_TIMER_FREQ 8000000.0 // Timer 3 runs at 8MHz
#define TMR3_PAGE 0x01 /* TIMER 3 */
#define CP1F_VECTOR 12 /* comparator 1 falling edge */
#define TF3_VECTOR 14 /* timer3 reload timer */
sfr TMR3CN = 0xC8; /* TIMER 3 CONTROL */
sfr TMR3L = 0xCC; /* TIMER 3 LOW BYTE */
sfr TMR3H = 0xCD; /* TIMER 3 HIGH BYTE */
// global variables
volatile unsigned int xdata timer3_overflow_tmp; // temporary counter for the current period
volatile unsigned int xdata timer3_lastValue; // snapshot of the last timer value
volatile unsigned int xdata timer3_overflow; // current overflow counter, used in the main routine
volatile unsigned int xdata tempVar; // temporary variable
volatile unsigned long int xdata period; // the caluclated period
volatile float xdata period_in_SI; // calculated period in seconds
volatile float xdata frequency; // calculated frequency in Hertz
// Comparator 1 ISR has priority "high": EIP1 = 0x40
void comp1_falling_isr (void) interrupt CP1F_VECTOR
{
SFRPAGE = TMR3_PAGE;
TMR3CN &= 0xfb; // stop timer 3
timer3_lastValue = (unsigned int) TMR3H;
timer3_lastValue <<= 8;
timer3_lastValue |= (unsigned int) TMR3L;
// check if timer 3 overflow is pending
if (TMR3CN & 0x80)
{
timer3_overflow_tmp++; // increment overflow counter
TMR3CN &= 0x7f; // Clear over flow flag. This will also clear a pending interrupt request.
}
timer3_overflow = timer3_overflow_tmp;
// Reset all the timer 3 values to zero
TMR3H = 0;
TMR3L = 0;
timer3_overflow_tmp = 0;
TMR3CN |= 0x04; // restart timer 3
}
// Timer 3 ISR has priority "low", which means it can be interrupted by the
// comparator ISR: EIP2 = 0x00
// Timer 3 runs at 8MHz in 16 bit auto-reload mode
void timer3_isr(void) interrupt TF3_VECTOR using 2
{
SFRPAGE = TMR3_PAGE;
timer3_overflow_tmp++;
TMR3CN &= 0x7f; // Clear over flow flag. This will also clear a pending interrupt request.
}
void main(void)
{
for(;;)
{
...
calcFrequencyLabel: // this goto label is a kind of synchronization mechanism
// and is used to prevent race conditions caused by the ISRs
// which invalidates the current copied timer values
tempVar = timer3_lastValue;
period = (unsigned long int)timer3_overflow;
period <<= 16;
period |= (unsigned long int)timer3_lastValue;
// If both values are not equal, a race condition has been occured.
// Therefore the the current time values are invalid and needs to be dropped.
if (tempVar != timer3_lastValue)
goto calcFrequencyLabel;
// Caluclate period in seconds
period_in_SI = (float) period / PERIOD_TIMER_FREQ;
// Caluclate period in Hertz
frequency = 1 / period_in_SI; // Should be always stable about ~122Hz
...
}
}
有人可以帮我找出实施中的错误吗?
我无法查明特定错误,但您在这段代码中遇到了一些问题。
主要问题是 8051 不是 PC,而是成为主流的最可怕的 8 位 MCU。这意味着您应该极力避免使用 32 位整数和浮点数之类的东西。如果你反汇编这段代码,你就会明白我的意思。
这里绝对没有理由需要使用浮点数。 32 位变量也可以避免。您应该尽可能使用 uint8_t
并避免使用 unsigned int
。您的 C 代码不需要知道以秒为单位的时间或以 Hz 为单位的频率,而只需要关心定时器周期数。
您有多个竞争条件错误。您的 goto
hack in main 是一个肮脏的解决方案 - 相反,您应该首先防止竞争条件的发生。 timer3_overflow_tmp
.
的 ISR 之间还有另一个竞争条件
ISR 和 main
之间或两个具有不同优先级的不同 ISR 之间共享的每个变量都必须防止竞争条件。这意味着您必须确保原子访问或使用某种方式的保护机制。在这种情况下,您可能只使用 bool 标志。另一种选择是更改为 8 位变量并在内联汇编程序中编写访问它的代码。通常,您不能对 8 位内核上的 unsigned int
进行原子访问。
对于低频正弦和输入滞后不足的慢边沿(默认为none),只需要一点点上升沿的噪声看起来像下降沿并导致频率减半。
代码片段不包含设置滞后的CPT1CN
设置。对于您的信号,您可能需要将其最大化,并确保信号的峰峰值噪声小于 30mV。
我只是想用比较器输入(在下降沿)连续计算正弦信号的频率。有效目标频率约为 ~122 Hz,我的实现大部分时间都能正常工作,但有时它会计算出错误的频率,始终约为 ~61Hz(这是不可能的,我用示波器验证了这一点)。
我的实现似乎有一个弱点,可能是竞争条件或误用计时器的形式,因为它使用并发中断服务例程并手动启动和停止计时器。
我还认为该错误与约 ~122Hz 的测量频率相关,因为一个定时器溢出几乎相同:
One Timer Overflow = 1 / (1/8 MHz * 2^16 [Bits]) = 122.0703125 Hz
我正在使用带有以下代码的 8051 微控制器 (Silicon Labs C8051F121):
// defines
#define PERIOD_TIMER_FREQ 8000000.0 // Timer 3 runs at 8MHz
#define TMR3_PAGE 0x01 /* TIMER 3 */
#define CP1F_VECTOR 12 /* comparator 1 falling edge */
#define TF3_VECTOR 14 /* timer3 reload timer */
sfr TMR3CN = 0xC8; /* TIMER 3 CONTROL */
sfr TMR3L = 0xCC; /* TIMER 3 LOW BYTE */
sfr TMR3H = 0xCD; /* TIMER 3 HIGH BYTE */
// global variables
volatile unsigned int xdata timer3_overflow_tmp; // temporary counter for the current period
volatile unsigned int xdata timer3_lastValue; // snapshot of the last timer value
volatile unsigned int xdata timer3_overflow; // current overflow counter, used in the main routine
volatile unsigned int xdata tempVar; // temporary variable
volatile unsigned long int xdata period; // the caluclated period
volatile float xdata period_in_SI; // calculated period in seconds
volatile float xdata frequency; // calculated frequency in Hertz
// Comparator 1 ISR has priority "high": EIP1 = 0x40
void comp1_falling_isr (void) interrupt CP1F_VECTOR
{
SFRPAGE = TMR3_PAGE;
TMR3CN &= 0xfb; // stop timer 3
timer3_lastValue = (unsigned int) TMR3H;
timer3_lastValue <<= 8;
timer3_lastValue |= (unsigned int) TMR3L;
// check if timer 3 overflow is pending
if (TMR3CN & 0x80)
{
timer3_overflow_tmp++; // increment overflow counter
TMR3CN &= 0x7f; // Clear over flow flag. This will also clear a pending interrupt request.
}
timer3_overflow = timer3_overflow_tmp;
// Reset all the timer 3 values to zero
TMR3H = 0;
TMR3L = 0;
timer3_overflow_tmp = 0;
TMR3CN |= 0x04; // restart timer 3
}
// Timer 3 ISR has priority "low", which means it can be interrupted by the
// comparator ISR: EIP2 = 0x00
// Timer 3 runs at 8MHz in 16 bit auto-reload mode
void timer3_isr(void) interrupt TF3_VECTOR using 2
{
SFRPAGE = TMR3_PAGE;
timer3_overflow_tmp++;
TMR3CN &= 0x7f; // Clear over flow flag. This will also clear a pending interrupt request.
}
void main(void)
{
for(;;)
{
...
calcFrequencyLabel: // this goto label is a kind of synchronization mechanism
// and is used to prevent race conditions caused by the ISRs
// which invalidates the current copied timer values
tempVar = timer3_lastValue;
period = (unsigned long int)timer3_overflow;
period <<= 16;
period |= (unsigned long int)timer3_lastValue;
// If both values are not equal, a race condition has been occured.
// Therefore the the current time values are invalid and needs to be dropped.
if (tempVar != timer3_lastValue)
goto calcFrequencyLabel;
// Caluclate period in seconds
period_in_SI = (float) period / PERIOD_TIMER_FREQ;
// Caluclate period in Hertz
frequency = 1 / period_in_SI; // Should be always stable about ~122Hz
...
}
}
有人可以帮我找出实施中的错误吗?
我无法查明特定错误,但您在这段代码中遇到了一些问题。
主要问题是 8051 不是 PC,而是成为主流的最可怕的 8 位 MCU。这意味着您应该极力避免使用 32 位整数和浮点数之类的东西。如果你反汇编这段代码,你就会明白我的意思。
这里绝对没有理由需要使用浮点数。 32 位变量也可以避免。您应该尽可能使用 uint8_t
并避免使用 unsigned int
。您的 C 代码不需要知道以秒为单位的时间或以 Hz 为单位的频率,而只需要关心定时器周期数。
您有多个竞争条件错误。您的 goto
hack in main 是一个肮脏的解决方案 - 相反,您应该首先防止竞争条件的发生。 timer3_overflow_tmp
.
的 ISR 之间还有另一个竞争条件
ISR 和 main
之间或两个具有不同优先级的不同 ISR 之间共享的每个变量都必须防止竞争条件。这意味着您必须确保原子访问或使用某种方式的保护机制。在这种情况下,您可能只使用 unsigned int
进行原子访问。
对于低频正弦和输入滞后不足的慢边沿(默认为none),只需要一点点上升沿的噪声看起来像下降沿并导致频率减半。
代码片段不包含设置滞后的CPT1CN
设置。对于您的信号,您可能需要将其最大化,并确保信号的峰峰值噪声小于 30mV。