Led指示灯在相邻值内闪烁,以及如何避免这种情况(嵌入式-C)



我正在设计一种在30个LED条上有可见用户输出的测量仪器。程序逻辑以这种方式工作(伪代码)

while(1)
{
(1)Sensor_Read();
(2)Transform_counts_into_leds();
(3)Send_to_bar();
{

相关函数(2)是一种简单的算法,它将I2C传感器的计数转换为串行发送到控制单个LED的移位寄存器的值。发送给函数(3)的变量只是必须保持开启的LED数量(所有LED关闭为0,所有LED开启为30)

uint8_t Transform_counts_into_leds(uint16_t counts)
{
float on_leds;
on_leds = (uint8_t)(counts * 0.134);    /*0.134 is a dummy value*/
return on_leds;
}

当计数值在两个led之间的阈值上时,使用此程序逻辑,下一个led闪烁

我认为这对我的设备来说是一种糟糕的用户体验,我希望LED一旦点亮,就能在小范围内保持稳定。

问题:如何在我的项目中实现这个问题的解决方案

滞后对许多应用程序都很有用,但我建议在这种情况下不合适。问题是,例如,如果水平真的从8下降到7,你不会看到任何变化,直到至少有一个样本在6,它会跳到6,在它回到7之前,必须有一个8的样本。

在这种情况下,一个更合适的解决方案是移动平均,尽管使用移动和并使用给出的更高分辨率更简单、更有用。例如,16的移动和有效地增加了(几乎)4位的分辨率,使8位传感器有效地增加12位——当然是以带宽为代价的;你不会白得到什么。在这种情况下,较低的带宽(即对较高频率的响应较低,这正是您所需要的)

移动总和:

#define BUFFER_LEN 16 ;
#define SUM_MAX (255 * BUFFER_LEN)
#define LED_MAX 30
uint8_t buffer[BUFFER_LEN] = {0} ;
int index = 0 ;
uint16_t sum = 0 ;
for(;;)
{
uint8_t sample = Sensor_Read() ;
// Maintain sum of buffered values by 
// subtracting oldest buffered value and 
// adding the new sample
sum -= buffer[index] ;              
sum += sample ;
// Replace oldest sample with new sample 
// and increment index to next oldest sample
buffer[index] = sample ;
index = (index + 1) % BUFFER_LEN ;
// Transform to LED bar level
int led_level = (LED_MAX * sum) / SUM_MAX ;
// Show level
setLedBar( led_level ) ;
}

底层问题——以人性化的方式显示传感器数据——非常有趣。以下是我在伪代码中的方法:

Loop:
Read sensor
If sensor outside valid range:
Enable warning LED
Sleep in a low-power state for a while
Restart loop            
Else:
Disable warning LED
Filter sensor value
Compute display value from sensor value with extra precision:
If new display value differs sufficiently from current value:
Update current displayed value
Update display with scaled-down display value

滤波处理测量中的噪声。过滤可以平滑测量中的任何突然变化,消除突然的尖峰。它就像侵蚀,把尖锐的锯齿状山脉变成连绵起伏的丘陵。

滞后隐藏了微小的变化,但不会过滤结果。滞后不会影响嘈杂或锯齿状的数据,它只隐藏微小的变化。

因此,这两种方法是独立但互补的,以不同的方式影响读数。

下面,我将描述两种不同的滤波器,以及适用于数字和条形图显示的简单滞后实现的两种变体。

如果可能的话,我建议您编写一些脚本或测试程序,输出输入数据和经过各种过滤的输出数据,并在您最喜欢的绘图程序中绘图(我的是Gnuplot)。或者,更好的是,实验!没有什么比人机界面的实际实验更好的了(至少如果你以现有的建议和已知的理论为基础,并从中向前迈进)。


移动平均线:

创建一个N传感器读数阵列,以循环方式更新它们,并使用它们的平均值作为当前读数。这产生了非常好的结果(就像人类友好、直观的结果一样),因为只有N最新的传感器读数会影响平均值。

当应用程序第一次启动时,您应该将第一次读取复制到平均数组中的所有N条目中。例如:

#define  SENSOR_READINGS  32
int sensor_reading[SENSOR_READINGS];
int sensor_reading_index;
void sensor_init(const int reading)
{
int i;
for (i = 0; i < SENSOR_READINGS; i++)
sensor_reading[i] = reading;
sensor_reading_index = 0;
}
int sensor_update(const int reading)
{
int i, sum;
sensor_reading_index = (sensor_reading_index + 1) % SENSOR_READINGS;
sensor_reading[sensor_reading_index] = reading;
sum = sensor_reading[0];
for (i = 1; i < SENSOR_READINGS; i++)
sum += sensor_reading[i];
return sum / SENSOR_READINGS;
}

启动时,使用第一个有效传感器读数调用sensor_init(),使用以下传感器读数调用CCD _5。sensor_update()将返回过滤后的结果。

当定期轮询传感器时,上述方法效果最佳,并且SENSOR_READINGS可以选择得足够大,以正确过滤传感器读数中的任何不需要的噪声。当然,阵列需要RAM,在一些微控制器中,RAM可能供不应求。


指数平滑:

当没有足够的RAM来使用移动平均值来过滤数据时,通常会应用指数平滑滤波器。

我们的想法是保持一个平均值,并使用(A * average + B * reading) / (A + B)使用每个新的传感器读数重新计算平均值。每个传感器读数对平均值的影响呈指数衰减:最新传感器读数的权重始终为B/(A+B),前一个读数的权重为A*B/(A+B)^2,前一传感器读数的权值为A^2*B/(A+B)^3,依此类推(^表示取幂);过去第CCD_ 13’个传感器读数的权重(当前为CCD_。

与上一个过滤器对应的代码现在是

#define SENSOR_AVERAGE_WEIGHT 31
#define SENSOR_CURRENT_WEIGHT  1
int sensor_reading;
void sensor_init(const int reading)
{
sensor_reading = reading;
}
int sensor_update(const int reading)
return sensor_reading = (sensor_reading * SENSOR_AVERAGE_WEIGHT +
reading * SENSOR_CURRENT_WEIGHT) /
(SENSOR_AVERAGE_WEIGHT + SENSOR_CURRENT_WEIGHT);
}

请注意,如果您选择权重,使其和为2的幂,则大多数编译器会将除法优化为简单的位偏移。


应用滞后:

(本节,包括示例代码,为清晰起见,于2016年12月22日编辑。)

适当的滞后支持包括具有比用于输出更高精度的显示值。否则,应用滞后的输出值将永远不会改变一个单位,我认为这是用户界面中的糟糕设计。(老实说,我更喜欢一个值每隔几秒钟在两个连续值之间闪烁一次——这就是我在例如我最喜欢的带有良好温度传感器的气象站中看到的。)

滞后如何应用于读数有两种典型的变体:固定和动态。固定滞后意味着每当值相差固定限度时,显示的值就会更新;动态意味着限制是动态设置的。(动态滞后要罕见得多,但与移动平均值相结合时可能非常有用;可以使用标准偏差(或误差条)来设置滞后极限,也可以根据新值是小于还是大于前一值来设置不对称极限。)

固定迟滞的实现非常简单。首先,因为我们需要将滞后应用于比输出更高的精度值,所以我们选择了一个合适的乘法器。也就是说,display_value = value / DISPLAY_MULTIPLIER,其中value是可能过滤的传感器值,而display_value是显示的整数值(例如,点亮的条数)。

请注意,下面的display_value和函数返回的值是指显示的整数值,例如点亮的LED条的数量。value是(可能经过过滤的)传感器读数,saved_value包含当前显示的传感器读数。

#define DISPLAY_HYSTERESIS 10
#define DISPLAY_MULTIPLIER 32
int saved_value;
void display_init(const int value)
{
saved_value = value;
}
int display_update(const int value)
{
const int delta = value - saved_value;
if (delta < -DISPLAY_HYSTERESIS ||
delta >  DISPLAY_HYSTERESIS)
saved_value = value;
return saved_value / DISPLAY_MULTIPLIER;
}

delta只是新传感器值与当前显示值对应的传感器值之间的差值。

此处,以显示值为单位的有效滞后为DISPLAY_HYSTERESIS/DISPLAY_MULTIPLIER = 10/32 = 0.3125。这意味着在看到可见变化之前,显示的值可以更新三次(例如,如果缓慢下降或增加;当然,如果值只是波动,则会更新更多)。这消除了两个可见值之间的快速闪烁(当值位于两个显示值的中间时),但确保读数的误差小于一半显示单位(平均值;在最坏的情况下,一半加上有效滞后)。

在实际应用程序中,通常使用更完整的形式return (saved_value * DISPLAY_SCALE + DISPLAY_OFFSET) / DISPLAY_MULTIPLIER,它按DISPLAY_SCALE/DISPLAY_MULTIPLIER缩放过滤后的传感器值,按DISPLAY_OFFSET/DISPLAY_MULTIPLIER移动零点,两者都以1.0/DISPLAY_MULTIPLIER精度进行评估,但仅使用整数运算。然而,为了简单起见,我只假设要导出显示值value,比如发光的LED条的数量,只需将传感器值除以DISPLAY_MULTIPLIER。在任何一种情况下,滞后都是输出单元的DISPLAY_HYSTERESIS/DISPLAY_MULTIPLIER。约0.1至0.5的比例工作良好;下面的测试值1032产生0.3125,这大约是我认为效果最好的比率范围的一半。

动态滞后与上述非常相似:

#define DISPLAY_MULTIPLIER 32
int saved_value_below;
int saved_value;
int saved_value_above;
void display_init(const int value, const int below, const int above)
{
saved_value_below = below;
saved_value = value;
saved_value_above = above;
}
int display_update(const int value, const int below, const int above)
{
if (value < saved_value - saved_value_below ||
value > saved_value + saved_value_above) {
saved_value_below = below;
saved_value = value;
saved_value_above = above;
}
return saved_value / DISPLAY_MULTIPLIER;
}

请注意,如果是DISPLAY_HYSTERESIS*2 <= DISPLAY_MULTIPLIER,则显示的值始终在实际(滤波)传感器值的显示单位内。换句话说,迟滞可以很容易地处理闪烁,但它不需要给显示值增加太多误差。

在许多实际情况下,施加的滞后的最佳量取决于传感器样本中的短期变化量。这不仅包括噪声,还包括要测量的信号类型。当传感器读数将滤波后的传感器值在映射到不同整数输出的两个连续整数之间翻转时,仅0.3(相对于输出单元)的滞后就足以完全消除闪烁,因为它确保了滤波后的传感器值在影响显示变化之前必须至少变化0.3(在输出显示单元中)。

滞后的最大误差是显示单位的一半加上电流滞后。半个单位是可能的最小误差(因为连续的单位相隔一个单位,所以当真实值在中间时,所示的任何一个值都在半个单位内)。对于动态滞后,如果当读数变化足够大时,你总是从某个固定的滞后值开始,但当读数在滞后范围内时,你只会减少滞后(如果大于零)。这种方法可以正确地跟踪不断变化的传感器值(最大误差为半个单位加上初始滞后),但可以尽可能准确地显示相对静态的值(在半个单位的最大误差下)。我没有展示这方面的例子,因为它添加了另一个可调的(磁滞如何向零衰减),并要求您首先验证(校准)传感器(包括任何滤波);否则,这就像打磨草皮:可能,但没有用。

还要注意,如果显示中有30个条形,则实际上有31个状态(0个条形、1个条形、..、30个条形),因此value的正确范围为031*DISPLAY_MULTIPLIER - 1(包括CCD_38和CCD_39)。

相关内容

最新更新