ATmega16 + DS18B20 + LED + Matlab/Simulink = AR
Задумал я как-то поиграться с датчиками 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. Нужно лишь помнить что датчик должен быть на шине один, иначе будет коллизия адресов.
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
Желтым цветом обозначены блоки COM-порта. Отдельно передатчик, приемник и конфигуратор. Настройки порта должны в точности совпадать с микроконтроллерскими (скорость, четность, кол-во бит и т.д.). Приемник принимает температуру, группируя ее в одномерный массив размера [n 1] типа int16, где n — количество DS18B20 (у меня 6). Каждый элемент этого массива далее делится на 16. Это из даташита стр. 6. Передатчик отправляет текущее значение счетчика Counter. Оно как раз зажигает определенный светодиод. Тикает от 0 до n. Период 2 семпла. Синим сгруппированы блоки отвечающие за отображение/сохранение видеопотока. Зеленым — блоки получения видеоинформации. Собственно сама веб-камера. Тут куча настроек, разных, зависит от производителя. Картинка выдается в серых тонах. Так интереснее. Блок Diff производит разницу между предыдущим и текущим кадрами. Блок Downsample odd выделяет только разницу зажженный — не зажженный светодиод, но не наоборот. Блок Downsample even пропускает только те кадры, на которых светодиод потушен.
Frame Rate Display отображает текущий FPS. Вся обработка идет в блоке LEDs. Его мы рассмотрим следующим.
Модуль LEDs
Фиолетовым сгруппированы блоки получения 2D гауссиан. Нам нужны две: Interference и Opacity. Отличаются сигмой. Их центр в точке максимума (где горел светодиод). Координаты находятся блоком Maximum. Вместо постоянного генерирования таких гауссиан (а экспонента очень трудоемкая мат. операция) было принято решение их вырезать. Для этого, в m-файле, генерируются две Int и Op размерами в 2 раза больше с центром посредине, из которых, дальше, просто кропаются нужные, блоками Crop interference и Crop opacity.
Зеленым обведены блоки памяти. Их назначение — хранить координаты и гауссианы для каждого светодиода. Детальнее рассмотрим ниже. Блок To color предназначен для постройки распределения температур и цветовой карты. Также будет рассмотрен ниже. Блок композиции сигнала Compositing смешивает два изображения Image1 и Image2 по следующему закону:
Блок Insert Text накладывает форматированный текст (в данном случае температуру) на изображение. Принимает n переменных и координат в формате [X Y]. Можно выбирать шрифт и его размер. Блоки внутри красного прямоугольника реализуют алгоритм скользящего среднего. Переходы становятся менее дерганными, что сохраняет нервы и радует глаз.
Модули memory
Memory interference и Memory opacity хранят наборы 2D гауссиан, Memory Pts — координаты для каждого LED.
Модуль 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 количество усреднений в скользящем среднем. Чем оно больше — тем плавнее переходы, но теряется актуальность (появляется временной сдвиг). Чтобы понять влияние оставшихся переменных, без наглядных примеров не разобраться.
Ниже приведены некоторые эксперименты.
Открытое окно
Датчики разбросаны на кровати у окна. Окно открывается и через время закрывается. Наглядный пример различия относительного (слева) и абсолютного (справа) режимов. С помощью первого удобно рассматривать распределение, а второго — как распространяется холод или восстанавливается тепло.
Подоконник
Датчики расположены вдоль подоконника. Окно открывается — профиль меняется. Наглядно видны самая холодная и теплая зоны.
Сверху теплее?
Говорят мол сверху теплее. Этот эксперимент полное тому подтверждение. На 10-ой секунде окно открывается, на 30-ой — закрывается.
Такая схема не заменит полноценный тепловизор. Но и он не способен увидеть распространение воздушных масс. А по цене эта конструкция несоизмеримо ниже. В ней можно использовать другую цветовую карту. Можно вместо гауссиан взять другие функции. Можно даже изменить законы построения. Или переписать на OpenCV + QT для скорости и удобства. Но того чего задумал я — достиг. Просто Just For Fun.