Пришла зима, короткий день, домашней пальме мало света. Нужно организовать подсветку. Готовую лампу покупать как-то неловко, да и надо ж чем-то заняться долгими зимними вечерами. Поехали 😉
1. Купил на Али мощные светодиоды с питанием в 220В. Вот такие:
Отзывы оказались верными: греются, и очень сильно. Нужно огранизовать охлаждение. Пассивного (прикрутил к дюралевому профилю) недостаточно, значит, нужно придумать что-то активное. Например, радиатор с кулером от старого процессора — это недорого, и их как грязи. Правда, где кулер — там питание 12В. Ок, это тоже несложно. Но при питании от 12 В кулер (особенно недорогой бу) будет достаточно громко шуметь.
2. Регулятор оборотов кулера
Пусть это будет step-down конвертор. Хороший КПД, меньше помех в цепях питания.
Ключевой транзистор и диод выпаял из покойной материнской платы, дроссель оттуда же. Оригинальную обмотку из толстого провода срезал, намотал свою проводом 0.3мм — сколько поместилось. Транзистор Q1 — какой-то маломощный npn из закромов. Подаем на вход импульсы разной скважности — на выходе получаем напряжение от 0 до 12В. Удобно, но нужен генератор импульсов с достаточно высокой частотой (десятки кГц и выше).
3. Как сделать генератор импульсов со скважностью от 0 до 100%?
Причем с возможностью автоматической регулировки? Проще всего взять микроконтроллер, который умеет в PWM. Я склоняюсь к AtMega8 — простой, отлично документированный и дешевый. Но использовать микроконтроллер только для вращения кулера — это из пушки по воробьям. Можно на этом же железе решить и дополнительные задачи.
Подсветку неплохо автоматически включать и выключать. Напрашивается функция часов/будильника, но делать часы (и усложнять простое устройство) не хочется. В часах нужен индикатор (или веб-интерфейс), нужен способ синхронизации (ручной или через интернет), нужен резервный источник питания. Но в лампе всего этого не нужно. Поэтому алгоритм будет такой:
-
подсветка работает 8 часов и отключается по таймеру или кнопкой;
-
включается подсветка по кнопке или с наступлением рассвета;
-
рассвет детектируется светодиодом. Для этого функционала достаточно фоторезистора и АЦП.
Кулер желательно крутить помедленнее, но не допуская ни полной остановки, ни перегрева светодиода. Для детектирования скорости вращения у кулера есть встроенный датчик, для измерения температуры возьмем терморезистор. Мне попался NTC MF52A1, с сопротивлением при 25С 10кОм. В даташите есть таблица с зависимостью сопротивления от температуры; так что можно сделать простой и очень дешевый измеритель температуры с приемлемой точностью. Итого, от контроллера нужно:
-
АЦП — 2 входа
-
PWM — 1 выход
-
прерывание датчика вращения кулера — 1 вход
-
кнопка вкл/выкл — 1 вход
-
управление подсветкой — 1 выход
-
UART — удобно для диагностики в железе
4. Конфигурирование микроконтроллера
Fuse bits.
Чтобы не получить окирпиченный МК, проще всего воспользоваться готовым калькулятором fuse битов. Например, таким. Главное, что нужно — выбрать синхронизацию от внешнего кварца. Остальные биты можно не трогать 😉 У меня получилось
lfuse |
0xFF |
hfuse |
0xD9 |
Таймеры
Во первых, PWM. Для этого пригодны 2 таймера: 16-битный Timer1 и 8-битный Timer2. Timer1 будет считать системное время (об этом позже), Timer2 (с 256 уровнями PWM) — отлично подходит для регулятора напряжения. При тактовой частоте 16 МГц максимальная частота PWM будет:
Длительность самого короткого импульса:
Думаю, это приемлемо. В даташите на ключевой транзистор есть Rise Time 33 nS. В окончательном варианте устройства можно использовать встроенный генератор на 8МГц — это тоже сработает.
Настройка Timer2
См https://sites.google.com/site/qeewiki/books/avr-guide/pwm-atmega8
Нам нужны следующие функции:
-
No Prescaling — чтобы получить частоту повыше
-
Fast PWM
-
Inverted mode (LOW at bottom, HIGH on Match) — преобразователь напряжения с инвертором; когда напряжение на выходе прямо зависит от значения счетчика — это удобно.
// PWM // Выход жестко приколочен к пину PB3 #define PWM_FAN_PIN _BV(PB3) DDRB |= PWM_FAN_PIN; OCR2 = 255; // set PWM for 100% duty cycle TCCR2 |= (1 << COM21); TCCR2 |= (1 << COM20); // set inverting mode TCCR2 |= (1 << WGM21) | (1 << WGM20); // set fast PWM Mode TCCR2 |= (1 << CS20); // set prescaler to 0 and starts PWM
Измерение времени
В Arduino принято измерять время в миллисекундах — с такой частотой тикает таймер. Такая точность для наших целей не нужна, да и милисекундный счетчик переполняется через 49 суток. Мы будем считать интервалы по 100 мС, этот счетчик переполнится очень не скоро.
Чтобы получить такие временные интервалы с кварцем 16 МГц, нужен предделитель 256 и счетчик до 6250. Прекрасно.
Настройка Timer1
См https://sites.google.com/site/qeewiki/books/avr-guide/timer-on-the-atmega8
// 16000000/256/6250 = 10 hz // timer 1, CTC, 256 prescaler TCCR1B |= (1 << CS12) | (1 << WGM12); TIMSK |= (1 << OCF1A); // enable interrupt OCR1A = 6250;
Код счетчика времени очень простой:
uint32_t uptime_x_0_1s = 0; ISR (TIMER1_COMPA_vect) { // action to be done every 0.1 sec uptime_x_0_1s++; }
Переполнится счетчик через
Больше чем достаточно 😉
Измерение частоты вращения кулера
В кулере есть встроенный датчик, который замыкается на землю 1 раз (или 2 раза — разные источники пишут разное) за оборот. Для измерения частоты вращения подключим датчик к пину, умеющему генерировать прерывания, сконфигурируем его как вход с внутренним pull-up резистором.
Прерывания от кулера
См http://www.atmega8.ru/wiki/view/doc.9.html
// FAN interrupt #define TAHO_FAN_PIN _BV(PD3) SREG |= (1<<7); PORTD |= TAHO_FAN_PIN; // pull-up GIMSK |= _BV(INT1); MCUCR |= _BV(ISC11); // Прерывание вызывается по возрастающему фронту сигнала на входе INT
Счетчик прерываний совсем простой:
uint16_t ts_fan_interrupt; ISR (INT1_vect) { /* interrupt code here */ // убираем дребезг - на совсем малых оборотах // он почему-то наблюдался if (TCNT1 - ts_fan_interrupt <10) return; ts_fan_interrupt = TCNT1; fan_cnt ++; }
Функция do_fan_rps срабатывает не чаще чем 1 раз в секунду и подсчитывает число прерываний за 1 секунду.
unsigned int rps = 0; void do_fan_rps() { static uint32_t rps_ts; unsigned int fan_cnt_; int dt = uptime_x_0_1s - rps_ts; if ( dt < 10) return; cli(); fan_cnt_ = fan_cnt; fan_cnt = 0; sei(); rps = fan_cnt_ * 10 /dt ; rps_ts = uptime_x_0_1s; }
АЦП будем использовать для измерения температуры и освещенности. АЦП в AtMega 10-разрядный, и он может измерять 1024 уровня от 0 до опорного напряжения Vref. 2 младших разряда можно отбросить, и работать с результатом в uint8_t — это экономит немного памяти. Имеется также встроенный источник опорного напряжения 2.56 В.
Обработка АЦП
См https://narodstream.ru/avr-urok-22-izuchaem-acp-chast-1/
ADCSRA |= (1<<ADEN); // Разрешение использования АЦП ADCSRA |=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);//Делитель 128 = 125 кГц ADMUX |= (1<<REFS1)|(1<<REFS0)|(1<<ADLAR); //Внутренний Источник ОН 2,56в, adc0, use high 8b (ADCH)
Чтение из АЦП процесс медленный, лучше им не злоупотреблять
uint8_t ADC_read(uint8_t channel) { ADMUX &= ~7; ADMUX |= (channel & 7); ADCSRA |= (1<<ADSC); //Начинаем преобразование while((ADCSRA & (1<<ADSC))); //проверим закончилось ли аналого-цифровое преобразование return ADCH; }
Измеритель температуры представляет собой резисторный делитель напряжения. Зависимость напряжения от температуры, скорее всего, можно представить в виде формулы. Но проще составить таблицу соответствия температуры и напряжения с точностью 1 С — размер ее не будет слишком велик. Я взял datasheet на терморезистор, librecalc, закон Ома и получил что-то такое:
uint8_t adc_measures_base24[] = { 255, 250, 245, 239, 234, 228, 223, 218, 213, 207, 202, 197, 192, 188, 183, 178, 173, 169, 164, 160, 156, 151, 147, 143, 139, 135, 132, 128, 124, 121, 117, 114, 111, 108, 105, 102, 99, 96, 93, 90, 88, 85, 83, 80, 78, 76, 73, 71, 69, 67, 65, 63, 61, 60, 58, 56, 55, 53, 52, 50, 49, 47, 46, 45, 43, 42, 41, 40, 39, 38, 36, 35, 34, 33, 33, 32, 31, 30, 29, 28, 28, 27, 26, 25, 25, 24, 23, 23, 22, 22, 21, 20, 20, 19, 19, 18, 18, 18, 17, 17, 16, 16};
Температуру ниже 24С и выше 120С измерять не имеет смысла.
UART. Как оказалось, здесь тоже имеются свои тонкости. Передача данных через UART требует высокой точности временных интервалов; поэтому использовать UART без внешнего кварца не стоит. Также, не любую частоту передачи можно получить с помощью встроенных делителей частоты. Очень удобно, что есть готовая таблица, из которой можно найти скорость с минимальной погрешностью. У меня уверенно заработала передача на 38400bps.
Настройка UART
#define BAUD 38400 #define MY_UBRR (F_CPU/16/BAUD - 1) // UART UBRRH = (unsigned char) ((MY_UBRR) >> 8); UBRRL = (unsigned char) (MY_UBRR); UCSRB = (1<<TXEN); UCSRC = (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);
PID-регулятор
Что ж за самоделка без PID-регулятора? 😉 Здесь из 2. Нужно поддерживать температуру около 40С, и обороты около 30 rps. 2 датчика (цифровой и аналоговый), и одно управляющее воздействие — скважность PWM.
Код регулятора
#define GOAL_T 40 #define GOAL_RPS 30 // K_T = 1/2 #define K_T_A 1 #define K_T_B 2 // K_RPS = 1/5 #define K_RPS_A 1 #define K_RPS_B 5 void do_pid() { static uint32_t pid_ts; if (uptime_x_0_1s-pid_ts < 10) return; // не чаще 1 раз/сек pid_ts = uptime_x_0_1s; int pwm_val = OCR2; int dt = t1 - GOAL_T; int d_rps = GOAL_RPS - rps; int d_pwm_t = (dt * K_T_A ) / K_T_B; if (d_pwm_t < -5) { d_pwm_t = -5; }; int d_pwm_rps = (d_rps * K_RPS_A) / K_RPS_B; int pwm_val_t = pwm_val + d_pwm_t; int pwm_val_rps = pwm_val + d_pwm_rps; int d_pwm; if (pwm_val_t > pwm_val_rps) { d_pwm = d_pwm_t; } else { d_pwm = d_pwm_rps; }; pwm_val += d_pwm; if (pwm_val > 255) pwm_val = 255; if (pwm_val <0 ) pwm_val = 0; OCR2 = pwm_val; }
Коэффициенты для обоих регуляторов заданы в виде натуральной дроби, чтобы не использовать float вычисления.
Про обработку кнопки с подавлением дребезга и прочее мигание светодиодом, пожалуй, писать не нужно 😉
В результате получилась вот такая конструкция на макетной плате.
Шакальное фото
Выводы
Поставленная задача решается: светодиод светится, кулер крутится, температура 40С держится с достаточно низкими оборотами кулера; можно брать и более мощные светодиоды.
Код прошивки и схема устройства выложены на github.
TODO:
-
Научиться разводить платы в KiCad и сделать более изящную конструкцию.
-
Избавиться от кварца; точности встроенного генератора должно хватить для измерения интервала 8ч.
-
Вместо step-down конвертора использовать источник питания 5В и step-up конвертор.
-
Сделать то же самое, но на AtTiny — ресурсов должно хватить.
ЗЫ: Пожалуй, нужно упомянуть о использованном софте. При работе над устройством ни один правообладатель не пострадал.
ОС — Ubuntu 18
Компилятор — avr-gcc
IDE — geany
Программатор — Arduino Uno с прошивкой ArduinoISP
Схема электрическая принципиальная рисовалась в KiCad
Прототип паялся на макетной плате.
ссылка на оригинал статьи https://habr.com/ru/post/532636/
Добавить комментарий