Как стать автором
Обновить

ATmega16 + DS18B20 + LED + Matlab/Simulink = AR

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


Схемотехника


Схемотехника крайне проста. Сердцем является 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


Желтым цветом обозначены блоки 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


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

Пример работы
Сверху слева — входящее разностное изображение. Снизу большое — статическое изображение с разрешением в два раза больше требуемого. Бегающий прямоугольник — область которая вырезается с нужным разрешением. Вверху справа — то что имеем на выходе. Лучше смотреть на скорости 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
Идентичен двум предыдущим за небольшим исключением. На выходе все склеивается не по третьему а по первому измерению. А блок Permute Matrix просто меняет местами столбцы, так как формат координат [Y X], а нужен [X Y].

Модуль To color


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


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



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



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



Эксперименты


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

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


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



Подоконник


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



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


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



Заключение


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

Публикации

Истории

Ближайшие события