电化学-NanoStat-开源代码解析(一)
本文主要从整体介绍NanoStat开源代码,让读者能够从宏观层面对代码有一定的了解,根据需要能够快速定位到自己最关心的代码片段。本文只对函数做基本的解析,具体实现细节还需要读者自行分析解读。
文章顶端有开源工程代码下载,或者直接访问原网站链接
1. platformio.ini 解析
设置环境
开发平台:espressif32(芯片)
开发板:pico32
开发框架:arduino
串口监控器速度:115200
依赖库:LMP91000、AsyncTCP、ESPAsyncWebServer、ArduinoJson、WebSockets、DNSServer。
这一部分比较简单,不做过多说明
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:pico32]
platform = espressif32
board = pico32
framework = arduino
monitor_speed = 115200
lib_deps =
linneslab/LMP91000 @ ^1.0.0
me-no-dev/AsyncTCP@^1.1.1
ottowinter/ESPAsyncWebServer-esphome@^1.2.7
bblanchon/ArduinoJson@^6.17.3
links2004/WebSockets@^2.3.6
bbx10/DNSServer@^1.1.0
2. main.cpp 解析
由于作者将全部代码都写到了main.cpp,这里只对关键代码进行解析,其他代码请自行理解。
2.1 变量解析
NaonoStat 硬件系统相关变量
该代码需要配合芯片说明手册和电路图理解。
// Microcontroller specific info (pinouts, Vcc, etc)
const uint16_t opVolt = 3300; //3300 mV
const uint8_t adcBits = 12; // ESP32 ADC is 12 bits
const uint16_t dacResolution = pow(2, 8) - 1; // ESP32 DAC is 8 bits, that is 12.9 mV for 3300 mV reference...
//LMP91000 control pins
const uint8_t dac = 25; // DAC pin for Vref to drive LMP91000. Pin 25 on BurkeLab ESP32Stat Rev 3.5
const uint8_t MENB = 5; // LMP91000 enable pin. Hard wired to ground in BurkeLab ESP32Stat Rev 3.5
//analog input pins to read voltages
const uint8_t LMP_C1 = 27; // Note C1, C2 not wired to microcontroller in BurkeLab ESP32Stat Rev 3.5
const uint8_t LMP_C2 = 39; // Note C1, C2 not wired in microcontroller in BurkeLab ESP32Stat Rev 3.5
const uint8_t LMP = 35; // ADC pin for Vout reading. Pin 35 on BurkeLab ESP32Stat Rev 3.5
// calibration of ESP32 ADC (see calibration routine)
float a_coeff = -146.63; // hard code coeffs but they can be updated with calibration routine
float b_coeff = 7.64; // hard code coeffs but they can be updated with calibration routine
// For BurkeLab ESP32Stat Rev 3.5, LED "blinky" pin.
int LEDPIN = 26;
扫描模式相关变量
Sweep_Mode_Type枚举定义了数种电化学扫描方法,读者也可以根据需要自行在其枚举类型中添加新的电化学扫描方法如DPV等。
// Global mode control (sweep type)
enum Sweep_Mode_Type
{
dormant, // not sweeping
NPV, // normal pulsed voltametry
CV, // cyclic voltametry
SQV, // square wave voltametry
CA, // chronoamperometry
IV, // IV curve
CAL, // calibrate
DCBIAS, // DC bias at a fixed point
CTLPANEL, // Control panel type interface
MISC_MODE // miscellanous
};
Sweep_Mode_Type Sweep_Mode = dormant;
1 CV 扫描变量
// CV sweep parameters:
// runCV(sweep_param_lmpGain, sweep_param_cycles_CV, sweep_param_startV_CV,
// sweep_param_endV_CV, sweep_param_vertex1_CV, sweep_param_vertex2_CV, sweep_param_stepV_CV,
// sweep_param_rate_CV, sweep_param_setToZero);
int sweep_param_cycles_CV = 3; // (#) number of times to run the scan
int sweep_param_startV_CV = 0; // (mV) voltage to start the scan
int sweep_param_endV_CV = 0; // (mV) voltage to stop the scan
int sweep_param_vertex1_CV = 100; // (mV) edge of the scan
int sweep_param_vertex2_CV = -100; // (mV) edge of the scan
int sweep_param_stepV_CV = 5; // (mV) how much to increment the voltage by
int sweep_param_rate_CV = 100; // (mV/sec) scanning rate
2 NPV 扫描变量
// NPV sweep parameters:
// runNPV(sweep_param_lmpGain, sweep_param_startV_NPV, sweep_param_endV_NPV,
// sweep_param_pulseAmp_NPV, sweep_param_pulseAmp_NPV, sweep_param_period_NPV,
// sweep_param_quietTime_NPV, uint8_t range, sweep_param_setToZero)
int sweep_param_startV_NPV = -200; // (mV) voltage to start the scan
int sweep_param_endV_NPV = 200; // (mV) voltage to stop the scan
int sweep_param_pulseAmp_NPV = 20;
int sweep_param_width_NPV = 50;
int sweep_param_period_NPV = 200;
int sweep_param_quietTime_NPV = 1000;
3 SWV 扫描变量
// SWV sweep parameters:
// runSWV(sweep_param_lmpGain, sweep_param_startV_SWV, sweep_param_endV_SWV,
// sweep_param_pulseAmp_SWV, sweep_param_stepV_SWV, sweep_param_freq_SWV, sweep_param_setToZero)
int sweep_param_startV_SWV = -200; // (mV) voltage to start the scan
int sweep_param_endV_SWV = 200; // (mV) voltage to stop the scan
int sweep_param_pulseAmp_SWV = 20;
int sweep_param_stepV_SWV = 5; // (mV) how much to increment the voltage by
int sweep_param_freq_SWV = 10;
4 CA 扫描变量
// CA sweep parameters:
// runAmp(sweep_param_lmpGain, sweep_param_pre_stepV_CA, sweep_param_quietTime_CA,
// sweep_param_V1_CA, sweep_param_t1_CA, sweep_param_V2_CA, sweep_param_t2_CA,
// sweep_param_samples_CA, uint8_t range, sweep_param_setToZero)
int sweep_param_pre_stepV_CA = 50;
int sweep_param_quietTime_CA = 2000;
int sweep_param_V1_CA = 100;
int sweep_param_t1_CA = 2000;
int sweep_param_V2_CA = 50;
int sweep_param_t2_CA = 2000;
int sweep_param_samples_CA = 100;
5 噪声测试扫描变量
// Noise test sweep parameters:
// testNoiseAtABiasPoint(sweep_param_biasV_noisetest, sweep_param_numPoints_noisetest,
// sweep_param_delayTime_ms_noisetest)
int sweep_param_biasV_noisetest = -100;
int sweep_param_numPoints_noisetest = 100;
int sweep_param_delayTime_ms_noisetest = 50;
6 IV 扫描变量
// IV sweep parameters:
// testIV(sweep_param_startV_IV, sweep_param_endV_IV, sweep_param_numPoints_IV,
// sweep_param_delayTime_ms_IV)
int sweep_param_startV_IV = -200; // (mV) voltage to start the scan
int sweep_param_endV_IV = 200; // (mV) voltage to stop the scan
int sweep_param_numPoints_IV = 701;
int sweep_param_delayTime_ms_IV = 50;
7 校正扫描变量
// Cal sweep parameters:
// calibrateDACandADCs(sweep_param_delayTime_ms_CAL)
int sweep_param_delayTime_ms_CAL = 50;
8 IV 曲线变量(画图用)
// Arrays of IV curves etc:
const uint16_t arr_samples = 5000; //use 1000 for EIS, can use 2500 for other experiments (10k does not fit in DRAM)
uint16_t arr_cur_index = 0;
int16_t volts[arr_samples] = {0}; // single sweep IV curve "V"
float amps[arr_samples] = {0}; // single sweep IV curve "I"
int32_t time_Voltammaogram[arr_samples] = {0};
int number_of_valid_points_in_volts_amps_array = 0; // rest of them are all zeros...
Json 相关变量
// JSON string to sent over websockets to browswer client:
// char Voltammogram_JSON_cstr[31000];
// char Voltammogram_JSON_cstr[31000];
//char Voltammogram_JSON_cstr2[31000];
int16_t volts_temp = 0;
float amps_temp = 0;
float v1_temp = 0;
float v2_temp = 0;
float analog_read_avg_bits_temp = 0;
String temp_json_string = "";
LMP91000 相关变量
可以通过更改num_adc_readings_to_average 的大小来优化设备性能,作者将ADC采集平均数设置为1,读者可以增加ADC采集平均数从而提高设备性能,减少噪声干扰。
const float v_tolerance = 0.008; //0.0075 works every other technique with 1mV step except CV which needs minimum 2mV step
unsigned long lastTime = 0; // global variable, last time xyz was called in ms....
uint16_t dacVout = 1500; // desired output of the DAC in mV
float adc_avg = 0; // used in noise test subroutine, better to make it local eventually...
float adc_std_dev = 0; // used in noise test subroutine, better to make it local eventually...
int num_adc_readings_to_average = 1; // when reading ADC, how many points to average....
// LMP91000 global status settings:
// Parameters the user can set on LMP91000:
// TIA gain (keep max at 350k feedback resistor)
// Rload (keep at 10 ohms)
// Ref source int/ext (keep ext)
// Int_Z zero 50 20 67% (keep at 50%)
// Bias_Sign (plus/minus)
// Bias 1%-24%
// FET_Short (keep off)
// Mode (keep at 3-lead)
uint8_t LMPgainGLOBAL = 7; // Feedback resistor of TIA.
//void LMP91000::setGain(uint8_t gain) const
//@param gain: the gain to be set to
//param - value - gain resistor
//0 - 000 - External resistor
//1 - 001 - 2.75 kOhm
//2 - 010 - 3.5 kOhm
//3 - 011 - 7 kOhm
//4 - 100 - 14 kOhm
//5 - 101 - 35 kOhm
//6 - 110 - 120 kOhm
//7 - 111 - 350 kOhm
uint8_t bias_setting = 0; // determines percentage of VREF applied to CE opamp, from 1% to 24%
// from LMP91000.h:
//const double TIA_BIAS[] = {0, 0.01, 0.02, 0.04, 0.06, 0.08, 0.1, 0.12, 0.14,
// 0.16, 0.18, 0.2, 0.22, 0.24};
float RFB = 1e6; // feedback resistor if using external feedback resistor
Websockets 相关变量
// Websockets:
// Tutorial: https://www.youtube.com/watch?v=ZbX-l1Dl4N4&list=PL4sSjlE6rMIlvrllrtOVSBW8WhhMC_oI-&index=8
// Tutorial: https://www.youtube.com/watch?v=mkXsmCgvy0k
// Code tutorial: https://shawnhymel.com/1882/how-to-create-a-web-server-with-websockets-using-an-esp32-in-arduino/
// Github: https://github.com/Links2004/arduinoWebSockets
WebSocketsServer m_websocketserver = WebSocketsServer(81);
String m_websocketserver_text_to_send = "";
String m_websocketserver_text_to_send_2 = "";
int m_time_sent_websocketserver_text = millis();
int m_microsbefore_websocketsendcalled = micros();
int last_time_loop_called = millis();
int last_time_sent_websocket_server = millis();
float m_websocket_send_rate = 1.0; // Hz, how often to send a test point to websocket...
bool m_send_websocket_test_data_in_loop = false;
Wifi 相关变量
//WifiTool object
int WAIT_FOR_WIFI_TIME_OUT = 6000;
const char *PARAM_MESSAGE = "message"; // message server receives from client
std::unique_ptr<DNSServer> dnsServer;
std::unique_ptr<AsyncWebServer> m_wifitools_server;
const byte DNS_PORT = 53;
bool restartSystem = false;
控制面板相关变量
// Control panel mode settings: (Control panel mode is browser page like a front panel of instrument)
uint8_t LMPgain_control_panel = 6; // Feedback resistor of TIA.
int num_adc_readings_to_average_control_panel = 1;
int sweep_param_delayTime_ms_control_panel = 50;
int cell_voltage_control_panel = 100;
2.2 函数解析
- boolean connectAttempt(String ssid, String password)
用于连接wifi的函数,连接成功返回true,连接失败返回false。 - void sendTimeOverWebsocketJSON()
将当前时间以 TXT 格式发送至 WebSocket - void sendValueOverWebsocketJSON(int value_to_send_over_websocket)
将整数以 TXT 格式发送至 WebSocket - void sendStringOverWebsocket(String string_to_send_over_websocket)
将字符串以 TXT 格式发送至 WebSocket - void send_is_sweeping_status_over_websocket(bool is_sweeping)
通过 WebSocket 发送扫描状态(是/否) - void send_expect_binary_data_over_websocket(bool expect_binary_data)
通过 WebSocket 发送二进制数据状态(是/否) - void sendVoltammogramWebsocketJSON()
将实验数据(I,V,t)以 TXT 格式发送至 WebSocket,为旧版本函数,在发送大量数据时会出现内存问题。 - void sendVoltammogramWebsocketBIN()
将实验数据(I,V,t)以 BIN 格式发送至 WebSocket,为新版本函数,解决了通过TXT发送数据时会出现数据丢失的问题。
- void blinkLED(int pin, int blinkFrequency_Hz, int duration_ms)
LED闪烁函数 - void pulseLED_on_off(int pin, int on_duration_ms)
LED发光函数
- inline void setLMPBias(int16_t voltage)
设置LMP电压偏置(正/负) - void setOutputsToZero()
将恒电位设置为0V偏置 - void initLMP(uint8_t lmpGain)
初始化LMP器件,其中pStat.setIntZ(1)的作用是设置零电位为参考电压的50%,作者在代码中注释存在错误 - inline uint16_t convertDACVoutToDACVal(uint16_t dacVout)
将DAC电压转化成DAC写入值 - inline float analog_read_avg(int num_points, int pin_num)
读取ADC平均值 - inline void setVoltage(int16_t voltage)
设置偏置电压(Vre-Vwe),该部分代码建议仔细阅读,用了迭代的方法确认DAC输出电压与 TIA_BIAS偏置参数。不过需要注意的是,实际的偏置电压与设置的偏置电压存在一定的误差,约为0.1mv。 - inline float biasAndSample(int16_t voltage, uint32_t rate)
设定恒电位电路的偏置电压,然后采集由此产生的电流。在此作者用了ADC校正模型,该部分代码建议仔细阅读,好好品味。
- void writeVoltsCurrentArraytoFile()
写入电压电流数组文件 - void readFileAndPrintToSerial()
读取文件并将其打印至串口,该函数用于测试 - void writeCalFile()
写入校正文件 - void readCalFile()
读取校正文件
- bool readSSIDPWDfile(String m_pwd_filename_to_read)
读取wifi名称密码文件,并尝试连接至相应wifi网络 - void setUpAPService()
开启“NanoStatAP”热点 - void process()
等待wifi连接,在void runWifiPortal()有使用到 - void handleGetSavSecreteJson(AsyncWebServerRequest *request)
保存网页端设置的wifi名称和密码并重启 - void handleGetSavSecreteJsonNoReboot(AsyncWebServerRequest *request)
保存网页端设置的wifi名称和密码不重启 - void handleFileList(AsyncWebServerRequest *request)
传输文件目录 - void handleUpload(AsyncWebServerRequest *request, String filename, String redirect, size_t index, uint8_t *data, size_t len, bool final)
上传文件 - void handleFirmwareUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
固件更新 - void handleFilesystemUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
文件系统更新 - void getWifiScanJson(AsyncWebServerRequest *request)
wifi扫描 - void runWifiPortal()
运行wifi主页 - void runWifiPortal_after_connected_to_WIFI()
连接wifi后运行wifi主页
- void listDir(const char *dirname, uint8_t levels)
串口打印文件目录(调试用) - void reset_Voltammogram_arrays()
重置时间,电流,电压矩阵 - inline void saveVoltammogram(float voltage, float current, bool debug)
保存单次电压,电流,时间数据 - void testIV(int16_t startV, int16_t endV, int16_t numPoints,
uint32_t delayTime_ms)
IV法测试 - void testNoiseAtABiasPoint(int16_t biasV, int16_t numPoints,
uint32_t delayTime_ms)
噪声测试(论文中有提到) - void testDACs(uint32_t delayTime_ms)
DAC测试 - void testDACandADCs(uint32_t delayTime_ms)
DAC和ADC测试 - void calibrateDACandADCs(uint32_t delayTime_ms)
DAC和ADC校正 - void testLMP91000(uint32_t delayTime_ms, uint8_t bias_setting_local)
LMP91000测试 - void runNPVForward(int16_t startV, int16_t endV, int8_t pulseAmp,
uint32_t pulse_width, uint32_t off_time)
NPV正向信号 - void runNPVBackward(int16_t startV, int16_t endV, int8_t pulseAmp,
uint32_t pulse_width, uint32_t off_time)
NPV反向信号 - void runNPV(uint8_t lmpGain, int16_t startV, int16_t endV,
int8_t pulseAmp, uint32_t pulse_width, uint32_t pulse_period,
uint32_t quietTime, uint8_t range, bool setToZero)
NPV测试 - void runCVForward(uint8_t cycles, int16_t startV, int16_t endV,
int16_t vertex1, int16_t vertex2, int16_t stepV, uint16_t rate)
CV正向信号 - void runCVBackward(uint8_t cycles, int16_t startV, int16_t endV,
int16_t vertex1, int16_t vertex2, int16_t stepV, uint16_t rate)
CV反向信号 - void runCV(uint8_t lmpGain, uint8_t cycles, int16_t startV,
int16_t endV, int16_t vertex1, int16_t vertex2,
int16_t stepV, uint16_t rate, bool setToZero)
CV测试 - void runSWVForward(int16_t startV, int16_t endV, int16_t pulseAmp,
int16_t stepV, double freq)
SWV正向信号 - void runSWVBackward(int16_t startV, int16_t endV, int16_t pulseAmp,
int16_t stepV, double freq)
SWV反向信号 - void runSWV(uint8_t lmpGain, int16_t startV, int16_t endV,
int16_t pulseAmp, int16_t stepV, double freq, bool setToZero)
SWV测试 - void runDPVForward(int16_t startV, int16_t endV, int8_t pulseAmp,
int8_t stepV, uint32_t pulse_width, uint32_t off_time)
DPV正向信号 - void runDPVBackward(int16_t startV, int16_t endV, int8_t pulseAmp,
int8_t stepV, uint32_t pulse_width, uint32_t off_time)
DPV反向信号 - void runDPV(uint8_t lmpGain, int16_t startV, int16_t endV,
int8_t pulseAmp, int8_t stepV, uint32_t pulse_period,
uint32_t pulse_width, bool setToZero)
DPV测试 - void runAmp(uint8_t lmpGain, int16_t pre_stepV, uint32_t quietTime,
int16_t v1, uint32_t t1, int16_t v2, uint32_t t2,
uint16_t samples, uint8_t range, bool setToZero)
AMP测试 - void runNPVandPrintToSerial()
运行NPV测试,并将结果打印至串口(调试) - void runCVandPrintToSerial()
运行CV测试,并将结果打印至串口(调试) - void runSWVForwardandPrintToSerial()
运行SWV测试,并将结果打印至串口(调试) - void runSWVReverseandPrintToSerial()
运行SWV测试,并将结果打印至串口(调试) - void runCAandPrintToSerial()
运行CA测试,并将结果打印至串口(调试)
- void set_sweep_parameters_from_form_input(String form_id, String form_value)
设置扫描参数 - void handleFileDelete(AsyncWebServerRequest *request)
删除文件 - void handle_websocket_text(uint8_t *payload)
控制面板参数设置 - void onWebSocketEvent(uint8_t num,
WStype_t type,
uint8_t *payload,
size_t length)
网络套接字处理 - void configureserver()
服务器设置(需要相关网络知识)
- void setup()
系统初始化设置 - void loop()
系统主体循环执行代码