This is a simple voltage and current meter that uses the TI INA219 chip. Voltage measurement range is 0 to 26 volts with 4 mV resolution and current measurement range is -4 to +4 amps with 1 mV resolution (when using a 0.01 ohm shunt). The specs are inferior to a pair of quality multimeters, but it is a fraction of the price and shows wattage in addition to voltage and current. The Nokia 5110 display is used so the firmware could be enhanced to do simple graphing. Sending the measurements to a computer could also be done.
Using the INA219 makes for a very simple circuit.
The normal display is three lines with voltage, amperage and wattage.
Pressing the P1.3 switch will show the 6 registers in the INA219 in hex and signed decimal.
The code is written in C++ and uses templates for the LCD, IIC and INA219. Software SPI and IIC is used for maximum portability.
vam.zip
This uses code from Software RTC, Using the internal temperature sensor and Nokia 5110 template class. The code has been modified and refined a bit for this project.
main.cpp
#include
#include
#include
#include
#include "nokia7110tl.h"
using namespace nokia7110;
static const unsigned char ti[] = {
48, 48 / 8,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,
0xFC,0xFC,0xFC,0xFC,0x00,0x00,0x00,0x00,0x00,0xF0,
0xF0,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFC,0xFC,0x3C,0x80,0xFC,0xFD,
0xFD,0xFD,0x3D,0x81,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,
0xFC,0xFC,0xFC,0xFC,0x00,0x00,0x00,0x00,
0x00,0x1C,0x7C,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,
0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xF7,0xF0,0xF0,0x00,0xF0,0xFF,0xFF,0xFF,
0xFF,0x0F,0xF0,0xF0,0xF0,0xF0,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xF8,0xF0,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x03,0x07,0x3F,0x7F,0xFF,
0xFF,0xFF,0xFF,0xFF,0x7F,0x3F,0x3F,0x7F,0xFF,0xFF,
0xFF,0xFF,0xFF,0x1F,0x00,0x1E,0x1F,0x1F,0x1F,0x1F,
0x01,0x1E,0x1F,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0x7F,0x7F,0x3F,0x3F,0x3F,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x0F,0x3F,0xFF,0xFE,0xFE,0xFE,0xFC,0xFC,0xFC,0xFC,
0xFC,0xFC,0xFC,0x7F,0x1F,0x07,0x03,0x03,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x07,0x0F,0x0F,0x1F,0x1F,0x3F,
0x3F,0x3F,0x3F,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
/*
P1.0 Reset
P1.3 Temp Sensor (Not used)
P2.0 Serial Data
P2.1 Backlight
P2.2 Chip Select
P2.3 Data/Command
P2.4 Serial Clock
P2.5 Pushbutton
*/
// P1
static const unsigned LCD_RESET = BIT0;
static const unsigned RXD = BIT2;
static const unsigned SWITCH = BIT3;
// P2
static const unsigned LCD_DATA = BIT0;
static const unsigned LCD_BACKLIGHT = BIT1;
static const unsigned LCD_CE = BIT2;
static const unsigned LCD_DC = BIT3;
static const unsigned LCD_CLK = BIT4;
static const unsigned LCD_BTN = BIT5;
Nokia7110 lcd;
void show_time(const struct tm *t) // Show time on LCD
{
static const char *dow[7] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
};
int x, w;
const char *s;
div_t d;
if(t->tm_hour < 10) {
x = -2;
lcd.fill(0, 0, 9, 2, 0);
d.rem = t->tm_hour;
} else {
x = 3;
lcd.fill(0, 0, 3, 2, 0);
d = div(t->tm_hour, 10);
lcd.pd12(d.quot, x, 0);
}
lcd.pd12(d.rem, x + 11, 0);
lcd.pd12(11, x + 22, 0);
d = div(t->tm_min, 10);
lcd.pd12(d.quot, x + 27, 0);
lcd.pd12(d.rem, x + 38, 0);
lcd.pd12(11, x + 49, 0);
d = div(t->tm_sec, 10);
lcd.pd12(d.quot, x + 54, 0);
lcd.pd12(d.rem, x + 65, 0);
lcd.fill(x + 76, 0, 7 - x, 2, 0);
if(t->tm_mon < 9) {
x = -5;
lcd.fill(0, 2, 6, 2, 0);
d.rem = t->tm_mon + 1;
} else {
x = 0;
d = div(t->tm_mon + 1, 10);
lcd.pd12(d.quot, x, 2);
}
lcd.pd12(d.rem, x + 11, 2);
lcd.pd12(13, x + 22, 2);
d = div(t->tm_mday, 10);
lcd.pd12(d.quot, x + 30, 2);
lcd.pd12(d.rem, x + 41, 2);
lcd.pd12(13, x + 52, 2);
d = div(t->tm_year % 100, 10);
lcd.pd12(d.quot, x + 60, 2);
lcd.pd12(d.rem, x + 71, 2);
if(x) lcd.fill(x + 82, 2, 1 - x, 2, 0);
s = dow[t->tm_wday];
w = strlen(s) * 6;
x = (83 - w) >> 1;
lcd.fill(0, 4, x, 1, 0);
lcd.print((unsigned char)x, (unsigned char)4, s);
x += w;
lcd.fill(x, 4, 83 - x, 1, 0);
}
// Print integer from -999 to 9999 using 12 x 16 font
void print_int(int i, const unsigned y)
{
if(i < -999 || i > 9999) return;
const unsigned neg = i < 0;
if(neg) i = -i;
div_t d; d.quot = i;
unsigned x = 48;
do {
d = div(d.quot, 10);
lcd.pd12(d.rem, x -= 12, y);
} while(d.quot);
if(neg) lcd.pd12(14, x -= 12, y);
while(x) lcd.pd12(10, x -= 12, y);
}
// Print integer from -999 to 9999 using 6 x 8 font
void print_int(int i, unsigned x, const unsigned y)
{
if(i < -999 || i > 9999) return;
const unsigned e = x;
x += 24;
const unsigned neg = i < 0;
if(neg) i = -i;
div_t d; d.quot = i;
do {
d = div(d.quot, 10);
lcd.print(x -= 6, y, '0' + d.rem);
} while(d.quot);
if(neg) lcd.print(x -= 6, y, '-');
while(x > e) lcd.print(x -= 6, y, ' ');
}
void draw_bargraph(int f)
{
int x, y, bg;
char c;
unsigned char bgc[9] = { 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0 };
char *bgl[8] = { "90", "80", "70", "60", "50", "40", "30", "20" };
x = 20; //
bg = (f - (x - 5)) * 4 / 5; //
for(y = 7; y >= 0; --y) { //
lcd.pos(83, y); //
if(bg < 0) {
c = bgc[8];
} else if (bg > 7) {
c = bgc[7];
} else {
c = bgc[bg];
}
lcd.write((unsigned char *)&c, 1, lcd_data);
lcd.print(bgl[y], c);
bg -= 8;
} //
}
time_t tt; // Time in seconds since epoch
unsigned adc; // ADC value
#pragma vector = WDT_VECTOR // - Watchdog timer interrupt vector
__interrupt void wdt_isr(void) // This interrupt will occur once per second
{ //
++tt; // Increment time_t
__bic_SR_register_on_exit(LPM0_bits); // Wakeup main code
} //
//
#pragma vector = ADC10_VECTOR // ADC conversion complete interrupt
__interrupt void ADC10_ISR(void) //
{ //
adc = ADC10MEM; // Read ADC
__bic_SR_register_on_exit(LPM0_bits); // Wakeup main code
} //
int main(void)
{
int x, y;
char c;
struct tm ts;
int dc, dk, df; // Temperature in degrees C, K, and F
WDTCTL = WDTPW | WDTHOLD;
P1REN = RXD | SWITCH;
P1DIR = LCD_RESET;
P1OUT = RXD | SWITCH | LCD_RESET;
P2DIR = LCD_DC | LCD_CE | LCD_CLK | LCD_BACKLIGHT | LCD_DATA;
P2REN = LCD_BTN;
P2OUT = LCD_CLK | LCD_DC | LCD_CE | LCD_BACKLIGHT | LCD_BTN;
ADC10CTL0 = 0; // Configure ADC
ADC10CTL1 = INCH_10 | ADC10DIV_3; //
ADC10CTL0 = SREF_1 | ADC10SHT_3 | REFON | ADC10ON | ADC10IE;
ADC10CTL0 |= ADC10IE; // Enable ADC conversion complete interrupt
//
// 32 kHz xtal loading
//BCSCTL3 = XCAP_1; // 6 pF (default)
BCSCTL3 = XCAP_2; // 10 pF
//BCSCTL3 = XCAP_3; // 12.5 pF
//
WDTCTL = WDTPW | WDTTMSEL | WDTCNTCL | WDTSSEL; // Use WDT as interval timer
IE1 |= WDTIE; // Enable WDT interrupt
_EINT(); // Enable interrupts
//
// Set initial time - there is no UI for this
ts.tm_hour = 13; // Hour
ts.tm_min = 37; // Minute
ts.tm_sec = 42; // Second
ts.tm_mon = 3; // Month (0 based!)
ts.tm_mday = 20; // Day of Month
ts.tm_year = 2012 - 1900; // Year
ts.tm_wday = 5; // Day of Week - Not used by mktime()
ts.tm_yday = 0; // Not used by mktime()
ts.tm_isdst = 0; // DST flag - Not used by rtc_tick()
//
tt = mktime(&ts); // Convert tm struct to time_t
//
lcd.reset();
lcd.init();
lcd.clear();
lcd.print(30, 6, "MSP430");
lcd.print(18, 7, "Nokia 7110");
for(x = -47; x < 24; ++x) {
lcd.bitmap(ti, x, 0);
__delay_cycles(20000);
}
__delay_cycles(2000000);
for(; x < 96; ++x) {
lcd.bitmap(ti, x, 0);
__delay_cycles(20000);
}
lcd.clear();
lcd.print(9, 7, "Character Set");
x = y = 0;
lcd.pos(x, y);
for(c = 32; c < 128; ++c) {
lcd.print(c);
if(++x >= 16) x = 0, lcd.pos(x, ++y);
}
__delay_cycles(3000000);
lcd.clear();
ts.tm_hour = ts.tm_min = ts.tm_sec = 88;
ts.tm_mon = 87;
ts.tm_mday = ts.tm_year = 88;
ts.tm_wday = 5;
show_time(&ts);
lcd.pd12(15, 48, 5); lcd.pd12(17, 59, 5);
lcd.print(24, 7, "\x7F""C"); lcd.print(66, 7, "\x7F""K");
print_int(8888, 5); print_int(8888, 0, 7); print_int(8888, 42, 7);
for(x = 15; x < 96; ++x) draw_bargraph(x);
__delay_cycles(3000000);
lcd.clear(); //
lcd.pd12(15, 48, 5); // Degrees
lcd.pd12(17, 59, 5); // F
lcd.print(24, 7, "\x7F""C"); // C
lcd.print(66, 7, "\x7F""K"); // K
//
for(; { // for-ever
show_time(localtime(&tt)); // Convert time_t to tm struct and show on LCD
__bis_SR_register(LPM0_bits + GIE); // Sleep until WDT interrupt
ADC10CTL0 |= (ENC | ADC10SC); // Begin ADC conversion
__bis_SR_register(LPM0_bits + GIE); // Sleep until conversion complete
//
// Convert to temperature
dc = ((27069L * adc) - 18169625L) >> 16; // Vref = 1.5V
dk = ((27069L * adc) - 268467L) >> 16; // Vref = 1.5V
df = ((48724L * adc) - 30634388L) >> 16; // Vref = 1.5V
//
// Display on LCD
print_int(df, 5); // Degrees F
print_int(dc, 0, 7); // Degrees C
print_int(dk, 42, 7); // Degrees K
draw_bargraph(df); // Deg F Bargraph
} //
return 0;
}
nokia7110tl.h - C++ template class for Nokia 7110 (SED1565)
Got my boards a few days a go. Powered one up and it works a treat! Nice work SA.
Hey, is anyone working on a graphics library? I noted RobG mentioned it somewhere, but I didn't see any other activity. If no one is actively progressing this, I'm up for it.
Looking over the data sheet, implementing dot addressed graphics is going to be a challenge, because when addressing the board in serial mode it appears you cannot read the SED1565 display memory. There are more dots on the screen than there are bits in the G2553's SRAM which poses a problem if you can't read the current state of the display memory. Graphics data is refreshed in byte chunks, so you need to know what is already there when adding new points (if you want to plot two crossing lines for example).
Now, I'm not going to let a little thing like that stop me. But if someone else is already working on it, HOLLER and I'll stop. I don't want to spoil someone else's party. :-)
Please don't beat me, but will this work for windows as well?
I normally use CCS, but having an opensource alternative and using the mspgcc would be preferable. A easy to setup 100% opensource IDE for linux, windows and macos would be a big step into the right direction.
At this point this is a Linux only thing (probably won't work on any BSDs either), but if there is interest for it, I'm willing to try to make it work on Windows as well.
Well, if there are two users there are probably more :-). I'll look into it. However, I think I'll have to change the way I bundle the binary toolchains first. Currently, they are installed as eclipse packages (features), but it does not seem that Eclipse is made for distributing binary executables. I'm going to create a small binary toolchain manager, which needs to be run after the msp430 plugin is installed. It is then responsible for downloading the toolchain and extracting it to a user specified directory. This also solves the issues that the plugin currently has with distribution provided Eclipse-versions.