ATmega16 + DS18B20 + LED + Matlab/Simulink = AR

e2a4a8d7092845e082baee85d185ebd2.bmpЗадумал я как-то поиграться с датчиками DS18B20. Да не просто получить значения температуры (что умеет каждый), но и как-то ее визуализировать. Возникла простая идея. Ставим вебкамеру. Зажигаем светик на четном кадре, на не четном — тушим. Вычитаем картинку — остается только вспышка. По ней и ищем местоположение датчика, который физически привязан к светодиоду в пространстве. А дальше математическая обработка. Ну и все это в симулинке. Под катом описано как получить красивые картиночки. А для тех кто разбираться не желает — предлагаю посмотреть эксперименты в конце статьи.
Схемотехника крайне проста. Сердцем является ATmega16. Все датчики DS18B20 висят на одном пине (в моем случае на PB0 порта PORTB). Сам пин подтянут к напряжению питания через резистор 4.7 кОм. Схема масштабируема. Картинка кликабельна.

1d687f7ddbbc4e29858d20b4ceebcfe7.bmp

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

Схемотехника в сборе
20a9b36fade242d9b70e10be068168ad.jpg


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


Прошивка писалась на 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.
Версия Matlab R2015b. В Simulink, помимо встроенной библиотеки, в основном использовались Computer Vision System Toolbox и Image Aquisition Toolbox. Вся модель и сопутствующие файлы выложены на GitHub. Ниже детальное описание с наглядными примерами. Все картинки кликабельны.

Модуль WebCamTemp


f1db358bbf3f4bb4b9b31dc3724daf27.bmpЖелтым цветом обозначены блоки COM-порта. Отдельно передатчик, приемник и конфигуратор. Настройки порта должны в точности совпадать с микроконтроллерскими (скорость, четность, кол-во бит и т.д.). Приемник принимает температуру, группируя ее в одномерный массив размера [n 1] типа int16, где n — количество DS18B20 (у меня 6). Каждый элемент этого массива далее делится на 16. Это из даташита стр. 6. Передатчик отправляет текущее значение счетчика Counter. Оно как раз зажигает определенный светодиод. Тикает от 0 до n. Период 2 семпла. Синим сгруппированы блоки отвечающие за отображение/сохранение видеопотока. Зеленым — блоки получения видеоинформации. Собственно сама веб-камера. Тут куча настроек, разных, зависит от производителя. Картинка выдается в серых тонах. Так интереснее. Блок Diff производит разницу между предыдущим и текущим кадрами. Блок Downsample odd выделяет только разницу зажженный — не зажженный светодиод, но не наоборот. Блок Downsample even пропускает только те кадры, на которых светодиод потушен.

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



Img gray
Слева оригинал, справа — картинка, на которую впоследствии накладывается карта. После выхода в стабильный режим видно, что светодиоды не моргают. Сделано чтобы не напрягало.



Frame Rate Display отображает текущий FPS. Вся обработка идет в блоке LEDs. Его мы рассмотрим следующим.

Модуль LEDs


63673d92d053459c9c0c226edded2d13.bmpФиолетовым сгруппированы блоки получения 2D гауссиан. Нам нужны две: Interference и Opacity. Отличаются сигмой. Их центр в точке максимума (где горел светодиод). Координаты находятся блоком Maximum. Вместо постоянного генерирования таких гауссиан (а экспонента очень трудоемкая мат. операция) было принято решение их вырезать. Для этого, в m-файле, генерируются две Int и Op размерами в 2 раза больше с центром посредине, из которых, дальше, просто кропаются нужные, блоками Crop interference и Crop opacity.

Пример работы
Сверху слева — входящее разностное изображение. Снизу большое — статическое изображение с разрешением в два раза больше требуемого. Бегающий прямоугольник — область которая вырезается с нужным разрешением. Вверху справа — то что имеем на выходе. Лучше смотреть на скорости 0.25.



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

84a62d3d604b42c5b0923eb74d8d58d4.gif


Блок Insert Text накладывает форматированный текст (в данном случае температуру) на изображение. Принимает n переменных и координат в формате [X Y]. Можно выбирать шрифт и его размер. Блоки внутри красного прямоугольника реализуют алгоритм скользящего среднего. Переходы становятся менее дерганными, что сохраняет нервы и радует глаз.

Пример
Слева оригинал, справа — скользящее среднее для 8-ми выборок. Когда температуры всех датчиков отличаются между собой на десятые доли градусов (парочку разрешений датчика) такие рывки имеют место быть.



Модули memory


Memory interference и Memory opacity хранят наборы 2D гауссиан, Memory Pts — координаты для каждого LED.

Memory interference и Memory opacity
06df1523e1d245f7b824b151f7dfa0e3.bmpЭти два модуля идентичны. На вход Address подается номер ячейки куда запишется входной гауссиан. Приходит от счетчика. Совпадает с номером горящего светодиода. Блок Delay модуля LEDs служит для дополнительной синхронизации (пока приходит картинка, счетчик уже успевает оттикать). Так что все у нас синхронизировано. Сигнал Enable разрешает запись. Имеет истинное значение, если значение максимума выше порога (смотрите блок Maximum и Threshold модуля LEDs). Если значение ложно — содержание ячейки не меняется. На выходе все склеивается по третьему измерению. Получается такой себе бутерброд размера [H W n], где HxW — разрешение веб-камеры, ну, а n — количество датчиков/светодиодов.


Memory Pts
f2502b4415bd4badad2de75142a84044.bmpИдентичен двум предыдущим за небольшим исключением. На выходе все склеивается не по третьему, а по первому измерению. А блок Permute Matrix просто меняет местами столбцы, так как формат координат [Y X], а нужен [X Y].


Модуль To color


ff4661f7313342bc87aab6257d405ee9.bmpЗеленым — обработка Opacity. Суммируем входной массив по третьему измерению. Нормализуем его по максимуму. Умножаем на значение gain (от 0 до 1) (1). Итого имеем массив с наложенными друг на друга гауссианами и максимумом gain. Используется как Factor для смешения изображений. Красное — получение цветовой карты температур. Тут немного другая математика, все тех же гауссиан. Описывается формулой (2). Грубо говоря, температура в произвольной точке — средневзвешенная ото всех датчиков. Но влияние каждого датчика в процентном соотношении пропорционально значению гауссианы в ней. Сумма всех принимается за 100%.

87ae2d4a31ba4759bb9cf4d093fff846.gif


c51f48e29b614eb28a0134f517ee49e8.bmpОсталось рассмотреть как распределение температур превращается в цветовую карту. То что обведено синим превращает конкретную температуру в значение между 0 и 1. В красной зоне блоком Prelookup вычисляется индекс, по которому ищется значение красного, зеленого и синего. Массив цветов содержит 64 значения. Промежуточные вычисляются методом интерполяции. Из особенностей есть два режима: относительный и абсолютный. В относительном самому холодному и горячему месту соответствует минимум и максимум входного массива. В абсолютном — некие константные значения. В первом удобнее смотреть профиль распределения температур. В другом — ее абсолютные изменения.

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
При dd452efa07f74879864a7da50c880f39.gif картинка вырождается в некое подобие разбиения Вороного. При 5de994dd0e834600b8fff948ae13eb07.gif вся карта будет одного цвета соответствующего средней, от всех датчиков, температуре. При увеличении — границы как бы размываются.



Влияние sigmaOp
Устанавливает скорость спада прозрачности от расстояния.



Влияние gain
По-сути устанавливает прозрачность карты. Само значение соответствует «прозрачности» самого «не прозрачного» пикселя карты.



Ниже приведены некоторые эксперименты.

Открытое окно


Датчики разбросаны на кровати у окна. Окно открывается и через время закрывается. Наглядный пример различия относительного (слева) и абсолютного (справа) режимов. С помощью первого удобно рассматривать распределение, а второго — как распространяется холод или восстанавливается тепло.

Подоконник


Датчики расположены вдоль подоконника. Окно открывается — профиль меняется. Наглядно видны самая холодная и теплая зоны.

Сверху теплее?


Говорят мол сверху теплее. Этот эксперимент полное тому подтверждение. На 10-ой секунде окно открывается, на 30-ой — закрывается.


Такая схема не заменит полноценный тепловизор. Но и он не способен увидеть распространение воздушных масс. А по цене эта конструкция несоизмеримо ниже. В ней можно использовать другую цветовую карту. Можно вместо гауссиан взять другие функции. Можно даже изменить законы построения. Или переписать на OpenCV + QT для скорости и удобства. Но того чего задумал я — достиг. Просто Just For Fun.

© Geektimes