Предыстория
После карантина захотелось потренироваться в разводке плат в Easy EDA и их изготовлению методом ЛУТ, да и особого желания делать сразу что-то сложное не было. Поэтому на роль "подопытного кролика" выступил проект цифрового термометра. К тому же это был отличный шанс попрактиковаться в написании программ для МК на C++.
Немного теории
Основой термометра будет терморезистор. Терморезистор - это резистор, у которого его сопротивление меняется от температуры. Терморезисторы разделяют на 2 большие группы: с положительным коэффициентом сопротивления (Сопротивление растёт от температуры) и с отрицательным (Сопротивление понижается от температуры). От типа терморезистора зависят и необходимые расчёты.
Теоретический практикум
Для проекта был подобран следующий терморезистор (рис. 1). Данный артефакт ещё советских времён ( штекер "СШ-5"), но несмотря на свой возраст, работает отлично.
Рисунок 1 - терморезистор
Как показала быстрая проверка датчика - данный терморезистор с положительным коэффициентом сопротивления (Сопротивление растёт от температуры), что немного необычно (Большинство современных терморезисторов обладает отрицательным коэффициентом сопротивления).
После первичного осмотра необходимо снять зависимость сопротивления от температуры. Для этого нам понадобится образцовый термометр и мультиметр (рис. 2).
Рисунок 2 - необходимые инструменты
План замеров таков:
- Замеряем температуру и сопротивление датчика при комнатной температуре.
- Записываем результаты замеров.
- Замеряем температуру и сопротивление датчика при различной температуре (В горячей, холодной, тёплой воде и тд).
- Записываем результаты замеров.
- Повторяем пункты 3 - 4 до тех пор, пока значений будет не менее 3-х.
После снятия первичных значений необходимо их обработать.
Для этого необходимо:
- Определить ТКС (Температурный Коэффициент Сопротивления). Если ТКС < 0, то расчёты производятся по уравнению Стейнхарта-Харта, если ТКС > 0, то зависимость на некотором участке диапазона близка к линейной. В моём случае ТКС > 0, а зависимость линейная (Значит терморезистор изготовлен из какого-то металла).
- Для положительного ТКС: вбиваем показания в Excel и строим график. Далее добавляем линию тренда и выводим полученное уравнение (рис. 3).
Рисунок 3 - получение прогноза температуры от сопротивления
T = 17.173R - 130.82,
где T - температура в градусах Цельсия, а R - сопротивление в килоомах. Данная функция понадобится позже, а именно при написании скетча.
P.s. Данный практикум больше служит для выяснения типа датчика, нежели для получения итоговой функции (Т.к. проще составить прямую зависимость значения АЦП от температуры).
Схема электрическая принципиальная
Рисунок 4 - схема электрическая принципиальная
Да, опять я не пользуюсь сдвиговыми регистрами или драйверами семисегментников... С другой стороны, проект простенький и поэтому особого смысла в экономии выводов ATMEGA328 я не видел. Датчик подключается в разъём H1, который вместе с резистором R1 образуют делитель напряжения. Напряжение с делителя напрямую поступает на аналоговый вывод МК. Данный узел необходим для измерения сопротивления терморезистора.
Печатная плата и 3D модель устройства
Так как изначальной идеей проекта была тренировка в разводке ПП в Easy EDA, то и делать плату мы будем в этой программе (рис. 5). Итоговый размер платы 90x63 мм.
Рисунок 5 - рисунок ПП в Easy EDA
Также для статьи была подготовлена 3D модель устройства (рис. 6).
Рисунок 6 - 3D модель устройства
Помимо большого количества дорожек и малой ширины промежутков между ними, плата ещё и двусторонняя, что доставит ещё дополнительных проблем при её изготовлении, но это нам и нужно (Изначальная цель проекта - изготовление сложной ПП в Easy EDA).
Перед печатью необходимо экспортировать плату в .pdf из программы (Верхний и нижний слои) в чёрно-белом варианте без шелкографии (Это легко настраивается в окне экспортирования (рис. 7)). Нижний слой экспортируется без изменений, а верхний зеркально!
Рисунок 7 - настройки экспортирования для верхнего слоя
Изготовление печатной платы
- Необходимо вырезать трафареты с запасом (по 5 мм с каждой стороны).
- Потом их необходимо совместить на просвет так, чтобы все отверстия совпали.
- Когда отверстия совпадут, скрепляем шаблоны по краям степлером (Аккуратно и не спеша). Лучше скреплять стороны по очереди, следя за отверстиями.
- После соединения шаблонов подготавливаем кусок текстолита по размерам платы и собираем "бутерброд".
- После выравнивания текстолита и шаблонов фиксируем "бутерброд" степлером.
Далее "бутерброд" необходимо разгладить с обеих сторон - тут нужен опыт, у меня так и не получается всё сделать идеально. После хорошо отмываем, так чтобы не осталось белого налёта на медном слое (Тут главное не перегреть плату при переносе рисунка утюгом, иначе налёт будет очень трудно снять), и получаем результат (рис. 8.1 и рис. 8.2).
Рисунок 8.1 - нижняя сторона ПП
Рисунок 8.2 - верхняя сторона ПП
Результат неидеален, но приемлем. Полученную "плату" кидаем в ваш любимый раствор для травления (Я пользуюсь медным купоросом) и вытравливаем плату. Потом отмываем, сверлим отверстия, паяем... Итоговый результат сборки представлен на рисунке 9.1 и рисунке 9.2.
Рисунок 9.1 - верх
Рисунок 9.2 - низ
Вспоминая о соотнесении отверстий на просвет степлером, считаю необходимым показать результат совмещения (рис. 10).
Рисунок 10 - результат совмещения слоёв
Также скажу пару слов о сборке: перед программированием МК необходимо убедится в качестве пайки и травления, иначе можно получить самые разные неприятные сюрпризы.
Практика
Когда дело доходит до практики, то начинаются проблемы с теорией... Основной причиной проблем стала плохая пайка аналогового входа МК, поэтому пришлось перелопатить весь делитель (рис. 11), чтобы понять где была проблема (Совет: Всегда проверяйте пайку мультиметром - это поможет избежать различных проблем). Поэтому все прошлые расчёты можно смело забыть (Но закон Ома забывать не советую) ;).
Рисунок 11 - окончательная схема
Изменения в схеме в основном коснулись делителя, а также был отвязан вывод AREF от +5 В. Теперь R1 притягивает пин PC0 к земле, и номинал резистора был изменён с 10 кОм до 8.1 кОм
Теперь что касается формул. Так как конфигурация делителя изменилась, а на момент отладки уже работал вывод целых чисел на дисплей, то гораздо проще и лучше стало построить график зависимости температуры от значения АЦП и спрогнозировать его (рис. 12).
Рисунок 12 - прогноз температуры от значения АЦП
Итоговой формулой для моего датчика (Напомню, он обладает положительным ТКС и имеет линейную характеристику) стало следующее выражение:
T = -0.7309*X + 378.49,
где T - температура в градусах Цельсия, а X - значение АЦП. Данная формула оказалась вполне рабочей и показания контрольного и самодельного термометров совпадают +- погрешность округления (обрезки дробной части =) ) (рис. 13).
Рисунок 13 - первое включение и тесты (Затемнение на фото вызвано высокой яркостью индикатора)
Помимо всего прочего, схему необходимо обеспечить питанием от стабильного источника напряжение, иначе показания будут "плавать".
Скетч
Помимо проблем с пайкой были ещё и проблемы с самой ATMEGA328PU: экземпляр, который я купил в радиомагазине тактируется исключительно от 1 МГц (брак какой-то), поэтому всеми любимый ардуино код пришлось переделать в гибридный вариант. Изменения в основном коснулись динамической индикации, так как при тактовой частоте 1 МГц ардуино код вызывал сильное мерцание сегментов. Поэтому пришлось писать динамическую индикацию на прямом обращении к регистрам портов (рис. 14).
Рисунок 14 - порты ATMEGA328P
У ATMEGA328P всего 3 порта: PORTB, PORTC, PORTD, из которых PORTC - аналоговый (Содержит АЦП). Как видно из рисунка 15 выводы кварца и даже "Reset" являются частью портов и могут быть сконфигурированы для работы с помощью фьюзов,как и обычные пины ("Reset" трогать не советую ;) ).
Для конфигурации порта необходимо использовать регистр DDR + "Имя порта" (B,C или D и тд.). Пример:
void setup() { DDRC = B111110; //Количество бит = количеству пинов от 0 до максимального. В данном случае: от PC0 до PC5. DDRD = B11111100; // Данный вариант аналогичен пачке pinMode(), но при этом выполняется гораздо быстрее и занимает меньше памяти. DDRB = B00111111; // Но в отличии от pinMode() данный метод не имеет защит, поэтому нужно быть внимательным. }
Нумерация битов идёт справа налево - это нужно помнить!"1" соответствует режиму выхода, "0" - входу.
Теперь краткий экскурс по правильному управлению большим количеством пинов. Пример:
int data[4] = {0,0,0,0}; double T = 0; bool negative = false; const bool chisla[10][8] = { { 0,0,0,0,0,0,1,1 },{ 1,0,0,1,1,1,1,1 },{ 0,0,1,0,0,1,0,1 },{ 0,0,0,0,1,1,0,1 },{ 1,0,0,1,1,0,0,1 },{ 0,1,0,0,1,0,0,1 },{ 0,1,0,0,0,0,0,1 },{ 0,0,0,1,1,1,1,1 },{ 0,0,0,0,0,0,0,1 },{ 0,0,0,0,1,0,0,1 } }; void show() { for (int i = 0; i < 4; i++) { unsigned long sec = micros(); while (micros() - sec < 500) { PORTC = 32 * chisla[data[i]][0]; if ((i == 3) && negative) { PORTC = B100010; PORTD = B01111100; } else { PORTD = 4 * chisla[data[i]][1] + 8 * chisla[data[i]][2] + 16 * chisla[data[i]][3] + 32 * chisla[data[i]][4] + 64 * chisla[data[i]][5] + 128 * chisla[data[i]][6]; } PORTC |= 1 << (4 - i); } } }
Данный метод выводит значение, записанное по цифрам в массив data[]. В отличии от варианта на ардуино коде эта реализация более компактная и оптимизированная, но гораздо сложнее для понимания и отладки (Я потратил на отладку 2 дня).
И так. в массиве chisla[][] записаны коды чисел для семисегментного индикатора с общим анодом. Цикл for перебирает цифры в массиве data[] и подставляет их в один из индексов массива chisla[][], тем самым получая код нужной цифры. Далее массив из 0 и 1 необходимо преобразовать в двоичное число для регистра... На первый взгляд задача сложная, но на самом деле всё гораздо проще.
Для перевода кода цифры необходимо воспользоваться школьными познаниями в информатике: вспомнить перевод числа из десятичной системы счисления в двоичную. Умножая 0 и 1 на 2 в нужной степени мы получим необходимый двоичный код. Пример: для того чтобы "потушить" сегмент "А" необходимо на сегмент "А" подать 5В. Для этого согласно схеме необходимо подать "1" на вывод PC5, т.е. сформировать вот такое двоичное число: "B100000" или умножить 1 на 25 или на 32 (В двоичном виде 32 представляет собой как раз "B100000") =). Код на С++:
PORTC = 32 * chisla[data[i]][0];
В данном случае "0" или "1" берутся из массива chisla[][]. По такому же принципу работают и другие сегменты.
Теперь поговорим о переключении разрядов. Тут всё элементарно - побитовый сдвиг. Т.к. индикатор с общим анодом, то двигаем мы "1". Код на С++:
PORTC |= 1 << (4 - i);
Цикл while в этом методе нужен для "удержания" цифры, чтобы всё случайно не смешалось во едино.
Питание
Для корректной работы АЦП вход AREF необходимо обеспечить стабильным источником опорного напряжения. Т.к. в режиме "DEFAULT" AREF притянут к AVCC, а AVCC питается у нас от 5 В, то для корректной работы АЦП необходим ИОН на 5 В. Чтобы не городить огород с кучей проводов питания - объединим AVCC и VCC, а весь прибор запитаем от DC-DC преобразователя на 5 В (Только необходимо помнить о возможных шумах преобразователя, всё же это китайский модуль).
Итоги
Рисунок 15 - предфинальный результат (Показывает температуру в комнате)
Чтобы не растягивать статью покажу предфинальный результат (Без корпуса, DC-DC преобразователя и немного сыроватым скетчем (Я кнопочку так пока и не задействовал)). Также прибору необходима калибровка, но это последний этап и выполняется он в случае необходимости, а пока меня всё устраивает, но пару слов по данной теме я скажу:
- Для калибровки понадобятся 2 ёмкости: с кипящей водой и с почти замёрзшей (Чтобы плавал лёд). Кипящая вода имеет температуру в 100 °C, а почти замёрзшая - 0 °C.
- Далее необходимо зафиксировать значения АЦП в этих 2-х точках и добавить их на график (в таблицу) (рис. 12,).
Т.к. данные 2 точки постоянны, то по ним можно откалибровать формулу более точно, чем при использовании спиртового комнатного термометра.
Теперь собственно итоги:
- Цифровой термометр - это не такой простой прибор, как кажется, если подойти к вопросу не со стороны готовых модулей и ардуинки.
- Проектирование даже простых устройств начинается с теории и расчётов, даже если потом они не понадобятся ;).
- Сделать двустороннюю плату в домашних условиях ЛУТом можно и это относительно просто, но муторно.
- "Заливка" платы земляным полигоном - хорошо и удобно, но с зазором нужно быть внимательным, так как при изготовлении платы ЛУТом можно поиметь много неприятных сюрпризов.
- Ардуино код - удобно, но не эффективно. При работе с большим количеством пинов рекомендуется напрямую работать с регистрами.
P.s. Данная статья является больше дневником разработки и сборки этого термометра со всеми нюансами всплывшими в это время, чем руководством или инструкцией, т.к. вряд ли кто-то ещё будет так заморачиваться.
Также, для тех кому будет интересно, прикрепил для всех исходники с Easy EDA и скетч.
Прикрепленные файлы:
- Thermometr.zip (211 Кб)
Комментарии (8) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
[Автор]
[Автор]
2. Терморезистор имеет не линейную характеристику
3. Нумерация битов идёт не справа налево, а наоборот.
4. И ряд других ...
1. Порты могут работать, как в дискретном, так и в аналоговом режиме - не все, конечно, и не у всех контроллеров, и даже не все выводы порта.
2. Терморезисторы могут быть и с линейной характеристикой - термометры сопротивления на чистых металлах - типа ТСМ, ТСП - тоже, по сути, терморезисторы, и имеют почти идеально линейную характкристику, А для полупроводниковых терморезисторов надо снимать характеристику не по двум точкам - иначе будет большая погрешность - и таблицу записывать в прошивку, либо пытаться линеаризовать с помощью дополнительных элементов.
3. А биты в байте считают именно справа-налево, т.е. нулевой бит самый правый, а седьмой самый левый!
А на самом деле цифровой термометр - вопреки высказыванию автора - это достаточно простой прибор. Вне зависимости от типа датчика или микроконтроллера. На восьмилапой TIny13 можно сделать термометр с LCD-дисплеем.