簡介
? ? ? ? 在開發項目中,有時會遇到需要使用程控放大的情況,如果沒有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
三級程控放大倍數(無)
? ? ? ? 依照相同的原理,三級也能實現,不過計算量實在太大,不建議編程來確定最佳區間。