Dritter und (vorläufig) letzter Teil:
Das Hauptprogramm ist recht kurz
CodeBox C
#include "main.h"
ISR(WDT_vect)
{
// dummy-ISR zum Aufwecken des Controllers
}
void ina_init(uint8_t p, uint8_t h, uint8_t l)
{
i2c_init(); // enable I2C, wegen Doppelbenutzung des SCL-Pins für I2C und LCD
i2c_start(INA219 + I2C_WRITE);
i2c_write(p); // address
i2c_write(h); // MSB
i2c_write(l); // LSB
i2c_stop();
PORTB |= SCL; // enable CS für LCD
DDRB |= SCL;
}
int16_t ina_read(uint8_t p)
{
uint16_t temp;
i2c_init(); // s.o.
i2c_start(INA219 + I2C_WRITE);
i2c_write(p);
i2c_rep_start(INA219 + I2C_READ);
temp = i2c_readAck() << 8; // MSB
temp |= i2c_readNak(); // LSB
i2c_stop();
PORTB |= SCL; // s.o.
DDRB |= SCL;
return temp;
}
int main(void)
{
DDRB = CLK | SI | RS | SCL; // 0b00010111
PORTB = SCL; // 0b00010000
PRR = (1<<PRADC) | (1<<PRTIM0);
lcd_init();
xfunc_out = lcd_putc; // Ausgabefunktion initialisieren
ina_init(0, 0x1F, 0xFF); // 16V, 320mV, 128 Samples
ina_init(5, 0x03, 0x28); // calibration bei 0,5 Ohm Imax +/- 640 mA
set_sleep_mode(1<<SM1); // sleep mode power down
wdt_enable(WDTO_500MS); // watchdog auf 500 ms
WDTCR |= (1<<WDTIE); // wdt interrupt enable
sei();
FOREVER {
sleep_mode();
wdt_reset();
WDTCR |= (1<<WDTIE); // re-enable wdt-interrupt
lcd_cursor(0, 8);
xitoa((ina_read(2) >> 1) & 0xFFFE, 10, 5); // Spannung
xputs(PSTR(" mV"));
lcd_cursor(1, 8);
xitoa((int32_t) ((ina_read(4) + 5) / 10), -10, 5); // Strom
xputs(PSTR(" mA"));
lcd_cursor(1, 0);
xitoa(ina_read(3) << 1, 10, 5); // Leistung
xputs(PSTR(" mW"));
if (MCUSR & (1<<WDRF)) {
lcd_cursor(1, 0);
xputs(PSTR("WDR")); // anzeigen ob ein watchdog-reset aufgetreten ist
}
}
}
Die Spannung steht mit 13 Bit linksbündig im Bus Voltage Register. Das LSB entspricht 4 mV, muß also um ein Bit nach rechts verschoben werden, damit die Zahl als mV interpretiert werden kann. Das Conversion-Ready-Bit steht damit auf Bitposition null und muß noch ausgeblendet werden.
Mit dem gewählten Kalibrierungswert wird die Shunt-Spannung in Zehntel mA umgerechnet. Also muß der Wert im Current Register noch gerundet und durch 10 geteilt, und da der Strom auch negativ werden kann und die Ausgabefunktion 32-Bit-Werte erwartet, auf int32 gecastet werden.
Den Kalibrierungswert habe ich experimentell ermittelt durch Vergleich des Stromes mit einem Multimeter. Leider habe ich kein Meßgerät, mit dem ich die 1 Ω Widerstände auf eine für die Berechnung notwendige Genauigkeit hätte ausmessen können.
Und aus irgendeinem unerfindlichen Grund, muß der Wert im Power Register noch mit zwei multipliziert werden, um auf mW zu kommen.
Hier noch die Header-Datei
CodeBox C
#ifndef _MAIN_H
#define _MAIN_H
#define F_CPU 9600000UL / 8
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/wdt.h>
#include "Display.h"
#include "xitoa.h"
#include "i2cmaster.h"
#define FOREVER for(;;)
#define INA219 0x80
#define SI 0x01 // 0b00000001 PB0
#define RS 0x02 // 0b00000010 PB1
#define CLK 0x04 // 0b00000100 PB2
#define SDA 0x08 // 0b00001000 PB3
#define SCL 0x10 // 0b00010000 PB4 auch als CS für LCD mißbraucht
#endif
und die Displayfunktionen
CodeBox C
#include "main.h"
void lcd_out(uint8_t byte) // soft-SPI
{
CLR_CS;
for (uint8_t i = 0; i < 8; i++) {
(byte & 0x80) ? (PORTB |= SI) : (PORTB &= ~SI);
PORTB |= CLK;
byte <<= 1;
PORTB &= ~CLK;
}
SET_CS;
_delay_us(30);
}
void lcd_putc(uint8_t byte)
{
SET_RS;
lcd_out(byte);
}
void lcd_cmd(uint8_t byte)
{
CLR_RS;
lcd_out(byte);
}
void lcd_init() // DOGM 162
{
CLR_CS;
_delay_ms(40);
lcd_cmd(0x38); // 8 bit
lcd_cmd(0x39); // 8 bit, 2 line, normal font, instruction table 1
lcd_cmd(0x14); // bias 1/5, 2 line
lcd_cmd(0x55); // booster on, contrast high
lcd_cmd(0x6E); // follower on ,V generator
lcd_cmd(0x72); // contrast low byte
_delay_ms(200);
lcd_cmd(0x38); // 8 bit, 2 line, normal font, instruction table 0
lcd_cmd(0x0C); // display on, cursor off, blink off
lcd_cmd(0x01); // clear display
_delay_ms(2);
lcd_cmd(0x06); // increment, no display shift
SET_CS;
}
void lcd_cursor(uint8_t row, uint8_t col)
{
lcd_cmd(0x80 | (row << 6) | col);
}
CodeBox C
#ifndef _DISPLAY_H
#define _DISPLAY_H
#define CLR_CS (PORTB &= ~SCL) // 0b00010000
#define SET_CS (PORTB |= SCL)
#define CLR_RS (PORTB &= ~RS) // 0b00000010
#define SET_RS (PORTB |= RS)
void lcd_init();
void lcd_putc(uint8_t byte);
void lcd_cursor(uint8_t row, uint8_t col);
#endif
Die I2C-Funktionen sind bei Peter Fleury und die Ausgabe-Funktionen bei Elm-ChaN zu finden. (Die sind übrigens alle in Assembler geschrieben.) Man muß das Rad ja nicht jedesmal wieder neu erfinden…