====== Une nouvelle maison pour Arietty ======
===== L'aqua-terrarium =====
Les modèles du commerce semblent assez petits ... La cible serait 80-100cm x 60cm x 12-15cm d'eau.
===== La filtration =====
==== Pompe / filtre ====
Voir si il faut ensemencer l'e900 ...
==== Stérilisateur ====
Construit sur la base d'un tube G4T5 à UV-C de 4W :
{{sketch>sterilisateur.svg}}
===== Le chauffage =====
On veut éviter les plongeurs, l'idée serait un câble chauffant ([[http://www.ebay.com/itm/310246489792]]) régulé, tension max 24V, 12V préféré (alim PC).
==== Puissance ====
P = 10 * S * Delta T ???
S est la surface d'échange eau-air, le 10 est une constante de conduction en W/{m^2 °C}.
On prendra le pire Delta T possible, Soit 10°C (15°C ambiants pour 25°C dans l'eau).
^ Hauteur d'eau ^ Toutes faces exposées |^ Moitiée des faces en bois ||
^ ^ Surface (m²) ^ Power (W) ^ Surface (m²) ^ Power (W) ^
| 12cm | 1.3m2 | 130W | 0.65m2 | 65W |
| 15cm | 1.38m2 | 138W | 0.69m2 | 69W |
On essayera d'atteindre 180W, soit 15A sous 12V éventuellement en 2 brins si l'alim ATX ne peut tout prendre sur un seul rail, ou alors il faudrait une alim 12V / 240W cheap ...
R = 12 / 15 = 0.8 Ohm
Il faudrait donc 2 brins de 1.6Ω en parallele.
Le fil fait 0.127Ω/pied, soit 0.41666Ω/m, il faut donc des brins de 3.84m, disons 4m (commande de 8m soit 26.25 pieds soit 3 lots pour un cout de 18USD).
Reçu 9.7m de fil (pour 9.15m), 4.2Ω au total, 2.1Ω par brin => 137W@12V si on met toute la longueur.
^ Longueur de brin ^ R brin (Ω) ^ R total (Ω) ^ Puissance @ 12V (W) ^ Courant @ 12V (A) ^ Ok alim ATX 18A ^ MOS power (max) (W) ^
| 3m | 1.25 | 0.625 | 230 | 19.2 | X | 6.5 |
| 3.5m | 1.46 | 0.73 | 197 | 16.5 | V | 4.8 |
| 4m | 1.66 | 0.83 | 173 | 14.4 | V | 3.6 |
| 4.5m | 1.875 | 0.937 | 154 | 12.8 | V | 2.9 |
| 4.85m (tout) | 2.02 | 1.01 | 142 | 11.9 | V | 2.5 |
==== Régulation ====
On utilise la moyenne de 2 sondes à base de TMP36 au fond du bac entre 2 passages de câble chauffant.
Un ATMega8L s'occupe de la régulation proportionnelle-intégrale et commande le câble en PWM à environ 30kHz.
Un écran et une paire de boutons servent à contrôler la consigne.
Un troisième TMP36 est utilisé pour récupérer la température ambiante.
Le contrôleur transmet un relevé 1-2 fois par minute par ASK-OOK@433MHz (utilisation d'un module standard) sur la base du [[:private:serialdevice|protocole SerialDevice]].
{{:private:arietty:chauffage_arietty.png?nolink}}
{{chauffage_arietty.wt5|Typon}}
=== Format de transmission sans fil ===
Base : 1 start, 1 stop, parité paire, 4800 bauds.
Les frames ont la forme suivante :
* 1 start byte (STX = 0x02)
* 1 sender address byte
* 16 data bytes
* 1 CRC (Fletcher 8-bit) byte
* 1 stop byte (ETX = 0x03)
L'émission est **OBLIGATOIREMENT** faite à intervalles **IRREGULIERS** pour éviter l'overlap si plusieurs senders (implémenter un générateur pseudo-random 16bits à XOR).
Une frame fait 20 octets, soit 220 "états", il faut 46ms pour l’émettre.
=== Firmware ===
/**
* @project Arietty temperature controller
* @part main
* @autor MELEARD Etienne
* @date 2012
* @device ATMega8L
* @clock 8MHz (internal RC oscillator)
* @avrdude -U lfuse:w:0xc4:m -U hfuse:w:0xd1:m
*
* Dual probe heating wire aquarium temperature controller with PI regulator and wireless data link reporting.
*
**/
/**@macros**/
#include
#include
#include
#include
/**@iodef**/
// Buttons
#define BTN_PLUS Bit(PORTD).bit2
#define SIG_PLUS SIG_INTERRUPT0
#define BTN_MINUS Bit(PORTD).bit3
#define SIG_MINUS SIG_INTERRUPT1
// LCD
#define LCD_RS Bit(PORTC).bit0
#define LCD_EN Bit(PORTC).bit1
#define LCD_D0 Bit(PORTC).bit2
#define LCD_D1 Bit(PORTC).bit3
#define LCD_D2 Bit(PORTC).bit4
#define LCD_D3 Bit(PORTD).bit0
// Probes
#define PROBE_AMB 5 // ADC5
#define PROBE_0 6 // ADC6
#define PROBE_1 7 // ADC7
// Heater driver
#define PWM_OUT Bit(PORTB).bit1
// Debug led
#define LED Bit(PIND).bit4
/**@constants**/
// Temperature limits
#define MIN_TEMP 24
#define MAX_TEMP 32
// PI regulator parameters
#define PI_K 0.5 // 1/[°C] => [W]/[°C] => full power if 2°C error
#define PI_I 0.05 // 1/([°C][t_unit]) => full power if 1°C error for 20 minutes (1/20)
/**@globals**/
// Temperatures
typedef struct { // In °C
float target, probe_0, probe_1, tank, ambient;
} Ttemperatures;
volatile Ttemperatures temperatures = {.target = 0.0, .probe_0 = 0.0, .probe_1 = 0.0, .tank = 0.0, .ambient = 0.0};
// Lcd
typedef struct {
uint8_t target, probe_0, probe_1, tank, power, ambient;
} TintValues;
volatile TintValues int_values = {.target = 0, .probe_0 = 0, .probe_1 = 0, .tank = 0, .power = 0, .ambient = 0};
#define LCD_NORMAL_MODE_NORMAL_SCREEN_LINE 0
#define LCD_NORMAL_MODE_ADVANCED_SCREEN_LINE 1
#define LCD_SET_MODE_LINE 1
#define LCD_NORMAL_MODE_NORMAL_SCREEN_TANK_INDEX 12
#define LCD_NORMAL_MODE_NORMAL_SCREEN_TARGET_INDEX 2
#define LCD_NORMAL_MODE_NORMAL_SCREEN_POWER_INDEX 12
#define LCD_NORMAL_MODE_ADVANCED_SCREEN_PROBE_0_INDEX 3
#define LCD_NORMAL_MODE_ADVANCED_SCREEN_PROBE_1_INDEX 13
#define LCD_NORMAL_MODE_ADVANCED_SCREEN_AMBIENT_INDEX 8
#define LCD_SET_MODE_TARGET_INDEX 11
volatile const char lcd_strings[3][2][16] = {
{"Temperature XX°C", "C=XX°C P=XXX%"},
{"P0=XX°C P1=XX°C", "Ambient XX°C "},
{"Temperature to ", "maintain : XX°C"}
};
// wireless data link message, MUST be 16 byte long
#define SERIALDEVICE_ADDRESS 0x01
volatile uint8_t message[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Random number generator parameters
typedef struct {
uint8_t a, b, c, x;
} TrandomData;
volatile TrandomData random_data = {.a = 0, .b = 0, .c = 0, .x = 0};
// UI
typedef struct {
uint8_t value, counter, previous;
} Tstate;
#define BTN_CHANGE_MODE_PUSH_DURATION 8
volatile Tstate btn = {.value = 0, .counter = 0, previous = 0};
#define NORMAL_MODE 0
#define SET_MODE 1
#define MODE_IDLE_DURATION 20
volatile Tstate mode = {.value = NORMAL_MODE, .counter = 0, previous = NORMAL_MODE};
#define NORMAL_SCREEN 0
#define ADVANCED_SCREEN 1
#define SCREEN_IDLE_DURATION 20
volatile Tstate screen = {.value = NORMAL_SCREEN, .counter = 0, previous = ADVANCED_SCREEN}; // At startup there must be a difference between value and previous somewhere to force a screen refresh.
/**@functions**/
/**@function-category I/O and peripherals init **/
void init(void) {
cli();
// Inputs
//DDRD &= 0xf3; // Buttons as inputs
PORTD = 0x0c; // Pull-up on PD3/INT1 and PD2/INT0
// Outputs
DDRB |= 0x02; // PWM output
DDRC |= 0x1f; // LCD control
DDRD |= 0x11; // LCD control + Led
// Button Irq
MCUCR |= 0x05; // Irq on all edges
GICR |= 0xc0; // on both INT0 and INT1
// Timer 1 used for PWM
TCCR1A = 0x81; // Fast PWM, 8bit
TCCR1B = 0x40; // 8 bit, prescaler = 1 => 31.25kHz
OCR1A = 0x0000; // Power level
// ADC
// Vref given from 5V through 12k / 47k divider => 1.017V
// 1 bit is equal to 0.993mV so 1 degree is roughly 10bit
// Exact formulae : T = {1 / 0.01} * (n * {{5 * 12000} / {1023 * (12000 + 47000)}} - 0.5)
// T = 0.0994 * n - 50
ADCSRA = 0x87; // Manual mode, 128 prescaler (0.2ms/conv)
ADMUX = 0x00; // Right adjust, external ref
// USART
UCSRA = 0x00; // Normal speed
UCSRB = 0x08; // Tx enable, 8 bit data
UCSRC = 0xa6; // URSEL, Async mode, even parity, 1 stop bit, 8 bit data
UBRR = 103; // 4800 bds @ 8MHz (0.2% error, musn't transmit more than 50 bytes at once)
sei();
}
/**@function-category EEPROM functions **/
// Read byte from EEPROM
uint8_t eepromReadByte(uint16_t addr) {
while(EECR & (1<>1) ^ random_data.a);
return random_data.c;
}
// Seed the generator
void randomSeed(uint8_t s1, uint8_t s2, uint8_t s3) {
random_data.a ^= s1;
random_data.b ^= s2;
random_data.c ^= s3;
random();
}
/**@function-category Wireless data link **/
// Send a single byte
void serialDeviceSendByte(uint8_t data) {
while(!(UCSRA & (1<> 4);
sum1 = (sum1 & 0x0f) + (sum1 >> 4);
sum2 = (sum2 & 0x0f) + (sum2 >> 4);
sum2 = (sum2 & 0x0f) + (sum2 >> 4);
serialDeviceSendByte(sum2<<4 | sum1); // Send checksum
serialDeviceSendByte(0x03); // ETX
}
/**@function-category Sensors interface **/
float getTempForProbe(uint8_t addr) {
ADMUX = addr & 0x07; // Probe channel selection
ADCSRA |= 1<>4); // MSB
lcdHalfCmd(command); // LSB
_delay_us(40); // > 37us
}
// Init cycle
void lcdInit(void) {
LCD_RS = 0; // Control mode
_delay_ms(16); // > 15ms
lcdHalfCmd(0x03); //
_delay_ms(5); // > 4.1ms
lcdHalfCmd(0x03); //
_delay_us(110); // > 100us
lcdHalfCmd(0x02); // 4 bit mode
_delay_us(40); // > 37us
// Now we have full 4 bit mode, we can use lcd_command
lcdCommand(0x28, 0); // 4 bit mode, 2 lines, 5x8 digits
lcdCommand(0x0c, 0); // display on, no cursor, no blink
lcdCommand(0x01, 0); // clear
lcdCommand(0x06, 0); // entry mode increment, no shift
}
// Goto position
void lcdGoto(uint8_t p) {
if(p >= 16) p = (p - 16) + 0x40;
lcdCommand(0x80 | (p & 0x7f));
}
// Send line
void lcdLine(uint8_t line, char *str) {
uint8_t i;
lcdCommand(line ? 0xc0 : 0x80);
for(i=0; i<16; i++) lcdCommand(str[i], 1);
}
// Send integer
void lcdInt(uint8_t v, uint8_t offset, uint8_t digits) {
uint8_t i, n, m = 1;
lcdGoto(offset);
for(i=0; i>4)); // 26 - 34s
lcdString(LCD_STRING);
LED = !LED;
_delay_ms(250);
LED = !LED;
_delay_ms(250);
LED = !LED;
_delay_ms(250);
LED = !LED;
_delay_ms(250);
temperatures.target = realisticTemp((float)eepromReadByte(0)); // Load temp from memory
while(1) {
// Get probe values
temperatures.probe_0 = getTempForProbe(PROBE_0);
int_values.probe_0 = (uint8_t)temperatures.probe_0;
temperatures.probe_1 = getTempForProbe(PROBE_1);
int_values.probe_1 = (uint8_t)temperatures.probe_1;
temperatures.ambient = getTempForProbe(PROBE_AMB);
int_values.ambient = (uint8_t)temperatures.ambient;
// Compute average
temperatures.tank = (temperatures.probe_0 + temperatures.probe_1) / 2;
int_values.tank = (uint8_t)temperatures.tank;
// Compute and set power
error = temperatures.target - temperatures.tank;
if(!loops) integrator += error; // simple integration, only take one sample every 256 loops so loop should be arround 234ms long => 59.9s
power = PI_K * error + PI_I * integrator;
if(power < 0) power = 0;
if(power > 1) power = 1;
int_values.power = (uint8_t)(100 * power);
updateCounters();
updateScreen();
LED = !(loops % 4); // 1/4 duty cycle flash
// Process up to here takes approximately 20ms (to be estimated)
report_in--;
if(report_in) {
_delay_ms(210); // Completion up to 234ms
}else{
report_in = 120 + (16 - (random()>>4)); // schedule next report, 26 - 34s
sendReport();
_delay_ms(174); // Completion up to 234ms
}
}
return 0;
}
=== Configuration SerialDevice ===
== devices.map ==
1 aquariumTemperatureController
== Handler SerialDevice ==
#!/usr/bin/perl
package aquariumTemperatureController;
use DBI;
# To use along with collectd's DBI plugin
my $dsn = 'mysql:serial_devices;host=localhost';
my $user = 'serial_devices';
my $pwd = 'shjHtmrEPcTXZQ5y';
my $dbh = DBI->connect('dbi:'.$dsn, $user, $pwd);
sub terminate {
$dbh->disconnect();
}
sub gotMessage {
my $addr = shift;
my @data = @_;
my $type = chr(shift @data);
if($type eq 'r') { # report
my ($tank, $target, $power, $probe_0, $probe_1, $ambient) = @data;
$dbh->do(
'INSERT INTO aquarium_temperature_controller'.
'(dt, tank, target, power, probe_0, probe_1, ambient)'.
' VALUES(NOW(), ?, ?, ?, ?, ?, ?)',
undef,
$tank, $target, $power, $probe_0, $probe_1, $ambient
) if($dbh);
}
}
1;
== Configuration collectd ==
Statement "SELECT tank, target, power FROM aquarium_temperature_controller ORDER BY dt DESC LIMIT 1"
Type "gauge"
InstancePrefix "aquariumtemperature-tank"
ValuesFrom "tank"
Type "gauge"
InstancePrefix "aquariumtemperature-target"
ValuesFrom "target"
Type "gauge"
InstancePrefix "aquariumtemperature-power"
ValuesFrom "power"
Statement "SELECT probe_0, probe_1, ABS(probe_0 - probe_1) AS delta FROM aquarium_temperature_controller ORDER BY dt DESC LIMIT 1"
Type "gauge"
InstancePrefix "aquariumtemperaturedetails-probe_0"
ValuesFrom "probe_0"
Type "gauge"
InstancePrefix "aquariumtemperaturedetails-probe_1"
ValuesFrom "probe_1"
Type "gauge"
InstancePrefix "aquariumtemperaturedetails-delta"
ValuesFrom "delta"
Statement "SELECT ambient FROM aquarium_temperature_controller ORDER BY dt DESC LIMIT 1"
Type "gauge"
InstancePrefix "aquariumtemperatureambient-ambient"
ValuesFrom "ambient"
Driver "mysql"
DriverOption "host" "localhost"
DriverOption "username" "serial_devices"
DriverOption "password" "shjHtmrEPcTXZQ5y"
DriverOption "dbname" "serial_devices"
SelectDB "serial_devices"
Query "aquarium_temperature"
Query "aquarium_temperature_details"
Query "aquarium_ambient_temperature"