Arduino управляет освещением аквариума


Arduino управляет освещением аквариума
Для освещения аквариума, Kalle Hyvönen использовал четыре мощных белых светодиода (закреплённых на паре радиаторов), а для более интеллектуального освещения, он подключил их к контроллеру Arduino, который управляет импульсным источником питания светодиодов, с помощью PWM.
Также, автор реализовал возможность автоматического переключения света и возможность изменения графика освещения при помощи команд через последовательный порт (USB).

Скетч:

#include <SPI.h>
#include <EEPROM.h>
#include "pins_arduino.h"

// RTC registry addresses
#define HOURS_WRITE 0x82
#define HOURS 0x02
#define MINUTES_WRITE 0x81
#define MINUTES 0x01
#define SECONDS_WRITE 0x80
#define SECONDS 0x00
#define ALARM2_HOURS_WRITE 0x8C
#define ALARM2_HOURS 0x0C
#define ALARM2_MINUTES_WRITE 0x8B
#define ALARM2_MINUTES 0x0B
#define ALARM1_HOURS_WRITE 0x89
#define ALARM1_HOURS 0x09
#define ALARM1_MINUTES_WRITE 0x88
#define ALARM1_MINUTES 0x08
#define ALARM1_SECONDS_WRITE 0x87
#define ALARM1_SECONDS 0x07
#define ALARM1_DATE_WRITE 0x8A
#define ALARM1_DATE 0x0A
#define CONTROL_WRITE 0x8E
#define CONTROL 0x0E
#define STATUS_WRITE 0x8F
#define STATUS 0x0F

// define pin connected to the RTC alarm (!INT) pin
#define ALARM 2
// define the pin which is connected to the LED driver
#define LED 3

// this sets the maximum PWM level for brightness
int max_brightness = 255;
// this sets the timing period, ie. for how long the lights are on (in hours)
char timing_period = 11;
// variable so we know if we are going to increase or decrease the PWM value
bool direction_up = true;
// this variable sets how many interrupts (they come in once a second) it takes to change the PWM value
int pwm_speed = 3;

// these variables not for the user to modify
int brightness = 0;
char incomingByte = 0x00;
int interrupt_cycle = 0;
bool enabled = true;

void setup() {

	noInterrupts();

// define IO
	pinMode(ALARM, INPUT);
	pinMode(LED, OUTPUT);
	digitalWrite(ALARM, HIGH);
	digitalWrite(LED, LOW);

	pwm_speed = EEPROM.read(0x01);
	timing_period = EEPROM.read(0x02);
// check if the eeprom was empty or not
	if(pwm_speed == 0xff) { pwm_speed = 0; }
	if(timing_period == 0xff) { timing_period = 11; }

// start UART
	Serial.begin(9600);
// set SPI mode 3
	SPI.setDataMode(SPI_MODE3);
// set SPI clock to system clock / 16 ie. 1MHz
	SPI.setClockDivider(SPI_CLOCK_DIV16);
// enable SPI
	SPI.begin();

	if(EEPROM.read(0x00) == 0) {
		enabled = false;
		enableAlarm1(false);
		enableAlarm2(false);
		brightness = 0;
		invAnalogWrite(LED, brightness);
	}
	else {
		enabled = true;
		enableAlarm1(true);
		enableAlarm2(true);
// set A2M4 bit (needed for alarm)
		writeRTC(0x8D, 0x80);
// disable both alarm flags, leave other bits untouched
		writeRTC(STATUS_WRITE, (readRTC(STATUS)&0xFC));

// check the time and see if we should put the lights on or off
		checkTime();
	}

// attach RTC alarm to interrupt on pin 2
	attachInterrupt(0, changePWM, FALLING);
	interrupts();

}
void loop() {

	if (Serial.available() > 0) {

// read the incoming byte:
		incomingByte = Serial.read();
		switch(incomingByte) {

// if we get the symbol "t", we want to read the temperature from the RTC
			case 0x74:

				Serial.flush();
				initTempConv();
// wait for the conversion to finish
				delay(250);

				Serial.print("Temperature: ");
				Serial.print(readRTC(0x11), DEC);
				Serial.print(".");

// figure out the decimals, does not work, needs fixing...
				switch (readRTC(0x12)) {
					case 0x00:
						Serial.print("0");
						break;
					case 0x40:
						Serial.print("25");
						break;
					case 0x80:
						Serial.print("50");
						break;
					case 0xC0:
						Serial.print("75");
						break;
				}
				Serial.println("C");
				break;

// if we receive symbol "h" which has to be followed by an ascii number value for the hours (eg. 01 or 13)
			case 0x68:

				incomingByte = hoursToBCD(readAsciiHours());
				checkAndWrite(HOURS_WRITE, incomingByte);
				printTime();
				checkTime();
				break;

// if we receive symbol "m" which has to be followed by a value for the minutes register (the amount of minutes straight in hex, like 0x40 is 40 minutes etc.)
			case 0x6D:

				incomingByte = minutesToBCD(readAsciiMinutes());
				checkAndWrite(MINUTES_WRITE, incomingByte);
				printTime();
				checkTime();
				break;

// if we receive symbol "s" which has to be followed by a value for the seconds register (the amount of seconds straight in hex)
			case 0x73:

				incomingByte = minutesToBCD(readAsciiMinutes());
				checkAndWrite(SECONDS_WRITE, incomingByte);
				printTime();
				break;

// if we receive symbol "a" which has to be followed by an int value for the alarm2 hours (ie. convert decimal to hex)
			case 0x61:

				incomingByte = hoursToBCD(readAsciiHours());
				checkAndWrite(ALARM2_HOURS_WRITE, incomingByte);
				printAlarm2();
				checkTime();
				break;

// if we receive symbol "b" which has to be followed by a value for the alarm2 minutes register (the amount of minutes straight in hex)
			case 0x62:

				incomingByte = minutesToBCD(readAsciiMinutes());
				checkAndWrite(ALARM2_MINUTES_WRITE, incomingByte);
				printAlarm2();
				checkTime();
				break;

// if we receive symbol "c" which has to be followed by the timing_period value as ascii
			case 0x63:

				incomingByte = readAsciiHours();
				if(incomingByte > 0) {
					timing_period = incomingByte;
					EEPROM.write(0x02, timing_period);
					checkTime();
				}
				printTp();
				break;

// if we receive symbol "d" which has to be followed by the pwm_speed value as two-number ascii (eg "01")
			case 0x64:

				incomingByte = readAsciiMinutes();
				pwm_speed = incomingByte;
				EEPROM.write(0x01, pwm_speed);
				printPwmSpeed();
				break;

// if we get symbol "e", disable or enable the alarms
			case 0x65:
				if(enabled == true) {
					enableAlarm1(false);
					enableAlarm2(false);
					Serial.println("Disabled");
					enabled = false;
					EEPROM.write(0x00, 0);
					brightness = 0;
					invAnalogWrite(LED, brightness);
				}
				else {
					enableAlarm1(true);
					enableAlarm2(true);
					Serial.println("Enabled");
					enabled = true;
					EEPROM.write(0x00, 1);
					checkTime();
				}
				break;

// if we receive symbol "p" which prints the current time and current alarm2 values to serial port
			case 0x70:

				printTime();
				printAlarm2();
				printAlarm1();
				printTp();
				printPwmSpeed();
				if(enabled = true) {
					Serial.println("Enabled: yes");
				}
				else {
					Serial.println("Enabled: no");
				}
				break;

// if we get LF ('\n') just do nothing and ignore it
			case 0x0A:
				break;

			default:

				Serial.flush();
// return input if it makes no sense and print some help
				Serial.println("Incorrect input");
				Serial.println("Correct input  (replace 00 with value):");
				Serial.println("h00, m00, s00 to change the time");
				Serial.println("a00, b00 to change the time of alarm");
				Serial.println("c00 to change the timing period");
				Serial.println("d00 to change the dimming speed");
				Serial.println("p to print current values");
				Serial.println("t to print temperature");
				Serial.println("e to enable or disable");
		}
	}

}
void changePWM() {

// disable both alarm flags, leave other bits untouched
	writeRTC(STATUS_WRITE, (readRTC(STATUS)&0xFC));

	switch (direction_up) {
		case true:
			if(brightness == 0) {
// pwm_speed variable does not affect the first cycle
				setAlarm1_every_second(true);
				enableAlarm1(true);
				brightness++;
				invAnalogWrite(LED, brightness);
			}
			else {
// if not enough interrupts have not occurred after last pwm change, just increase the interrupt_cycle timer
				if(interrupt_cycle == pwm_speed) {
					brightness++;
					interrupt_cycle = 0;

					if(brightness == max_brightness) {
						invAnalogWrite(LED, brightness);
						direction_up = false;
						setAlarm1_every_second(false);
// we are at full brightness, set alarm1 to timing_period ahead of alarm2
						setAlarm1();
					}
					else { invAnalogWrite(LED, brightness); }
				}
				else { interrupt_cycle++; }
			}
			break;
		case false:
			if(brightness == max_brightness) {
				setAlarm1_every_second(true);
				enableAlarm1(true);
				brightness--;
				invAnalogWrite(LED, brightness);
			}
			else {
				if(interrupt_cycle == pwm_speed) {
					brightness--;
					interrupt_cycle = 0;

					if(brightness == 0) {
						direction_up = true;
						enableAlarm1(false);
						invAnalogWrite(LED, brightness);
					}
					else { invAnalogWrite(LED, brightness); }
				}
				else { interrupt_cycle++; }
			}
			break;
	}
}
void checkAndWrite(char reg, char val) {

	if(val >= 0x00) {
		writeRTC(reg, val);
	}
	else {
		Serial.println("Incorrect input");
	}
	Serial.flush();

}
// check the time and if we should put the lights on or off
void checkTime() {

	int lighting_time = 0;
	int time = 0;
	lighting_time = convertTime(readRTC(ALARM2_HOURS), readRTC(ALARM2_MINUTES)) + (timing_period * 60);
	time = convertTime(readRTC(HOURS), readRTC(MINUTES));

	if(lighting_time >= 1440) {
  		lighting_time -= 1440;
	}

// if the time is inside the lighting period, fire up the lights
	if(time < lighting_time && time > convertTime(readRTC(ALARM2_HOURS), readRTC(ALARM2_MINUTES))) {

// make sure we are going to dim the lights next
		direction_up = false;
		brightness = max_brightness;
		invAnalogWrite(LED, brightness);

// set control bits, enable both of the alarms
		writeRTC(CONTROL_WRITE, 0x07);
// set alarm1 to timing_period ahead of alarm2
		setAlarm1();
// set A1M4 bit to enable hours, minutes, seconds match for alarm1
		setAlarm1_every_second(false);

	}
	else {

		direction_up = true;
		brightness = 0;
		invAnalogWrite(LED, brightness);
// disable alarm1
		enableAlarm1(false);

	}

}
char convertAsciiNumber(char character) {

// deduct 0x30 from the ascii character to get a number
	return (character - 0x30);

}
// converts hours from BCD to int
char convertHours(char hours) {

	char temp = 0x00;

// check tens of hours
	if(((hours >> 4)&1) == 1) {
		temp += 10;
	}
// check 20h
	if(((hours >> 5)&1) == 1) {
		temp += 20;
	}

// add all the remaining hours
	temp += hours&0x0F;
	return temp;

}
// converts time from register value to an int which contains amount of minutes passed
int convertTime(char hours, char minutes) {

	int time = 0;

// check tens of hours
	if(((hours >> 4)&1) == 1) {
		time += 600;
	}
// check 20h
	if(((hours >> 5)&1) == 1) {
		time += 1200;
	}
// add all the remaining hours
	time += (hours&0x0F)*60;

	time += (minutes >> 4)*10;
	time += minutes&0x0F;

	return time;
}
void enableAlarm1(bool val) {

	switch (val) {
		case true:
			writeRTC(CONTROL_WRITE, (readRTC(CONTROL)|0x01));
			break;
		case false:
// disable alarm1
			writeRTC(CONTROL_WRITE, (readRTC(CONTROL)&0xFE));
			break;
	}
}
void enableAlarm2(bool val) {

	switch (val) {
		case true:
			writeRTC(CONTROL_WRITE, (readRTC(CONTROL)|0x02));
			break;
		case false:
// disable alarm2
			writeRTC(CONTROL_WRITE, (readRTC(CONTROL)&0xFD));
			break;
	}
}
// converts hours as int to hours as BCD
char hoursToBCD(char hours) {

	char temp = 0x00;

	if(hours >= 20) {
  // switch 20h bit
		temp = temp|0x20;
		hours -= 20;
	}
	else if(hours >= 10) {
		temp = temp|0x10;
		hours -= 10;
	}

// add rest of the hours
	temp += hours;
	return temp;

}
void initTempConv() {

	writeRTC(CONTROL_WRITE, (readRTC(CONTROL)|0x20));

}
void invAnalogWrite(int pin, int value) {

	analogWrite(pin, ~value);

}
char minutesToBCD(char minutes) {

	char temp = 0x00;

// take off tens of minutes and add to temp until we have 10min or less
	while(minutes >= 10) {
		minutes -= 10;
		temp++;
	}
// shift to make them tens of minutes in bcd
	temp = temp << 4;
// add rest of the minutes
	if(minutes > 0) {
		temp += minutes;
	}

	return temp;

}
void printAlarm1() {

	Serial.print("Current alarm1: ");
	Serial.print(convertHours(readRTC(ALARM1_HOURS)), DEC);
	Serial.print(":");
// while printing the hours, ignore the config bit
	Serial.print((readRTC(ALARM1_MINUTES)&0x7F), HEX);
	Serial.print("\n");

}
void printAlarm2() {

	Serial.print("Current alarm: ");
	Serial.print(convertHours(readRTC(ALARM2_HOURS)), DEC);
	Serial.print(":");
	Serial.print(readRTC(ALARM2_MINUTES), HEX);
	Serial.print("\n");

}
void printTime() {

	Serial.print("Current time: ");
	Serial.print(convertHours(readRTC(HOURS)), DEC);
	Serial.print(":");
	Serial.print(readRTC(MINUTES), HEX);
	Serial.print(":");
	Serial.print(readRTC(SECONDS), HEX);
	Serial.print("\n");

}
void printPwmSpeed() {

	Serial.print("Current PWM speed: ");
	Serial.print(pwm_speed, DEC);

}
void printTp() {

	Serial.print("Current timing period: ");
	Serial.print(timing_period, DEC);

}
char readAsciiHours() {

	char number1 = 0x00;
	char number2 = 0x00;
	char number = 0x00;

// convert user input to an int value
	number1 = convertAsciiNumber(waitForByte());
	number2 = convertAsciiNumber(waitForByte());

	number = 10*number1 + number2;

// check if the input makes any sense
	if(number >= 24 || number < 0) {
		Serial.println("Incorrect input");
		number = 0x00;
	}

	return number;

}
// this works for seconds as well
char readAsciiMinutes() {

	char number1 = 0x00;
	char number2 = 0x00;
	char number = 0x00;

// convert user input to an int value
	number1 = convertAsciiNumber(waitForByte());
	number2 = convertAsciiNumber(waitForByte());

	number = 10*number1 + number2;

// check if the input makes any sense
	if(number > 60 || number < 0) {
		Serial.println("Incorrect input");
		number = 0x00;
	}

	return number;

}
char readRTC(char reg) {

	noInterrupts();

	char temp = 0x00;
	digitalWrite(SS, LOW);
	SPI.transfer(reg);
// dont write anything, just put out clock signal and read
	temp = SPI.transfer(0x00);
	digitalWrite(10, HIGH);

	interrupts();
	return temp;

}
// set alarm1 to timing_period ahead of alarm2
void setAlarm1() {

	char hours = 0x00;

// calculate hours for timer1 hours
	hours = (convertHours(readRTC(ALARM2_HOURS)) + timing_period);

// if hours more than 24, deduct 24 for correct time
	if(hours >= 24) {
		hours -= 24;
	}

// make sure we dont touch the A1M4 bit
	writeRTC(ALARM1_HOURS_WRITE, ((readRTC(ALARM1_HOURS)&0x80)|hoursToBCD(hours)));
// set alarm1 minutes to same as alarm2, just the hours are alarm2_hours + timing period
	writeRTC(ALARM1_MINUTES_WRITE, ((readRTC(ALARM1_MINUTES)&0x80)|readRTC(ALARM2_MINUTES)));
// set alarm1 seconds to 0
	writeRTC(ALARM1_SECONDS_WRITE, ((readRTC(ALARM1_SECONDS)&0x80)));
	hours = 0x00;

}
void setAlarm1_every_second(bool val) {

	switch (val) {
		case true:

// change A1M1 bit via bitmask to make alarm1 give alarms every second
			writeRTC(ALARM1_SECONDS_WRITE, (readRTC(ALARM1_SECONDS)|0x80));
// change A1M2 bit via bitmask
			writeRTC(ALARM1_MINUTES_WRITE, (readRTC(ALARM1_MINUTES)|0x80));
// change A1M3 bit via bitmask
			writeRTC(ALARM1_HOURS_WRITE, (readRTC(ALARM1_HOURS)|0x80));
// change A1M4 bit via bitmask
			writeRTC(ALARM1_DATE_WRITE, (readRTC(ALARM1_DATE)|0x80));
			break;

		case false:

// change A1M1 bit via bitmask to make alarm1 give alarms when hours, seconds, and minutes match
			writeRTC(ALARM1_SECONDS_WRITE, (readRTC(ALARM1_SECONDS)&0x7F));
// change A1M2 bit via bitmask
			writeRTC(ALARM1_MINUTES_WRITE, (readRTC(ALARM1_MINUTES)&0x7F));
// change A1M3 bit via bitmask
			writeRTC(ALARM1_HOURS_WRITE, (readRTC(ALARM1_HOURS)&0x7F));
			break;
	}


}
char waitForByte() {

	char temp = 0x00;
// wait for the UART to fill up
	while(Serial.available() == 0) { }
	temp = Serial.read();
	return temp;

}
void writeRTC(char reg, char val) {

	noInterrupts();

	digitalWrite(SS, LOW);
// select register
	SPI.transfer(reg);
// print value to register
	SPI.transfer(val);
	digitalWrite(SS, HIGH);

	interrupts();

}

Ссылки
LED aquarium lighting with an Arduino based PWM timer


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

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение