Menu
翘楚小站
  • 主页
  • 小项目
  • 算法
  • 摄影
翘楚小站
light_graph

测绘伏安特性曲线(I-U图像)

Posted on 2019年10月12日2021年8月3日 by WonderBoy

测绘伏安特性曲线(I-U图像)

作者:xzqiaochu 版权所有,转载请注明原作者与出处
关键字:伏安特性曲线 I-U 图像 Arduino 电流传感器 电流模块 ACS712模块 DIY 电子
简介:基于Arduino测绘电子元件伏安特性曲线,是一个低成本的解决方案。


目录(点击可跳转)

  • 测绘伏安特性曲线(I-U图像)
    • 1 项目介绍
      • 1.1 背景
      • 1.2 所用器材
        • 1.2.1 硬件
        • 1.2.2 软件
      • 1.3 成本统计
        • 1.3.1 财务成本
        • 1.3.2 时间成本
    • 2 成果
      • 2.1 数据部分
        • 2.1.1 小灯泡(2.5V 0.3A)
        • 2.1.2 定值电阻(5Ω)
      • 2.2 电路部分
    • 3 研究过程
      • 3.1 数据采集
        • 3.1.1 传感器设计
        • 3.1.2 程序设计
        • 3.1.3 电路设计
        • 3.1.4 串口通讯 & 数据测量
      • 3.2 数据处理
        • 3.2.1 初步尝试
        • 3.2.2 数据优化处理
        • 3.2.3 最终成果
    • 4 评价与反思
      • 4.1 几点说明
        • 4.1.1 平均数的选取
        • 4.1.2 数据的回归分析
      • 4.2 不足之处 & 改进方案
        • 4.2.1 测得的图像不过原点
        • 4.2.2 数据处理算法的优化
        • 4.2.3 测量过程集成化、自动化

1 项目介绍

1.1 背景

高中物理课本有这样一个实验 测绘小灯泡的伏安特性曲线。

书上的方法是:利用电学实验器材中的 电压表 和 电流表 测量若干组数据,将其标记在坐标图中,并用平滑的曲线连接,从而得到 I-U 图像。

这种方法有以下几个弊端:

  • 实验所用到的电压、电流表并非理想测量器件,内阻对测量造成的影响较大。

  • 由于 测量的数据较少,无法准确描地绘伏安特性曲线。

  • 实验中的读数和计算均为手工操作,容易造成 人为误差。

物理张老师告诉我们,有一款 电子式的电压、电流传感器,可以实现较为精确的测量。
随后我便在网上查询,发现这种传感器的售价大约在 ¥500 左右,性价比较低。
介于曾经自学过 单片机 的相关知识,在 编程 方面也有一定的基础,笔者打算自己设计一款 电子式电压、电流传感器。

1.2 所用器材

1.2.1 硬件
  • Arduino 单片机
  • ACS712 电流传感器
  • 物理电学实验器材
1.2.2 软件
  • Arduino IDE
  • 串口调试助手
  • Dev C++
  • Excel、WPS

1.3 成本统计

1.3.1 财务成本
  • 单片机 约¥25
  • 电流传感器 约¥5
  • 其他 约¥5

共计约 ¥35

网上的传感器成品售价约 ¥500。

1.3.2 时间成本

笔者是一位高中生,热爱编程和单片机设计,实验阶段花费 约8小时。


2 成果

2.1 数据部分

2.1.1 小灯泡(2.5V 0.3A)

light_graph.png

2.1.2 定值电阻(5Ω)

r_5_graph.png

2.2 电路部分

circuit.jpg


3 研究过程

3.1 数据采集

3.1.1 传感器设计

Arduino 单片机有模拟输入针脚,可读取输入电压值。
电流测量需要用到电流模块,如下图:
ACS712.jpg
左侧两根线为 被测电流输入和输出;
右侧三根线(从上往下)分别为 GND(参考地线)、OUT(信号输出)、VCC(模块供电)。

3.1.2 程序设计

Arduino 编程采用 Arduino IDE。

电压测量实现如下:


void readU()
{
    for (int i = 0; i < SZ; i++)
        val_u[i] = analogRead(PINU);
}

// 调用入口(返回值:mV)
double getU()
{
    readU();
    long long sum = 0;
    double avg = 0, rst = 0;
    for(int i = 0; i < SZ; i++)
        sum += val_u[i];
    avg = (double)sum / SZ;
    rst = avg / 1024.0 * vref / 1000;
    return rst;
}

电流测量:

void readI()
{
    for (int i = 0; i < SZ; i++)
        val_i[i] = analogRead(PINI);
}

// 调用入口(返回值:mA)
double getI()
{
    readI();
    long long sum = 0;
    double avg = 0, rst = 0;
    for(int i = 0; i < SZ; i++)
        sum += val_i[i];
    avg = (double)sum / SZ;
    rst = (avg / 1024.0 * vref - vref / 2.0) / mVperAmp;
    rst = rst < 0 ? 0 : rst;
    return rst;
}

以上程序中的常量(变量)定义:

const int PINU = A1; // 电压测量针脚
const int PINI = A0; // 电流测量针脚
const int SZ = 100; // 每次测量采样个数
const int mVperAmp = 185; // 电流模块转换系数

int vref = readVref(); // 最大电压
int val_i[SZ], val_u[SZ]; // 保存采样点

/*read reference voltage*/
long readVref()
{
    long result;
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined (__AVR_ATmega328P__)
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90USB1286__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    ADCSRB &= ~_BV(MUX5);   // Without this the function always returns -1 on the ATmega2560 http://openenergymonitor.org/emon/node/2253#comment-11432
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
#endif
#if defined(__AVR__)
    delay(2);                                        // Wait for Vref to settle
    ADCSRA |= _BV(ADSC);                             // Convert
    while (bit_is_set(ADCSRA, ADSC));
    result = ADCL;
    result |= ADCH << 8;
    result = 1126400L / result;  //1100mV*1024 ADC steps http://openenergymonitor.org/emon/node/1186
    return result;
#elif defined(__arm__)
    return (3300);                                  //Arduino Due
#else
    return (3300);                                  //Guess that other un-supported architectures will be running a 3.3V!
#endif
}

最终程序:

const int PINU = A1;
const int PINI = A0;
const int SZ = 100;
const int REPT = 5; // 每次采集重复次数
const int mVperAmp = 185;

int vref = 0;
int val_i[SZ], val_u[SZ];

void setup() {
    Serial.begin(115200);
    vref = readVref(); //read the reference votage(default:VCC)
}

void loop() {
    char c = Serial.read();
    if (c != 0x00 && c != 0x01)
        return;

    double sum = 0, data_i = 0, data_u = 0;
    for (int i = 0; i < REPT; i++)
        sum += getU();
    data_u = sum / REPT;
    output((int)(data_u * 1000));

    sum = 0;
    for (int i = 0; i < REPT; i++)
        sum += getI();
    data_i = sum / REPT;
    Serial.print(" "), output((int)(data_i * 1000));

    if (c == 0x01)
        Serial.print(" "), Serial.print(data_u / data_i);

    Serial.println();
    while (Serial.available())
        Serial.read();
    delay(10);
}

void output(int val)
{
    if (val < 1000)
        Serial.print(0);
        if (val < 100)
            Serial.print(0);
            if (val < 10)
                Serial.print(0);
    Serial.print(val);
}

void readI()
{
    for (int i = 0; i < SZ; i++)
        val_i[i] = analogRead(PINI);
}

double getI()
{
    readI();
    long long sum = 0;
    double avg = 0, rst = 0;
    for(int i = 0; i < SZ; i++)
        sum += val_i[i];
    avg = (double)sum / SZ;
    rst = (avg / 1024.0 * vref - vref / 2.0) / mVperAmp;
    rst = rst < 0 ? 0 : rst;
    return rst;
}

void readU()
{
    for (int i = 0; i < SZ; i++)
        val_u[i] = analogRead(PINU);
}

double getU()
{
    readU();
    long long sum = 0;
    double avg = 0, rst = 0;
    for(int i = 0; i < SZ; i++)
        sum += val_u[i];
    avg = (double)sum / SZ;
    rst = avg / 1024.0 * vref / 1000;
    return rst;
}

/*read reference voltage*/
long readVref()
{
    long result;
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined (__AVR_ATmega328P__)
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90USB1286__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    ADCSRB &= ~_BV(MUX5);   // Without this the function always returns -1 on the ATmega2560 http://openenergymonitor.org/emon/node/2253#comment-11432
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
#endif
#if defined(__AVR__)
    delay(2);                                        // Wait for Vref to settle
    ADCSRA |= _BV(ADSC);                             // Convert
    while (bit_is_set(ADCSRA, ADSC));
    result = ADCL;
    result |= ADCH << 8;
    result = 1126400L / result;  //1100mV*1024 ADC steps http://openenergymonitor.org/emon/node/1186
    return result;
#elif defined(__arm__)
    return (3300);                                  //Arduino Due
#else
    return (3300);                                  //Guess that other un-supported architectures will be running a 3.3V!
#endif
}

串口通讯说明:

  • 当接收到 0x00 时,返回格式如下:
    电压(mV) 电流(mA)

  • 当接收到 0x01 时,返回格式如下:
    电压(mV) 电流(mA) 电阻(Ω)

若不修改程序参数,一组数据的采集时间约为 60ms

3.1.3 电路设计

和高中物理书中的实验一样,滑动变阻器采用 分压式接法,可为被测元件提供从 0 开始的连续电压。

由于电流传感器内阻基本为 0,电压传感器内阻非常大,所以传感器内阻对实验造成的误差基本上可以忽略。在这里,为保证实验的严谨性,笔者仍然选择了 电流表外接法。

实物图如下:

circuit.jpg

3.1.4 串口通讯 & 数据测量

在 3.1.2 的最后,笔者提到了串口通讯的格式。

接下来,我们开始测量数据。

serial.jpg

上图为串口调试助手。

我们将收到的数据保存到文件,以便于后续处理。

3.2 数据处理

3.2.1 初步尝试

拿到实验数据后,笔者的第一个想法是用Excel绘图。

first_graph.png

图像出现了环状结构。

经过分析,笔者发现:由于测量误差存在,电压和电流并不是 一一对映 的关系。

3.2.2 数据优化处理

我们所希望得到的是 I-U图像,即每个电压数据,只能对映一个电流数据。

所以笔者写了一个 c++ 小程序,如果一个电压对映多个电流,则取电流的平均值(这里是算术平均数,关于平均数的选择,我们会在 4.1.1 中讨论)。

程序如下:

#include <cstdio>
#include <map>

using std::map;

map<int, int> data;
map<int, int> num;
map<int, int>::iterator iter;

int main()
{
    freopen("data.txt", "r", stdin);
    freopen("deal.txt", "w", stdout);
    int data_u, data_i;
    while (~scanf("%d%d", &data_u, &data_i))
        data[data_u] += data_i, num[data_u]++;
    for (iter = data.begin(); iter != data.end(); iter++)
    {
        data[iter->first] = (double)data[iter->first] / num[iter->first] + 0.5;
        printf("%d %d\n", iter->first, iter->second);
    }
    return 0;
}

原始数据文件:data.txt
处理后文件:deal.txt

3.2.3 最终成果

我们将处理后的数据导入Excel,进行绘图,并为图像生成一条 趋势线。

小灯泡(2.5V 0.3A)

light_graph.png

定值电阻(5Ω)

r_5_graph.png


4 评价与反思

4.1 几点说明

4.1.1 平均数的选取

在 3.1.2 程序设计 和 3.2.2 数据优化处理 中,都出现了求平均的步骤。
根据公式 R = U / I, 为了使电阻等效,我们应该计算电流的 调和平均数,但笔者最终还是选择了 算术平均数,主要有以下几点原因:

  • 误差来自 传感器的精度问题,以及 电磁干扰、静电噪音,调和平均数并不能真实地反应瞬时电流值,反而会使得测量结果偏小。

  • 单片机的 计算能力有限,调和平均数的计算时间较长,不利于数据的测量。

4.1.2 数据的回归分析

在 3.2.3 最终成果 中,笔者使用Excel完成对数据的 回归分析。其中 小灯泡 的趋势线是一个多项式回归方程(最高项次数为 6),而 定值电阻 的趋势线是一个线性回归方程。

4.2 不足之处 & 改进方案

4.2.1 测得的图像不过原点

相比较电压传感器,电流传感器的精度较低,检测不到很小的电流。

这主要是电流传感器的原理所致,笔者所用到的这款传感器是一个 霍尔元件,价格便宜,但精度不高(精度大约 10mA),而且容易受到电磁干扰。

笔者留意到网上有一款精度较高的电流传感器(精度大约 1.25mA),这款传感器采用 GMR巨磁阻 技术,价格稍高(约 ¥60)。这是一个不错的改进方案。

4.2.2 数据处理算法的优化

在 3.2.3 最终成果 中,笔者使用Excel完成对数据的 回归分析。随着数据规模的增大,这样回归算法的效率并不是很高。而且Excel只能完成 6 次多项式回归方程的计算。

笔者曾经接触过 神经元网络 算法,这种算法可以在较短的时间内完成 数据点->曲线 的拟合操作。笔者计划建立一个数学模型,实现对数据更加精确的分析。

4.2.3 测量过程集成化、自动化

在这个项目中,笔者需要手动完成数据从一个软件到另一个软件的拷贝,稍有些繁琐。笔者打算编写一个软件,集成数据采集和分析工作,从而方便大家的使用。

  • Arduino
  • DIY
  • 电子
  • 4 thoughts on “测绘伏安特性曲线(I-U图像)”

    1. 赵群说道:
      2019年10月14日 下午1:55

      能画出连接电路图,应该更好一些

      回复
    2. 晨鹤说道:
      2019年10月14日 下午4:46

      综合了好多东西.. 厉害

      回复
      1. xzqiaochu说道:
        2019年10月23日 下午9:28

        蟹蟹您的鼓励└(^o^)┘

        回复
    3. 王清筠说道:
      2019年12月1日 上午12:53

      这才是中国最需要培养的高中生,学会实践,而不是死学习。
      张同学,加油!!!

      回复

    向王清筠进行回复 取消回复

    您的电子邮箱地址不会被公开。 必填项已用*标注

    标签

    Arduino DIY NOIP python tensorflow 一中文创 人工智能 信息学 徐州一中 摄影 撷秀极客社区 数学 电子 短片 航拍

    分类

    归档

    近期评论

    • 王清筠发表在《【徐州一中】再一次出发》
    • 王清筠发表在《测绘伏安特性曲线(I-U图像)》
    • xzqiaochu发表在《测绘伏安特性曲线(I-U图像)》
    • 晨鹤发表在《测绘伏安特性曲线(I-U图像)》
    • 赵群发表在《测绘伏安特性曲线(I-U图像)》

    友情链接

    晨鹤小站(。・∀・)ノ゙

    清筠小站

    ©2022 翘楚小站 | Powered by WordPress & Superb Themes