29、C++实用工具与信号处理技巧

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 滤波器级联操作步骤
  1. 定义两个滤波器 f1 f2
  2. f1 输入样本数据: f1.new_sample<5, 5, 6, 6, 5, 5>(s);
  3. 获取 f1 的滤波结果: filter_type::result_type r = f1.get_result();
  4. f1 的结果输入到 f2 中: f2.new_sample<5, 5, 6, 6, 5, 5>(r);
  5. 获取 f2 的最终结果并输出: std::cout << f2.get_result() << std::endl;
8.2 使用 nothing 结构区分构造函数步骤
  1. 定义 nothing 结构: struct nothing {};
  2. 在类中定义需要区分的构造函数,一个带有 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) { }
};
  1. 根据需要调用不同的构造函数。
8.3 使用 noncopyable 类禁止复制操作步骤
  1. 定义 noncopyable 类:
class noncopyable
{
protected:
    noncopyable() {}
    ~noncopyable() {}
private:
    noncopyable(const noncopyable&) = delete;
    const noncopyable& operator=(const noncopyable&) = delete;
};
  1. 让需要禁止复制的类继承 noncopyable 类:
class led : private noncopyable
{
    // 类的其他成员
};
8.4 使用模板定时器类进行定时操作步骤
  1. 定义模板定时器类:
template<typename unsigned_tick>
class timer
{
    // 类的成员定义
};
  1. 选择合适的定时器类型,如 typedef timer<std::uint16_t> timer_type;
  2. 设置定时时间: timer_type time(timer_type::microseconds(250U));
  3. 在需要的地方检查定时器是否超时: if(time.timeout()) { /* 执行相应操作 */ }
8.5 线性插值操作步骤
  1. 定义支持 < 运算符的 point 类:
template<typename x_type,
         typename y_type = x_type>
class point
{
    // 类的成员定义
};
  1. 准备数据点:
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 }
    }
};
  1. 调用线性插值函数:
const std::uint16_t y
    = linear_interpolate(points.begin(),
                         points.end(),
                         std::uint16_t(15U),
                         std::uint16_t( 0U));
8.6 使用循环缓冲区模板类操作步骤
  1. 定义循环缓冲区模板类:
template<typename T,
         const std::size_t N>
class circular_buffer
{
    // 类的成员定义
};
  1. 创建循环缓冲区对象:
circular_buffer<int, 10> buffer;
  1. 向缓冲区中写入数据: buffer.in(10);
  2. 从缓冲区中读取数据: 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
定时任务调度 模板定时器类
数据点曲线拟合 线性插值
数据缓存和处理 循环缓冲区模板类
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值