|
|
@ -0,0 +1,550 @@ |
|
|
|
/*************************************************************************** |
|
|
|
* Copyright (C) 11/2012 by Olaf Rempel * |
|
|
|
* razzor@kopf-tisch.de * |
|
|
|
* * |
|
|
|
* This program is free software; you can redistribute it and/or modify * |
|
|
|
* it under the terms of the GNU General Public License as published by * |
|
|
|
* the Free Software Foundation; version 2 of the License, * |
|
|
|
* * |
|
|
|
* This program is distributed in the hope that it will be useful, * |
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|
|
|
* GNU General Public License for more details. * |
|
|
|
* * |
|
|
|
* You should have received a copy of the GNU General Public License * |
|
|
|
* along with this program; if not, write to the * |
|
|
|
* Free Software Foundation, Inc., * |
|
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * |
|
|
|
***************************************************************************/ |
|
|
|
#include <avr/io.h> |
|
|
|
#include <avr/interrupt.h> |
|
|
|
#include <avr/pgmspace.h> |
|
|
|
#include <avr/eeprom.h> |
|
|
|
#include <util/crc16.h> |
|
|
|
|
|
|
|
#define F_CPU 8000000 |
|
|
|
#include <util/delay.h> |
|
|
|
|
|
|
|
#define OSCCAL_VALUE 0xA4 |
|
|
|
|
|
|
|
/* |
|
|
|
* attiny26 |
|
|
|
* lfuse: 0xe4 (internal 8MHz) |
|
|
|
* hfuse: 0x14 (BOD enabled) |
|
|
|
* |
|
|
|
* PA0 => current sense (0-2.56V => 0-25.6A) |
|
|
|
* PA1 => volatage sense (0-2.56V => 0-30.72V) |
|
|
|
* PA2 => mode button (low active) |
|
|
|
* PA3 => ext. 2.56V reference |
|
|
|
* PA4-7 => LCD D4-7 |
|
|
|
* PB0 => MOSI (ISP) |
|
|
|
* PB1 => MISO (ISP) |
|
|
|
* PB2 => SCK (ISP) |
|
|
|
* PB3 => free |
|
|
|
* PB4 => LCD RS |
|
|
|
* PB5 => LCD RW |
|
|
|
* PB6 => LCD EN |
|
|
|
* PB7 => /RST (ISP) |
|
|
|
*/ |
|
|
|
|
|
|
|
#define LCD_DATA_MASK 0xF0 |
|
|
|
#define LCD_RS PORTB4 |
|
|
|
#define LCD_RW PORTB5 |
|
|
|
#define LCD_EN PORTB6 |
|
|
|
#define BUTTON PORTA2 |
|
|
|
|
|
|
|
#define ADC_CURRENT 0 |
|
|
|
#define ADC_VOLTAGE 1 |
|
|
|
#define ADC_COMPLETE 0xFE |
|
|
|
#define ADC_IDLE 0xFF |
|
|
|
|
|
|
|
/* 8ms @8MHz / 256 */ |
|
|
|
#define TIMER_TICK_RELOAD (256 - 250) |
|
|
|
|
|
|
|
/* 25 * 8ms => 200ms */ |
|
|
|
#define TIMER_LCD_UPDATE 25 |
|
|
|
|
|
|
|
/* 125 * 8ms => 1s */ |
|
|
|
#define TIMER_SECOND 125 |
|
|
|
|
|
|
|
/* autosave every minute */ |
|
|
|
#define TIMER_NVRAM_SAVE 60 |
|
|
|
|
|
|
|
/* bargraph 20 - 28V */ |
|
|
|
#define VOLTAGE_BAR_MIN 2000 |
|
|
|
#define VOLTAGE_BAR_MAX 2800 |
|
|
|
|
|
|
|
/* bargraph 0 - 20A */ |
|
|
|
#define CURRENT_BAR_MIN 0 |
|
|
|
#define CURRENT_BAR_MAX 2000 |
|
|
|
|
|
|
|
/* minimum discharge current 100mA */ |
|
|
|
#define CURRENT_IDLE 10 |
|
|
|
|
|
|
|
|
|
|
|
#define BUTTON_TIMER_IDLE 0xFF |
|
|
|
|
|
|
|
#define EVENT_NONE 0 |
|
|
|
#define EVENT_BUTTON_PRESSED 1 |
|
|
|
#define EVENT_BUTTON_RELEASED 2 |
|
|
|
#define EVENT_BUTTON_TIMEOUT 3 |
|
|
|
|
|
|
|
#define STATE_IDLE 0 |
|
|
|
#define STATE_PRESSED 1 |
|
|
|
#define STATE_WARNING 2 |
|
|
|
|
|
|
|
struct _nvdata { |
|
|
|
uint8_t nvram_size; /* first */ |
|
|
|
uint16_t discharge_time; |
|
|
|
uint32_t discharge_product; |
|
|
|
uint16_t nvram_crc; /* last */ |
|
|
|
}; |
|
|
|
|
|
|
|
static uint8_t nvram_write_pos; |
|
|
|
static struct _nvdata nvram_data; |
|
|
|
static struct _nvdata nvram_eeprom EEMEM; |
|
|
|
static struct _nvdata nvram_defaults PROGMEM = { 0 }; |
|
|
|
|
|
|
|
/* create crc and store nvram data to eeprom */ |
|
|
|
static void nvram_start_write(void) |
|
|
|
{ |
|
|
|
uint8_t i; |
|
|
|
uint16_t crc = 0x0000; |
|
|
|
uint8_t *tmp = (uint8_t *)&nvram_data; |
|
|
|
|
|
|
|
/* write in progress? */ |
|
|
|
if (EECR & (1<<EEWE)) |
|
|
|
return; |
|
|
|
|
|
|
|
nvram_data.nvram_size = sizeof(struct _nvdata); |
|
|
|
|
|
|
|
for (i = 0; i < sizeof(struct _nvdata) -2; i++) { |
|
|
|
crc = _crc_ccitt_update(crc, *tmp++); |
|
|
|
} |
|
|
|
|
|
|
|
nvram_data.nvram_crc = crc; |
|
|
|
nvram_write_pos = 0; |
|
|
|
|
|
|
|
EEAR = nvram_write_pos; |
|
|
|
EEDR = ((uint8_t *)&nvram_data)[nvram_write_pos++]; |
|
|
|
cli(); |
|
|
|
EECR |= (1<<EEMWE); |
|
|
|
EECR |= (1<<EEWE); |
|
|
|
EECR |= (1<<EERIE); |
|
|
|
sei(); |
|
|
|
} |
|
|
|
|
|
|
|
/* store nvram data to eeprom */ |
|
|
|
ISR(EE_RDY_vect) { |
|
|
|
if (nvram_write_pos < sizeof(struct _nvdata)) { |
|
|
|
EEAR = nvram_write_pos; |
|
|
|
EEDR = ((uint8_t *)&nvram_data)[nvram_write_pos++]; |
|
|
|
EECR |= (1<<EEMWE); |
|
|
|
EECR |= (1<<EEWE); |
|
|
|
EECR |= (1<<EERIE); |
|
|
|
|
|
|
|
} else { |
|
|
|
EECR &= ~(1<<EERIE); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* read nvram from eeprom and check crc */ |
|
|
|
static void nvram_read(void) |
|
|
|
{ |
|
|
|
uint8_t i; |
|
|
|
uint16_t crc = 0x0000; |
|
|
|
uint8_t *tmp = (uint8_t *)&nvram_data; |
|
|
|
|
|
|
|
eeprom_read_block(&nvram_data, &nvram_eeprom, sizeof(struct _nvdata)); |
|
|
|
|
|
|
|
for (i = 0; i < sizeof(struct _nvdata); i++) { |
|
|
|
crc = _crc_ccitt_update(crc, *tmp++); |
|
|
|
} |
|
|
|
|
|
|
|
/* if nvram content is invalid, overwrite with defaults */ |
|
|
|
if ((nvram_data.nvram_size != sizeof(struct _nvdata)) || (crc != 0x0000)) { |
|
|
|
memcpy_P(&nvram_data, &nvram_defaults, sizeof(struct _nvdata)); |
|
|
|
nvram_start_write(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static PROGMEM uint8_t bargraph[] = { |
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|
|
|
0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, |
|
|
|
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, |
|
|
|
0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x1c, 0x00, 0x00, |
|
|
|
0x00, 0x00, 0x1e, 0x1e, 0x1e, 0x1e, 0x00, 0x00, |
|
|
|
0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, |
|
|
|
}; |
|
|
|
|
|
|
|
static uint8_t lcd_port_write(uint8_t data) |
|
|
|
{ |
|
|
|
uint8_t retval; |
|
|
|
|
|
|
|
/* keep button pullup */ |
|
|
|
PORTA = (data & LCD_DATA_MASK) | (1<<BUTTON); |
|
|
|
|
|
|
|
asm volatile("NOP"); |
|
|
|
asm volatile("NOP"); |
|
|
|
|
|
|
|
PORTB |= (1<<LCD_EN); |
|
|
|
asm volatile("NOP"); |
|
|
|
asm volatile("NOP"); |
|
|
|
retval = (PINA & LCD_DATA_MASK); |
|
|
|
PORTB &= ~(1<<LCD_EN); |
|
|
|
|
|
|
|
return retval; |
|
|
|
} |
|
|
|
|
|
|
|
static uint8_t lcd_read(uint8_t reg) |
|
|
|
{ |
|
|
|
uint8_t retval; |
|
|
|
|
|
|
|
if (reg) { |
|
|
|
PORTB |= (1<<LCD_RS); |
|
|
|
} else { |
|
|
|
PORTB &= ~(1<<LCD_RS); |
|
|
|
} |
|
|
|
|
|
|
|
PORTB |= (1<<LCD_RW); |
|
|
|
DDRA &= ~(LCD_DATA_MASK); |
|
|
|
|
|
|
|
retval = lcd_port_write(0x00); |
|
|
|
asm volatile("NOP"); |
|
|
|
asm volatile("NOP"); |
|
|
|
retval |= (lcd_port_write(0x00) >> 4); |
|
|
|
|
|
|
|
return retval; |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_write_no_busy_check(uint8_t reg, uint8_t data) |
|
|
|
{ |
|
|
|
if (reg) { |
|
|
|
PORTB |= (1<<LCD_RS); |
|
|
|
} else { |
|
|
|
PORTB &= ~(1<<LCD_RS); |
|
|
|
} |
|
|
|
|
|
|
|
PORTB &= ~(1<<LCD_RW); |
|
|
|
DDRA |= LCD_DATA_MASK; |
|
|
|
|
|
|
|
lcd_port_write((data & 0xF0)); |
|
|
|
asm volatile("NOP"); |
|
|
|
asm volatile("NOP"); |
|
|
|
lcd_port_write(((data & 0x0F) << 4)); |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_write(uint8_t reg, uint8_t data) |
|
|
|
{ |
|
|
|
lcd_write_no_busy_check(reg, data); |
|
|
|
while ((lcd_read(0x00) & 0x80)); |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_print_dec2(uint8_t value) |
|
|
|
{ |
|
|
|
lcd_write(0x01, '0' + (value / 10)); |
|
|
|
lcd_write(0x01, '0' + (value % 10)); |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_print_dec2p2(uint16_t value) |
|
|
|
{ |
|
|
|
lcd_print_dec2(value / 100); |
|
|
|
lcd_write(0x01, '.'); |
|
|
|
lcd_print_dec2(value % 100); |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_print_dec2p3(uint16_t value) |
|
|
|
{ |
|
|
|
lcd_print_dec2(value / 1000); |
|
|
|
lcd_write(0x01, '.'); |
|
|
|
|
|
|
|
value = value % 1000; |
|
|
|
|
|
|
|
lcd_write(0x01, '0' + (value / 100)); |
|
|
|
lcd_print_dec2(value % 100); |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_print_time(uint16_t time) |
|
|
|
{ |
|
|
|
lcd_print_dec2(time / 3600); |
|
|
|
lcd_write(0x01, ':'); |
|
|
|
|
|
|
|
time = time % 3600; |
|
|
|
|
|
|
|
lcd_print_dec2(time / 60); |
|
|
|
lcd_write(0x01, ':'); |
|
|
|
|
|
|
|
lcd_print_dec2(time % 60); |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_bargraph(uint8_t len, uint16_t min, uint16_t max, uint16_t value) |
|
|
|
{ |
|
|
|
if (value < min) |
|
|
|
value = min; |
|
|
|
else if (value > max) |
|
|
|
value = max; |
|
|
|
|
|
|
|
/* scale to bargraph length */ |
|
|
|
value = ((value - min) * (len * 5)) / (max - min) ; |
|
|
|
|
|
|
|
while (len--) { |
|
|
|
if (value >= 5) { |
|
|
|
lcd_write(0x01, 0x05); |
|
|
|
value -= 5; |
|
|
|
|
|
|
|
} else { |
|
|
|
lcd_write(0x01, value); |
|
|
|
value = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void lcd_write_stringP(const char *ptr) |
|
|
|
{ |
|
|
|
while (1) { |
|
|
|
uint8_t data = pgm_read_byte_near(ptr++); |
|
|
|
|
|
|
|
if (data == 0x00) |
|
|
|
break; |
|
|
|
|
|
|
|
lcd_write(0x01, data); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static volatile uint8_t adc_state; |
|
|
|
static volatile uint16_t adc_value[2]; |
|
|
|
|
|
|
|
static void adc_start(uint8_t channel) |
|
|
|
{ |
|
|
|
adc_state = channel; |
|
|
|
|
|
|
|
/* ext. 2.56V ref, start, irq enable, F_CPU/64 -> ~225us conversion time */ |
|
|
|
ADMUX = (1<<REFS0) | channel; |
|
|
|
ADCSR = (1<<ADEN) | (1<<ADSC) | (1<<ADIE) | (1<<ADIF) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); |
|
|
|
} |
|
|
|
|
|
|
|
ISR(ADC_vect) |
|
|
|
{ |
|
|
|
uint8_t channel = adc_state; |
|
|
|
adc_value[channel] = ADCW; |
|
|
|
|
|
|
|
/* get next channel */ |
|
|
|
if (channel == ADC_VOLTAGE) { |
|
|
|
adc_start(ADC_CURRENT); |
|
|
|
|
|
|
|
} else if (channel == ADC_CURRENT) { |
|
|
|
adc_state = ADC_COMPLETE; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static volatile uint8_t timer_tick; |
|
|
|
|
|
|
|
ISR(TIMER0_OVF0_vect) |
|
|
|
{ |
|
|
|
TCNT0 = TIMER_TICK_RELOAD; |
|
|
|
|
|
|
|
/* start sampling both channels */ |
|
|
|
adc_start(ADC_VOLTAGE); |
|
|
|
|
|
|
|
timer_tick = 1; |
|
|
|
} |
|
|
|
|
|
|
|
int main(void) __attribute__ ((noreturn)); |
|
|
|
int main(void) |
|
|
|
{ |
|
|
|
/* calibrate to 8Mhz */ |
|
|
|
OSCCAL = OSCCAL_VALUE; |
|
|
|
|
|
|
|
DDRA = LCD_DATA_MASK; |
|
|
|
DDRB = (1<<LCD_RS) | (1<<LCD_RW) | (1<<LCD_EN) | (1<<PORTB3); |
|
|
|
|
|
|
|
/* pullup for mode button */ |
|
|
|
PORTA = (1<<BUTTON); |
|
|
|
|
|
|
|
/* F_CPU/256, overflow irq */ |
|
|
|
TCCR0 = (1<<CS02); |
|
|
|
TIMSK = (1<<TOV0); |
|
|
|
|
|
|
|
/* execute reset (according to datasheet) */ |
|
|
|
_delay_ms(50); |
|
|
|
lcd_write_no_busy_check(0x00, 0x30); |
|
|
|
_delay_ms(5); |
|
|
|
lcd_write_no_busy_check(0x00, 0x30); |
|
|
|
_delay_ms(1); |
|
|
|
lcd_write_no_busy_check(0x00, 0x30); |
|
|
|
|
|
|
|
/* switch to 4bit */ |
|
|
|
lcd_write(0x00, 0x28); /* 4bit data bus */ |
|
|
|
/* again, now with valid lower nibble */ |
|
|
|
lcd_write(0x00, 0x28); /* 4bit data bus, 2 lines */ |
|
|
|
|
|
|
|
lcd_write(0x00, 0x0C); /* display on, no cursor, no blinking */ |
|
|
|
lcd_write(0x00, 0x06); /* increase address, no shift */ |
|
|
|
lcd_write(0x00, 0x01); /* clear display */ |
|
|
|
|
|
|
|
{ |
|
|
|
uint8_t i; |
|
|
|
|
|
|
|
lcd_write(0x00, 0x40); /* write CGRAM address 0x00 */ |
|
|
|
for (i = 0; i < sizeof(bargraph); i++) { |
|
|
|
lcd_write(0x01, pgm_read_byte_near(&bargraph[i])); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sei(); |
|
|
|
nvram_read(); |
|
|
|
|
|
|
|
uint8_t sec_timer = 0; |
|
|
|
uint8_t lcd_timer = 0; |
|
|
|
uint8_t button_timer = BUTTON_TIMER_IDLE; |
|
|
|
|
|
|
|
uint8_t lcd_page = 0x02; |
|
|
|
uint8_t button_prev = 0; |
|
|
|
uint8_t button_state = 0; |
|
|
|
uint8_t nvram_save = 0; |
|
|
|
|
|
|
|
uint16_t voltage = 0; |
|
|
|
uint16_t current = 0; |
|
|
|
|
|
|
|
uint8_t discharging = 0; |
|
|
|
uint16_t discharge_time = nvram_data.discharge_time; |
|
|
|
uint32_t discharge_product = nvram_data.discharge_product; |
|
|
|
|
|
|
|
while (1) { |
|
|
|
if (timer_tick) { |
|
|
|
timer_tick = 0; |
|
|
|
|
|
|
|
lcd_timer++; |
|
|
|
|
|
|
|
if (discharging) { |
|
|
|
sec_timer++; |
|
|
|
if (sec_timer == TIMER_SECOND) { |
|
|
|
sec_timer = 0; |
|
|
|
discharge_time++; |
|
|
|
|
|
|
|
/* autosave during discharge */ |
|
|
|
if ((discharge_time % TIMER_NVRAM_SAVE) == 0) { |
|
|
|
nvram_save = 1; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
uint8_t button_event = EVENT_NONE; |
|
|
|
|
|
|
|
uint8_t button = PINA & (1<<BUTTON); |
|
|
|
if (!button && button_prev) { |
|
|
|
button_event = EVENT_BUTTON_PRESSED; |
|
|
|
|
|
|
|
} else if (button && !button_prev) { |
|
|
|
button_event = EVENT_BUTTON_RELEASED; |
|
|
|
|
|
|
|
} else { |
|
|
|
if (button_timer == 0) { |
|
|
|
button_event = EVENT_BUTTON_TIMEOUT; |
|
|
|
button_timer = BUTTON_TIMER_IDLE; |
|
|
|
|
|
|
|
} else if (button_timer != BUTTON_TIMER_IDLE) { |
|
|
|
button_timer--; |
|
|
|
} |
|
|
|
} |
|
|
|
button_prev = button; |
|
|
|
|
|
|
|
if ((button_state == STATE_IDLE) && (button_event == EVENT_BUTTON_PRESSED)) { |
|
|
|
lcd_write(0x00, 0x01); /* clear display */ |
|
|
|
lcd_timer = TIMER_LCD_UPDATE; |
|
|
|
lcd_page ^= 0x01; |
|
|
|
|
|
|
|
button_state = STATE_PRESSED; |
|
|
|
button_timer = 250; /* 2s timeout */ |
|
|
|
|
|
|
|
} else if ((button_event == EVENT_BUTTON_TIMEOUT) && (button_state == STATE_PRESSED)) { |
|
|
|
lcd_write(0x00, 0x01); /* clear display */ |
|
|
|
lcd_write(0x00, 0x80 | 1); |
|
|
|
lcd_write_stringP(PSTR("reset counter?")); |
|
|
|
lcd_page = 0; |
|
|
|
|
|
|
|
button_state = STATE_WARNING; |
|
|
|
button_timer = 250; /* another 2s timeout */ |
|
|
|
|
|
|
|
} else if (((button_event == EVENT_BUTTON_TIMEOUT) && (button_state == STATE_WARNING)) || |
|
|
|
(button_event == EVENT_BUTTON_RELEASED)) { |
|
|
|
if (button_event == EVENT_BUTTON_TIMEOUT) { |
|
|
|
discharge_time = 0; |
|
|
|
discharge_product = 0; |
|
|
|
nvram_save = 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (button_state == STATE_WARNING) { |
|
|
|
lcd_write(0x00, 0x01); /* clear display */ |
|
|
|
lcd_timer = TIMER_LCD_UPDATE; |
|
|
|
lcd_page = 2; |
|
|
|
} |
|
|
|
|
|
|
|
button_state = STATE_IDLE; |
|
|
|
button_timer = BUTTON_TIMER_IDLE; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (adc_state == ADC_COMPLETE) { |
|
|
|
adc_state = ADC_IDLE; |
|
|
|
|
|
|
|
/* voltage in 10mV increments */ |
|
|
|
voltage = adc_value[ADC_VOLTAGE] * 3; |
|
|
|
|
|
|
|
/* current in 10mA increments */ |
|
|
|
current = (adc_value[ADC_CURRENT] * 5) >> 1; |
|
|
|
|
|
|
|
if (discharging) { |
|
|
|
discharge_product += current; |
|
|
|
} |
|
|
|
|
|
|
|
if (current >= CURRENT_IDLE) { |
|
|
|
discharging = 1; |
|
|
|
|
|
|
|
} else if (sec_timer == 0) { |
|
|
|
discharging = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (lcd_timer == TIMER_LCD_UPDATE) { |
|
|
|
lcd_timer = 0; |
|
|
|
|
|
|
|
if (lcd_page) { |
|
|
|
lcd_write(0x00, 0x80 | 0); |
|
|
|
lcd_print_dec2p2(voltage); |
|
|
|
lcd_write(0x01, 'V'); |
|
|
|
|
|
|
|
lcd_write(0x00, 0x80 | 40); |
|
|
|
lcd_print_dec2p2(current); |
|
|
|
lcd_write(0x01, 'A'); |
|
|
|
|
|
|
|
if (lcd_page == 2) { |
|
|
|
lcd_write(0x00, 0x80 | 8); |
|
|
|
lcd_print_time(discharge_time); |
|
|
|
|
|
|
|
lcd_write(0x00, 0x80 | 48); |
|
|
|
|
|
|
|
/* 125 ticks per second, 3600s per hour, 10mA Steps */ |
|
|
|
lcd_print_dec2p3(discharge_product / (TIMER_SECOND * 3600ULL / 10ULL)); |
|
|
|
lcd_write(0x01, 'A'); |
|
|
|
lcd_write(0x01, 'h'); |
|
|
|
|
|
|
|
} else if (lcd_page == 3) { |
|
|
|
lcd_write(0x00, 0x80 | 7); |
|
|
|
lcd_bargraph(9, VOLTAGE_BAR_MIN, VOLTAGE_BAR_MAX, voltage); |
|
|
|
|
|
|
|
lcd_write(0x00, 0x80 | 47); |
|
|
|
lcd_bargraph(9, CURRENT_BAR_MIN, CURRENT_BAR_MAX, current); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (nvram_save) { |
|
|
|
nvram_save = 0; |
|
|
|
|
|
|
|
nvram_data.discharge_time = discharge_time; |
|
|
|
nvram_data.discharge_product = discharge_product; |
|
|
|
nvram_start_write(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |