
Схемотехника
Схемотехника крайне проста. Сердцем является ATmega16. Все датчики DS18B20 висят на одном пине (в моем случае на PB0 порта PORTB). Сам пин подтянут к напряжению питания через резистор 4.7 кОм. Схема масштабируема. Картинка кликабельна.

Все LEDы подключены к порту PORTA через ограничительные резисторы. Серый полигон означает что данный LED физически связан с DS18B20. Вывод ресет подтянут к высокому через резистор в 10 кОм для избежания случайного сброса из-за наводок. Микроконтроллер тактируется кварцем в 16 МГц. Ставить как можно ближе к выводам. Нагрузочные емкости используются внутренние. Настраиваются через фъюзы. Отдельно выведены разъемы ICP (для заливки прошивки) и UART для «общения». Ёмкости С1 (электролит 10 мкФ) и C2 (керамика 100 нФ). Ставить как можно ближе к выводам питания микроконтроллера. Используются во избежания случайных сбросов во время переключения нагрузки.
Схемотехника в сборе

Что представляет собой серый полигон

Прошивка + алгоритм работы
Прошивка писалась на C в Atmel Studio 7 IDE. Исходники выложены на GitHub. Код максимально документируем.
Проект разбит на несколько уровней абстракции:
- Hardware — наинизший уровень, максимальная привязка к железу. Работа с периферией микроконтроллера.
- Middleware — связующий уровень между Hardware и Drivers. К примеру реализация протокола 1-Wire.
- Drivers — уровень драйверов. К примеру работа с микросхемой DS18B20.
- Application — наивысший уровень абстракции. Например получение и передача температуры по UART.
Бегло пробежимся по main функции. Вначале стоит таблица ROM адресов. Необходимо чтобы в нулевой позиции был адрес датчика физически связанного с нулевым светодиодом (висящем на PA0 порта PORTA) ну и т.д. Для получения ROM есть функция sendROMToUART. Нужно лишь помнить что датчик должен быть на шине один, иначе будет коллизия адресов.
main
int main(void)
{
const uint8_t ROM[][sizeof(ROM_T)] = /* ROM array */
{
{0x26, 0x00, 0x00, 0x04, 0x4B, 0x15, 0x89, 0x28}, // 0
{0x71, 0x00, 0x00, 0x04, 0x4A, 0xC0, 0x65, 0x28}, // 1
{0xA5, 0x00, 0x00, 0x04, 0x4A, 0xCB, 0xCE, 0x28}, // 2
{0x41, 0x00, 0x00, 0x04, 0x4A, 0xAC, 0x65, 0x28}, // 3
{0x22, 0x00, 0x00, 0x04, 0x4B, 0x06, 0x0D, 0x28}, // 4
{0x86, 0x00, 0x00, 0x04, 0x4A, 0xF6, 0x46, 0x28} // 5
};
uint8_t nDevices = sizeof(ROM) / sizeof(ROM_T); /* Number of DS18B20 devices */
initUART(MYUBRR); /* Initialization of UART with appropriate baudrate */
initTimer0(); /* Initialization of Timer/counter0 */
initLED(nDevices); /* Initialization of LEDs */
{ /* DS18B20s initialization */
uint8_t nDevices = sizeof(ROM) / sizeof(ROM_T); /* Number of DS18B20 devices */
ROM_T *pROM = (ROM_T *)&ROM; /* Pointer to ROM array */
initDQ(); /* Initialization of DQ pin */
while (nDevices--) /* For all DS18B20 */
initDS18B20(pROM++, RESOLUTION_11BIT); /* Initialization of DS18B20 with appropriate resolution */
}
sei(); /* Global enable interrupts */
while (1) /* Infinite loop */
{
sendTemperatureToUART((ROM_T *)&ROM, nDevices); /* Execute function routine */
}
}
Далее идет инициализация периферии и самих DS-ок соответствующим разрешением. От него зависит период выборки температуры. Для 11 бит это 375 мс. В бесконечном цикле программа беспрерывно вычитывает температуру с каждого датчика и шлет ее по UART.
Работа со светодиодами основана на прерываниях. По UART приходит ID светодиода 2 раза подряд на четном и нечетном кадре. На первом светодиод зажигается. Тушит его таймер через определенное время (в моем случае 15 мс). Второй раз просто игнорируем. Таймер настроен в режиме CTC так, чтобы прерывание происходило раз в 1 мс.
код
volatile uint8_t ledID = 0; /* Current ledID value */
volatile uint8_t ledID_prev = 255; /* Previous ledID value */
volatile uint8_t duration = FLASH_DURATION; /* Flash duration value */
ISR(USART_RXC_vect) /* UART interrupt handler */
{
ledID = UDR; /* Assign ledID to receive via UART value */
if (ledID != ledID_prev) /* If current ledID equal to previous value */
{
turnOnLED(ledID); /* Turn on the ledID LED */
timer0Start(); /* Start Timer0 */
ledID_prev = ledID; /* Previous ledID assign to current */
duration = FLASH_DURATION; /* Update LED flash duration */
}
}
ISR(TIMER0_COMP_vect) /* Timer0 compare interrupt handler */
{
if (--duration == 0) /* Decrement Duration value each 1ms and if it reach to 0 */
{
timer0Stop(); /* Stop Timer0 */
turnOffAllLED(); /* Turn off all LEDs */
timer0Clear(); /* Clear Timer0 counter register */
}
}
Участки кода, чувствительные ко времени, а это 1-Wire сигналы, обернуты в ATOMIC_BLOCK конструкцию. Все основные настройки вынесены в global.h. UART работает на скорости 250000. Быстро и без ошибок для кварца в 16 МГц. Драйвер DS18B20 имеет функционал минимально необходимый для данного проекта. Остальное — смотрите код. Будут вопросы — спрашивайте, не стесняйтесь. Отдельно хочется напомнить о настройках фъюзов. В них необходимо выставить возможность тактирования от внешнего кварца иначе будет от внутреннего генератора (а он максимум на 8 МГц и не очень стабильный). Ну и запрограммировать CKOPT бит, иначе кварцы выше 8 МГц не заведутся. У меня High Fuse = 0xD9, Low Fuse = 0xFF.
Модель Simulink + алгоритм работы
Версия Matlab R2015b. В Simulink, помимо встроенной библиотеки, в основном использовались Computer Vision System Toolbox и Image Aquisition Toolbox. Вся модель и сопутствующие файлы выложены на GitHub. Ниже детальное описание с наглядными примерами. Все картинки кликабельны.
Модуль WebCamTemp

Img diff
Слева оригинал, справа — разностная картинка. По ней в дальнейшем ищутся координаты светодиодов (соответственно датчиков тоже). Лучше смотреть по кадрам, но видимо в ютубе такой возможности нет. Можно поставить скорость 0.25.
Img gray
Слева оригинал, справа — картинка, на которую впоследствии накладывается карта. После выхода в стабильный режим видно, что светодиоды не моргают. Сделано чтобы не напрягало.
Frame Rate Display отображает текущий FPS. Вся обработка идет в блоке LEDs. Его мы рассмотрим следующим.
Модуль LEDs

Пример работы
Сверху слева — входящее разностное изображение. Снизу большое — статическое изображение с разрешением в два раза больше требуемого. Бегающий прямоугольник — область которая вырезается с нужным разрешением. Вверху справа — то что имеем на выходе. Лучше смотреть на скорости 0.25.
Зеленым обведены блоки памяти. Их назначение — хранить координаты и гауссианы для каждого светодиода. Детальнее рассмотрим ниже. Блок To color предназначен для постройки распределения температур и цветовой карты. Также будет рассмотрен ниже. Блок композиции сигнала Compositing смешивает два изображения Image1 и Image2 по следующему закону:

Блок Insert Text накладывает форматированный текст (в данном случае температуру) на изображение. Принимает n переменных и координат в формате [X Y]. Можно выбирать шрифт и его размер. Блоки внутри красного прямоугольника реализуют алгоритм скользящего среднего. Переходы становятся менее дерганными, что сохраняет нервы и радует глаз.
Пример
Слева оригинал, справа — скользящее среднее для 8-ми выборок. Когда температуры всех датчиков отличаются между собой на десятые доли градусов (парочку разрешений датчика) такие рывки имеют место быть.
Модули memory
Memory interference и Memory opacity хранят наборы 2D гауссиан, Memory Pts — координаты для каждого LED.
Memory interference и Memory opacity
Эти два модуля идентичны. На вход Address подается номер ячейки куда запишется входной гауссиан. Приходит от счетчика. Совпадает с номером горящего светодиода. Блок Delay модуля LEDs служит для дополнительной синхронизации (пока приходит картинка, счетчик уже успевает оттикать). Так что все у нас синхронизировано. Сигнал Enable разрешает запись. Имеет истинное значение, если значение максимума выше порога (смотрите блок Maximum и Threshold модуля LEDs). Если значение ложно — содержание ячейки не меняется. На выходе все склеивается по третьему измерению. Получается такой себе бутерброд размера [H W n], где HxW — разрешение веб-камеры, ну а n — количество датчиков/светодиодов.

Memory Pts
Модуль To color



m-файл
Выполняется вначале, перед симуляцией, внося переменные в Workspace.
код
H = 480; % Height of image
W = 640; % Width of image
minT = 20; % Min temperature
maxT = 25; % Max temperature
sigmaInt = 40; % Sigma interference
sigmaOp = 80; % Sigma opacity
gain = 1.0; % Gain value
T = 0.3; % Threshold value
nAvr = 8; % number of means
% ------------------------------------------------------
[M,N] = meshgrid(-W:W, -H:H); % Meshgrid function
R = sqrt(M.^2 + N.^2); % Distance from the center
Int = normpdf(R, 0, sigmaInt); % 2D gaussian for interference
Op = normpdf(R, 0, sigmaOp); % 2D gaussian for opacity
Int = Int/max(max(Int)); % Normalization of interference gaussian
Op = Op/max(max(Op)); % Normalization of opacity gaussian
clear M N R sigmaInt sigmaOp % Delete unused variables from memory
load('MyColormaps','mycmap'); % Load colormap
Содержит основные управляющие переменные среди которых:
- H — разрешение видео по высоте.
- W — разрешение видео по ширине.
- minT — минимальная абсолютная температура.
- maxT — максимальная абсолютная температура.
- sigmaInt — сигма гауссианы Interference.
- sigmaOp — сигма гауссианы Opacity.
- gain — максимальное значение Factor.
- T — порог для избежания ошибок.
- nAvr — количество усреднений для скользящего среднего.
H и W должно совпадать с текущим в блоке WebCamera. minT и maxT влияют на цветовую карту в режиме абсолютной температуры. T устанавливается от 0 до 1. Иногда происходит рассинхронизация COM-порта и веб-камеры. Фаза разностного изображения может поменяться на 180°. И там где должен быть максимум — находится минимум. А координату система может выбрать произвольную — не соответствующую действительности. Для этого и существует система порога. nAvr количество усреднений в скользящем среднем. Чем оно больше — тем плавнее переходы, но теряется актуальность (появляется временной сдвиг). Чтобы понять влияние оставшихся переменных, без наглядных примеров не разобраться.
Влияние sigmaInt
При
картинка вырождается в некое подобие разбиения Вороного. При
вся карта будет одного цвета соответствующего средней, от всех датчиков, температуре. При увеличении — границы как бы размываются.


Влияние sigmaOp
Устанавливает скорость спада прозрачности от расстояния.
Влияние gain
По-сути устанавливает прозрачность карты. Само значение соответствует «прозрачности» самого «не прозрачного» пикселя карты.
Эксперименты
Ниже приведены некоторые эксперименты.
Открытое окно
Датчики разбросаны на кровати у окна. Окно открывается и через время закрывается. Наглядный пример различия относительного (слева) и абсолютного (справа) режимов. С помощью первого удобно рассматривать распределение, а второго — как распространяется холод или восстанавливается тепло.
Подоконник
Датчики расположены вдоль подоконника. Окно открывается — профиль меняется. Наглядно видны самая холодная и теплая зоны.
Сверху теплее?
Говорят мол сверху теплее. Этот эксперимент полное тому подтверждение. На 10-ой секунде окно открывается, на 30-ой — закрывается.
Заключение
Такая схема не заменит полноценный тепловизор. Но и он не способен увидеть распространение воздушных масс. А по цене эта конструкция несоизмеримо ниже. В ней можно использовать другую цветовую карту. Можно вместо гауссиан взять другие функции. Можно даже изменить законы построения. Или переписать на OpenCV + QT для скорости и удобства. Но того чего задумал я — достиг. Просто Just For Fun.