Ещё одна фитолампа на Arduino

от автора

Пришла зима, короткий день, домашней пальме мало света. Нужно организовать подсветку. Готовую лампу покупать как-то неловко, да и надо ж чем-то заняться долгими зимними вечерами. Поехали 😉

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 будет:

16000000/256=62500 Гц

Длительность самого короткого импульса:

1/16МГц=62 нС

Думаю, это приемлемо. В даташите на ключевой транзистор есть 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 мС, этот счетчик переполнится очень не скоро.

16000000/256/6250 = 10 Гц

Чтобы получить такие временные интервалы с кварцем 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++; }

Переполнится счетчик через

2^{32}/3600/24/365/10 = 13 лет

Больше чем достаточно 😉

Измерение частоты вращения кулера

В кулере есть встроенный датчик, который замыкается на землю 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *