C++实用工具与信号处理技巧
在微控制器编程中,我们常常会遇到各种重复性的问题,而C++提供了许多实用工具来帮助我们高效地解决这些问题。同时,信号处理也是微控制器编程中的重要部分,下面将详细介绍一些相关的实用工具和信号处理技巧。
1. 滤波器级联与高通滤波器
在信号处理中,滤波器的使用至关重要。通过级联滤波器可以在保证滤波质量的同时,显著降低所需的CPU功率。以下是一个使用两个滤波器
f1
和
f2
进行级联的代码示例:
f1.new_sample<5, 5, 6, 6, 5, 5>(s);
filter_type::result_type r = f1.get_result();
f2.new_sample<5, 5, 6, 6, 5, 5>(r);
std::cout << f2.get_result() << std::endl;
这个级联滤波器操作对于采样频率为4 kHz的16位架构是可接受的。在8位目标上,
new_sample()
函数的运行时间从17阶滤波器的56 µs 减少到两个级联的5阶滤波器的22 µs,CPU功率的占用率从约22%降低到了约9%。
另外,我们还可以使用高通滤波器对测试数据进行滤波。设计高通滤波器时,需要指定一些参数,例如阻带上限频率为80 Hz,衰减为40 dB,通带下限频率为600 Hz,通带波纹为1 dB。最终得到的是一个10阶、11抽头的高通滤波器,其整数系数为
(1, 2, 4, 6, 8, -40, 8, 6, 4, 2, 1)
。
2.
nothing
结构
nothing
结构是一个非常特殊的结构,它不包含任何成员,也没有任何功能。但它可以作为其他函数和模板参数的占位符,起到重要的作用。
例如,在
fixed_point
类中,构造函数在处理不同输入时会有不同的操作。对于普通的整数输入,会进行左移操作以适应固定点表示;而对于已知的整数值,如数学常数π的固定点表示,不需要进行左移。这时,
nothing
结构就可以用来区分这两种构造函数:
// A simplified Q7.8 fixed-point representation.
class fixed_point
{
public:
// Construct from integer with left-shift.
fixed_point(std::uint16_t u) : value(u << 8) { }
// Create pi with the special constructor.
static fixed_point value_pi()
{
return fixed_point(nothing(), 0x0324U);
}
private:
std::uint16_t value;
// Constructor from integer without left-shift.
fixed_point(const nothing&, std::uint16_t u) : value(u) { }
};
我们还可以使用
nothing
结构创建一个模板类
triple
,它类似于标准库中的
std::pair
,但包含三个元素:
struct nothing {};
template <typename first_type = nothing,
typename second_type = nothing,
typename third_type = nothing>
class triple
{
public:
// Constructor with default values.
triple(const first_type& t1_ = first_type(),
const second_type& t2_ = second_type(),
const third_type& t3_ = third_type())
: t1(t1_), t2(t2_), t3(t3_)
{
}
// Element access.
first_type& first() { return t1; }
second_type& second() { return t2; }
third_type& third() { return t3; }
private:
first_type t1;
second_type t2;
third_type t3;
};
3.
noncopyable
类
在微控制器编程中,有时需要禁止类对象的复制操作。
noncopyable
类可以帮助我们实现这一功能。以下是
noncopyable
类的实现:
class noncopyable
{
protected:
noncopyable() {}
~noncopyable() {}
private:
noncopyable(const noncopyable&) = delete;
const noncopyable& operator=(const noncopyable&) = delete;
};
以LED类为例,我们通常不希望LED对象被复制,因为每个LED对象应该与特定的硬件引脚关联。通过继承
noncopyable
类,可以禁止LED类的复制操作:
class led : private noncopyable
{
public:
// The led class constructor.
led(const port_type p, const bval_type b) : port(p), bval(b)
{
// ...
}
void toggle() const
{
// ...
}
private:
// Private member variables of the class.
port_type port;
bval_type bval;
};
4. 模板定时器类
定时器类在实时C++编程中非常有用,可以用于各种定时应用。以下是一个模板定时器类的定义:
template<typename unsigned_tick>
class timer
{
public:
// A class-specific tick type.
typedef unsigned_tick tick_type;
// Utility functions for creating timespans.
template<typename other>
static tick_type microseconds(const other&);
template<typename other>
static tick_type milliseconds(const other&);
template<typename other>
static tick_type seconds(const other&);
template<typename other>
static tick_type minutes(const other&);
template<typename other>
static tick_type hours(const other&);
// Constructors.
timer();
explicit timer(const tick_type&);
timer(const timer&);
// Copy assignment operator.
timer& operator=(const timer&);
// Interval and relative timeout functions.
void start_interval(const tick_type&);
void start_relative(const tick_type&);
// The timeout, now, and delay functions.
bool timeout() const;
static tick_type now();
static void blocking_delay(const tick_type&);
};
这个定时器类提供了以下操作:
- 使用
now()
查询当前时间点。
- 使用
start_relative()
设置相对超时时间。
- 使用
start_interval()
设置间隔超时时间。
- 使用
blocking_delay()
进行阻塞延迟。
例如,我们可以使用一个16位定时器设置一个250 µs 后的相对超时时间:
typedef timer<std::uint16_t> timer_type;
timer_type time(timer_type::microseconds(250U));
5. 线性插值
线性插值是一种使用线性多项式对数据点进行曲线拟合的方法,在实时微控制器编程中经常会用到。以下是线性插值的公式:
对于两点
(x0, y0)
和
(x1, y1)
,直线方程为:
[
\frac{y - y0}{x - x0} = \frac{y1 - y0}{x1 - x0}
]
求解未知值
y
可得:
[
y = y0 + (x - x0) \frac{y1 - y0}{x1 - x0}
]
以下是一个基于上述公式的线性插值模板子例程:
template<typename point_iterator,
typename x_type,
typename y_type = x_type>
y_type linear_interpolate(point_iterator pts_begin,
point_iterator pts_end,
const x_type& x,
const y_type& offset)
{
if(pts_begin == pts_end)
{
// There are no data points to interpolate.
return y_type();
}
else if(
(x <= pts_begin->x)
|| (pts_begin + 1U == pts_end))
{
// We are beneath the lower x-range or there
// is only one data point to interpolate.
return pts_begin->y;
}
else if(x >= (pts_end - 1U)->x)
{
// We are above the upper x-range.
return (pts_end - 1U)->y;
}
else
{
// Find interpolation pair with binary search.
point_iterator it
= std::lower_bound(pts_begin,
pts_end,
point<x_type>(x));
// Do the linear interpolation.
const x_type xn
= (it - 1U)->x;
const x_type delta_xn = it->x - xn;
const x_type delta_x
= x - xn;
const y_type yn
= (it - 1U)->y;
const y_type delta_yn = it->y - yn;
const y_type delta_y
= (delta_x * delta_yn) / delta_xn;
return (yn + delta_y) + offset;
}
}
使用线性插值时,需要一个支持
<
运算符的
point
类:
template<typename x_type,
typename y_type = x_type>
class point
{
public:
x_type x;
y_type y;
point(const x_type& x_ = x_type(),
const y_type& y_ = y_type()) : x(x_), y(y_) { }
bool operator<(const point& other) const
{
return (x < other.x);
}
};
以下是一个使用线性插值的示例:
// The data points.
const std::array<point<std::uint16_t>, 6U> points
{
{
point<std::uint16_t> { 0U, 0U },
point<std::uint16_t> { 10U, 44U },
point<std::uint16_t> { 20U, 53U },
point<std::uint16_t> { 30U, 28U },
point<std::uint16_t> { 40U, 22U },
point<std::uint16_t> { 50U, 47U }
}
};
const std::uint16_t y
= linear_interpolate(points.begin(),
points.end(),
std::uint16_t(15U),
std::uint16_t( 0U));
// The value of y is 48.
6. 循环缓冲区模板类
循环缓冲区是一种高效的存储队列,常用于通信接口和其他输入输出操作。以下是一个循环缓冲区模板类的实现:
template<typename T,
const std::size_t N>
class circular_buffer
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef std::size_t size_type;
typedef value_type& reference;
typedef const value_type& const_reference;
circular_buffer(
const T& value = value_type(),
const size_type count = size_type(0U))
: in_ptr (buffer),
out_ptr(buffer)
{
const size_type the_count =
(std::min)(N, count);
std::fill(in_ptr,
in_ptr + the_count,
value);
in_ptr += the_count;
}
circular_buffer(const circular_buffer& other)
: in_ptr (other.in_ptr),
out_ptr(other.out_ptr)
{
std::copy(other.buffer,
other.buffer + N,
buffer);
}
circular_buffer& operator=(
const circular_buffer& other)
{
if(this != &other)
{
in_ptr = other.in_ptr;
out_ptr = other.out_ptr;
std::copy(other.buffer,
other.buffer + N,
buffer);
}
return *this;
}
size_type capacity() const { return N; }
bool empty() const
{
return (in_ptr == out_ptr);
}
size_type size() const
{
const bool is_wrap = (in_ptr < out_ptr);
return size_type((is_wrap == false)
? size_type(in_ptr - out_ptr)
: N - size_type(out_ptr - in_ptr));
}
void clear()
{
in_ptr = buffer;
out_ptr = buffer;
}
void in(const value_type value)
{
if(in_ptr >= (buffer + N))
{
in_ptr = buffer;
}
*in_ptr = value;
++in_ptr;
}
value_type out()
{
if(out_ptr >= (buffer + N))
{
out_ptr = buffer;
}
const value_type value = *out_ptr;
++out_ptr;
return value;
}
reference front()
{
return ((out_ptr >= buffer + N)
? buffer[N - 1U]
: *out_ptr);
}
const_reference front() const
{
return ((out_ptr >= buffer + N)
? buffer[N - 1U]
: *out_ptr);
}
reference back()
{
return ((in_ptr >= buffer + N)
? buffer[N - 1U]
: *in_ptr);
}
const_reference back() const
{
return ((in_ptr >= buffer + N)
? buffer[N - 1U]
: *in_ptr);
}
private:
value_type buffer[N];
pointer in_ptr;
pointer out_ptr;
};
这个循环缓冲区类支持元素的输入和输出排队,提供了一些类似STL的成员函数,如
size()
和
empty()
。
总结
通过上述介绍,我们了解了滤波器级联、
nothing
结构、
noncopyable
类、模板定时器类、线性插值和循环缓冲区模板类等实用工具和技巧。这些工具和技巧在微控制器编程中可以帮助我们提高编程效率,解决各种实际问题。在实际应用中,我们可以根据具体需求选择合适的工具和方法。
流程图示例
graph TD;
A[开始] --> B[滤波器级联];
B --> C[使用nothing结构];
C --> D[使用noncopyable类];
D --> E[使用模板定时器类];
E --> F[进行线性插值];
F --> G[使用循环缓冲区类];
G --> H[结束];
表格示例
| 工具/技巧 | 作用 |
|---|---|
| 滤波器级联 | 降低CPU功率,保证滤波质量 |
nothing
结构
| 区分不同构造函数 |
noncopyable
类
| 禁止类对象的复制操作 |
| 模板定时器类 | 实现各种定时应用 |
| 线性插值 | 对数据点进行曲线拟合 |
| 循环缓冲区模板类 | 高效存储队列 |
7. 各工具和技巧的应用场景分析
在实际的微控制器编程中,不同的工具和技巧有着各自独特的应用场景。下面我们来详细分析一下:
7.1 滤波器级联
滤波器级联主要用于对信号进行高效滤波,同时降低CPU的功率消耗。在一些对信号处理要求较高,但硬件资源有限的场景中非常适用。例如,在音频处理设备中,需要对音频信号进行滤波以去除噪声,但设备的CPU处理能力有限,这时就可以使用滤波器级联的方式,在保证滤波效果的同时,减少CPU的负担。
7.2
nothing
结构
nothing
结构主要用于解决构造函数的歧义问题。当一个类有多个构造函数,且这些构造函数的参数类型可能会产生歧义时,
nothing
结构可以作为一个特殊的参数来区分不同的构造函数。例如,在处理固定点表示的数值时,对于不同来源的数值(需要左移和不需要左移的),可以使用
nothing
结构来区分不同的构造方式。
7.3
noncopyable
类
noncopyable
类主要用于禁止类对象的复制操作。在微控制器编程中,很多类对象与特定的硬件资源相关联,如LED类与特定的端口引脚相关联。为了避免多个对象同时操作同一个硬件资源,导致硬件状态混乱,我们可以使用
noncopyable
类来禁止类对象的复制。
7.4 模板定时器类
模板定时器类可以用于各种定时应用,如定时任务调度、延时操作等。在实时系统中,需要精确控制任务的执行时间,模板定时器类可以提供高精度的定时功能。例如,在一个自动化控制系统中,需要定时采集传感器数据,就可以使用模板定时器类来设置定时任务。
7.5 线性插值
线性插值主要用于对数据点进行曲线拟合。在传感器校准、位置数据分析等场景中,经常会遇到需要根据已知数据点来估算未知数据点的情况,这时就可以使用线性插值的方法。例如,在温度传感器校准中,根据已知的几个温度点和对应的传感器输出值,使用线性插值来估算其他温度点的传感器输出值。
7.6 循环缓冲区模板类
循环缓冲区模板类主要用于高效存储和处理数据。在通信接口、输入输出操作等场景中,需要对数据进行缓存和处理,循环缓冲区可以提供高效的存储和访问方式。例如,在SPI通信中,使用循环缓冲区来存储发送和接收的数据,提高通信效率。
8. 操作步骤总结
8.1 滤波器级联操作步骤
-
定义两个滤波器
f1和f2。 -
向
f1输入样本数据:f1.new_sample<5, 5, 6, 6, 5, 5>(s); -
获取
f1的滤波结果:filter_type::result_type r = f1.get_result(); -
将
f1的结果输入到f2中:f2.new_sample<5, 5, 6, 6, 5, 5>(r); -
获取
f2的最终结果并输出:std::cout << f2.get_result() << std::endl;
8.2 使用
nothing
结构区分构造函数步骤
-
定义
nothing结构:struct nothing {}; -
在类中定义需要区分的构造函数,一个带有
nothing类型的参数,另一个不带:
class fixed_point
{
public:
fixed_point(std::uint16_t u) : value(u << 8) { }
static fixed_point value_pi()
{
return fixed_point(nothing(), 0x0324U);
}
private:
std::uint16_t value;
fixed_point(const nothing&, std::uint16_t u) : value(u) { }
};
- 根据需要调用不同的构造函数。
8.3 使用
noncopyable
类禁止复制操作步骤
-
定义
noncopyable类:
class noncopyable
{
protected:
noncopyable() {}
~noncopyable() {}
private:
noncopyable(const noncopyable&) = delete;
const noncopyable& operator=(const noncopyable&) = delete;
};
-
让需要禁止复制的类继承
noncopyable类:
class led : private noncopyable
{
// 类的其他成员
};
8.4 使用模板定时器类进行定时操作步骤
- 定义模板定时器类:
template<typename unsigned_tick>
class timer
{
// 类的成员定义
};
-
选择合适的定时器类型,如
typedef timer<std::uint16_t> timer_type; -
设置定时时间:
timer_type time(timer_type::microseconds(250U)); -
在需要的地方检查定时器是否超时:
if(time.timeout()) { /* 执行相应操作 */ }
8.5 线性插值操作步骤
-
定义支持
<运算符的point类:
template<typename x_type,
typename y_type = x_type>
class point
{
// 类的成员定义
};
- 准备数据点:
const std::array<point<std::uint16_t>, 6U> points
{
{
point<std::uint16_t> { 0U, 0U },
point<std::uint16_t> { 10U, 44U },
point<std::uint16_t> { 20U, 53U },
point<std::uint16_t> { 30U, 28U },
point<std::uint16_t> { 40U, 22U },
point<std::uint16_t> { 50U, 47U }
}
};
- 调用线性插值函数:
const std::uint16_t y
= linear_interpolate(points.begin(),
points.end(),
std::uint16_t(15U),
std::uint16_t( 0U));
8.6 使用循环缓冲区模板类操作步骤
- 定义循环缓冲区模板类:
template<typename T,
const std::size_t N>
class circular_buffer
{
// 类的成员定义
};
- 创建循环缓冲区对象:
circular_buffer<int, 10> buffer;
-
向缓冲区中写入数据:
buffer.in(10); -
从缓冲区中读取数据:
int value = buffer.out();
9. 总结与展望
通过对滤波器级联、
nothing
结构、
noncopyable
类、模板定时器类、线性插值和循环缓冲区模板类等实用工具和技巧的介绍,我们可以看到这些工具在微控制器编程中有着广泛的应用。它们可以帮助我们提高编程效率,解决各种实际问题。
在未来的微控制器编程中,随着硬件技术的不断发展和应用场景的不断拓展,这些工具和技巧可能会得到进一步的优化和扩展。例如,滤波器级联可能会与更先进的信号处理算法相结合,提供更高质量的滤波效果;模板定时器类可能会支持更多的定时模式和更高的精度。我们需要不断学习和掌握这些工具和技巧,以适应不断变化的编程需求。
流程图示例
graph TD;
A[选择应用场景] --> B{是否需要滤波};
B -- 是 --> C[使用滤波器级联];
B -- 否 --> D{是否有构造函数歧义};
D -- 是 --> E[使用nothing结构];
D -- 否 --> F{是否禁止对象复制};
F -- 是 --> G[使用noncopyable类];
F -- 否 --> H{是否需要定时功能};
H -- 是 --> I[使用模板定时器类];
H -- 否 --> J{是否需要数据拟合};
J -- 是 --> K[进行线性插值];
J -- 否 --> L{是否需要数据缓存};
L -- 是 --> M[使用循环缓冲区类];
L -- 否 --> N[结束];
C --> N;
E --> N;
G --> N;
I --> N;
K --> N;
M --> N;
表格示例
| 应用场景 | 适用工具/技巧 |
|---|---|
| 信号滤波且资源有限 | 滤波器级联 |
| 构造函数歧义 |
nothing
结构
|
| 禁止对象复制 |
noncopyable
类
|
| 定时任务调度 | 模板定时器类 |
| 数据点曲线拟合 | 线性插值 |
| 数据缓存和处理 | 循环缓冲区模板类 |
超级会员免费看
873

被折叠的 条评论
为什么被折叠?



