/* FUSES: Low Fuse: 0x62 Default 0xC0 External Oscillator, BOD (write lfuse 0 0xC0) 0xF7 Full Swing Crystal Oscillator (write lfuse 0 0xF7) 0xFF Low Power Crystal Oscillator (untested) High Fuse 0xDF Default 0xDC BOD enabled (4.3V) 0xCF watchdog enabled 0xCC BOD enabled (4.3V) and watchdog enabled Extended Fuse 0x01 Default avrdude -c stk500 -P /dev/ttyS0 -p atmega88 -U flash:w:upper-tank-sensor.srec avrdude -c usbasp -p atmega168p -U flash:w:upper-tank-sensor.srec avrdude -c stk500 -P /dev/ttyS0 -p atmega88 -t avrdude -c usbasp -p atmega168p -t dump lfuse 0 1 write lfuse 0 PINS: AVR Conn Functions dir Usage 1 P10-3 PD3 OC2B I backlight switch 2 P4-13 PD4 3 GND 4 VCC 5 GND 6 VCC 7 PB6 XTAL1 crystal 8 PB7 XTAL2 crystal 9 P8-3 PD5 OC0B 10 P6-3 PD6 OC0A 11 P4-14 PD7 AIN1 O LCD backlight 12 P2-6 PB0 ICP1 13 P2-4 PB1 OC1A O pump relais output 14 P2-5 PB2 OC1B 15 P1-4 PB3 MOSI/OC2A - ISP 16 P1-1 PB4 MISO - ISP 17 P1-3 PB5 SCK - ISP 18 AVCC 19 P4-4 ADC6 20 P4-5 AREF 21 GND 22 P4-6 ADC7 23 P4-7 PC0 ADC0 O LCD DB4 24 P4-8 PC1 ADC1 O LCD DB4 25 P4-9 PC2 ADC2 O LCD DB4 26 P4-10 PC3 ADC3 O LCD DB4 27 P4-11 PC4 ADC4 O LCD EN 28 P4-12 PC5 ADC5 O LCD RS 29 P1-5 PC6 /RESET I ISP 30 PD0 RXD I RS485 RX 31 PD1 TXD O RS485 TX 32 PD2 O RS485 driver enable AVR PIN USAGE: pin port function dir usage 1 PC6 /RESET ISP (5) 2 PD0 RXD I RS485 MAX485 pin 1 3 PD1 TXD O (RS485) 4 PD2 I level sensors wire 1 (CHG) 5 PD3 I level sensors wire 2 (CHG) 6 PD4 I level sensors wire 3 (CHG) 7 VCC power, ISP (2) 8 GND power, ISP (6) 9 PB6 XTAL1 crystal 10 PB7 XTAL2 crystal 11 PD5 - 12 PD6 - 13 PD7 O LCD backlight 14 PB0 ICP1 I (CHG) 15 PB1 O pump relais output 16 PB2 OC1B (CHG) 17 PB3 MOSI ISP (4), backlight switch (CHG) 18 PB4 MISO ISP (1) 19 PB5 SCK ISP (3) 20 AVCC power 21 AREF analog reference voltage, block cap 22 GND power 23 PC0 O LCD DB4 24 PC1 O LCD DB5 25 PC2 O LCD DB6 26 PC3 O LCD DB7 27 PC4 O LCD EN 28 PC5 O LCD RS LCD PINS: pin func connection 1 VSS GND 2 VCC VCC 3 VEE poti 4 RS AVR PC5 5 R/W GND 6 E AVR PC4 7 DB0 - 8 DB1 - 9 DB2 - 10 DB3 - 11 DB4 AVR PC0 12 DB5 AVR PC1 13 DB6 AVR PC2 14 DB7 AVR PC3 15 LED+ VCC 16 LED- light button? */ #include #include #include #include #include #include #include #include #include #include #include #define SENSOR_VALUE_COUNT 8 #define USART_DATA_COUNT 8 #define DELAY (125+1) #define BAUD_RATE 9600 #define MAX_RECV_DELAY 5 // backlight switch and output //#define BACKLIGHT_SW D,3 #define BACKLIGHT_OUT D,7 // output to switch on the pump using a relais #define PUMP_RELAIS B,1 enum { TANK_INVALID = 0, TANK_EMPTY, TANK_LOW, TANK_MED, TANK_FULL }; typedef struct { uint8_t delay; uint8_t counter; uint8_t update_flag; uint8_t sensor_value_latest; uint8_t sensor_values[SENSOR_VALUE_COUNT]; uint8_t lower_sensor_state; uint8_t upper_errors; uint8_t upper_counter; uint8_t upper_sensor_state; uint8_t usart_data_time; uint8_t usart_data_count; uint8_t usart_data[USART_DATA_COUNT]; uint8_t lower_tank; uint8_t upper_tank; uint8_t upper_overflow; uint8_t pump_on; uint16_t pumping; uint8_t backlight; uint8_t rcvbytes; } data_t; static data_t data; static void initialize() { int8_t i; data.delay = 0; data.counter = 0; data.update_flag = 0; data.upper_errors = 255; data.upper_counter = 0; data.upper_sensor_state = 0; data.usart_data_count = 0; data.sensor_value_latest = 0; for( i=SENSOR_VALUE_COUNT-1; i>=0; i-- ) { data.sensor_values[i] = 0; } #if F_CPU == 18432000 // timer 2: Fast PWM, Top=OCR2A, clock/1024, overflow @ 125Hz TCCR2A = (1<> 2) & 0x07) | (PINB & 0x08) ); sensors_on = ~sensors; sensors_off = sensors; for( i=SENSOR_VALUE_COUNT-1; i>=0; i-- ) { sensors_on &= ~data.sensor_values[i]; sensors_off &= data.sensor_values[i]; } data.sensor_values[data.sensor_value_latest] = sensors; data.sensor_value_latest++; if( data.sensor_value_latest >= SENSOR_VALUE_COUNT ) { data.sensor_value_latest = 0; } data.lower_sensor_state = (data.lower_sensor_state & ~sensors_off) | sensors_on; return 1; } #if 0 static void hd44780_hex_char( uint8_t x ) { uint8_t y = x & 0x0F; x = x >> 4; hd44780_char( x < 10 ? '0' + x : 'A'-10 + x ); hd44780_char( y < 10 ? '0' + y : 'A'-10 + y ); } #endif static uint8_t check_data() { uint16_t checksum; #if 0 { uint8_t i; hd44780_setcursor( 0, 2 ); for( i=0; i<6; i++ ) { hd44780_hex_char( data.usart_data[i] ); } } #endif if( data.usart_data_count != 6 ) { #if 0 hd44780_setcursor( 8, 1 ); hd44780_char( 'N' ); hd44780_char( '0' ); hd44780_char( '0'+data.usart_data_count ); #endif return 0; } // if( data.usart_data[0] != ~data.usart_data[1] ) if( (data.usart_data[0] + data.usart_data[1]) != 0xFF ) { #if 0 hd44780_setcursor( 8, 1 ); hd44780_char( '0' ); hd44780_hex_char( data.usart_data[0] + data.usart_data[1] ); #endif return 0; } if( (data.usart_data[2] + data.usart_data[3]) != 0xFF ) { #if 0 hd44780_setcursor( 8, 1 ); hd44780_char( '2' ); hd44780_hex_char( data.usart_data[2] + data.usart_data[3] ); #endif return 0; } xorshift16_init( XORSHIFT16_INIT_DEFAULT ); xorshift16_next(); xorshift16_add( data.usart_data[0] ); xorshift16_next(); xorshift16_add( data.usart_data[2] ); xorshift16_next(); checksum = xorshift16_get_state(); #if 0 hd44780_setcursor( 8, 1 ); hd44780_hex_char( checksum ); hd44780_hex_char( checksum>>8 ); #endif if( (checksum & 0xFF) != data.usart_data[4] ) { return 0; } if( (checksum >> 8) != data.usart_data[5] ) { return 0; } return 1; } void recv_data() { uint8_t delay; if( data.usart_data_count > 0 ) { // have a byte in the buffer delay = data.counter - data.usart_data_time; if( delay > MAX_RECV_DELAY ) { // timeout, clear buffer data.usart_data_count = 0; } } if( USART0_CAN_RECV ) { data.rcvbytes++; // store new byte data.usart_data[data.usart_data_count++] = USART0_DO_RECV; data.usart_data_time = data.counter; if( data.usart_data_count >= 6 ) { // got a message, check it if( check_data() ) { data.upper_counter = data.usart_data[0]; data.upper_sensor_state = data.usart_data[2]; data.update_flag = 1; data.delay = DELAY; data.upper_errors = 0; } data.usart_data_count = 0; } } } static void write_tank_value( uint8_t v ) { switch( v ) { case TANK_EMPTY: hd44780_flash_string( PSTR("Empty") ); break; case TANK_LOW: hd44780_flash_string( PSTR("Low ") ); break; case TANK_MED: hd44780_flash_string( PSTR("Med ") ); break; case TANK_FULL: hd44780_flash_string( PSTR("High ") ); break; default: hd44780_flash_string( PSTR("INV!!") ); break; } } void do_updates() { if( data.upper_errors >= 10 ) { data.upper_tank = TANK_INVALID; data.upper_overflow = 255; } else { switch( data.upper_sensor_state & 3 ) { case 0: data.upper_tank = TANK_LOW; break; case 1: data.upper_tank = TANK_MED; break; case 2: data.upper_tank = TANK_INVALID; break; case 3: data.upper_tank = TANK_FULL; break; } if( (data.upper_sensor_state & 4) == 0 ) { data.upper_overflow = 0; } else if( data.upper_overflow < 255 ) { data.upper_overflow++; } } switch( data.lower_sensor_state & 0x07 ) { case 4: // H=1 M=0 L=0 data.lower_tank = TANK_EMPTY; break; case 5: // H=1 M=0 L=1 data.lower_tank = TANK_LOW; break; case 7: // H=1 M=1 L=1 data.lower_tank = TANK_MED; break; case 3: // H=0 M=1 L=1 data.lower_tank = TANK_FULL; break; default: data.lower_tank = TANK_INVALID; break; } if( (data.lower_sensor_state & 0x08) == 0 ) { AVRPIN_SET(BACKLIGHT_OUT); data.backlight = 240; } else if( data.backlight > 0 ) { data.backlight--; } else { AVRPIN_CLR(BACKLIGHT_OUT); } if( data.upper_tank == TANK_INVALID || data.lower_tank == TANK_INVALID ) { // switch off if any state is invalid data.pump_on = 0; } #if 0 else if( data.lower_tank == TANK_EMPTY ) { /* switching off here leads to on-off-on-off... * because the pump fills the tube and triggers the sensor, the * pump switches off and the water flows back and the sensor goes * to normal. just let the pump run for a maximum of 30 seconds... */ // switch off when lower tank is empty data.pump_on = 0; data.pumping = 0; } #endif else if( data.upper_overflow >= 30 ) { // switch off when warm water tank is overflowing for 30 seconds data.pump_on = 0; } else if( data.pump_on == 0 ) { // pump is off if( data.lower_tank > TANK_EMPTY ) { // lower tank has water if( data.upper_tank == TANK_LOW ) { // upper tank is low, start pumping data.pump_on = 60; data.pumping = 15*60; } else if( data.pumping ) { // continue pumping after a break... data.pump_on = 60; } } } else if( data.upper_tank == TANK_FULL ) { // upper tanks are full, stop pumping data.pump_on = 0; data.pumping = 0; } else { // check the lower tank, then count down the maximum on time if( data.lower_tank == TANK_EMPTY ) { /* lower tank is empty, let the pump run for at most 30 more * seconds to prevent water flowing back to trigger the sensor * again */ if( data.pumping > 30 ) { data.pumping = 30; } } // count down data.pumping--; if( data.pumping == 0 ) { // reached maximum pumping time data.pump_on = 0; } } if( data.pump_on ) { AVRPIN_SET( PUMP_RELAIS ); } else { AVRPIN_CLR( PUMP_RELAIS ); } // update display hd44780_setcursor( 2, 1 ); write_tank_value( data.upper_tank ); hd44780_setcursor( 10, 1 ); write_tank_value( data.lower_tank ); hd44780_setcursor( 2, 2 ); hd44780_flash_string( data.pump_on ? PSTR("ON ") : PSTR("Off") ); #if 0 hd44780_setcursor( 7, 1 ); hd44780_char( '0' + (data.upper_sensor_state & 0x07) ); hd44780_setcursor( 15, 1 ); hd44780_char( '0' + (data.lower_sensor_state & 0x07) ); #endif hd44780_setcursor( 10, 2 ); hd44780_char( data.upper_overflow ? '~' : ' ' ); #if 0 hd44780_char( (data.lower_sensor_state & 0x08) ? '|' : '-' ); hd44780_char( AVRPIN_GET(BACKLIGHT_OUT) ? '*' : '.' ); #endif } int main() { uint8_t counter = 0; uint8_t c; initialize(); hd44780_init(); hd44780_setcursor( 0, 1 ); hd44780_flash_string( PSTR("U: D: ") ); hd44780_setcursor( 0, 2 ); hd44780_flash_string( PSTR("P: WWT: ") ); sei(); AVRPIN_SET(BACKLIGHT_OUT); data.backlight = 10; while( 1 ) { if( timing() ) { data.counter++; data.delay--; if( data.delay == 0 ) { data.delay = DELAY; data.update_flag = 2; } } recv_data(); if( data.update_flag ) { if( data.update_flag == 2 && data.upper_errors < 255 ) { data.upper_errors++; } do_updates(); hd44780_setcursor( 14, 2 ); hd44780_char( '0'+data.rcvbytes ); data.rcvbytes = 0; c = (counter++ & 15) + (data.update_flag == 2 ? 'a' : 'A'); hd44780_char( c ); data.update_flag = 0; } } }