Как стать автором
Поиск
Написать публикацию
Обновить

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

Время на прочтение7 мин
Количество просмотров15K

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

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

Прототип паялся на макетной плате.

Теги:
Хабы:
Всего голосов 13: ↑11 и ↓2+14
Комментарии19

Публикации

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