当前位置: 首页 > news >正文

_二级继电器程控放大倍数自动设置

简介

        在开发项目中,有时会遇到需要使用程控放大的情况,如果没有opa那种可编程放大器,那么就需要通过继电器来控制放大倍数。而在继电器程控中,常用的是二级程控,三级程控相较于二级就复杂了许多。

        在二级程控中,每级继电器都有两种状态,因此每级都可以设置两个倍数,两级级联共4种放大倍数。对于具体应用场景来说,难的是如何设置合适的不同放大倍数划分不同的子区间,使其可以对输入区间的不同子区间进行选择性放大,并且每个档位都能合理覆盖这些子区间,进而使得增益后的子区间均处于适合的范围。

具体场景

        举个具体例子,现在ADC采集电压峰峰值范围为0~2V,而输入的电压为30mV到600mV。对于电压30mV和600mV来说,放大倍数应分别接近66.7和3.3,那么就需要对其不同区间进行选择性放大。那么这就设计到了如何选取区间,4个放大倍数自然需要把输入区间划分为4个连续子区间。放大之后还需要确保,增益的子区间应在ADC采集的范围内。

        也就是说,我们能获得的信息是输入的区间范围(如30mV~600mV)和输出的区间范围(如0~2V),而我们需要的信息则是不同的放大倍数和划分的子区间。在此例中,为了能及时察觉到放大倍数是否过大,那么应让每个区间放大后的上限小于ADC的上限(2V),以便可以通过检测ADC电压是否大于子区间上限并接近上限电压,进而及时调整程控倍数。下限亦是如此。

建模

        根据具体的应用场景,可以很轻松地为其建模:

        对于一个输入电压范围x1,x2,可以把它分为4个连续区间范围(左闭右开),而现在有一级放大倍数O1min和O1max,二级放大倍数O2min和O2max,这四个数可以排列组合得到不同的乘积a,b,c,d。那么如何找到四个乘积a、b、c、d(由一级放大倍数O1和二级放大倍数O2的组合产生),以及四个区间的分割点,使得每个分割后的区间内的x乘以对应的乘积后落在[Vmin, Vmax]内,并且分割后的四个区间连续覆盖输入范围。

解决

        进行建模后,并不好从数学角度快速解出4个放大倍数和4个连续子区间。但可以通过编程来遍历所有合适的条件。如图,这是个简单的C++程序,可以向其指定输入区间、输出区间、步长和线程数。不过需要注意的是,找到的结果是对称的,也就是说两级放大倍数互换,在程序里是两种情况,而非一种情况。

小工具的使用

简介 

       使用方法很简单,与一般工具无异,通过指定参数来进行相应操作。此处,小工具的名称为auto_OPA.exe

输入区间 

       以前面的例子而言,输入区间为30mV到600mV,那么可以写成下面形式(这里以mV为单位,也可以以V为单位,即-i 0.03 0.6。需要确保后面也使用相同单位)

.\auto_OPA.exe -i 30 600

输出区间

        各个子区间增益后的区间范围

.\auto_OPA.exe -o 875 1900

步长

        决定了放大倍数的最小分辨率

.\auto_OPA.exe -s 0.5

        步长不建议选择太小,比如0.1,其耗时远远高于步长为0.5用时的5倍,并且消耗内存会会大幅增加,因为找到的结果会先保存起来

        另一方面,整数放大或者半整数放大在实际电路设计中较为常见,也比较好设计。

线程数

        决定了程序运行快慢。由于程序里内嵌套了多个for循环,建议有多少核就用多少核,如果满核的话,CPU占用率会达到100%,这很正常。

.\auto_OPA.exe -j 16

说明

参数也可以联合指定,不区分先后顺序

.\auto_OPA.exe -i 30 600 -o 875 1900 -s 0.5 -j 16

        此外,程序还有默认值(就是任何参数都不指定的情况下),可自行到源码里设置,然后编译运行

        其中放大倍数的上下限是根据输入区间范围和输出区间范围动态决定的,并不需要额外指定。

如果需要,那么可以显示设定上下限

vector<double> generate_O_values(double x_min, double x_max, 
                               double Vmin, double Vmax, double step) {
    vector<double> values;
    double min_O = ceil((Vmin/x_max)/step)*step;  // 显式下限
    double max_O = floor((Vmax/x_min)/step)*step;  // 精确上限
    
    for (double v = min_O; v <= max_O + 1e-9; v += step) {
        values.push_back(v);
    }
    return values;
}

运行结果

程序运行

保存的结果,其中O1为一级放大器的两个放大倍数,O2位二级放大器的放大倍数

源代码

gitcode:GitCode - 全球开发者的开源社区,开源代码托管平台

github:ichliebedich-DaCapo/auto_OPA

#include <iostream>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <atomic>
#include <fstream>
#include <string>
#include <cmath>
#include <iomanip>
#include <getopt.h>

using namespace std;

// ANSI颜色代码
#define BLUE    "\033[34m"
#define GREEN   "\033[32m"
#define RESET   "\033[0m"

struct Result
{
    double O1min{}, O1max{}, O2min{}, O2max{};
    vector<double> gains;
    vector<double> split_points;
};

vector<Result> global_results;
mutex results_mutex;
atomic<int> processed(0);
atomic<int> found_results(0);

vector<double> generate_O_values(double x, double Vmax, double step)
{
    vector<double> values;
    double max_O = Vmax / x;
    for (double v = step; v <= max_O + 1e-9; v += step)
    {
        values.push_back(v);
    }
    return values;
}

void display_progress(int total, int found)
{
    const float progress = static_cast<float>(processed) / total;
    const int bar_width = 50;
    cout << BLUE << "\r[";
    const int pos = bar_width * progress;
    for (int i = 0; i < bar_width; ++i)
    {
        if (i < pos) cout << "=";
        else if (i == pos) cout << ">";
        else cout << " ";
    }
    cout << "] " << static_cast<int>(progress * 100.0) << "%" << RESET;
    cout << " " << GREEN << "Found: " << found << RESET << flush;
}

void display_thread_func(int total)
{
    while (processed < total)
    {
        this_thread::sleep_for(chrono::milliseconds(100));
        int current_processed = processed.load();
        int current_found = found_results.load();
        display_progress(total, current_found);
    }
    display_progress(total, found_results.load());
    cout << endl;
}

void worker(const vector<pair<int, int> > &O1_combs, const vector<pair<int, int> > &O2_combs,
            const vector<double> &O1_values, const vector<double> &O2_values,
            double x1, double x2, double Vmin, double Vmax, int total_O1_combs)
{
    while (true)
    {
        int idx = processed.fetch_add(1);
        if (idx >= total_O1_combs) break;

        const auto &[fst, snd] = O1_combs[idx];
        const double O1min = O1_values[fst];
        const double O1max = O1_values[snd];

        for (auto &O2_pair: O2_combs)
        {
            const double O2min = O2_values[O2_pair.first];
            const double O2max = O2_values[O2_pair.second];

            const double k1 = O1min * O2min;
            const double k2 = O1min * O2max;
            const double k3 = O1max * O2min;
            const double k4 = O1max * O2max;

            if (k1 == k2 || k1 == k3 || k1 == k4 || k2 == k3 || k2 == k4 || k3 == k4) continue;

            vector<double> gains = {k1, k2, k3, k4};
            ranges::sort(gains);

            do
            {
                if (gains[3] < Vmin / x2 - 1e-9 || gains[3] > Vmax / x2 + 1e-9) continue;
                if (gains[0] < Vmin / x1 - 1e-9) continue;

                double d0 = x1;
                double d1_low = max(d0, Vmin / gains[1]);
                double d1_high = Vmax / gains[0];
                if (d1_low > d1_high + 1e-9) continue;

                double d1 = d1_low;
                double d2_low = max(d1, Vmin / gains[2]);
                double d2_high = Vmax / gains[1];
                if (d2_low > d2_high + 1e-9) continue;

                double d2 = d2_low;
                const double d3_low = max(d2, Vmin / gains[3]);
                const double d3_high = min(Vmax / gains[2], x2);
                if (d3_low > d3_high + 1e-9) continue;

                double d3 = d3_low;
                if (d3 > x2 + 1e-9) continue;

                Result res;
                res.O1min = O1min;
                res.O1max = O1max;
                res.O2min = O2min;
                res.O2max = O2max;
                res.gains = gains;
                res.split_points = {d1, d2, d3};

                lock_guard<mutex> lock(results_mutex);
                global_results.push_back(res);
                found_results.fetch_add(1);
            } while (ranges::next_permutation(gains).found);
        }
    }
}

int main(int argc, char *argv[])
{
    double x1 = 30, x2 = 600, Vmin = 875, Vmax = 1950, step = 0.5;
    int threads = 16;

    // 解析命令行参数
    int opt;
    while ((opt = getopt(argc, argv, "i:o:s:j:h")) != -1)
    {
        switch (opt)
        {
            case 'i':
                x1 = stod(optarg);
                x2 = stod(argv[optind++]);
                break;
            case 'o':
                Vmin = stod(optarg);
                Vmax = stod(argv[optind++]);
                break;
            case 's':
                step = stod(optarg);
                break;
            case 'j':
                threads = stoi(optarg);
                break;
            case 'h':
                // 中文会乱码
                // cout << "[-i]:输入区间范围,比如-i 0.03 0.6\n"
                //         << "[-o]:输出区间范围,比如-o 0.9 2\n"
                //         << "[-s]:步长,比如-s 0.5\n"
                //         << "[-j]:线程数,比如-j 16\n"
                //         << "[-h]:帮助信息\n";;
                cout << "Two-Stage Programmable Amplifier Configuration Finder\n\n"
                        << "Usage:\n"
                        << "  ./auto_OPA -i <x_low> <x_high> -o <Vmin> <Vmax> [options]\n\n"
                        << "Required Parameters:\n"
                        << "  -i  Input voltage range (left-closed right-open interval)\n"
                        << "      Example: -i 0.03 0.6\n"
                        << "  -o  Desired output voltage range\n"
                        << "      Example: -o 1.0 3.3\n\n"
                        << "Options:\n"
                        << "  -s  Step size for gain search (default: 0.1)\n"
                        << "  -j  Number of parallel threads (default: CPU core count)\n"
                        << "  -h  Display this help message\n\n"
                        << "Validation Criteria:\n"
                        << "  1. Input coverage: [x_low, x_high] must be fully covered\n"
                        << "  2. Output constraint: ∀x∈[x_low,x_high], Vmin ≤ x·gain ≤ Vmax\n"
                        << "  3. Gain continuity: Adjacent regions must have overlapping gains\n";
                exit(1);
            default:
                cerr << "Usage: " << argv[0]
                        << " -i x_low x_high -o Vmin Vmax -s step -j threads\n";
                exit(1);
        }
    }

    // 打印参数
    cout << "Vin  [" << x1 << "," << x2 << "]\nVout [" << Vmin << "," << Vmax << "]\nstep=" << step << "\nthreads=" <<
            threads << endl;

    auto O1_values = generate_O_values(x1, Vmax, step);
    auto O2_values = generate_O_values(x1, Vmax, step);

    vector<pair<int, int> > O1_combs;
    for (int i = 0; i < O1_values.size(); ++i)
        for (int j = i + 1; j < O1_values.size(); ++j)
            O1_combs.emplace_back(i, j);

    vector<pair<int, int> > O2_combs;
    for (int i = 0; i < O2_values.size(); ++i)
        for (int j = i + 1; j < O2_values.size(); ++j)
            O2_combs.emplace_back(i, j);

    int total_O1_combs = O1_combs.size();

    thread display_thread(display_thread_func, total_O1_combs);
    vector<thread> workers;
    for (int i = 0; i < threads; ++i)
        workers.emplace_back(worker, ref(O1_combs), ref(O2_combs), ref(O1_values),
                             ref(O2_values), x1, x2, Vmin, Vmax, total_O1_combs);

    for (auto &t: workers) t.join();
    display_thread.join();

    ofstream out("results.txt");
    for (int i = 0; i < global_results.size(); ++i)
    {
        auto &[O1min, O1max, O2min, O2max, gains, split_points] = global_results[i];
        out << "Result " << i + 1 << ":\n";
        out << fixed << setprecision(6);
        out << "O1: [" << O1min << ", " << O1max << "]\n";
        out << "O2: [" << O2min << ", " << O2max << "]\n";
        out << "Gains: ";
        for (auto g: gains) out << g << " ";
        out << "\nSplit Zone: ";
        // 子区间
        double zone[]={x1, split_points[0], split_points[1], split_points[2], x2};
        for (int n = 0; n < 4; ++n)
        {
            out << "\n[" << zone[n] << "," << zone[n + 1] << "]\t\t"<<
                "[" << gains[n] * zone[n] << "," << gains[n]*zone[n + 1] << "]";
        }
        out << "\n\n";
    }

    return 0;
}

下载小工具

        程序已上传gitcode和github,可通过下面链接进行下载

Release详情 - auto_OPA - GitCode

Release auto_OPA v1.0.0 · ichliebedich-DaCapo/auto_OPA

三级程控放大倍数(无)

        依照相同的原理,三级也能实现,不过计算量实在太大,不建议编程来确定最佳区间。

相关文章:

  • WWW 2025 | 时间序列(Time Series)论文总结
  • 【计算机网络】深入解析 HTTP 中的 GET 方法、POST 方法和 GET 和 POST 的区别
  • SpringCloud——LoadBalancer负载均衡服务调用
  • Docker入门篇1:搜索镜像、拉取镜像、查看本地镜像列表、删除本地镜像
  • 第13章 安全加固OSI的第8层(网络安全防御实战--蓝军武器库)
  • k倍区间 | 哈希 分巧克力 | 二分 青蛙跳杯子 | BFS
  • Lab18_ SQL injection with filter bypass via XML encoding
  • Codeforces Round 566 (Div. 2) E. Product Oriented Recurrence 矩阵加速、欧拉降幂
  • 通过Nacos API实现微服务不间断部署
  • 从传统到智能:Node-red工控机助力农业大棚高效监控
  • 【Python】Django 中的算法应用与实现
  • Android Configuration相关问题如何定位分析(中英文切换、黑夜白天模式等)
  • 【GPU】什么是 NVLink?
  • 4G铁路工控机在高铁信号控制中的关键作用
  • 【SpringBoot3】SpringBoot项目Web拦截器使用
  • 小程序 -- uni-app开发微信小程序环境搭建(HBuilder X+微信开发者工具)
  • C++性能分析工具
  • 基于遗传算法的IEEE33节点配电网重构程序
  • 网络安全 信息安全 计算机系统安全
  • while……else
  • 无人机企业从科技园区搬到乡村后,村子里变得不一样了
  • 河南一县政府党组成员签订抵制违规吃喝问题承诺书,现场交给县长
  • 韧性十足的中国外贸企业:“不倒翁”被摁下去,还会再弹起来
  • 钕铁硼永磁材料龙头瞄准人形机器人,正海磁材:已向下游客户完成小批量供货
  • 因港而兴,“长江黄金水道”上的宜宾故事
  • 曾犯强奸罪教师出狱后办教培机构?柳州鱼峰区教育局回应