====== 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"