Browse Source

Working version

master
Olaf Rempel 1 year ago
commit
f6e28df0b1
3 changed files with 611 additions and 0 deletions
  1. +6
    -0
      .gitignore
  2. +55
    -0
      Makefile
  3. +550
    -0
      main.c

+ 6
- 0
.gitignore View File

@@ -0,0 +1,6 @@
*.o
*.elf
*.bin
*.hex
*.lst
*.map

+ 55
- 0
Makefile View File

@@ -0,0 +1,55 @@
PRG = powermeter
OBJ = main.o
MCU_TARGET = attiny26
OPTIMIZE = -Os

AVRDUDE_PROG = -c avr910 -b 115200 -P /dev/ttyUSB0
#AVRDUDE_PROG = -c dragon_isp -P usb
AVRDUDE_MCU = attiny26

DEFS =
LIBS =

# Override is only needed by avr-lib build system.
override CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS)
override LDFLAGS = -Wl,-Map,$(PRG).map

CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size

all: $(PRG).elf lst text
$(SIZE) -x -A $(PRG).elf

$(PRG).elf: $(OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)

%.o: %.c $(MAKEFILE_LIST)
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -rf *.o $(PRG).lst $(PRG).map $(PRG).elf $(PRG).hex $(PRG).bin

lst: $(PRG).lst

%.lst: %.elf
$(OBJDUMP) -h -S $< > $@

text: hex bin

hex: $(PRG).hex
bin: $(PRG).bin

%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@

%.bin: %.elf
$(OBJCOPY) -j .text -j .data -O binary $< $@

install: text
avrdude $(AVRDUDE_PROG) -p $(AVRDUDE_MCU) -V -U flash:w:$(PRG).hex

fuses:
avrdude $(AVRDUDE_PROG) -p $(AVRDUDE_MCU) -U hfuse:w:0x14:m
avrdude $(AVRDUDE_PROG) -p $(AVRDUDE_MCU) -U lfuse:w:0xe4:m

+ 550
- 0
main.c View File

@@ -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();
}
}
}

Loading…
Cancel
Save