Цифровой милливольтметр
Часть 3. Программное обеспечение и настройка
Программное обеспечение

Управляющая программа для микроконтроллера ("прошивка", "скетч") определяет функциональные возможности прибора, его реакции на действия пользователя и отображаемую на дисплее информацию, поэтому, не меняя ничего в аппаратной части, а только внося изменения в код программы, читатель может получить в итоге свою модификацию цифрового милливольтметра, довольно сильно отличающуюся по своим характеристикам и поведению от описанной здесь. Автор не считает, что разработанная им прошивка является самой лучшей и единственно возможной, поэтому призывает читателей вносить свой вклад в ее улучшение, модернизацию и устранение наверняка имеющихся в ней ошибок. Все позитивное, что будет предложено читателями, включая даже полную замену авторской прошивки на совершенно новую, если кто-нибудь захочет ее написать, будет опубликовано на этом сайте.
Исходные тексты прошивки
Прошивка микроконтроллера написана на языке C++ в среде Arduino IDE верс.2.1.1. Исходные тексты программы содержатся в двух файлах:
- DigitalMillivoltmeter.ino - главный файл программы, содержащий функции setup() и loop(), а также функции, связанные с измерением напряжения и частоты сигнала и обработкой нажатий на кнопки
- display.ino - функции отображения информации на дисплее
Такое разбиение удобно для облегчения навигации в коде и для его возможной последующей модификации. Например, если монохромный символьный ЖК дисплей будет когда-нибудь заменен на цветной графический, то все изменения в тексте программы нужно будет вносить только в файл display.ino без какого-либо вторжения в главный файл программы.
Ниже под спойлером можно прочитать краткое объяснение того, как работает основной цикл программы - функция loop(). Это объяснение поможет понять структуру управляющей программы тем читателям, кто захочет в ней поглубже разобраться и, возможно, внести в нее какие-нибудь изменения. Все остальные могут это объяснение пропустить и двигаться дальше.
Главный цикл программы (функция loop) выполняет следующие действия:
- Считывает состояние кнопки переключения режимов "MODE" и, если обнаружено, что кнопка нажата, ждет ее отпускания, а затем переключает режим "AUTO" -> "MANUAL" и наоборот.
- Если текущий режим - "MANUAL", то считывает состояние кнопок "UP" и "DOWN" и, если одна из них нажата, ждет ее отпускания, а затем переключает поддиапазон вверх или вниз, соответственно.
- Проверяет готовность данных измерения частоты сигнала, и если метод available() объекта FreqMeasure сообщает, что данные готовы, считывает их методом read() и добавляет результат в переменную sumF. В этой переменной накапливается сумма результатов 10-ти циклов для последующего усреднения. Когда 10 циклов выполнено, значение sumF делится на число циклов (10) для получения усредненного значения частоты и результат передается функции displayFrequency для отображения на дисплее. Кроме этого, в процессе выполнения циклов измерения в переменных maxF и minF сохраняются максимальная и минимальная частоты за 10 циклов и, если их разница составит 60% или более (variation > 0.6), то делается вывод о том, что сигнал является непериодическим и на дисплей в этом случае вместо значения частоты выводится сообщение "non-periodic".
- Считывает результат аналогово-цифрового преобразования напряжения сигнала из рабочего регистра АЦП методом getData() объекта ads и добавляет результат в переменную sumU. В этой переменной накапливается сумма результатов нескольких циклов измерений для последующего усреднения. Число циклов может составлять 10 в нормальном режиме измерения напряжения или 4 в процессе поиска и автоматического переключения поддиапазонов измерений. Когда заданное число циклов выполнено, значение sumU делится на это число для получения усредненного значения. Это значение передается функции autoRange, которая следит за тем, чтобы измеренное значение попадало в текущий поддиапазон измерений и, если необходимо, переключает поддиапазоны вверх или вниз. Далее результат передается функции showResults для отображения на дисплее. В процессе выполнения циклов измерения в переменных maxU и minU сохраняются максимальный и минимальный результат, считанный из рабочего регистра АЦП и, если их разница составит 20% или более (variation > 0.2), то делается вывод о том, что сигнал является нестационарным и на дисплей в этом случае вместо значения напряжения выводится сообщение "точки".
Загрузить файлы исходного текста (в виде архива .zip) можно по ссылке в конце этой публикации. Тексты снабжены подробными комментариями, поэтому назначение каждой константы, переменной или функции должно быть понятно читателям, владеющим минимальными навыками программирования на C++. Если потребуются более детальные пояснения - задавайте вопросы, автор будет рад на них ответить.
Компиляция
Для компиляции кода и его загрузки в микроконтроллер, необходимо оба файла с исходными текстами поместить в одну папку на жестком диске компьютера, названную так же как и главный файл - DigitalMillivoltmeter. В этом случае при открытии главного файла в среде Arduino IDE второй файл будет автоматически загружен, открыт в отдельной вкладке и будет компилироваться совместно с главным.
Кроме того для компиляции необходимы следующие библиотеки, подключаемые в коде программы директивами #include:
- Wire - работа с интерфейсом I2C
- LiquidCrystal_I2C - работа с символьным ЖК дисплеем LCD1602
- ADS1110 - работа с модулем АЦП на базе микросхемы ADS1110
- FreqMeasure - измерение частоты сигнала
Эти библиотеки широко используются во многих проектах, они скорее всего уже есть на ваших компьютерах, а если нет, то их легко найти на сайтах arduino или github. Загрузить их, а затем обновлять по мере выхода новых версий проще всего с помощью встроенного в среду разработки Менеджера библиотек.
Компиляция кода может выполняться в двух вариантах - в режиме отладки или в режиме релиза. Для выбора режима отладки нужно раскомментировать находящуюся в самом начале файла SignalGenerator.ino строчку:
#define __DEBUG__
В режиме отладки программа будет передавать среде разработки Arduino IDE детальные сообщения обо всех выполняемых действиях через последовательный порт. Для того чтобы увидеть эти сообщения, необходимо установить связь между компьютером и микроконтроллером через кабель USB, запустить на компьютере Arduino IDE и открыть в ней окно Serial Monitor. Такая трассировка хода выполнения программы может существенно облегчить поиск неисправности в случае, если в работе прошивки что-то пойдет не так.
По окончании отладки следует перейти в режим релиза, закомментировав указанную выше строчку, после чего нужно будет вновь откомпилировать программу и загрузить получившийся двоичный код в микроконтроллер. В этом случае отладочные сообщения в среду разработки передаваться не будут, объем, занимаемый прошивкой в памяти контроллера заметно уменьшится, а скорость выполнения кода повысится.
Настройка милливольтметра
Прежде чем приступить к настройке прибора в целом, необходимо выполнить настройку каждого из трех модулей нашего набора, входящих в состав милливольтметра, в соответствии с рекомендациями в их описаниях. Если это еще не было сделано, то необходимо обратиться к соответствующим публикациям на этом сайте и выполнить те операции по настройке, которые там описаны.
Далее следует убедиться, что перемычки ("джамперы") на платах модулей аттенюатора ATT01 и измерительного усилителя AMP01 находятся в следующих положениях:
- перемычка SV1 в модуле аттенюатора установлена в положение 2-3, при этом модуль работает в режиме усиления в 3.16 раза (+10 дБ)
- перемычка SV1 в модуле измерительного усилителя установлена в положение 1-2, что соответствует усилению в 31,6 раза (+30 дБ)
Затем необходимо выполнить еще несколько подготовительных операций:
- соединить разъем USB микроконтроллера Arduino Nano с USB портом компьютера при помощи соответствующего кабеля, на компьютере запустить среду разработки Arduino IDE,
- откомпилировать прошивку в режиме отладки и загрузить ее в микроконтроллер,
- в Arduino IDE открыть окно Serial Monitor для просмотра отладочных сообщений,
- подключить ко входу прибора звуковой генератор. При этом надо учитывать, что диапазон измерения напряжений милливольтметра доходит до 30 В (RMS), поэтому подойдет не любой звуковой генератор, а такой, который может обеспечить этот уровень сигнала на выходе,
- подготовить другие средства измерений, которые обязательно потребуются по ходу настройки: осциллограф, универсальный вольтметр постоянного/переменного тока (можно современный цифровой мультиметр хорошего качества) и, что весьма важно, образцовый милливольтметр, по показаниям которого вы будете калибровать изготовленный вами прибор.
Подавая питание от внешнего источника 9 В при первом включении прибора, необходимо контролировать потребляемый прибором ток. Если он окажется в пределах 160-200 мА, то можно приступать к дальнейшим операциям по настройке, если больше или меньше, а особенно, если значительно больше, то лучше сразу же выключить питание и тщательно проверить монтаж - вероятно, где-то допущены ошибки, приводящие к замыканию цепей питания и к возрастанию потребляемого тока или имеются обрывы цепей, из-за которых ток потребления снижен.
1-й этап настройки
На этом этапе мы не занимаемся точностью получаемых разультатов, нам лишь необходимо убедиться, что прибор функционирует в соответствии с описанием, приведенным в первой части этой публикации, а именно:
- результаты измерений, включая значения напряжения в мВ или в В, уровня в дБ и частоты в Гц отображаются на дисплее и их формат соответствует приведенным в описании примерам,
- в режиме "AUTO" при изменени напряжения на входе прибора происходит автоматическое переключение поддиапазонов измерений (какой именно поддиапазон включен в данный момент времени, можно понять по сообщениям в окне Serial Monitor), при этом на время поиска оптимального поддиапазона на дисплей выдается соответствующая индикация ("звездочки"),
- в режиме "MANUAL" переключение поддиапазонов происходит по нажатиям кнопок "UP" и "DOWN", при этом в случае перегрузки на дисплее отображается сообщение "=OVERLOAD=",
- прибор выявляет ситуации, когда на вход подается нестационарный сигнал (индикация "точки") и непериодический сигнал (индикация "non-periodic").
Если поведение прибора не соответствует описанию или он просто не работает и на дисплее отсутствует какая-либо информация, это может быть вызвано следующими причинами:
- несоответствие адресов модулей на шине I2C константам в тексте программы. Модули АЦП и ЖК дисплея могут иметь до 8-ми различных адресов в зависимости от установленной в модуле микросхемы и от положения перемычек на плате. Проверьте, какие адреса I2C имеют ваши модули, исправьте их в исходном тексте и перекомпилируйте программу, если необходимо,
- ошибки монтажа или неисправности самих модулей. Выявляются при помощи мультиметра и осциллографа. Проверьте все напряжения питания на каждом из модулей, опорные напряжения 2.0 В и 2.5 В в контрольных точках на вспомогательной плате, наличие сигналов на входах и выходах каждого модуля и на входе и выходе компаратора на вспомогательной плате, убедитесь в срабатывании реле и в замыкании/размыкании его контактов при переходе на два старших поддиапазона, проверьте состояние сигналов A, B и C на входах модуля аттенюатора на соответствие таблице, приведенной на принципиальной схеме прибора.
Большую помощь в поиске и устранении неисправностей оказывают отладочные сообшения, появляющиеся в окне Serial monitor. По ним легко понять, какие именно функции вызываются и каковы значения переменных, возвращаемых этими функциями. Это дает возможность выявить тот участок кода, где что-то идет не так, а значит, определить, какая именно аппаратная неисправность или ошибка монтажа вызывает проблему.
2-й этап настройки
На этом этапе выполняется калибровка цифрового милливольтметра по показаниям прибора, принятого вами в качестве образцового. Говоря о погрешности того или иного средства измерения, в том числе и того, который мы сейчас настраиваем, мы не можем утверждать, что она составляет столько-то процентов от истинного значения измеряемой величины. На самом деле мы можем судить об этом истином значении только измерив его другим прибором, желательно таким, показаниям которого мы доверяем. Поэтому правильно будет говорить о погрешности результатов измерений нашего прибора по отношению к показаниям другого прибора, принятого за эталонный. Сейчас мы будем калибровать изготовленный нами прибор с тем, чтобы свести эту погрешность к минимуму.
До начала калибровки необходимо как можно точнее измерить опорное напряжение в контрольной точке TP2 на вспомогательной плате. Оно должно составлять 2.00 В с допустимым отклонением не более ±20 мВ. Если будет обнаружено отклонение больше указанного, необходимо подобрать резистор R1 или R2. Кроме того, полезно подержать прибор во включенном состоянии с закрытой крышкой хотя бы 20-30 минут, чтобы в его корпусе установился тепловой режим.
Для того чтобы понимать смысл дальнейших действий, полезно ознакомиться с объяснением, как именно измеряется напряжение в нашем приборе и как вычисляются значения напряжения, которые мы видим на дисплее. Это объяснение вы найдете под спойлером ниже, арифметика там достаточно нехитрая, но без ее понимания двигаться дальше нам будет сложно.

Аналогово-цифровой преобразователь (АЦП) ADS1110, являющийся основой описываемого цифрового милливольтметра, возвращает результат измерения в виде 16-ти разрядного целого числа со знаком, имеющего в программе тип int. В дальнейшем будем называть его ADCResult. Такое число, если выразить его в десятичном формате будет иметь диапазон значений от -32768 до 32767. Этот диапазон значений в том режиме, в котором мы используем АЦП, соответствует постоянному напряжению на входе IN+ модуля АЦП от 0,000 В до 4,096 В. Причем результат равный десятичному 0 будет соответствовать напряжению на входе, равному опорному напряжению, поданному на вход IN- модуля. Все это хорошо видно на графике, на котором горизонтальная ось соответствует входному напряжению АЦП в вольтах, а вертикальная - числу ADCResult, считываемому из рабочего регистра АЦП.
Зависимость между входным напряжением АЦП и ADCResult является строго линейной - разработчики и производители микросхем этого типа стараются изо всех сил чтобы эту линейность соблюсти как можно точнее, поэтому вычислить число ADCResult, зная входное напряжение, всегда можно с помощью хорошо известной из школьного курса математики линейной функции y = kx + b, где значение функции вычисляется по известному значению аргумента при помощи двух компонент: k - мультипликативная компонента (то-есть компонента, используемая для умножения или деления) и b - аддитивная компонента (используемая для сложения или вычитания). Милливольтметр решает обратную задачу - прочитав число ADCResult, он должен вычислить, какому напряжению на входе оно соответствует. Эта операция при известных мультипликативной и аддитивной компонентах также решается очень просто и соответствующее выражение в управляющей программе милливольтметра могло бы выглядеть так:
voltage = (ADCResult + VRef) / VDiv
где VRef - аддитивная компонента, а VDiv - мультипликативная.
Однако для каждого из восьми поддиапазонов измерений эти компоненты имеют разные значения и поэтому для их хранения в программе создан массив Ranges[8], состоящий из восьми структур Range, каждая из которых хранит не только VDiv и VRef, но и некоторую другую полезную информацию о каждом из поддиапазонов. Масив этот объявлен в файле DigitalMillivoltmeter.ino, в строке 90 и выглядит вот так:
const range Ranges[8] = {
{5059160.0, 31979, 0b11000011, "10mV"}, // Range[0]
{1601000.0, 31983, 0b11000111, "30mV"}, // Range[1]
{ 507430.0, 31987, 0b11001011, "100mV"}, // Range[2]
{ 160113.3, 31987, 0b11001111, "300mV"}, // Range[3]
{ 50465.0, 31987, 0b11010011, "1V"}, // Range[4]
{ 15904.0, 31987, 0b11010111, "3V"}, // Range[5]
{ 5031.6, 32022, 0b11110011, "10V"}, // Range[6]
{ 1548.0, 32022, 0b11110111, "30V"} // Range[7]
};
Каждый элемент массива (а всего элементов 8, по количеству поддиапазонов) содержит поля VDiv и VRef, назначение которых описано выше, двоичное число RCode, используемое для включения соответствующего поддиапазона, а также имя поддиапазона в виде строки, которая отображается на дисплее при ручном переключении.
Для того чтобы обратиться к этому массиву и извлечь нужные для вычисления компоненты, нужно знать, какой поддиапазон включен в данный момент времени. Это знание в программе находится в переменной currentRange, которая используется в качестве индекса в массиве Ranges[8]. В итоге выражение для вычисления входного напряжения приобретает такой вид:
voltage = (ADCResult + Ranges[currentRange].VRef) / Ranges[currentRange].VDiv
а находится оно в исходном тексте программы в теле функции showResults.
Физический смысл этих компонент заключается в том, что VRef смещает по вертикали параллельно самому себе график зависимости значения voltage от ADCResult, поэтому при настройке прибора мы будем использовать эту компоненту для установки нуля на каждом из поддиапазонов, а VDiv меняет наклон графика, не меняя точку прохождения его через ноль, и тем самым используется для настройки чувствительности прибора также индивидуально для каждого из поддиапазонов.
Калибровка показаний начинается с установки нулевых показаний на каждом из поддиапазонов. Для этого замыкаем накоротко вход милливольтметра, нажатием на кнопку "Mode" переводим его в режим "MANUAL" и, переключая поддиапазоны измерений кнопками "UP" и "DOWN", для каждого из них подбираем и записываем в массив Ranges[8] соответствующее значение компоненты VRef.
Выяснить, какое число нужно записать в поле VRef для установки нуля на выбранном поддиапазоне, проще всего, внимательно рассматривая отладочную информацию в окне Serial Monitor. Например, при установке нуля на поддиапазоне "30mV" видим в окне Serial Monitor периодически добавляющиеся отладочные сообщения примерно такого вида:
Range=1 minU=-31987 maxU=-31980 variation=0.0002 ADCResult=-31983 voltage=0.0000
Range=1 minU=-31987 maxU=-31978 variation=0.0003 ADCResult=-31983 voltage=0.0000
Range=1 minU=-31987 maxU=-31978 variation=0.0003 ADCResult=-31984 voltage=0.0000
Range=1 minU=-31985 maxU=-31977 variation=0.0003 ADCResult=-31982 voltage=0.0000
Здесь мы видим, что включен поддиапазон "30mV" (Range=1), что максимальное и минимальное значения, измеренные за 10 циклов, незначительно отличаются друг от друга (variation=0.0003), то-есть сигнал является стационарным и, наконец, значение ADCResult равно -31983 с небольшими отклонениями. Вот это значение, только со знаком "+" мы и записываем в соответствующее поле массива Ranges[8]. Далее эти же действия повторяем для остальных поддиапазонов.
Добившись нулевых показаний милливольтметра при отсутствии входного сигнала на всех поддиапазонах, приступаем к регулировке чувствительности. Для этого по очереди включаем в режиме "MANUAL" каждый из поддиапазонов и подаем на вход милливольтметра синусоидальный сигнал частотой 100...400 Гц со среднеквадратичным значением соответствующим верхней границе поддиапазона (например, на поддиапазоне "1V" подаем напряжение 1 В, на поддиапазоне "3V" - 3 В и так далее), контролируя его эталонным прибором. Далее подбираем значения поля VDiv в соответствующей строчке определения массива Ranges[8] так чтобы настраиваемый милливольтметр показал значение, равное показаниям эталонного прибора.
Как только эта процедура будет успешно закончена, необходимо перекомпилировать программу в режиме релиза и загрузить ее в микроконтроллер, затем взять фотоаппарат или смартфон, сфотографировать ваш прибор и прислать фото сюда на сайт, сопроводив его описанием того, какие трудности вы встретили в процессе изготовления и настройки и как именно вы эти трудности победили. Ваш опыт будет чрезвычайно ценен для наших читателей.
Файлы для загрузки
DigitalMillivoltmeterSourceCode.zip - исходные тексты прошивки микроконтроллера. В архиве содержатся 2 файла - DigitalMillivoltmeter.ino и display.ino.
AuxiliaryBoard.zip - все файлы, необходимые для изготовления вспомогательной платы милливольтметра. В архиве содержатся:
- AuxiliaryBoard.brd и AuxiliaryBoard.sch - принципиальная схема и печатная плата в формате САПР Eagle 7.5.0.
- AuxiliaryBoard V.1.1 Bottom print.png - фотошаблон для изготовления печатной платы методом пленочного фоторезиста
- AuxiliaryBoard V.1.1 Top.png и AuxiliaryBoard V.1.1 Bottom.png - рисунки расположения элементов на плате и конфигурации печатных проводников, экспортированные из Eagle