
Для освещения аквариума, 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
