FEMM 5

Электрическое поле тока · Плоскопараллельная

Определение координат резистивного сенсорного экрана

Однородный резистивный прямоугольник 100 × 60 мм, толщина 0.1 мм. Два режима питания последовательно измеряют координаты идеальной точки касания (30,42) мм.

Работа связывает полевое распределение с измеряемой электротехнической величиной. Все исходные данные заданы в программе; принятые идеализации сформулированы до расчёта. Основной результат проверяется независимой формулой или балансом, а чувствительность к геометрии исследуется отдельными вариантами.

Самостоятельная учебная модель FEMM 5: от физических соотношений к полевому решению и проверке результата.

Допущения и устройство учебной модели

Все размеры, свойства материалов и условия возбуждения заданы ниже. Численные результаты относятся к опубликованной версии модели FEMM 5.

Физическая и математическая постановка

Теория и физическая постановка

При подаче напряжения на полные противоположные края однородной резистивной плёнки потенциал линейно меняется вдоль выбранной оси. В режиме X питаются левый и правый края, верхний и нижний изолированы. В режиме Y питаются верхний и нижний края. Один отсчёт напряжения в каждом режиме даёт одну координату.

Точка касания представлена идеальным высокоомным измерителем: она считывает потенциал и не отбирает ток. Это принципиальное допущение, которое позволяет сначала изучить пространственную калибровку без контактной электроники. Металлическая площадка конечного размера и сопротивление второго слоя в данную работу не введены.

Программа строит две независимые модели на каждом уровне сетки, проверяет электрические балансы и преобразует измеренное напряжение обратно в миллиметры. Питание одинаково в обоих режимах, но размеры Lx и Ly различны. Нельзя умножать оба нормированных напряжения на одну длину.

x=L_xU_X/U_0,\qquad y=L_yU_Y/U_0

Как читать расчёт

Программа строит три сетки, сохраняет каждую модель, закрывает её и повторно открывает перед решением. Поэтому проверка относится к точному записанному файлу с русскими именами материалов, проводников и граничных условий. Результаты каждой серии находятся в новой папке calculated-XX рядом с программой; предыдущая серия не перезаписывается.

В result.json массив records содержит расчётные величины и параметры сетки, checks — фактические отклонения, допустимые пределы и логический результат. Проверяйте все критерии одновременно. Совпадение одной точки с ориентиром не заменяет проверки баланса и изменения результата по сетке. Для итерационной обратной связи отдельно контролируется сходимость итераций; она не является оценкой пространственной погрешности.

Практическое исследование

Перенесите точку в углы и в центр экрана. Проверьте нормированные отсчёты и отдельно восстановленные координаты. Объясните, почему диапазон напряжения не зависит от отношения сторон экрана.

Измените проводимость плёнки и напряжение питания. Разделите изменение тока потребления и изменение координатной характеристики. Исследуйте отношение Uизмеренное/Uпитания, которое используется для устранения дрейфа питания.

Предложите вариант неполного краевого электрода. Предскажите нелинейность координатного преобразования около этого края и сформулируйте, какую калибровочную карту пришлось бы строить вместо двух линейных формул.

Исходные данные

Однородный резистивный прямоугольник 100 × 60 мм, толщина 0.1 мм. Два режима питания последовательно измеряют координаты идеальной точки касания (30,42) мм.

Величины, единицы и происхождение параметров
ПараметрЗначениеЕдиницаПроисхождение
Ширина100ммПринято в учебной постановке
Высота60ммПринято в учебной постановке
Касание x30ммПринято в учебной постановке
Касание y42ммПринято в учебной постановке
Питание1ВПринято в учебной постановке

Порядок воспроизведения

  1. Скопируйте reproduce.py в отдельную рабочую папку и откройте встроенным редактором Python FEMM 5. Проверьте заданные параметры и единицы, затем выполните программу целиком. Внешний модуль pyFEMM для этой программы не используется.
  2. Откройте модель и решение окончательной сетки. Сопоставьте материалы, проводники и границы с теорией. Для осесимметричной задачи найдите ось вращения и объясните, какие поверхности образуются при вращении каждой линии поперечного рисунка.
  3. Составьте таблицу трёх сеток и приведите независимый расчёт по формуле. Для серии из нескольких геометрий сравнивайте только результаты одной и той же сетки, затем отдельно исследуйте её уточнение. Укажите абсолютные и относительные ошибки с единицами.
  4. Выполните предложенные варианты по одному. До запуска предскажите знак и примерный масштаб эффекта. В отчёте объясните, какие величины действительно определяет модель, какие параметры остаются предположениями и что пришлось бы уточнить для реального изделия.

Скачайте нужные файлы по отдельным ссылкам ниже в одну рабочую папку, затем откройте reproduce.py во встроенном Python FEMM 5 и выполните его. Сохраните имена файлов и структуру подпапок. Условия конкретного запуска и путь новых результатов указаны в комментариях программы.

Программа встроенного Python FEMM 5
Python · reproduce.py
"""Самостоятельная лабораторная работа курса FEMM 5.
Запуск: встроенный MicroPython FEMM 5, команды FEMM доступны непосредственно.
Геометрические координаты и глубина — мм; результаты интегралов — единицы СИ.
Данные, допущения, аналитические ориентиры и ход исследования заданы в README.md.
Каждая серия записывается в новую calculated-XX; прежние результаты сохраняются.
Сохранённая модель закрывается и открывается до расчёта, включая русские имена.
"""

import os
import sys
import json
import math

script = globals().get("__file__", "") or (sys.argv[0] if sys.argv else "")
base = os.path.dirname(os.path.abspath(script)) if script else os.getcwd()
index = 1
while "calculated-%02d" % index in os.listdir(base):
    index += 1
directory = base + "/calculated-%02d" % index
os.mkdir(directory)
records = []
checks = []


def real(value):
    if isinstance(value, (tuple, list)):
        return real(value[0])
    return complex(value).real


def check(name, value, limit):
    checks.append(
        {
            "name": name,
            "value": value,
            "limit": limit,
            "passed": math.isfinite(value) and value <= limit,
        }
    )


def polygon(prefix, points):
    for x, y in points:
        globals()[prefix + "_addnode"](x, y)
    for i, (x, y) in enumerate(points):
        x2, y2 = points[(i + 1) % len(points)]
        globals()[prefix + "_addsegment"](x, y, x2, y2)


def geometry(prefix, job, mesh):
    if job.get("polygon"):
        polygon(prefix, job["polygon"])
    for x1, y1, x2, y2 in job.get("interfaces", []) + job.get("segments", []):
        globals()[prefix + "_addnode"](x1, y1)
        globals()[prefix + "_addnode"](x2, y2)
        globals()[prefix + "_addsegment"](x1, y1, x2, y2)
    for x1, y1, x2, y2, angle, name in job.get("arcs", []):
        globals()[prefix + "_addnode"](x1, y1)
        globals()[prefix + "_addnode"](x2, y2)
        globals()[prefix + "_addarc"](x1, y1, x2, y2, angle, 1)
        cx, cy = job.get("arc_center", (0, 0))
        mx = (x1 + x2) / 2 - cx
        my = (y1 + y2) / 2 - cy
        radius = math.sqrt((x1 - cx) ** 2 + (y1 - cy) ** 2)
        factor = radius / math.sqrt(mx * mx + my * my)
        globals()[prefix + "_selectarcsegment"](cx + mx * factor, cy + my * factor)
        if prefix == "ci":
            ci_setarcsegmentprop(1, "<None>", 0, 0, name or "<None>")
        else:
            hi_setarcsegmentprop(1, name or "<None>", 0, 0, "<None>")
        globals()[prefix + "_clearselected"]()
    for x, y, radius, name in job.get("circles", []):
        globals()[prefix + "_addnode"](x - radius, y)
        globals()[prefix + "_addnode"](x + radius, y)
        globals()[prefix + "_addarc"](x - radius, y, x + radius, y, 180, 1)
        globals()[prefix + "_addarc"](x + radius, y, x - radius, y, 180, 1)
        globals()[prefix + "_selectarcsegment"](x, y + radius)
        globals()[prefix + "_selectarcsegment"](x, y - radius)
        if prefix == "ci":
            ci_setarcsegmentprop(1, "<None>", 0, 0, name or "<None>")
        else:
            hi_setarcsegmentprop(1, name or "<None>", 0, 0, "<None>")
        globals()[prefix + "_clearselected"]()
    for x, y, name, group in job["blocks"]:
        globals()[prefix + "_addblocklabel"](x, y)
        globals()[prefix + "_selectlabel"](x, y)
        globals()[prefix + "_setblockprop"](
            name,
            0,
            mesh
            * job.get("block_mesh_multipliers", {}).get(
                str(group), job.get("mesh_multipliers", {}).get(name, 1)
            ),
            group,
        )
        globals()[prefix + "_clearselected"]()


def current(job, mesh, stem):
    newdocument(3)
    ci_probdef("millimeters", job.get("type", "planar"), 0, 1e-10, job["depth"], 30)
    for name, sx, sy in job["materials"]:
        ci_addmaterial(name, sx, sy, 1, 1, 0, 0)
    for name, voltage, amperes, kind in job["terminals"]:
        ci_addconductorprop(name, voltage, amperes, kind)
    geometry("ci", job, mesh)
    for x, y, name in job.get("electrode_segments", []):
        ci_selectsegment(x, y)
        ci_setsegmentprop("<None>", mesh, 0, 0, 0, name)
        ci_clearselected()
    path = directory + "/" + stem + ".fec"
    ci_saveas(path)
    ci_close()
    opendocument(path)
    ci_analyze(1)
    ci_loadsolution()
    co_smooth("off")
    terminals = {}
    terminal_power = 0
    balance = 0
    current_scale = 0
    for name, voltage, amperes, kind in job["terminals"]:
        u, i = co_getconductorproperties(name)
        terminals[name] = {"U_V": real(u), "I_A": real(i)}
        terminal_power += real(complex(u) * complex(i).conjugate())
        balance += complex(i)
        current_scale = max(current_scale, abs(complex(i)))
    powers = {}
    power = 0
    for x, y, name, group in job["blocks"]:
        if name == "<No Mesh>":
            continue
        co_groupselectblock(group)
        powers[str(group)] = real(co_blockintegral(0))
        power += powers[str(group)]
        co_clearblock()
    samples = []
    for x, y in job.get("samples", []):
        values = co_getpointvalues(x, y)
        samples.append(
            {
                "x_mm": x,
                "y_mm": y,
                "U_V": real(values[0]),
                "J_A_m2": math.sqrt(
                    abs(complex(values[1])) ** 2 + abs(complex(values[2])) ** 2
                ),
            }
        )
    row = {
        "model": stem + ".fec",
        "mesh_mm": mesh,
        "nodes": co_numnodes(),
        "elements": co_numelements(),
        "terminals": terminals,
        "P_W": power,
        "group_power_W": powers,
        "samples": samples,
        "current_balance_relative": abs(balance) / current_scale,
        "power_balance_relative": abs(power - terminal_power) / max(power, 1e-30),
    }
    check(stem + ": баланс токов", row["current_balance_relative"], 1e-6)
    check(stem + ": баланс мощности", row["power_balance_relative"], 1e-6)
    co_close()
    ci_close()
    return row


def heat(job, mesh, stem):
    newdocument(2)
    hi_probdef("millimeters", job.get("type", "planar"), 1e-10, job["depth"], 30)
    for name, kx, ky, q in job["materials"]:
        hi_addmaterial(name, kx, ky, q, 0)
    for boundary in job["boundaries"]:
        name, kind, temp, ambient, h = boundary[:5]
        beta = boundary[5] if len(boundary) > 5 else 0
        hi_addboundprop(name, kind, temp, 0, ambient, h, beta)
    geometry("hi", job, mesh)
    for x, y, name in job.get("boundary_segments", []):
        hi_selectsegment(x, y)
        hi_setsegmentprop(name, job.get("boundary_mesh", mesh), 0, 0, 0, "<None>")
        hi_clearselected()
    path = directory + "/" + stem + ".feh"
    hi_saveas(path)
    hi_close()
    opendocument(path)
    hi_analyze(1)
    hi_loadsolution()
    ho_smooth("off")
    averages = {}
    generated = 0
    for x, y, name, group in job["blocks"]:
        ho_groupselectblock(group)
        averages[str(group)] = real(ho_blockintegral(0))
        volume = real(ho_blockintegral(2))
        q = [m[3] for m in job["materials"] if m[0] == name][0]
        generated += q * volume
        ho_clearblock()
    samples = []
    for x, y in job.get("samples", []):
        samples.append({"x_mm": x, "y_mm": y, "T_K": real(ho_getpointvalues(x, y)[0])})
    convection = 0
    for x1, y1, x2, y2, ambient, h in job.get("convection_edges", []):
        count = 200
        total = 0
        for n in range(count + 1):
            t = real(
                ho_getpointvalues(
                    x1 + (x2 - x1) * n / count, y1 + (y2 - y1) * n / count
                )[0]
            )
            total += (0.5 if n in (0, count) else 1) * (t - ambient)
        length = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) * 0.001
        convection += total / count * length * job["depth"] * 0.001 * h
    row = {
        "model": stem + ".feh",
        "mesh_mm": mesh,
        "nodes": ho_numnodes(),
        "elements": ho_numelements(),
        "samples": samples,
        "group_average_K": averages,
        "generated_W": generated,
        "convection_W": convection,
    }
    if job.get("convection_edges") and not job.get("skip_convection_balance"):
        check(
            stem + ": баланс конвективного теплоотвода",
            abs(convection - generated) / max(generated, 1e-30),
            0.005,
        )
    ho_close()
    hi_close()
    return row


JOB = {
    "polygon": [[0, 0], [100, 0], [100, 60], [0, 60]],
    "depth": 0.1,
    "materials": [["Резистивный экран", 1000, 1000]],
    "blocks": [[50, 30, "Резистивный экран", 1]],
    "terminals": [["Положительный вывод", 1, 0, 1], ["Нулевой вывод", 0, 0, 1]],
    "samples": [[30, 42]],
}
for level, mesh in enumerate((3, 1.5, 0.75)):
    readings = []
    for axis in (0, 1):
        job = json.loads(json.dumps(JOB))
        job["electrode_segments"] = (
            [[100, 30, "Положительный вывод"], [0, 30, "Нулевой вывод"]]
            if axis == 0
            else [[50, 60, "Положительный вывод"], [50, 0, "Нулевой вывод"]]
        )
        row = current(job, mesh, "mesh-%d-axis-%d" % (level + 1, axis))
        coordinate = row["samples"][0]["U_V"] * (100 if axis == 0 else 60)
        row["coordinate_mm"] = coordinate
        readings.append(row)
        check(
            "Координата оси %d, сетка %d" % (axis, level + 1),
            abs(coordinate - (30 if axis == 0 else 42)),
            1e-5,
        )
    records.append(
        {"mesh_mm": mesh, "readings": readings, "model": readings[-1]["model"]}
    )

result = {
    "runtime": "FEMM 5; embedded MicroPython",
    "records": records,
    "checks": checks,
    "passed": all(item["passed"] for item in checks),
}
with open(directory + "/result.json", "w") as output:
    json.dump(result, output)
print(json.dumps(result))
assert result["passed"]

Картина рассчитанного поля

Потенциал, В: mesh-3-axis-1.anc
Визуализация сохранённого решения FEMM 5 mesh-3-axis-1.anc. Электрический потенциал по сохранённым узловым значениям. Показана расчётная область. Контуры деталей взяты из той же модели. Области вне сохранённых треугольников не заполняются. Это научный график сохранённых данных. Нажмите на карту, чтобы открыть её отдельно и увеличить.

Окна FEMM 5

Снимки интерфейса FEMM показывают геометрию, настройки и работу с результатами в FEMM 5. Откройте изображение, чтобы рассмотреть подписи и значения в полном размере.

Рабочая область модели «Определение координат резистивного сенсорного экрана». Нативный препроцессор FEMM 5.
Рабочая область модели «Определение координат резистивного сенсорного экрана». Нативный препроцессор FEMM 5. Открыть в полном размере
Сохранённое решение «Определение координат резистивного сенсорного экрана». Модуль потенциала |V|, В, в одном координатном режиме резистивной сенсорной панели; питание 1 В.
Сохранённое решение «Определение координат резистивного сенсорного экрана». Модуль потенциала |V|, В, в одном координатном режиме резистивной сенсорной панели; питание 1 В. Открыть в полном размере

Результаты и численные проверки

Сохранено решений FEMM: 6. Выполнено проверок: 18. Полная точность чисел и условия сравнения доступны в таблицах результатов JSON.

Результаты расчётов и производные итоги
Масштаб сетки, ммМодельВосстановленная координата, ммМощность области, ВтЭлементовКонтрольные точкиЭлектродыОтносительная невязка токаОтносительная невязка мощностиМощность, ВтУзловКонтрольная точка: Плотность тока, А/м²Контрольная точка: Напряжение, ВКонтрольная точка: Координата x, ммКонтрольная точка: Координата y, ммЗаданная координата, ммОсь измерения
3mesh-1-axis-0.fec301 полей; полные данные JSON81481 точек; полные данные JSON2 полей; полные данные JSON664,53509 × 10⁻¹²69,208643 × 10⁻¹²0,064200100000,3304230x
3mesh-1-axis-1.fec421 полей; полные данные JSON81481 точек; полные данные JSON2 полей; полные данные JSON23,618274 × 10⁻¹²567,03509 × 10⁻¹²0,16666667420016666,6670,7304242y
1,5mesh-2-axis-0.fec301 полей; полные данные JSON81761 точек; полные данные JSON2 полей; полные данные JSON45,613976 × 10⁻¹²357,08149 × 10⁻¹²0,064230100000,3304230x
1,5mesh-2-axis-1.fec421 полей; полные данные JSON82251 точек; полные данные JSON2 полей; полные данные JSON596,45106 × 10⁻¹²62,467198 × 10⁻¹²0,16666667426716666,6670,7304242y
0,75mesh-3-axis-0.fec301 полей; полные данные JSON249631 точек; полные данные JSON2 полей; полные данные JSON2,526334 × 10⁻⁹2,7912493 × 10⁻⁹0,0612735100000,3304230x
0,75mesh-3-axis-1.fec421 полей; полные данные JSON255281 точек; полные данные JSON2 полей; полные данные JSON3,3218152 × 10⁻⁹1,7924603 × 10⁻⁹0,166666671305416666,6670,7304242y
Итоги исходных серий
Сводные записи; отдельные показания приведены выше
Контрольные показанияМасштаб сетки, ммМодель
2 точек; полные данные JSON3mesh-1-axis-1.fec
2 точек; полные данные JSON1,5mesh-2-axis-1.fec
2 точек; полные данные JSON0,75mesh-3-axis-1.fec
Проверки опубликованного расчёта
КритерийПолученоДопуск или ожидаемое значениеИтог
mesh-1-axis-0: баланс токов664,5 × 10⁻¹²1e-06Выполнен
mesh-1-axis-0: баланс мощности69,21 × 10⁻¹²1e-06Выполнен
Координата оси 0, сетка 113,08 × 10⁻⁹1e-05Выполнен
mesh-1-axis-1: баланс токов23,62 × 10⁻¹²1e-06Выполнен
mesh-1-axis-1: баланс мощности567 × 10⁻¹²1e-06Выполнен
Координата оси 1, сетка 15,327 × 10⁻⁹1e-05Выполнен
mesh-2-axis-0: баланс токов45,61 × 10⁻¹²1e-06Выполнен
mesh-2-axis-0: баланс мощности357,1 × 10⁻¹²1e-06Выполнен
Координата оси 0, сетка 25,344 × 10⁻⁹1e-05Выполнен
mesh-2-axis-1: баланс токов596,5 × 10⁻¹²1e-06Выполнен
mesh-2-axis-1: баланс мощности62,47 × 10⁻¹²1e-06Выполнен
Координата оси 1, сетка 216,11 × 10⁻⁹1e-05Выполнен
mesh-3-axis-0: баланс токов2,526 × 10⁻⁹1e-06Выполнен
mesh-3-axis-0: баланс мощности2,791 × 10⁻⁹1e-06Выполнен
Координата оси 0, сетка 34,485 × 10⁻⁹1e-05Выполнен
mesh-3-axis-1: баланс токов3,322 × 10⁻⁹1e-06Выполнен
mesh-3-axis-1: баланс мощности1,792 × 10⁻⁹1e-06Выполнен
Координата оси 1, сетка 365,95 × 10⁻⁹1e-05Выполнен

Допуски относятся к указанной учебной задаче и способу сравнения. Число показанных цифр не означает такой же физической точности.

Модели, решения и программа

Для нового расчёта сохраните программу, модели и необходимые данные в одну рабочую папку, сохраняя структуру подпапок.

Полные численные результаты

Границы применимости